github.com/dolthub/go-mysql-server@v0.18.0/sql/expression/function/aggregation/window_framer_test.go (about)

     1  // Copyright 2022 DoltHub, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package aggregation
    16  
    17  import (
    18  	"errors"
    19  	"io"
    20  	"testing"
    21  
    22  	"github.com/stretchr/testify/require"
    23  
    24  	"github.com/dolthub/go-mysql-server/sql"
    25  	"github.com/dolthub/go-mysql-server/sql/expression"
    26  	"github.com/dolthub/go-mysql-server/sql/types"
    27  )
    28  
    29  func TestWindowRowFramers(t *testing.T) {
    30  	tests := []struct {
    31  		Name     string
    32  		Framer   func(sql.WindowFrame, *sql.WindowDefinition) (sql.WindowFramer, error)
    33  		Expected []sql.WindowInterval
    34  	}{
    35  		{
    36  			Name:   "rows unbounded preceding to current row framer",
    37  			Framer: NewRowsUnboundedPrecedingToCurrentRowFramer,
    38  			Expected: []sql.WindowInterval{
    39  				{Start: 0, End: 1},
    40  				{Start: 0, End: 2},
    41  				{Start: 2, End: 3},
    42  				{Start: 2, End: 4},
    43  				{Start: 2, End: 5},
    44  				{Start: 2, End: 6},
    45  				{Start: 6, End: 7},
    46  				{Start: 6, End: 8},
    47  				{Start: 6, End: 9},
    48  			},
    49  		},
    50  		{
    51  			Name:   "rows unbounded preceding to 1 following framer",
    52  			Framer: NewRowsUnboundedPrecedingToNFollowingFramer,
    53  			Expected: []sql.WindowInterval{
    54  				{Start: 0, End: 2},
    55  				{Start: 0, End: 2},
    56  				{Start: 2, End: 4},
    57  				{Start: 2, End: 5},
    58  				{Start: 2, End: 6},
    59  				{Start: 2, End: 6},
    60  				{Start: 6, End: 8},
    61  				{Start: 6, End: 9},
    62  				{Start: 6, End: 9},
    63  			},
    64  		},
    65  		{
    66  			Name:   "rows 2 preceding to 1 following framer",
    67  			Framer: NewRowsNPrecedingToNFollowingFramer,
    68  			Expected: []sql.WindowInterval{
    69  				{Start: 0, End: 2},
    70  				{Start: 0, End: 2},
    71  				{Start: 2, End: 4},
    72  				{Start: 2, End: 5},
    73  				{Start: 2, End: 6},
    74  				{Start: 3, End: 6},
    75  				{Start: 6, End: 8},
    76  				{Start: 6, End: 9},
    77  				{Start: 6, End: 9},
    78  			},
    79  		},
    80  		{
    81  			Name:   "rows unbound preceding to unbound following framer",
    82  			Framer: NewRowsUnboundedPrecedingToUnboundedFollowingFramer,
    83  			Expected: []sql.WindowInterval{
    84  				{Start: 0, End: 2},
    85  				{Start: 0, End: 2},
    86  				{Start: 2, End: 6},
    87  				{Start: 2, End: 6},
    88  				{Start: 2, End: 6},
    89  				{Start: 2, End: 6},
    90  				{Start: 6, End: 9},
    91  				{Start: 6, End: 9},
    92  				{Start: 6, End: 9},
    93  			},
    94  		},
    95  		{
    96  			Name:   "rows 2 preceding to 1 preceding framer",
    97  			Framer: NewRowsNPrecedingToNPrecedingFramer,
    98  			Expected: []sql.WindowInterval{
    99  				{Start: 0, End: 0},
   100  				{Start: 0, End: 1},
   101  				{Start: 2, End: 2},
   102  				{Start: 2, End: 3},
   103  				{Start: 2, End: 4},
   104  				{Start: 3, End: 5},
   105  				{Start: 6, End: 6},
   106  				{Start: 6, End: 7},
   107  				{Start: 6, End: 8},
   108  			},
   109  		},
   110  		{
   111  			Name:   "rows 1 following to 1 following framer",
   112  			Framer: NewRowsNFollowingToNFollowingFramer,
   113  			Expected: []sql.WindowInterval{
   114  				{Start: 1, End: 2},
   115  				{Start: 2, End: 2},
   116  				{Start: 3, End: 4},
   117  				{Start: 4, End: 5},
   118  				{Start: 5, End: 6},
   119  				{Start: 6, End: 6},
   120  				{Start: 7, End: 8},
   121  				{Start: 8, End: 9},
   122  				{Start: 9, End: 9},
   123  			},
   124  		},
   125  		{
   126  			Name:   "rows current row to current row framer",
   127  			Framer: NewRowsCurrentRowToCurrentRowFramer,
   128  			Expected: []sql.WindowInterval{
   129  				{Start: 0, End: 1},
   130  				{Start: 1, End: 2},
   131  				{Start: 2, End: 3},
   132  				{Start: 3, End: 4},
   133  				{Start: 4, End: 5},
   134  				{Start: 5, End: 6},
   135  				{Start: 6, End: 7},
   136  				{Start: 7, End: 8},
   137  				{Start: 8, End: 9},
   138  			},
   139  		},
   140  	}
   141  
   142  	partitions := []sql.WindowInterval{
   143  		{}, // nil rows creates one empty partition
   144  		{Start: 0, End: 2},
   145  		{Start: 2, End: 6},
   146  		{Start: 6, End: 9},
   147  	}
   148  
   149  	for _, tt := range tests {
   150  		t.Run(tt.Name, func(t *testing.T) {
   151  			ctx := sql.NewEmptyContext()
   152  			frameDef := dummyFrame{}
   153  			framer, err := tt.Framer(frameDef, nil)
   154  			require.NoError(t, err)
   155  
   156  			_, err = framer.Interval()
   157  			require.Error(t, err)
   158  			require.Equal(t, err, ErrPartitionNotSet)
   159  
   160  			var res []sql.WindowInterval
   161  			var frame sql.WindowInterval
   162  			for _, p := range partitions {
   163  				framer, err = framer.NewFramer(p)
   164  				require.NoError(t, err)
   165  
   166  				for {
   167  					frame, err = framer.Next(ctx, nil)
   168  					if errors.Is(err, io.EOF) {
   169  						break
   170  					}
   171  					res = append(res, frame)
   172  				}
   173  			}
   174  			require.Equal(t, tt.Expected, res)
   175  		})
   176  	}
   177  }
   178  
   179  func TestWindowRangeFramers(t *testing.T) {
   180  	tests := []struct {
   181  		Name     string
   182  		Framer   func(sql.WindowFrame, *sql.WindowDefinition) (sql.WindowFramer, error)
   183  		OrderBy  []sql.Expression
   184  		Expected []sql.WindowInterval
   185  	}{
   186  		{
   187  			Name:   "range unbound preceding to unbound following framer",
   188  			Framer: NewRangeUnboundedPrecedingToUnboundedFollowingFramer,
   189  			Expected: []sql.WindowInterval{
   190  				{},
   191  				{Start: 0, End: 10}, {Start: 0, End: 10}, {Start: 0, End: 10}, {Start: 0, End: 10}, {Start: 0, End: 10}, {Start: 0, End: 10}, {Start: 0, End: 10}, {Start: 0, End: 10}, {Start: 0, End: 10}, {Start: 0, End: 10},
   192  				{Start: 10, End: 16}, {Start: 10, End: 16}, {Start: 10, End: 16}, {Start: 10, End: 16}, {Start: 10, End: 16}, {Start: 10, End: 16},
   193  				{Start: 16, End: 19}, {Start: 16, End: 19}, {Start: 16, End: 19},
   194  			},
   195  		},
   196  		{
   197  			Name:   "range 2 preceding to 1 preceding framer",
   198  			Framer: NewRangeNPrecedingToNPrecedingFramer,
   199  			Expected: []sql.WindowInterval{
   200  				{},
   201  				{Start: 0, End: 0}, {Start: 0, End: 0}, {Start: 0, End: 2}, {Start: 2, End: 3}, {Start: 3, End: 4}, {Start: 3, End: 4}, {Start: 4, End: 6}, {Start: 4, End: 7}, {Start: 4, End: 7}, {Start: 6, End: 9},
   202  				{Start: 10, End: 10}, {Start: 10, End: 10}, {Start: 10, End: 12}, {Start: 12, End: 13}, {Start: 13, End: 14}, {Start: 13, End: 14},
   203  				{Start: 16, End: 16}, {Start: 16, End: 17}, {Start: 16, End: 18},
   204  			},
   205  		},
   206  		{
   207  			Name:   "range current row to current row framer",
   208  			Framer: NewRangeCurrentRowToCurrentRowFramer,
   209  			Expected: []sql.WindowInterval{
   210  				{},
   211  				{Start: 0, End: 2}, {Start: 0, End: 2}, {Start: 2, End: 3}, {Start: 3, End: 4}, {Start: 4, End: 6}, {Start: 4, End: 6}, {Start: 6, End: 7}, {Start: 7, End: 9}, {Start: 7, End: 9}, {Start: 9, End: 10},
   212  				{Start: 10, End: 12}, {Start: 10, End: 12}, {Start: 12, End: 13}, {Start: 13, End: 14}, {Start: 14, End: 16}, {Start: 14, End: 16},
   213  				{Start: 16, End: 17}, {Start: 17, End: 18}, {Start: 18, End: 19},
   214  			},
   215  		},
   216  		{
   217  			Name:   "range unbounded preceding to current row framer",
   218  			Framer: NewRangeUnboundedPrecedingToCurrentRowFramer,
   219  			Expected: []sql.WindowInterval{
   220  				{},
   221  				{Start: 0, End: 2}, {Start: 0, End: 2}, {Start: 0, End: 3}, {Start: 0, End: 4}, {Start: 0, End: 6}, {Start: 0, End: 6}, {Start: 0, End: 7}, {Start: 0, End: 9}, {Start: 0, End: 9}, {Start: 0, End: 10},
   222  				{Start: 10, End: 12}, {Start: 10, End: 12}, {Start: 10, End: 13}, {Start: 10, End: 14}, {Start: 10, End: 16}, {Start: 10, End: 16},
   223  				{Start: 16, End: 17}, {Start: 16, End: 18}, {Start: 16, End: 19},
   224  			},
   225  		},
   226  		{
   227  			Name:   "range 1 following to 1 following framer",
   228  			Framer: NewRangeNFollowingToNFollowingFramer,
   229  			Expected: []sql.WindowInterval{
   230  				{},
   231  				{Start: 2, End: 3}, {Start: 2, End: 3}, {Start: 3, End: 3}, {Start: 4, End: 4}, {Start: 6, End: 7}, {Start: 6, End: 7}, {Start: 7, End: 9}, {Start: 9, End: 10}, {Start: 9, End: 10}, {Start: 10, End: 10},
   232  				{Start: 12, End: 13}, {Start: 12, End: 13}, {Start: 13, End: 13}, {Start: 14, End: 14}, {Start: 16, End: 16}, {Start: 16, End: 16},
   233  				{Start: 17, End: 18}, {Start: 18, End: 19}, {Start: 19, End: 19},
   234  			},
   235  		},
   236  		{
   237  			Name:   "range unbounded preceding to 1 following framer",
   238  			Framer: NewRangeUnboundedPrecedingToNFollowingFramer,
   239  			Expected: []sql.WindowInterval{
   240  				{},
   241  				{Start: 0, End: 3}, {Start: 0, End: 3}, {Start: 0, End: 3}, {Start: 0, End: 4}, {Start: 0, End: 7}, {Start: 0, End: 7}, {Start: 0, End: 9}, {Start: 0, End: 10}, {Start: 0, End: 10}, {Start: 0, End: 10},
   242  				{Start: 10, End: 13}, {Start: 10, End: 13}, {Start: 10, End: 13}, {Start: 10, End: 14}, {Start: 10, End: 16}, {Start: 10, End: 16},
   243  				{Start: 16, End: 18}, {Start: 16, End: 19}, {Start: 16, End: 19},
   244  			},
   245  		},
   246  		{
   247  			Name:   "rows 2 preceding to 1 following framer",
   248  			Framer: NewRangeNPrecedingToNFollowingFramer,
   249  			Expected: []sql.WindowInterval{
   250  				{},
   251  				{Start: 0, End: 3}, {Start: 0, End: 3}, {Start: 0, End: 3}, {Start: 2, End: 4}, {Start: 3, End: 7}, {Start: 3, End: 7}, {Start: 4, End: 9}, {Start: 4, End: 10}, {Start: 4, End: 10}, {Start: 6, End: 10},
   252  				{Start: 10, End: 13}, {Start: 10, End: 13}, {Start: 10, End: 13}, {Start: 12, End: 14}, {Start: 13, End: 16}, {Start: 13, End: 16},
   253  				{Start: 16, End: 18}, {Start: 16, End: 19}, {Start: 16, End: 19},
   254  			},
   255  		},
   256  	}
   257  
   258  	buffer := []sql.Row{
   259  		{0, 1}, {0, 1}, {0, 2}, {0, 4}, {0, 6}, {0, 6}, {0, 7}, {0, 8}, {0, 8}, {0, 9},
   260  		{1, 1}, {1, 1}, {1, 2}, {1, 4}, {1, 6}, {1, 6},
   261  		{2, 1}, {2, 2}, {2, 3},
   262  	}
   263  	partitions := []sql.WindowInterval{
   264  		{}, // nil rows creates one empty partition
   265  		{Start: 0, End: 10},
   266  		{Start: 10, End: 16},
   267  		{Start: 16, End: 19},
   268  	}
   269  	expr := expression.NewGetField(1, types.Int64, "", false)
   270  
   271  	for _, tt := range tests {
   272  		t.Run(tt.Name, func(t *testing.T) {
   273  			ctx := sql.NewEmptyContext()
   274  			frameDef := dummyFrame{}
   275  			w := &sql.WindowDefinition{OrderBy: sql.SortFields{{Column: expr, Order: 1}}}
   276  			framer, err := tt.Framer(frameDef, w)
   277  			require.NoError(t, err)
   278  
   279  			_, err = framer.Interval()
   280  			require.Error(t, err)
   281  			require.Equal(t, err, ErrPartitionNotSet)
   282  
   283  			var res []sql.WindowInterval
   284  			var frame sql.WindowInterval
   285  			for _, p := range partitions {
   286  				framer, err = framer.NewFramer(p)
   287  				require.NoError(t, err)
   288  				for {
   289  					frame, err = framer.Next(ctx, buffer)
   290  					if errors.Is(err, io.EOF) {
   291  						break
   292  					}
   293  					res = append(res, frame)
   294  				}
   295  			}
   296  			require.Equal(t, tt.Expected, res)
   297  		})
   298  	}
   299  }
   300  
   301  type dummyFrame struct{}
   302  
   303  var _ sql.WindowFrame = (*dummyFrame)(nil)
   304  
   305  func (d dummyFrame) String() string {
   306  	panic("implement me")
   307  }
   308  
   309  func (d dummyFrame) NewFramer(*sql.WindowDefinition) (sql.WindowFramer, error) {
   310  	panic("implement me")
   311  }
   312  
   313  func (d dummyFrame) UnboundedFollowing() bool {
   314  	return true
   315  }
   316  
   317  func (d dummyFrame) UnboundedPreceding() bool {
   318  	return true
   319  }
   320  
   321  func (d dummyFrame) StartCurrentRow() bool {
   322  	return true
   323  }
   324  
   325  func (d dummyFrame) EndCurrentRow() bool {
   326  	return true
   327  }
   328  
   329  func (d dummyFrame) StartNPreceding() sql.Expression {
   330  	return expression.NewLiteral(int8(2), types.Int8)
   331  }
   332  
   333  func (d dummyFrame) StartNFollowing() sql.Expression {
   334  	return expression.NewLiteral(int8(1), types.Int8)
   335  }
   336  
   337  func (d dummyFrame) EndNPreceding() sql.Expression {
   338  	return expression.NewLiteral(int8(1), types.Int8)
   339  }
   340  
   341  func (d dummyFrame) EndNFollowing() sql.Expression {
   342  	return expression.NewLiteral(int8(1), types.Int8)
   343  }