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 }