github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/sem/builtins/window_frame_builtins_test.go (about)

     1  // Copyright 2018 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package builtins
    12  
    13  import (
    14  	"bytes"
    15  	"context"
    16  	"fmt"
    17  	"math/rand"
    18  	"testing"
    19  
    20  	"github.com/cockroachdb/cockroach/pkg/settings/cluster"
    21  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    22  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    23  )
    24  
    25  const maxInt = 1000000
    26  const maxOffset = 100
    27  
    28  type indexedRows struct {
    29  	rows []indexedRow
    30  }
    31  
    32  func (ir indexedRows) Len() int {
    33  	return len(ir.rows)
    34  }
    35  
    36  func (ir indexedRows) GetRow(_ context.Context, idx int) (tree.IndexedRow, error) {
    37  	return ir.rows[idx], nil
    38  }
    39  
    40  type indexedRow struct {
    41  	idx int
    42  	row tree.Datums
    43  }
    44  
    45  func (ir indexedRow) GetIdx() int {
    46  	return ir.idx
    47  }
    48  
    49  func (ir indexedRow) GetDatum(colIdx int) (tree.Datum, error) {
    50  	return ir.row[colIdx], nil
    51  }
    52  
    53  func (ir indexedRow) GetDatums(firstColIdx, lastColIdx int) (tree.Datums, error) {
    54  	return ir.row[firstColIdx:lastColIdx], nil
    55  }
    56  
    57  func testSlidingWindow(t *testing.T, count int) {
    58  	evalCtx := tree.NewTestingEvalContext(cluster.MakeTestingClusterSettings())
    59  	defer evalCtx.Stop(context.Background())
    60  	wfr := makeTestWindowFrameRun(count)
    61  	wfr.Frame = &tree.WindowFrame{
    62  		Mode: tree.ROWS,
    63  		Bounds: tree.WindowFrameBounds{
    64  			StartBound: &tree.WindowFrameBound{BoundType: tree.OffsetPreceding},
    65  			EndBound:   &tree.WindowFrameBound{BoundType: tree.OffsetFollowing},
    66  		},
    67  	}
    68  	testMin(t, evalCtx, wfr)
    69  	testMax(t, evalCtx, wfr)
    70  	testSumAndAvg(t, evalCtx, wfr)
    71  }
    72  
    73  func testMin(t *testing.T, evalCtx *tree.EvalContext, wfr *tree.WindowFrameRun) {
    74  	for offset := 0; offset < maxOffset; offset += int(rand.Int31n(maxOffset / 10)) {
    75  		wfr.StartBoundOffset = tree.NewDInt(tree.DInt(offset))
    76  		wfr.EndBoundOffset = tree.NewDInt(tree.DInt(offset))
    77  		min := &slidingWindowFunc{}
    78  		min.sw = makeSlidingWindow(evalCtx, func(evalCtx *tree.EvalContext, a, b tree.Datum) int {
    79  			return -a.Compare(evalCtx, b)
    80  		})
    81  		for wfr.RowIdx = 0; wfr.RowIdx < wfr.PartitionSize(); wfr.RowIdx++ {
    82  			res, err := min.Compute(evalCtx.Ctx(), evalCtx, wfr)
    83  			if err != nil {
    84  				t.Errorf("Unexpected error received when getting min from sliding window: %+v", err)
    85  			}
    86  			minResult, _ := tree.AsDInt(res)
    87  			naiveMin := tree.DInt(maxInt)
    88  			for idx := wfr.RowIdx - offset; idx <= wfr.RowIdx+offset; idx++ {
    89  				if idx < 0 || idx >= wfr.PartitionSize() {
    90  					continue
    91  				}
    92  				row, err := wfr.Rows.GetRow(evalCtx.Ctx(), idx)
    93  				if err != nil {
    94  					panic(err)
    95  				}
    96  				datum, err := row.GetDatum(0)
    97  				if err != nil {
    98  					panic(err)
    99  				}
   100  				el, _ := tree.AsDInt(datum)
   101  				if el < naiveMin {
   102  					naiveMin = el
   103  				}
   104  			}
   105  			if minResult != naiveMin {
   106  				t.Errorf("Min sliding window returned wrong result: expected %+v, found %+v", naiveMin, minResult)
   107  				t.Errorf("partitionSize: %+v idx: %+v offset: %+v", wfr.PartitionSize(), wfr.RowIdx, offset)
   108  				t.Errorf(min.sw.string())
   109  				t.Errorf(partitionToString(evalCtx.Ctx(), wfr.Rows))
   110  				panic("")
   111  			}
   112  		}
   113  	}
   114  }
   115  
   116  func testMax(t *testing.T, evalCtx *tree.EvalContext, wfr *tree.WindowFrameRun) {
   117  	for offset := 0; offset < maxOffset; offset += int(rand.Int31n(maxOffset / 10)) {
   118  		wfr.StartBoundOffset = tree.NewDInt(tree.DInt(offset))
   119  		wfr.EndBoundOffset = tree.NewDInt(tree.DInt(offset))
   120  		max := &slidingWindowFunc{}
   121  		max.sw = makeSlidingWindow(evalCtx, func(evalCtx *tree.EvalContext, a, b tree.Datum) int {
   122  			return a.Compare(evalCtx, b)
   123  		})
   124  		for wfr.RowIdx = 0; wfr.RowIdx < wfr.PartitionSize(); wfr.RowIdx++ {
   125  			res, err := max.Compute(evalCtx.Ctx(), evalCtx, wfr)
   126  			if err != nil {
   127  				t.Errorf("Unexpected error received when getting max from sliding window: %+v", err)
   128  			}
   129  			maxResult, _ := tree.AsDInt(res)
   130  			naiveMax := tree.DInt(-maxInt)
   131  			for idx := wfr.RowIdx - offset; idx <= wfr.RowIdx+offset; idx++ {
   132  				if idx < 0 || idx >= wfr.PartitionSize() {
   133  					continue
   134  				}
   135  				row, err := wfr.Rows.GetRow(evalCtx.Ctx(), idx)
   136  				if err != nil {
   137  					panic(err)
   138  				}
   139  				datum, err := row.GetDatum(0)
   140  				if err != nil {
   141  					panic(err)
   142  				}
   143  				el, _ := tree.AsDInt(datum)
   144  				if el > naiveMax {
   145  					naiveMax = el
   146  				}
   147  			}
   148  			if maxResult != naiveMax {
   149  				t.Errorf("Max sliding window returned wrong result: expected %+v, found %+v", naiveMax, maxResult)
   150  				t.Errorf("partitionSize: %+v idx: %+v offset: %+v", wfr.PartitionSize(), wfr.RowIdx, offset)
   151  				t.Errorf(max.sw.string())
   152  				t.Errorf(partitionToString(evalCtx.Ctx(), wfr.Rows))
   153  				panic("")
   154  			}
   155  		}
   156  	}
   157  }
   158  
   159  func testSumAndAvg(t *testing.T, evalCtx *tree.EvalContext, wfr *tree.WindowFrameRun) {
   160  	for offset := 0; offset < maxOffset; offset += int(rand.Int31n(maxOffset / 10)) {
   161  		wfr.StartBoundOffset = tree.NewDInt(tree.DInt(offset))
   162  		wfr.EndBoundOffset = tree.NewDInt(tree.DInt(offset))
   163  		sum := &slidingWindowSumFunc{agg: &intSumAggregate{}}
   164  		avg := &avgWindowFunc{sum: newSlidingWindowSumFunc(&intSumAggregate{})}
   165  		for wfr.RowIdx = 0; wfr.RowIdx < wfr.PartitionSize(); wfr.RowIdx++ {
   166  			res, err := sum.Compute(evalCtx.Ctx(), evalCtx, wfr)
   167  			if err != nil {
   168  				t.Errorf("Unexpected error received when getting sum from sliding window: %+v", err)
   169  			}
   170  			sumResult := tree.DDecimal{Decimal: res.(*tree.DDecimal).Decimal}
   171  			res, err = avg.Compute(evalCtx.Ctx(), evalCtx, wfr)
   172  			if err != nil {
   173  				t.Errorf("Unexpected error received when getting avg from sliding window: %+v", err)
   174  			}
   175  			avgResult := tree.DDecimal{Decimal: res.(*tree.DDecimal).Decimal}
   176  			naiveSum := int64(0)
   177  			for idx := wfr.RowIdx - offset; idx <= wfr.RowIdx+offset; idx++ {
   178  				if idx < 0 || idx >= wfr.PartitionSize() {
   179  					continue
   180  				}
   181  				row, err := wfr.Rows.GetRow(evalCtx.Ctx(), idx)
   182  				if err != nil {
   183  					panic(err)
   184  				}
   185  				datum, err := row.GetDatum(0)
   186  				if err != nil {
   187  					panic(err)
   188  				}
   189  				el, _ := tree.AsDInt(datum)
   190  				naiveSum += int64(el)
   191  			}
   192  			s, err := sumResult.Int64()
   193  			if err != nil {
   194  				t.Errorf("Unexpected error received when converting sum from DDecimal to int64: %+v", err)
   195  			}
   196  			if s != naiveSum {
   197  				t.Errorf("Sum sliding window returned wrong result: expected %+v, found %+v", naiveSum, s)
   198  				t.Errorf("partitionSize: %+v idx: %+v offset: %+v", wfr.PartitionSize(), wfr.RowIdx, offset)
   199  				t.Errorf(partitionToString(evalCtx.Ctx(), wfr.Rows))
   200  				panic("")
   201  			}
   202  			a, err := avgResult.Float64()
   203  			if err != nil {
   204  				t.Errorf("Unexpected error received when converting avg from DDecimal to float64: %+v", err)
   205  			}
   206  			frameSize, err := wfr.FrameSize(evalCtx.Ctx(), evalCtx)
   207  			if err != nil {
   208  				t.Errorf("Unexpected error when getting FrameSize: %+v", err)
   209  			}
   210  			if a != float64(naiveSum)/float64(frameSize) {
   211  				t.Errorf("Sum sliding window returned wrong result: expected %+v, found %+v", float64(naiveSum)/float64(frameSize), a)
   212  				t.Errorf("partitionSize: %+v idx: %+v offset: %+v", wfr.PartitionSize(), wfr.RowIdx, offset)
   213  				t.Errorf(partitionToString(evalCtx.Ctx(), wfr.Rows))
   214  				panic("")
   215  			}
   216  		}
   217  	}
   218  }
   219  
   220  const noFilterIdx = -1
   221  
   222  func makeTestWindowFrameRun(count int) *tree.WindowFrameRun {
   223  	return &tree.WindowFrameRun{
   224  		Rows:         makeTestPartition(count),
   225  		ArgsIdxs:     []uint32{0},
   226  		FilterColIdx: noFilterIdx,
   227  	}
   228  }
   229  
   230  func makeTestPartition(count int) tree.IndexedRows {
   231  	partition := indexedRows{rows: make([]indexedRow, count)}
   232  	for idx := 0; idx < count; idx++ {
   233  		partition.rows[idx] = indexedRow{idx: idx, row: tree.Datums{tree.NewDInt(tree.DInt(rand.Int31n(maxInt)))}}
   234  	}
   235  	return partition
   236  }
   237  
   238  func partitionToString(ctx context.Context, partition tree.IndexedRows) string {
   239  	var buf bytes.Buffer
   240  	var err error
   241  	var row tree.IndexedRow
   242  	buf.WriteString("\n=====Partition=====\n")
   243  	for idx := 0; idx < partition.Len(); idx++ {
   244  		if row, err = partition.GetRow(ctx, idx); err != nil {
   245  			return err.Error()
   246  		}
   247  		buf.WriteString(fmt.Sprintf("%v\n", row))
   248  	}
   249  	buf.WriteString("====================\n")
   250  	return buf.String()
   251  }
   252  
   253  func TestSlidingWindow(t *testing.T) {
   254  	defer leaktest.AfterTest(t)()
   255  	for _, count := range []int{1, 17, 253} {
   256  		testSlidingWindow(t, count)
   257  	}
   258  }