github.com/dolthub/go-mysql-server@v0.18.0/enginetest/join_planning_tests.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 enginetest 16 17 import ( 18 "fmt" 19 "strings" 20 "testing" 21 22 "github.com/stretchr/testify/assert" 23 "github.com/stretchr/testify/require" 24 25 "github.com/dolthub/go-mysql-server/enginetest/scriptgen/setup" 26 "github.com/dolthub/go-mysql-server/sql" 27 "github.com/dolthub/go-mysql-server/sql/expression" 28 "github.com/dolthub/go-mysql-server/sql/plan" 29 "github.com/dolthub/go-mysql-server/sql/planbuilder" 30 "github.com/dolthub/go-mysql-server/sql/transform" 31 ) 32 33 type JoinPlanTest struct { 34 q string 35 types []plan.JoinType 36 indexes []string 37 mergeCompares []string 38 exp []sql.Row 39 // order is a list of acceptable join plan orders. 40 // used for statistics test plans that are unlikely but otherwise 41 // cause flakes in CI for lack of seed control. 42 order [][]string 43 skipOld bool 44 } 45 46 var JoinPlanningTests = []struct { 47 name string 48 setup []string 49 tests []JoinPlanTest 50 }{ 51 { 52 name: "filter pushdown through join uppercase name", 53 setup: []string{ 54 "create database mydb1", 55 "create database mydb2", 56 "create table mydb1.xy (x int primary key, y int)", 57 "create table mydb2.xy (x int primary key, y int)", 58 "insert into mydb1.xy values (0,0)", 59 "insert into mydb2.xy values (1,1)", 60 }, 61 tests: []JoinPlanTest{ 62 { 63 q: "select * from mydb1.xy, mydb2.xy", 64 exp: []sql.Row{{0, 0, 1, 1}}, 65 }, 66 }, 67 }, 68 { 69 name: "info schema plans", 70 setup: []string{ 71 "CREATE table xy (x int primary key, y int);", 72 }, 73 tests: []JoinPlanTest{ 74 { 75 q: "select count(t.*) from information_schema.columns c join information_schema.tables t on `t`.`TABLE_NAME` = `c`.`TABLE_NAME`", 76 types: []plan.JoinType{plan.JoinTypeHash}, 77 exp: []sql.Row{{734}}, 78 }, 79 }, 80 }, 81 { 82 name: "merge join unary index", 83 setup: []string{ 84 "CREATE table xy (x int primary key, y int, unique index y_idx(y));", 85 "create table rs (r int primary key, s int, index s_idx(s));", 86 "CREATE table uv (u int primary key, v int);", 87 "CREATE table ab (a int primary key, b int);", 88 "insert into xy values (1,0), (2,1), (0,2), (3,3);", 89 "insert into rs values (0,0), (1,0), (2,0), (4,4), (5,4);", 90 "insert into uv values (0,1), (1,1), (2,2), (3,2);", 91 "insert into ab values (0,2), (1,2), (2,2), (3,1);", 92 `analyze table xy update histogram on x using data '{"row_count":1000}'`, 93 `analyze table rs update histogram on r using data '{"row_count":1000}'`, 94 `analyze table uv update histogram on u using data '{"row_count":1000}'`, 95 `analyze table ab update histogram on a using data '{"row_count":1000}'`, 96 }, 97 tests: []JoinPlanTest{ 98 { 99 q: "select u,a,y from uv join (select /*+ JOIN_ORDER(ab, xy) MERGE_JOIN(ab, xy) */ * from ab join xy on y = a) r on u = r.a order by 1", 100 types: []plan.JoinType{plan.JoinTypeLookup, plan.JoinTypeMerge}, 101 exp: []sql.Row{{0, 0, 0}, {1, 1, 1}, {2, 2, 2}, {3, 3, 3}}, 102 }, 103 { 104 q: "select /*+ JOIN_ORDER(ab, xy) MERGE_JOIN(ab, xy)*/ * from ab join xy on y = a order by 1, 3", 105 types: []plan.JoinType{plan.JoinTypeMerge}, 106 exp: []sql.Row{{0, 2, 1, 0}, {1, 2, 2, 1}, {2, 2, 0, 2}, {3, 1, 3, 3}}, 107 }, 108 { 109 q: "select /*+ JOIN_ORDER(rs, xy) MERGE_JOIN(rs, xy) */ * from rs left join xy on y = s order by 1, 3", 110 types: []plan.JoinType{plan.JoinTypeLeftOuterMerge}, 111 exp: []sql.Row{{0, 0, 1, 0}, {1, 0, 1, 0}, {2, 0, 1, 0}, {4, 4, nil, nil}, {5, 4, nil, nil}}, 112 }, 113 { 114 // extra join condition does not filter left-only rows 115 q: "select /*+ JOIN_ORDER(rs, xy) MERGE_JOIN(rs, xy) */ * from rs left join xy on y = s and y+s = 0 order by 1, 3", 116 types: []plan.JoinType{plan.JoinTypeLeftOuterMerge}, 117 exp: []sql.Row{{0, 0, 1, 0}, {1, 0, 1, 0}, {2, 0, 1, 0}, {4, 4, nil, nil}, {5, 4, nil, nil}}, 118 }, 119 { 120 // extra join condition does not filter left-only rows 121 q: "select /*+ JOIN_ORDER(rs, xy) MERGE_JOIN(rs, xy) */ * from rs left join xy on y+2 = s and s-y = 2 order by 1, 3", 122 types: []plan.JoinType{plan.JoinTypeLeftOuterMerge}, 123 exp: []sql.Row{{0, 0, nil, nil}, {1, 0, nil, nil}, {2, 0, nil, nil}, {4, 4, 0, 2}, {5, 4, 0, 2}}, 124 }, 125 { 126 q: "select /*+ JOIN_ORDER(rs, xy) MERGE_JOIN(rs, xy) */ * from rs join xy on y = r order by 1, 3", 127 types: []plan.JoinType{plan.JoinTypeMerge}, 128 exp: []sql.Row{{0, 0, 1, 0}, {1, 0, 2, 1}, {2, 0, 0, 2}}, 129 }, 130 { 131 q: "select /*+ JOIN_ORDER(rs, xy) MERGE_JOIN(rs, xy) */ * from rs join xy on r = y order by 1, 3", 132 types: []plan.JoinType{plan.JoinTypeMerge}, 133 exp: []sql.Row{{0, 0, 1, 0}, {1, 0, 2, 1}, {2, 0, 0, 2}}, 134 }, 135 { 136 q: "select /*+ JOIN_ORDER(rs, xy) MERGE_JOIN(rs, xy) */ * from rs join xy on y = s order by 1, 3", 137 types: []plan.JoinType{plan.JoinTypeMerge}, 138 exp: []sql.Row{{0, 0, 1, 0}, {1, 0, 1, 0}, {2, 0, 1, 0}}, 139 }, 140 { 141 q: "select /*+ JOIN_ORDER(rs, xy) MERGE_JOIN(rs, xy) */ * from rs join xy on y = s and y = r order by 1, 3", 142 types: []plan.JoinType{plan.JoinTypeMerge}, 143 exp: []sql.Row{{0, 0, 1, 0}}, 144 }, 145 { 146 q: "select /*+ JOIN_ORDER(rs, xy) MERGE_JOIN(rs, xy) */ * from rs join xy on y+2 = s order by 1, 3", 147 types: []plan.JoinType{plan.JoinTypeMerge}, 148 exp: []sql.Row{{4, 4, 0, 2}, {5, 4, 0, 2}}, 149 }, 150 { 151 q: "select /*+ JOIN_ORDER(rs, xy) */ * from rs join xy on y = s-1 order by 1, 3", 152 types: []plan.JoinType{plan.JoinTypeLookup}, 153 exp: []sql.Row{{4, 4, 3, 3}, {5, 4, 3, 3}}, 154 }, 155 //{ 156 // TODO: cannot hash join on compound expressions 157 // q: "select /*+ JOIN_ORDER(rs, xy) */ * from rs join xy on y = mod(s,2) order by 1, 3", 158 // types: []plan.JoinType{plan.JoinTypeInner}, 159 // exp: []sql.Row{{0,0,1,0},{0, 0, 1, 0},{2,0,1,0},{4,4,1,0}}, 160 //}, 161 { 162 q: "select /*+ JOIN_ORDER(rs, xy) MERGE_JOIN(rs, xy) */ * from rs join xy on 2 = s+y order by 1, 3", 163 types: []plan.JoinType{plan.JoinTypeInner}, 164 exp: []sql.Row{{0, 0, 0, 2}, {1, 0, 0, 2}, {2, 0, 0, 2}}, 165 }, 166 { 167 q: "select /*+ JOIN_ORDER(rs, xy) MERGE_JOIN(rs, xy) */ * from rs join xy on y > s+2 order by 1, 3", 168 types: []plan.JoinType{plan.JoinTypeInner}, 169 exp: []sql.Row{{0, 0, 3, 3}, {1, 0, 3, 3}, {2, 0, 3, 3}}, 170 }, 171 }, 172 }, 173 { 174 name: "merge join multi match", 175 setup: []string{ 176 "CREATE table xy (x int primary key, y int, index y_idx(y));", 177 "create table rs (r int primary key, s int, index s_idx(s));", 178 "insert into xy values (1,0), (2,1), (0,8), (3,7), (5,4), (4,0);", 179 "insert into rs values (0,0),(2,3),(3,0), (4,8), (5,4);", 180 `analyze table xy update histogram on x using data '{"row_count":1000}'`, 181 `analyze table rs update histogram on r using data '{"row_count":1000}'`, 182 }, 183 tests: []JoinPlanTest{ 184 { 185 q: "select /*+ JOIN_ORDER(rs, xy) MERGE_JOIN(rs, xy) */ * from rs join xy on y = s order by 1,3", 186 types: []plan.JoinType{plan.JoinTypeMerge}, 187 exp: []sql.Row{{0, 0, 1, 0}, {0, 0, 4, 0}, {3, 0, 1, 0}, {3, 0, 4, 0}, {4, 8, 0, 8}, {5, 4, 5, 4}}, 188 }, 189 }, 190 }, 191 { 192 name: "merge join zero rows", 193 setup: []string{ 194 "CREATE table xy (x int primary key, y int, index y_idx(y));", 195 "create table rs (r int primary key, s int, index s_idx(s));", 196 "insert into xy values (1,0);", 197 `analyze table xy update histogram on x using data '{"row_count":10}'`, 198 `analyze table rs update histogram on r using data '{"row_count":1000}'`, 199 }, 200 tests: []JoinPlanTest{ 201 { 202 q: "select /*+ JOIN_ORDER(rs, xy) MERGE_JOIN(rs, xy) */ * from rs join xy on y = s order by 1,3", 203 types: []plan.JoinType{plan.JoinTypeMerge}, 204 exp: []sql.Row{}, 205 }, 206 }, 207 }, 208 { 209 // todo: rewrite implementing new stats interface 210 name: "merge join large and small table", 211 setup: []string{ 212 "CREATE table xy (x int primary key, y int, index y_idx(y));", 213 "create table rs (r int primary key, s int, index s_idx(s));", 214 "insert into xy values (1,0), (2,1), (0,8), (3,7), (5,4), (4,0);", 215 "insert into rs values (0,0),(2,3),(3,0), (4,8), (5,4);", 216 `analyze table xy update histogram on x using data '{"row_count":10}'`, 217 `analyze table rs update histogram on r using data '{"row_count":1000000000}'`, 218 }, 219 tests: []JoinPlanTest{ 220 { 221 // When primary table is much larger, doing many lookups is expensive: prefer merge 222 q: "select /*+ JOIN_ORDER(rs, xy) */ * from rs join xy on x = r order by 1,3", 223 types: []plan.JoinType{plan.JoinTypeLookup}, 224 exp: []sql.Row{{0, 0, 0, 8}, {2, 3, 2, 1}, {3, 0, 3, 7}, {4, 8, 4, 0}, {5, 4, 5, 4}}, 225 }, 226 { 227 // When secondary table is much larger, avoid reading the entire table: prefer lookup 228 q: "select /*+ JOIN_ORDER(xy, rs) */ * from xy join rs on x = r order by 1,3", 229 types: []plan.JoinType{plan.JoinTypeLookup}, 230 exp: []sql.Row{{0, 8, 0, 0}, {2, 1, 2, 3}, {3, 7, 3, 0}, {4, 0, 4, 8}, {5, 4, 5, 4}}, 231 }, 232 }, 233 }, 234 { 235 name: "merge join multi arity", 236 setup: []string{ 237 "CREATE table xy (x int primary key, y int, index yx_idx(y,x));", 238 "create table rs (r int primary key, s int, index s_idx(s));", 239 "insert into xy values (1,0), (2,1), (0,8), (3,7), (5,4), (4,0);", 240 "insert into rs values (0,0),(2,3),(3,0), (4,8), (5,4);", 241 `analyze table xy update histogram on x using data '{"row_count":1000}'`, 242 `analyze table rs update histogram on r using data '{"row_count":1000}'`, 243 }, 244 tests: []JoinPlanTest{ 245 { 246 q: "select /*+ JOIN_ORDER(rs, xy) MERGE_JOIN(rs, xy) */ * from rs join xy on y = s order by 1,3", 247 types: []plan.JoinType{plan.JoinTypeMerge}, 248 exp: []sql.Row{{0, 0, 1, 0}, {0, 0, 4, 0}, {3, 0, 1, 0}, {3, 0, 4, 0}, {4, 8, 0, 8}, {5, 4, 5, 4}}, 249 }, 250 }, 251 }, 252 { 253 name: "multi-column merge join", 254 setup: setup.Pk_tablesData[0], 255 256 tests: []JoinPlanTest{ 257 { 258 // Find a unique index, even if it has multiple columns 259 q: `SELECT /*+ MERGE_JOIN(l,r) JOIN_ORDER(r,l) */ l.pk1, l.pk2, l.c1, r.pk1, r.pk2, r.c1 FROM two_pk l JOIN two_pk r ON l.pk1=r.pk1 AND l.pk2=r.pk2`, 260 types: []plan.JoinType{plan.JoinTypeMerge}, 261 mergeCompares: []string{"((r.pk1, r.pk2) = (l.pk1, l.pk2))"}, 262 exp: []sql.Row{{0, 0, 0, 0, 0, 0}, {0, 1, 10, 0, 1, 10}, {1, 0, 20, 1, 0, 20}, {1, 1, 30, 1, 1, 30}}, 263 }, 264 { 265 // Prefer a two-column non-unique index over a one-column non-unique index 266 q: `SELECT /*+ MERGE_JOIN(l,r) JOIN_ORDER(r,l) */ l.pk, r.pk FROM one_pk_two_idx l JOIN one_pk_two_idx r ON l.v1=r.v1 AND l.v2=r.v2`, 267 types: []plan.JoinType{plan.JoinTypeMerge}, 268 mergeCompares: []string{"((r.v1, r.v2) = (l.v1, l.v2))"}, 269 exp: []sql.Row{{0, 0}, {1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}, {6, 6}, {7, 7}}, 270 }, 271 { 272 // Prefer a one-column unique index over a two-column non-unique index 273 q: `SELECT /*+ MERGE_JOIN(l,r) */ l.pk, r.pk FROM one_pk_three_idx l JOIN one_pk_three_idx r ON l.v1=r.v1 AND l.v2=r.v2 AND l.pk=r.v1`, 274 types: []plan.JoinType{plan.JoinTypeMerge}, 275 mergeCompares: []string{"(l.pk = r.v1)"}, 276 exp: []sql.Row{{0, 0}, {0, 1}}, 277 }, 278 { 279 // Allow an index with a prefix that is determined to be constant. 280 q: `SELECT /*+ MERGE_JOIN(l,r) */ l.pk1, l.pk2, r.pk FROM two_pk l JOIN one_pk_three_idx r ON l.pk2=r.v1 WHERE l.pk1 = 1`, 281 types: []plan.JoinType{plan.JoinTypeMerge}, 282 mergeCompares: []string{"(l.pk2 = r.v1)"}, 283 exp: []sql.Row{{1, 0, 0}, {1, 0, 1}, {1, 0, 2}, {1, 0, 3}, {1, 1, 4}}, 284 }, 285 { 286 // Allow an index where the final index column is determined to be constant. 287 q: `SELECT /*+ MERGE_JOIN(l,r) */ l.pk1, l.pk2, r.pk FROM two_pk l JOIN one_pk_three_idx r ON l.pk1=r.v1 WHERE l.pk2 = 1`, 288 types: []plan.JoinType{plan.JoinTypeMerge}, 289 mergeCompares: []string{"(r.v1 = l.pk1)"}, 290 exp: []sql.Row{{0, 1, 0}, {0, 1, 1}, {0, 1, 2}, {0, 1, 3}, {1, 1, 4}}, 291 }, 292 { 293 // Allow an index where the key expression is determined to be constant. 294 q: `SELECT /*+ MERGE_JOIN(l,r) */ l.pk, r.pk FROM one_pk_three_idx l JOIN one_pk_three_idx r ON l.pk=r.v1 WHERE l.pk = 1`, 295 types: []plan.JoinType{plan.JoinTypeMerge}, 296 mergeCompares: []string{"(r.v1 = l.pk)"}, 297 exp: []sql.Row{{1, 4}}, 298 }, 299 }, 300 }, 301 { 302 name: "merge join keyless index", 303 setup: []string{ 304 "CREATE table xy (x int, y int, index yx_idx(y,x));", 305 "create table rs (r int, s int, index s_idx(s));", 306 "insert into xy values (1,0), (2,1), (0,8), (3,7), (5,4), (4,0);", 307 "insert into rs values (0,0),(2,3),(3,0), (4,8), (5,4);", 308 `analyze table xy update histogram on x using data '{"row_count":1000}'`, 309 `analyze table rs update histogram on r using data '{"row_count":1000}'`, 310 }, 311 tests: []JoinPlanTest{ 312 { 313 q: "select /*+ JOIN_ORDER(rs, xy) MERGE_JOIN(rs, xy) */ * from rs join xy on y = s order by 1,3", 314 types: []plan.JoinType{plan.JoinTypeMerge}, 315 exp: []sql.Row{{0, 0, 1, 0}, {0, 0, 4, 0}, {3, 0, 1, 0}, {3, 0, 4, 0}, {4, 8, 0, 8}, {5, 4, 5, 4}}, 316 }, 317 }, 318 }, 319 { 320 name: "partial [lookup] join tests", 321 setup: []string{ 322 "CREATE table xy (x int primary key, y int);", 323 "create table rs (r int primary key, s int);", 324 "CREATE table uv (u int primary key, v int);", 325 "CREATE table ab (a int primary key, b int);", 326 "insert into xy values (1,0), (2,1), (0,2), (3,3);", 327 "insert into rs values (0,0), (1,0), (2,0), (4,4);", 328 "insert into uv values (0,1), (1,1), (2,2), (3,2);", 329 "insert into ab values (0,2), (1,2), (2,2), (3,1);", 330 `analyze table xy update histogram on x using data '{"row_count":100}'`, 331 `analyze table rs update histogram on r using data '{"row_count":100}'`, 332 `analyze table uv update histogram on u using data '{"row_count":100}'`, 333 `analyze table ab update histogram on a using data '{"row_count":100}'`, 334 }, 335 tests: []JoinPlanTest{ 336 { 337 q: "select /*+ LOOKUP_JOIN(ab,xy) JOIN_ORDER(ab,xy) */ * from xy where x = 1 and y in (select a from ab);", 338 types: []plan.JoinType{plan.JoinTypeLookup}, 339 exp: []sql.Row{{1, 0}}, 340 }, 341 { 342 q: "select /*+ LOOKUP_JOIN(xy,ab) */ * from xy where x in (select b from ab where a in (0,1,2));", 343 types: []plan.JoinType{plan.JoinTypeLookup}, 344 exp: []sql.Row{{2, 1}}, 345 }, 346 { 347 // TODO: RIGHT_SEMI_JOIN tuple equalities 348 q: "select /*+ LOOKUP_JOIN(xy,ab) */ * from xy where (x,y) in (select b,a from ab where a in (0,1,2));", 349 types: []plan.JoinType{plan.JoinTypeHash}, 350 exp: []sql.Row{{2, 1}}, 351 }, 352 { 353 q: "select /*+ LOOKUP_JOIN(xy,ab) */ * from xy where x in (select a from ab);", 354 types: []plan.JoinType{plan.JoinTypeSemiLookup}, 355 exp: []sql.Row{{2, 1}, {1, 0}, {0, 2}, {3, 3}}, 356 }, 357 { 358 q: "select /*+ LOOKUP_JOIN(xy,ab) */ * from xy where x in (select a from ab where a in (1,2));", 359 types: []plan.JoinType{plan.JoinTypeLookup}, 360 exp: []sql.Row{{2, 1}, {1, 0}}, 361 }, 362 { 363 q: "select /*+ LOOKUP_JOIN(xy,ab) */* from xy where x in (select a from ab);", 364 types: []plan.JoinType{plan.JoinTypeSemiLookup}, 365 exp: []sql.Row{{2, 1}, {1, 0}, {0, 2}, {3, 3}}, 366 }, 367 { 368 q: "select /*+ LOOKUP_JOIN(xy,ab) MERGE_JOIN(ab,uv) JOIN_ORDER(ab,uv,xy) */ * from xy where EXISTS (select 1 from ab join uv on a = u where x = a);", 369 types: []plan.JoinType{plan.JoinTypeLookup, plan.JoinTypeMerge}, 370 exp: []sql.Row{{2, 1}, {1, 0}, {0, 2}, {3, 3}}, 371 }, 372 { 373 q: "select * from xy where y+1 not in (select u from uv);", 374 types: []plan.JoinType{plan.JoinTypeLeftOuterHashExcludeNulls}, 375 exp: []sql.Row{{3, 3}}, 376 }, 377 { 378 q: "select * from xy where x not in (select u from uv where u not in (select a from ab where a not in (select r from rs where r = 1))) order by 1;", 379 types: []plan.JoinType{plan.JoinTypeLeftOuterHashExcludeNulls, plan.JoinTypeLeftOuterHashExcludeNulls, plan.JoinTypeLeftOuterMerge}, 380 exp: []sql.Row{{0, 2}, {2, 1}, {3, 3}}, 381 }, 382 { 383 q: "select * from xy where x != (select r from rs where r = 1) order by 1;", 384 types: []plan.JoinType{plan.JoinTypeLeftOuterMerge}, 385 exp: []sql.Row{{0, 2}, {2, 1}, {3, 3}}, 386 }, 387 { 388 // anti join will be cross-join-right, be passed non-nil parent row 389 q: "select x,a from ab, (select * from xy where x != (select r from rs where r = 1) order by 1) sq where x = 2 and b = 2 order by 1,2;", 390 types: []plan.JoinType{plan.JoinTypeCrossHash, plan.JoinTypeLeftOuterLookup}, 391 exp: []sql.Row{{2, 0}, {2, 1}, {2, 2}}, 392 }, 393 { 394 // scope and parent row are non-nil 395 q: ` 396 select * from uv where u > ( 397 select x from ab, ( 398 select x from xy where x != ( 399 select r from rs where r = 1 400 ) order by 1 401 ) sq 402 order by 1 limit 1 403 ) 404 order by 1;`, 405 types: []plan.JoinType{plan.JoinTypeSemi, plan.JoinTypeCrossHash, plan.JoinTypeLeftOuterMerge}, 406 exp: []sql.Row{{1, 1}, {2, 2}, {3, 2}}, 407 }, 408 { 409 // cast prevents scope merging 410 q: "select * from xy where x != (select cast(r as signed) from rs where r = 1) order by 1;", 411 types: []plan.JoinType{}, 412 exp: []sql.Row{{0, 2}, {2, 1}, {3, 3}}, 413 }, 414 { 415 // order by will be discarded 416 q: "select * from xy where x != (select r from rs where r = 1 order by 1) order by 1;", 417 types: []plan.JoinType{plan.JoinTypeLeftOuterMerge}, 418 exp: []sql.Row{{0, 2}, {2, 1}, {3, 3}}, 419 }, 420 { 421 // limit prevents scope merging 422 q: "select * from xy where x != (select r from rs where r = 1 limit 1) order by 1;", 423 types: []plan.JoinType{plan.JoinTypeLeftOuterMerge}, 424 exp: []sql.Row{{0, 2}, {2, 1}, {3, 3}}, 425 }, 426 { 427 q: "select * from xy where y-1 in (select u from uv) order by 1;", 428 types: []plan.JoinType{plan.JoinTypeSemiLookup}, 429 exp: []sql.Row{{0, 2}, {2, 1}, {3, 3}}, 430 }, 431 { 432 // semi join will be right-side, be passed non-nil parent row 433 q: "select x,a from ab, (select * from xy where x = (select r from rs where r = 1) order by 1) sq order by 1,2", 434 types: []plan.JoinType{plan.JoinTypeCrossHash, plan.JoinTypeMerge}, 435 exp: []sql.Row{{1, 0}, {1, 1}, {1, 2}, {1, 3}}, 436 }, 437 //{ 438 // scope and parent row are non-nil 439 // TODO: subquery alias unable to track parent row from a different scope 440 // q: ` 441 //select * from uv where u > ( 442 // select x from ab, ( 443 // select x from xy where x = ( 444 // select r from rs where r = 1 445 // ) order by 1 446 // ) sq 447 // order by 1 limit 1 448 //) 449 //order by 1;`, 450 //types: []plan.JoinType{plan.JoinTypeCrossHash, plan.JoinTypeLookup}, 451 //exp: []sql.Row{{2, 2}, {3, 2}}, 452 //}, 453 { 454 q: "select * from xy where y-1 in (select cast(u as signed) from uv) order by 1;", 455 types: []plan.JoinType{}, 456 exp: []sql.Row{{0, 2}, {2, 1}, {3, 3}}, 457 }, 458 { 459 q: "select * from xy where y-1 in (select u from uv order by 1) order by 1;", 460 types: []plan.JoinType{plan.JoinTypeSemiLookup}, 461 exp: []sql.Row{{0, 2}, {2, 1}, {3, 3}}, 462 }, 463 { 464 q: "select * from xy where y-1 in (select u from uv order by 1 limit 1) order by 1;", 465 types: []plan.JoinType{plan.JoinTypeHash}, 466 exp: []sql.Row{{2, 1}}, 467 }, 468 { 469 q: "select * from xy where x in (select u from uv join ab on u = a and a = 2) order by 1;", 470 types: []plan.JoinType{plan.JoinTypeHash, plan.JoinTypeMerge}, 471 exp: []sql.Row{{2, 1}}, 472 }, 473 { 474 // group by doesn't transform 475 q: "select * from xy where y-1 in (select u from uv group by v having v = 2 order by 1) order by 1;", 476 types: []plan.JoinType{plan.JoinTypeSemi}, 477 exp: []sql.Row{{3, 3}}, 478 }, 479 { 480 // window doesn't transform 481 q: "select * from xy where y-1 in (select row_number() over (order by v) from uv) order by 1;", 482 types: []plan.JoinType{}, 483 exp: []sql.Row{{0, 2}, {3, 3}}, 484 }, 485 }, 486 }, 487 { 488 name: "empty join tests", 489 setup: []string{ 490 "CREATE table xy (x int primary key, y int);", 491 "CREATE table uv (u int primary key, v int);", 492 "insert into xy values (1,0), (2,1), (0,2), (3,3);", 493 "insert into uv values (0,1), (1,1), (2,2), (3,2);", 494 }, 495 tests: []JoinPlanTest{ 496 { 497 q: "select * from xy where y-1 = (select u from uv limit 1 offset 5);", 498 types: []plan.JoinType{plan.JoinTypeSemi}, 499 exp: []sql.Row{}, 500 }, 501 { 502 q: "select * from xy where x != (select u from uv limit 1 offset 5);", 503 types: []plan.JoinType{plan.JoinTypeAnti}, 504 exp: []sql.Row{}, 505 }, 506 }, 507 }, 508 { 509 name: "unnest with scope filters", 510 setup: []string{ 511 "create table ab (a int primary key, b int);", 512 "create table rs (r int primary key, s int);", 513 "CREATE table xy (x int primary key, y int);", 514 "CREATE table uv (u int primary key, v int);", 515 "insert into ab values (0,2), (1,2), (2,2), (3,1);", 516 "insert into rs values (0,0), (1,0), (2,0), (4,4), (5,4);", 517 "insert into xy values (1,0), (2,1), (0,2), (3,3);", 518 "insert into uv values (0,1), (1,1), (2,2), (3,2);", 519 `analyze table xy update histogram on x using data '{"row_count":100}'`, 520 `analyze table rs update histogram on r using data '{"row_count":100}'`, 521 `analyze table uv update histogram on u using data '{"row_count":100}'`, 522 `analyze table ab update histogram on a using data '{"row_count":100}'`, 523 }, 524 tests: []JoinPlanTest{ 525 { 526 q: ` 527 SELECT x 528 FROM xy 529 WHERE EXISTS (SELECT count(v) AS count_1 530 FROM uv 531 WHERE y = v and v = 1 GROUP BY v 532 HAVING count(v) >= 1)`, 533 types: []plan.JoinType{}, 534 exp: []sql.Row{{2}}, 535 }, 536 { 537 q: "select * from xy where y-1 = (select u from uv where v = 2 order by 1 limit 1);", 538 types: []plan.JoinType{plan.JoinTypeHash}, 539 exp: []sql.Row{{3, 3}}, 540 }, 541 { 542 q: "select * from xy where x != (select u from uv where v = 2 order by 1 limit 1) order by 1;", 543 types: []plan.JoinType{plan.JoinTypeLeftOuterMerge}, 544 exp: []sql.Row{{0, 2}, {1, 0}, {3, 3}}, 545 }, 546 { 547 q: "select * from xy where x != (select distinct u from uv where v = 2 order by 1 limit 1) order by 1;", 548 types: []plan.JoinType{plan.JoinTypeLeftOuterMerge}, 549 exp: []sql.Row{{0, 2}, {1, 0}, {3, 3}}, 550 }, 551 { 552 q: "select * from xy where (x,y+1) = (select u,v from uv where v = 2 order by 1 limit 1) order by 1;", 553 types: []plan.JoinType{plan.JoinTypeHash}, 554 exp: []sql.Row{{2, 1}}, 555 }, 556 { 557 q: "select * from xy where x in (select cnt from (select count(u) as cnt from uv group by v having cnt > 0) sq) order by 1,2;", 558 types: []plan.JoinType{plan.JoinTypeLookup}, 559 exp: []sql.Row{{2, 1}}, 560 }, 561 { 562 q: `SELECT /*+ LOOKUP_JOIN(xy, alias2) LOOKUP_JOIN(xy, alias1) JOIN_ORDER(xy, alias2, alias1) */ * FROM xy WHERE ( 563 EXISTS (SELECT * FROM xy Alias1 WHERE Alias1.x = (xy.x + 1)) 564 AND EXISTS (SELECT * FROM uv Alias2 WHERE Alias2.u = (xy.x + 2)));`, 565 // These should both be JoinTypeSemiLookup, but for https://github.com/dolthub/go-mysql-server/issues/1893 566 types: []plan.JoinType{plan.JoinTypeSemiLookup, plan.JoinTypeSemiLookup}, 567 exp: []sql.Row{{0, 2}, {1, 0}}, 568 }, 569 { 570 q: `SELECT * 571 FROM ab A0 572 WHERE EXISTS ( 573 SELECT U0.a 574 FROM 575 ( 576 ab U0 577 LEFT OUTER JOIN 578 rs U1 579 ON (U0.a = U1.s) 580 ) 581 WHERE (U1.s IS NULL AND U0.a = A0.a) 582 );`, 583 types: []plan.JoinType{plan.JoinTypeLookup, plan.JoinTypeLeftOuterHash}, 584 exp: []sql.Row{ 585 {1, 2}, 586 {2, 2}, 587 {3, 1}, 588 }, 589 }, 590 { 591 q: `select * from xy where exists (select * from uv) and x = 0`, 592 types: []plan.JoinType{plan.JoinTypeCross}, 593 exp: []sql.Row{{0, 2}}, 594 }, 595 { 596 q: ` 597 select x from xy where 598 not exists (select a from ab where a = x and a = 1) and 599 not exists (select a from ab where a = x and a = 2)`, 600 types: []plan.JoinType{plan.JoinTypeLeftOuterHashExcludeNulls, plan.JoinTypeLeftOuterMerge}, 601 exp: []sql.Row{{0}, {3}}, 602 }, 603 { 604 q: ` 605 select * from xy where x in ( 606 with recursive tree(s) AS ( 607 SELECT 1 608 ) 609 SELECT u FROM uv, tree where u = s 610 )`, 611 types: []plan.JoinType{plan.JoinTypeLookup, plan.JoinTypeHash}, 612 exp: []sql.Row{{1, 0}}, 613 }, 614 { 615 q: ` 616 SELECT * 617 FROM xy 618 WHERE 619 EXISTS ( 620 SELECT 1 621 FROM ab 622 WHERE 623 xy.x = ab.a AND 624 EXISTS ( 625 SELECT 1 626 FROM uv 627 WHERE 628 ab.a = uv.v 629 ) 630 )`, 631 types: []plan.JoinType{plan.JoinTypeLookup, plan.JoinTypeLookup}, 632 exp: []sql.Row{{1, 0}, {2, 1}}, 633 }, 634 { 635 q: `select * from xy where exists (select * from uv join ab on u = a)`, 636 types: []plan.JoinType{plan.JoinTypeCross, plan.JoinTypeMerge}, 637 exp: []sql.Row{{0, 2}, {1, 0}, {2, 1}, {3, 3}}, 638 }, 639 }, 640 }, 641 { 642 name: "unnest non-equality comparisons", 643 setup: []string{ 644 "CREATE table xy (x int primary key, y int);", 645 "CREATE table uv (u int primary key, v int);", 646 "insert into xy values (1,0), (2,1), (0,2), (3,3);", 647 "insert into uv values (0,1), (1,1), (2,2), (3,2);", 648 }, 649 tests: []JoinPlanTest{ 650 { 651 q: "select /*+ LOOKUP_JOIN(uv, xy) */ * from xy where y >= (select u from uv where u = 2) order by 1;", 652 types: []plan.JoinType{plan.JoinTypeSemiLookup}, 653 exp: []sql.Row{{0, 2}, {3, 3}}, 654 }, 655 { 656 q: "select /*+ LOOKUP_JOIN(uv, xy) */ * from xy where x <= (select u from uv where u = 2) order by 1;", 657 types: []plan.JoinType{plan.JoinTypeSemiLookup}, 658 exp: []sql.Row{{0, 2}, {1, 0}, {2, 1}}, 659 }, 660 { 661 q: "select /*+ LOOKUP_JOIN(uv, xy) */ * from xy where x < (select u from uv where u = 2) order by 1;", 662 types: []plan.JoinType{plan.JoinTypeSemiLookup}, 663 exp: []sql.Row{{0, 2}, {1, 0}}, 664 }, 665 { 666 q: "select /*+ LOOKUP_JOIN(uv,xy) */ * from xy where x > (select u from uv where u = 2) order by 1;", 667 types: []plan.JoinType{plan.JoinTypeSemiLookup}, 668 exp: []sql.Row{{3, 3}}, 669 }, 670 { 671 q: "select /*+ LOOKUP_JOIN(uv, uv_1) */ * from uv where v <=> (select u from uv where u = 2) order by 1;", 672 types: []plan.JoinType{plan.JoinTypeSemiLookup}, 673 exp: []sql.Row{{2, 2}, {3, 2}}, 674 }, 675 }, 676 }, 677 { 678 name: "unnest twice-nested subquery", 679 setup: []string{ 680 "CREATE table xy (x int primary key, y int);", 681 "CREATE table uv (u int primary key, v int);", 682 "insert into xy values (1,0), (2,1), (0,2), (3,3);", 683 "insert into uv values (0,1), (1,1), (2,2), (3,2);", 684 }, 685 tests: []JoinPlanTest{ 686 { 687 q: "select * from xy where x in (select * from (select 1) r where x = 1);", 688 types: []plan.JoinType{plan.JoinTypeSemi}, 689 exp: []sql.Row{{1, 0}}, 690 }, 691 { 692 q: "select * from xy where x in (select 1 where 1 in (select 1 where 1 in (select 1 where x != 2)) and x = 1);", 693 types: []plan.JoinType{plan.JoinTypeSemi, plan.JoinTypeSemi, plan.JoinTypeSemi}, 694 exp: []sql.Row{{1, 0}}, 695 }, 696 { 697 q: "select * from xy where x in (select * from (select 1 where 1 in (select 1 where x != 2)) r where x = 1);", 698 types: []plan.JoinType{plan.JoinTypeSemi, plan.JoinTypeSemi}, 699 exp: []sql.Row{{1, 0}}, 700 }, 701 { 702 q: "select * from xy where x in (select * from (select 1) r);", 703 types: []plan.JoinType{plan.JoinTypeSemi}, 704 exp: []sql.Row{{1, 0}}, 705 }, 706 { 707 q: ` 708 with recursive rec(x) as (select 1 union select 1) 709 select * from xy where x in ( 710 select * from rec 711 );`, 712 types: []plan.JoinType{plan.JoinTypeSemi}, 713 exp: []sql.Row{{1, 0}}, 714 }, 715 { 716 q: ` 717 with recursive rec(x) as ( 718 select 1 719 union 720 select rec.x from rec join xy on rec.x = xy.y 721 ) 722 select * from uv 723 where u in (select * from rec);`, 724 types: []plan.JoinType{plan.JoinTypeSemi, plan.JoinTypeHash}, 725 exp: []sql.Row{{1, 1}}, 726 }, 727 { 728 q: "select x+1 as newX, y from xy having y in (select x from xy where newX=1)", 729 types: []plan.JoinType{}, 730 exp: []sql.Row{{1, 2}}, 731 }, 732 { 733 q: "select x, x+1 as newX from xy having x in (select * from (select 1 where 1 in (select 1 where newX != 1)) r where x = 1);", 734 types: []plan.JoinType{}, 735 exp: []sql.Row{{1, 2}}, 736 }, 737 { 738 q: "select * from uv where not exists (select * from xy where u = 1)", 739 exp: []sql.Row{{0, 1}, {2, 2}, {3, 2}}, 740 }, 741 { 742 q: "select * from uv where not exists (select * from xy where not exists (select * from xy where u = 1))", 743 exp: []sql.Row{{1, 1}}, 744 }, 745 { 746 q: "select * from uv where not exists (select * from xy where not exists (select * from xy where u = 1 or v = 2))", 747 exp: []sql.Row{{1, 1}, {2, 2}, {3, 2}}, 748 }, 749 { 750 q: "select * from uv where not exists (select * from xy where v = 1 and not exists (select * from xy where u = 1))", 751 exp: []sql.Row{{1, 1}, {2, 2}, {3, 2}}, 752 }, 753 { 754 q: "select * from uv where not exists (select * from xy where not exists (select * from xy where not(u = 1)))", 755 exp: []sql.Row{{0, 1}, {2, 2}, {3, 2}}, 756 }, 757 }, 758 }, 759 { 760 name: "convert semi to inner join", 761 setup: []string{ 762 "CREATE table xy (x int, y int, primary key(x,y));", 763 "CREATE table uv (u int primary key, v int);", 764 "CREATE table ab (a int primary key, b int);", 765 "insert into xy values (1,0), (2,1), (0,2), (3,3);", 766 "insert into uv values (0,1), (1,1), (2,2), (3,2);", 767 "insert into ab values (0,2), (1,2), (2,2), (3,1);", 768 `analyze table xy update histogram on x using data '{"row_count":100}'`, 769 `analyze table uv update histogram on u using data '{"row_count":100}'`, 770 `analyze table ab update histogram on a using data '{"row_count":100}'`, 771 }, 772 tests: []JoinPlanTest{ 773 { 774 q: "select * from xy where x in (select u from uv join ab on u = a and a = 2) order by 1;", 775 types: []plan.JoinType{plan.JoinTypeLookup, plan.JoinTypeMerge}, 776 exp: []sql.Row{{2, 1}}, 777 }, 778 { 779 q: `select x from xy where x in ( 780 select (select u from uv where u = sq.a) 781 from (select a from ab) sq);`, 782 types: []plan.JoinType{}, 783 exp: []sql.Row{{0}, {1}, {2}, {3}}, 784 }, 785 { 786 q: "select * /*+ LOOKUP_JOIN(xy,uv) */ from xy where y >= (select u from uv where u = 2) order by 1;", 787 types: []plan.JoinType{plan.JoinTypeSemiLookup}, 788 exp: []sql.Row{{0, 2}, {3, 3}}, 789 }, 790 { 791 q: "select * /*+ LOOKUP_JOIN(xy,uv) */ from xy where x <= (select u from uv where u = 2) order by 1;", 792 types: []plan.JoinType{plan.JoinTypeSemiLookup}, 793 exp: []sql.Row{{0, 2}, {1, 0}, {2, 1}}, 794 }, 795 { 796 q: "select /*+ LOOKUP_JOIN(xy,uv) */ * from xy where x < (select u from uv where u = 2) order by 1;", 797 types: []plan.JoinType{plan.JoinTypeSemiLookup}, 798 exp: []sql.Row{{0, 2}, {1, 0}}, 799 }, 800 { 801 q: "select /*+ LOOKUP_JOIN(xy,uv) */ * from xy where x > (select u from uv where u = 2) order by 1;", 802 types: []plan.JoinType{plan.JoinTypeSemiLookup}, 803 exp: []sql.Row{{3, 3}}, 804 }, 805 { 806 q: "select /*+ LOOKUP_JOIN(uv, uv_1) */ * from uv where v <=> (select u from uv where u = 2) order by 1;", 807 types: []plan.JoinType{plan.JoinTypeSemiLookup}, 808 exp: []sql.Row{{2, 2}, {3, 2}}, 809 }, 810 }, 811 }, 812 { 813 name: "convert anti to left join", 814 setup: []string{ 815 "CREATE table xy (x int, y int, primary key(x,y));", 816 "CREATE table uv (u int primary key, v int);", 817 "create table empty_tbl (a int, b int);", 818 "insert into xy values (1,0), (2,1), (0,2), (3,3);", 819 "insert into uv values (0,1), (1,1), (2,2), (3,2);", 820 }, 821 // write a bunch of left joins and make sure they are converted to anti joins 822 tests: []JoinPlanTest{ 823 { 824 q: "select /*+ HASH_JOIN(xy,empty_tbl) */ * from xy where x not in (select a from empty_tbl) order by x", 825 types: []plan.JoinType{plan.JoinTypeLeftOuterHashExcludeNulls}, 826 exp: []sql.Row{ 827 {0, 2}, 828 {1, 0}, 829 {2, 1}, 830 {3, 3}, 831 }, 832 }, 833 { 834 q: "select /*+ HASH_JOIN(xy,uv) */ * from xy where x not in (select v from uv) order by x", 835 types: []plan.JoinType{plan.JoinTypeLeftOuterHashExcludeNulls}, 836 exp: []sql.Row{ 837 {0, 2}, 838 {3, 3}, 839 }, 840 }, 841 { 842 q: "select /*+ HASH_JOIN(xy,uv) */ * from xy where x not in (select v from uv where u = 2) order by x", 843 types: []plan.JoinType{plan.JoinTypeLeftOuterHashExcludeNulls}, 844 exp: []sql.Row{ 845 {0, 2}, 846 {1, 0}, 847 {3, 3}, 848 }, 849 }, 850 { 851 q: "select /*+ HASH_JOIN(xy,uv) */ * from xy where x != (select v from uv where u = 2) order by x", 852 types: []plan.JoinType{plan.JoinTypeLeftOuterHashExcludeNulls}, 853 exp: []sql.Row{ 854 {0, 2}, 855 {1, 0}, 856 {3, 3}, 857 }, 858 }, 859 { 860 q: "select * from xy where not exists (select * from empty_tbl) order by x", 861 types: []plan.JoinType{plan.JoinTypeLeftOuter}, 862 exp: []sql.Row{ 863 {0, 2}, 864 {1, 0}, 865 {2, 1}, 866 {3, 3}, 867 }, 868 }, 869 { 870 q: "select * from xy where not exists (select * from empty_tbl) and x is not null order by x", 871 types: []plan.JoinType{plan.JoinTypeLeftOuter}, 872 exp: []sql.Row{ 873 {0, 2}, 874 {1, 0}, 875 {2, 1}, 876 {3, 3}, 877 }, 878 }, 879 { 880 q: "select /*+ MERGE_JOIN(xy,uv) */ * from xy where x not in (select u from uv WHERE u = 2) order by x", 881 types: []plan.JoinType{plan.JoinTypeLeftOuterMerge}, 882 exp: []sql.Row{ 883 {0, 2}, 884 {1, 0}, 885 {3, 3}, 886 }, 887 }, 888 { 889 q: "select /*+ LEFT_OUTER_LOOKUP_JOIN(xy,uv) */ * from xy where x not in (select u from uv WHERE u = 2) order by x", 890 types: []plan.JoinType{plan.JoinTypeLeftOuterLookup}, 891 exp: []sql.Row{ 892 {0, 2}, 893 {1, 0}, 894 {3, 3}, 895 }, 896 }, 897 }, 898 }, 899 { 900 name: "join concat tests", 901 setup: []string{ 902 "CREATE table xy (x int primary key, y int);", 903 "CREATE table uv (u int primary key, v int);", 904 "insert into xy values (1,0), (2,1), (0,2), (3,3);", 905 "insert into uv values (0,1), (1,1), (2,2), (3,2);", 906 `analyze table xy update histogram on x using data '{"row_count":100}'`, 907 `analyze table uv update histogram on u using data '{"row_count":100}'`, 908 }, 909 tests: []JoinPlanTest{ 910 { 911 q: "select x, u from xy inner join uv on u+1 = x OR u+2 = x OR u+3 = x;", 912 types: []plan.JoinType{plan.JoinTypeLookup}, 913 exp: []sql.Row{{3, 0}, {2, 0}, {1, 0}, {3, 1}, {2, 1}, {3, 2}}, 914 }, 915 }, 916 }, 917 { 918 name: "join order hint", 919 setup: []string{ 920 "CREATE table xy (x int primary key, y int);", 921 "CREATE table uv (u int primary key, v int);", 922 "insert into xy values (1,0), (2,1), (0,2), (3,3);", 923 "insert into uv values (0,1), (1,1), (2,2), (3,2);", 924 `analyze table xy update histogram on x using data '{"row_count":100}'`, 925 `analyze table uv update histogram on u using data '{"row_count":100}'`, 926 }, 927 tests: []JoinPlanTest{ 928 { 929 q: "select /*+ JOIN_ORDER(b, c, a) */ 1 from xy a join xy b on a.x+3 = b.x join xy c on a.x+3 = c.x and a.x+3 = b.x", 930 order: [][]string{{"b", "c", "a"}}, 931 }, 932 { 933 q: "select /*+ JOIN_ORDER(a, c, b) */ 1 from xy a join xy b on a.x+3 = b.x join xy c on a.x+3 = c.x and a.x+3 = b.x", 934 order: [][]string{{"a", "c", "b"}}, 935 }, 936 { 937 q: "select /*+ JOIN_ORDER(a,c,b) */ 1 from xy a join xy b on a.x+3 = b.x WHERE EXISTS (select 1 from uv c where c.u = a.x+2)", 938 order: [][]string{{"a", "c", "b"}}, 939 }, 940 { 941 q: "select /*+ JOIN_ORDER(b,c,a) */ 1 from xy a join xy b on a.x+3 = b.x WHERE EXISTS (select 1 from uv c where c.u = a.x+2)", 942 order: [][]string{{"b", "c", "a"}}, 943 }, 944 { 945 q: "select /*+ JOIN_ORDER(b,c,a) */ 1 from xy a join xy b on a.x+3 = b.x WHERE a.x in (select u from uv c)", 946 order: [][]string{{"b", "c", "a"}}, 947 }, 948 }, 949 }, 950 { 951 name: "join op hint", 952 setup: []string{ 953 "CREATE table xy (x int primary key, y int);", 954 "CREATE table uv (u int primary key, v int, key(v));", 955 "insert into xy values (1,0), (2,1), (0,2), (3,3);", 956 "insert into uv values (0,1), (1,1), (2,2), (3,2);", 957 }, 958 tests: []JoinPlanTest{ 959 { 960 q: "select /*+ LOOKUP_JOIN(xy,uv) */ 1 from xy join uv on x = u", 961 types: []plan.JoinType{plan.JoinTypeLookup}, 962 }, 963 { 964 q: "select /*+ MERGE_JOIN(xy,uv) */ 1 from xy join uv on x = u", 965 types: []plan.JoinType{plan.JoinTypeMerge}, 966 }, 967 { 968 q: "select /*+ INNER_JOIN(xy,uv) */ 1 from xy join uv on x = u", 969 types: []plan.JoinType{plan.JoinTypeInner}, 970 }, 971 { 972 q: "select /*+ HASH_JOIN(xy,uv) */ 1 from xy join uv on x = u", 973 types: []plan.JoinType{plan.JoinTypeHash}, 974 }, 975 { 976 q: "select /*+ JOIN_ORDER(a,b,c) HASH_JOIN(a,b) HASH_JOIN(b,c) */ 1 from xy a join uv b on a.x = b.u join xy c on b.u = c.x", 977 types: []plan.JoinType{plan.JoinTypeHash, plan.JoinTypeHash}, 978 order: [][]string{{"a", "b", "c"}}, 979 }, 980 { 981 q: "select /*+ JOIN_ORDER(b,c,a) LOOKUP_JOIN(b,a) HASH_JOIN(b,c) */ 1 from xy a join uv b on a.x = b.u join xy c on b.u = c.x", 982 types: []plan.JoinType{plan.JoinTypeLookup, plan.JoinTypeHash}, 983 }, 984 { 985 q: "select /*+ LOOKUP_JOIN(b,a) MERGE_JOIN(b,c) */ 1 from xy a join uv b on a.x = b.u join xy c on b.u = c.x", 986 types: []plan.JoinType{plan.JoinTypeLookup, plan.JoinTypeMerge}, 987 }, 988 { 989 q: "select /*+ JOIN_ORDER(b,c,a) LOOKUP_JOIN(b,a) MERGE_JOIN(b,c) */ 1 from xy a join uv b on a.x = b.u join xy c on b.u = c.x", 990 types: []plan.JoinType{plan.JoinTypeLookup, plan.JoinTypeMerge}, 991 order: [][]string{{"b", "c", "a"}}, 992 }, 993 { 994 q: "select /*+ JOIN_ORDER(a,b,c) LOOKUP_JOIN(b,a) HASH_JOIN(b,c) */ 1 from xy a join uv b on a.x = b.u join xy c on b.u = c.x", 995 types: []plan.JoinType{plan.JoinTypeHash, plan.JoinTypeLookup}, 996 order: [][]string{{"a", "b", "c"}}, 997 }, 998 { 999 q: "select /*+ JOIN_ORDER(c,a,b) MERGE_JOIN(a,b) HASH_JOIN(b,c) */ 1 from xy a join uv b on a.x = b.u join xy c on b.u = c.x", 1000 types: []plan.JoinType{plan.JoinTypeHash, plan.JoinTypeMerge}, 1001 order: [][]string{{"c", "a", "b"}}, 1002 }, 1003 { 1004 q: ` 1005 select /*+ JOIN_ORDER(d,c,b,a) MERGE_JOIN(d,c) MERGE_JOIN(b,a) INNER_JOIN(c,a)*/ 1 1006 from xy a 1007 join uv b on a.x = b.u 1008 join xy c on a.x = c.x 1009 join uv d on d.u = c.x`, 1010 types: []plan.JoinType{plan.JoinTypeInner, plan.JoinTypeMerge, plan.JoinTypeMerge}, 1011 order: [][]string{{"d", "c", "b", "a"}}, 1012 }, 1013 { 1014 q: ` 1015 select /*+ JOIN_ORDER(a,b,c,d) LOOKUP_JOIN(d,c) MERGE_JOIN(b,a) HASH_JOIN(c,a)*/ 1 1016 from xy a 1017 join uv b on a.x = b.u 1018 join xy c on a.x = c.x 1019 join uv d on d.u = c.x`, 1020 types: []plan.JoinType{plan.JoinTypeLookup, plan.JoinTypeHash, plan.JoinTypeMerge}, 1021 order: [][]string{{"a", "b", "c", "d"}}, 1022 }, 1023 { 1024 q: "select /*+ LOOKUP_JOIN(xy,uv) */ 1 from xy where x not in (select u from uv)", 1025 // This should be JoinTypeSemiLookup, but for https://github.com/dolthub/go-mysql-server/issues/1894 1026 types: []plan.JoinType{plan.JoinTypeLeftOuterLookup}, 1027 }, 1028 { 1029 q: "select /*+ ANTI_JOIN(xy,uv) */ 1 from xy where x not in (select u from uv)", 1030 types: []plan.JoinType{plan.JoinTypeAnti}, 1031 }, 1032 { 1033 q: "select /*+ LOOKUP_JOIN(xy,uv) */ 1 from xy where x in (select u from uv)", 1034 types: []plan.JoinType{plan.JoinTypeSemiLookup}, 1035 }, 1036 { 1037 q: "select /*+ SEMI_JOIN(xy,uv) */ 1 from xy where x in (select u from uv)", 1038 types: []plan.JoinType{plan.JoinTypeSemi}, 1039 }, 1040 { 1041 q: "select /*+ LOOKUP_JOIN(s,uv) */ 1 from xy s where x in (select u from uv)", 1042 types: []plan.JoinType{plan.JoinTypeSemiLookup}, 1043 }, 1044 { 1045 q: "select /*+ SEMI_JOIN(s,uv) */ 1 from xy s where x in (select u from uv)", 1046 types: []plan.JoinType{plan.JoinTypeSemi}, 1047 }, 1048 }, 1049 }, 1050 { 1051 // This is a regression test for https://github.com/dolthub/go-mysql-server/pull/1889. 1052 // We should always prefer a more specific index over a less specific index for lookups. 1053 name: "lookup join multiple indexes", 1054 setup: []string{ 1055 "create table lhs (a int, b int, c int);", 1056 "create table rhs (a int, b int, c int, d int, index a_idx(a), index abcd_idx(a,b,c,d));", 1057 "insert into lhs values (0, 0, 0), (0, 0, 1), (0, 1, 1), (1, 1, 1);", 1058 "insert into rhs values " + 1059 "(0, 0, 0, 0)," + 1060 "(0, 0, 0, 1)," + 1061 "(0, 0, 1, 0)," + 1062 "(0, 0, 1, 1)," + 1063 "(0, 1, 0, 0)," + 1064 "(0, 1, 0, 1)," + 1065 "(0, 1, 1, 0)," + 1066 "(0, 1, 1, 1)," + 1067 "(1, 0, 0, 0)," + 1068 "(1, 0, 0, 1)," + 1069 "(1, 0, 1, 0)," + 1070 "(1, 0, 1, 1)," + 1071 "(1, 1, 0, 0)," + 1072 "(1, 1, 0, 1)," + 1073 "(1, 1, 1, 0)," + 1074 "(1, 1, 1, 1);", 1075 }, 1076 tests: []JoinPlanTest{ 1077 { 1078 q: "select /*+ LOOKUP_JOIN(lhs, rhs) */ rhs.* from lhs left join rhs on lhs.a = rhs.a and lhs.b = rhs.b and lhs.c = rhs.c", 1079 types: []plan.JoinType{plan.JoinTypeLeftOuterLookup}, 1080 indexes: []string{"abcd_idx"}, 1081 exp: []sql.Row{ 1082 {0, 0, 0, 0}, 1083 {0, 0, 0, 1}, 1084 {0, 0, 1, 0}, 1085 {0, 0, 1, 1}, 1086 {0, 1, 1, 0}, 1087 {0, 1, 1, 1}, 1088 {1, 1, 1, 0}, 1089 {1, 1, 1, 1}, 1090 }, 1091 }, 1092 }, 1093 }, 1094 { 1095 name: "indexed range join", 1096 setup: []string{ 1097 "create table vals (val int unique key);", 1098 "create table ranges (min int unique key, max int, unique key(min,max));", 1099 "insert into vals values (null), (0), (1), (2), (3), (4), (5), (6);", 1100 "insert into ranges values (null,1), (0,2), (1,3), (2,4), (3,5), (4,6);", 1101 }, 1102 tests: []JoinPlanTest{ 1103 { 1104 q: "select * from vals join ranges on val between min and max", 1105 types: []plan.JoinType{plan.JoinTypeRangeHeap}, 1106 exp: []sql.Row{ 1107 {0, 0, 2}, 1108 {1, 0, 2}, 1109 {1, 1, 3}, 1110 {2, 0, 2}, 1111 {2, 1, 3}, 1112 {2, 2, 4}, 1113 {3, 1, 3}, 1114 {3, 2, 4}, 1115 {3, 3, 5}, 1116 {4, 2, 4}, 1117 {4, 3, 5}, 1118 {4, 4, 6}, 1119 {5, 3, 5}, 1120 {5, 4, 6}, 1121 {6, 4, 6}, 1122 }, 1123 }, 1124 { 1125 q: "select * from vals join ranges on val > min and val < max", 1126 types: []plan.JoinType{plan.JoinTypeRangeHeap}, 1127 exp: []sql.Row{ 1128 {1, 0, 2}, 1129 {2, 1, 3}, 1130 {3, 2, 4}, 1131 {4, 3, 5}, 1132 {5, 4, 6}, 1133 }, 1134 }, 1135 { 1136 q: "select * from vals join ranges on val >= min and val < max", 1137 types: []plan.JoinType{plan.JoinTypeRangeHeap}, 1138 exp: []sql.Row{ 1139 {0, 0, 2}, 1140 {1, 0, 2}, 1141 {1, 1, 3}, 1142 {2, 1, 3}, 1143 {2, 2, 4}, 1144 {3, 2, 4}, 1145 {3, 3, 5}, 1146 {4, 3, 5}, 1147 {4, 4, 6}, 1148 {5, 4, 6}, 1149 }, 1150 }, 1151 { 1152 q: "select * from vals join ranges on val > min and val <= max", 1153 types: []plan.JoinType{plan.JoinTypeRangeHeap}, 1154 exp: []sql.Row{ 1155 {1, 0, 2}, 1156 {2, 0, 2}, 1157 {2, 1, 3}, 1158 {3, 1, 3}, 1159 {3, 2, 4}, 1160 {4, 2, 4}, 1161 {4, 3, 5}, 1162 {5, 3, 5}, 1163 {5, 4, 6}, 1164 {6, 4, 6}, 1165 }, 1166 }, 1167 { 1168 q: "select * from vals join ranges on val >= min and val <= max", 1169 types: []plan.JoinType{plan.JoinTypeRangeHeap}, 1170 exp: []sql.Row{ 1171 {0, 0, 2}, 1172 {1, 0, 2}, 1173 {1, 1, 3}, 1174 {2, 0, 2}, 1175 {2, 1, 3}, 1176 {2, 2, 4}, 1177 {3, 1, 3}, 1178 {3, 2, 4}, 1179 {3, 3, 5}, 1180 {4, 2, 4}, 1181 {4, 3, 5}, 1182 {4, 4, 6}, 1183 {5, 3, 5}, 1184 {5, 4, 6}, 1185 {6, 4, 6}, 1186 }, 1187 }, 1188 { 1189 q: "select * from vals join ranges on val >= min and val <= max where min >= 2", 1190 types: []plan.JoinType{plan.JoinTypeInner}, 1191 exp: []sql.Row{ 1192 {2, 2, 4}, 1193 {3, 2, 4}, 1194 {3, 3, 5}, 1195 {4, 2, 4}, 1196 {4, 3, 5}, 1197 {4, 4, 6}, 1198 {5, 3, 5}, 1199 {5, 4, 6}, 1200 {6, 4, 6}, 1201 }, 1202 }, 1203 { 1204 q: "select * from vals join ranges on val between min and max where min >= 2 and max <= 5", 1205 types: []plan.JoinType{plan.JoinTypeInner}, 1206 exp: []sql.Row{ 1207 {2, 2, 4}, 1208 {3, 2, 4}, 1209 {3, 3, 5}, 1210 {4, 2, 4}, 1211 {4, 3, 5}, 1212 {5, 3, 5}, 1213 }, 1214 }, 1215 { 1216 q: "select * from vals join (select max, min from ranges) ranges on val between min and max where min >= 2 and max <= 5", 1217 types: []plan.JoinType{plan.JoinTypeRangeHeap}, 1218 exp: []sql.Row{ 1219 {2, 4, 2}, 1220 {3, 4, 2}, 1221 {3, 5, 3}, 1222 {4, 4, 2}, 1223 {4, 5, 3}, 1224 {5, 5, 3}, 1225 }, 1226 }, 1227 { 1228 q: "select * from vals join (select * from ranges where min >= 2 and max <= 5) ranges on val between min and max", 1229 types: []plan.JoinType{plan.JoinTypeRangeHeap}, 1230 exp: []sql.Row{ 1231 {2, 2, 4}, 1232 {3, 2, 4}, 1233 {3, 3, 5}, 1234 {4, 2, 4}, 1235 {4, 3, 5}, 1236 {5, 3, 5}, 1237 }, 1238 }, 1239 { 1240 q: "select * from vals join (select * from ranges where min >= 2 and max <= 5 limit 1) ranges on val between min and max", 1241 types: []plan.JoinType{plan.JoinTypeRangeHeap}, 1242 exp: []sql.Row{ 1243 {2, 2, 4}, 1244 {3, 2, 4}, 1245 {4, 2, 4}, 1246 }, 1247 }, 1248 { 1249 q: "select * from vals join (select * from ranges where min >= 2 and max <= 5) ranges on val between min and max limit 1", 1250 types: []plan.JoinType{plan.JoinTypeRangeHeap}, 1251 exp: []sql.Row{ 1252 {2, 2, 4}, 1253 }, 1254 }, 1255 { 1256 q: "select * from vals join (select * from ranges where min >= 2 and max <= 5 order by min, max asc) ranges on val between min and max", 1257 types: []plan.JoinType{plan.JoinTypeRangeHeap}, 1258 exp: []sql.Row{ 1259 {2, 2, 4}, 1260 {3, 2, 4}, 1261 {3, 3, 5}, 1262 {4, 2, 4}, 1263 {4, 3, 5}, 1264 {5, 3, 5}, 1265 }, 1266 }, 1267 { 1268 q: "select * from vals join (select distinct * from ranges where min >= 2 and max <= 5) ranges on val between min and max", 1269 types: []plan.JoinType{plan.JoinTypeRangeHeap}, 1270 exp: []sql.Row{ 1271 {2, 2, 4}, 1272 {3, 2, 4}, 1273 {4, 2, 4}, 1274 {3, 3, 5}, 1275 {4, 3, 5}, 1276 {5, 3, 5}, 1277 }, 1278 }, 1279 { 1280 q: "select * from vals where exists (select * from vals join ranges on val between min and max where min >= 2 and max <= 5)", 1281 types: []plan.JoinType{plan.JoinTypeCross, plan.JoinTypeInner}, 1282 exp: []sql.Row{ 1283 {nil}, 1284 {0}, 1285 {1}, 1286 {2}, 1287 {3}, 1288 {4}, 1289 {5}, 1290 {6}, 1291 }, 1292 }, 1293 { 1294 q: "select * from vals where exists (select * from ranges where val between min and max limit 1);", 1295 types: []plan.JoinType{plan.JoinTypeSemi}, 1296 exp: []sql.Row{ 1297 {0}, 1298 {1}, 1299 {2}, 1300 {3}, 1301 {4}, 1302 {5}, 1303 {6}, 1304 }, 1305 }, 1306 { 1307 q: "select * from vals where exists (select distinct val from ranges where val between min and max);", 1308 types: []plan.JoinType{plan.JoinTypeSemi}, 1309 exp: []sql.Row{ 1310 {0}, 1311 {1}, 1312 {2}, 1313 {3}, 1314 {4}, 1315 {5}, 1316 {6}, 1317 }, 1318 }, 1319 { 1320 q: "select * from vals where exists (select * from ranges where val between min and max order by 1) order by 1;", 1321 types: []plan.JoinType{plan.JoinTypeSemi}, 1322 exp: []sql.Row{ 1323 {0}, 1324 {1}, 1325 {2}, 1326 {3}, 1327 {4}, 1328 {5}, 1329 {6}, 1330 }, 1331 }, 1332 { 1333 q: "select * from vals where exists (select * from ranges where val between min and max limit 1 offset 1);", 1334 types: []plan.JoinType{}, // This expression cannot be optimized into a join. 1335 exp: []sql.Row{ 1336 {1}, 1337 {2}, 1338 {3}, 1339 {4}, 1340 {5}, 1341 }, 1342 }, 1343 { 1344 q: "select * from vals where exists (select * from ranges where val between min and max having val > 1);", 1345 types: []plan.JoinType{}, 1346 exp: []sql.Row{ 1347 {2}, 1348 {3}, 1349 {4}, 1350 {5}, 1351 {6}, 1352 }, 1353 }, 1354 }, 1355 }, 1356 { 1357 name: "keyless range join", 1358 setup: []string{ 1359 "create table vals (val int)", 1360 "create table ranges (min int, max int)", 1361 "insert into vals values (null), (0), (1), (2), (3), (4), (5), (6)", 1362 "insert into ranges values (null,1), (0,2), (1,3), (2,4), (3,5), (4,6)", 1363 }, 1364 tests: []JoinPlanTest{ 1365 { 1366 q: "select * from vals join ranges on val between min and max", 1367 types: []plan.JoinType{plan.JoinTypeRangeHeap}, 1368 exp: []sql.Row{ 1369 {0, 0, 2}, 1370 {1, 0, 2}, 1371 {1, 1, 3}, 1372 {2, 0, 2}, 1373 {2, 1, 3}, 1374 {2, 2, 4}, 1375 {3, 1, 3}, 1376 {3, 2, 4}, 1377 {3, 3, 5}, 1378 {4, 2, 4}, 1379 {4, 3, 5}, 1380 {4, 4, 6}, 1381 {5, 3, 5}, 1382 {5, 4, 6}, 1383 {6, 4, 6}, 1384 }, 1385 }, 1386 { 1387 q: "select * from vals join ranges on val > min and val < max", 1388 types: []plan.JoinType{plan.JoinTypeRangeHeap}, 1389 exp: []sql.Row{ 1390 {1, 0, 2}, 1391 {2, 1, 3}, 1392 {3, 2, 4}, 1393 {4, 3, 5}, 1394 {5, 4, 6}, 1395 }, 1396 }, 1397 { 1398 q: "select * from vals join ranges on min < val and max > val", 1399 types: []plan.JoinType{plan.JoinTypeRangeHeap}, 1400 exp: []sql.Row{ 1401 {1, 0, 2}, 1402 {2, 1, 3}, 1403 {3, 2, 4}, 1404 {4, 3, 5}, 1405 {5, 4, 6}, 1406 }, 1407 }, 1408 { 1409 q: "select * from vals join ranges on val >= min and val < max", 1410 types: []plan.JoinType{plan.JoinTypeRangeHeap}, 1411 exp: []sql.Row{ 1412 {0, 0, 2}, 1413 {1, 0, 2}, 1414 {1, 1, 3}, 1415 {2, 1, 3}, 1416 {2, 2, 4}, 1417 {3, 2, 4}, 1418 {3, 3, 5}, 1419 {4, 3, 5}, 1420 {4, 4, 6}, 1421 {5, 4, 6}, 1422 }, 1423 }, 1424 { 1425 q: "select * from vals join ranges on val > min and val <= max", 1426 types: []plan.JoinType{plan.JoinTypeRangeHeap}, 1427 exp: []sql.Row{ 1428 {1, 0, 2}, 1429 {2, 0, 2}, 1430 {2, 1, 3}, 1431 {3, 1, 3}, 1432 {3, 2, 4}, 1433 {4, 2, 4}, 1434 {4, 3, 5}, 1435 {5, 3, 5}, 1436 {5, 4, 6}, 1437 {6, 4, 6}, 1438 }, 1439 }, 1440 { 1441 q: "select * from vals join ranges on val >= min and val <= max", 1442 types: []plan.JoinType{plan.JoinTypeRangeHeap}, 1443 exp: []sql.Row{ 1444 {0, 0, 2}, 1445 {1, 0, 2}, 1446 {1, 1, 3}, 1447 {2, 0, 2}, 1448 {2, 1, 3}, 1449 {2, 2, 4}, 1450 {3, 1, 3}, 1451 {3, 2, 4}, 1452 {3, 3, 5}, 1453 {4, 2, 4}, 1454 {4, 3, 5}, 1455 {4, 4, 6}, 1456 {5, 3, 5}, 1457 {5, 4, 6}, 1458 {6, 4, 6}, 1459 }, 1460 }, 1461 { 1462 q: "select * from vals left join ranges on val > min and val < max", 1463 types: []plan.JoinType{plan.JoinTypeLeftOuterRangeHeap}, 1464 exp: []sql.Row{ 1465 {nil, nil, nil}, 1466 {0, nil, nil}, 1467 {1, 0, 2}, 1468 {2, 1, 3}, 1469 {3, 2, 4}, 1470 {4, 3, 5}, 1471 {5, 4, 6}, 1472 {6, nil, nil}, 1473 }, 1474 }, 1475 { 1476 q: "select * from ranges l join ranges r on l.min > r.min and l.min < r.max", 1477 types: []plan.JoinType{plan.JoinTypeRangeHeap}, 1478 exp: []sql.Row{ 1479 {1, 3, 0, 2}, 1480 {2, 4, 1, 3}, 1481 {3, 5, 2, 4}, 1482 {4, 6, 3, 5}, 1483 }, 1484 }, 1485 { 1486 q: "select * from vals left join ranges r1 on val > r1.min and val < r1.max left join ranges r2 on r1.min > r2.min and r1.min < r2.max", 1487 types: []plan.JoinType{plan.JoinTypeLeftOuterRangeHeap, plan.JoinTypeLeftOuterRangeHeap}, 1488 exp: []sql.Row{ 1489 {nil, nil, nil, nil, nil}, 1490 {0, nil, nil, nil, nil}, 1491 {1, 0, 2, nil, nil}, 1492 {2, 1, 3, 0, 2}, 1493 {3, 2, 4, 1, 3}, 1494 {4, 3, 5, 2, 4}, 1495 {5, 4, 6, 3, 5}, 1496 {6, nil, nil, nil, nil}, 1497 }, 1498 }, 1499 { 1500 q: "select * from (select vals.val * 2 as val from vals) as newVals join (select ranges.min * 2 as min, ranges.max * 2 as max from ranges) as newRanges on val > min and val < max;", 1501 types: []plan.JoinType{plan.JoinTypeRangeHeap}, 1502 exp: []sql.Row{ 1503 {2, 0, 4}, 1504 {4, 2, 6}, 1505 {6, 4, 8}, 1506 {8, 6, 10}, 1507 {10, 8, 12}, 1508 }, 1509 }, 1510 { 1511 // This tests that the RangeHeapJoin node functions correctly even if its rows are iterated over multiple times. 1512 q: "select * from (select 1 union select 2) as l left join (select * from vals join ranges on val > min and val < max) as r on max = max", 1513 types: []plan.JoinType{plan.JoinTypeLeftOuter, plan.JoinTypeRangeHeap}, 1514 exp: []sql.Row{ 1515 {1, 1, 0, 2}, 1516 {1, 2, 1, 3}, 1517 {1, 3, 2, 4}, 1518 {1, 4, 3, 5}, 1519 {1, 5, 4, 6}, 1520 {2, 1, 0, 2}, 1521 {2, 2, 1, 3}, 1522 {2, 3, 2, 4}, 1523 {2, 4, 3, 5}, 1524 {2, 5, 4, 6}, 1525 }, 1526 }, 1527 { 1528 q: "select * from vals left join (select * from ranges where 0) as newRanges on val > min and val < max;", 1529 types: []plan.JoinType{plan.JoinTypeLeftOuterRangeHeap}, 1530 exp: []sql.Row{ 1531 {nil, nil, nil}, 1532 {0, nil, nil}, 1533 {1, nil, nil}, 1534 {2, nil, nil}, 1535 {3, nil, nil}, 1536 {4, nil, nil}, 1537 {5, nil, nil}, 1538 {6, nil, nil}, 1539 }, 1540 }, 1541 }, 1542 }, 1543 { 1544 name: "range join vs good lookup join regression test", 1545 setup: []string{ 1546 "create table vals (val int, filter1 int, filter2 int, filter3 int)", 1547 "create table ranges (min int, max int, filter1 int, filter2 int, filter3 int, key filters (filter1, filter2, filter3))", 1548 "insert into vals values (0, 0, 0, 0), " + 1549 "(1, 0, 0, 0), " + 1550 "(2, 0, 0, 0), " + 1551 "(3, 0, 0, 0), " + 1552 "(4, 0, 0, 0), " + 1553 "(5, 0, 0, 0), " + 1554 "(6, 0, 0, 0), " + 1555 "(0, 0, 0, 1), " + 1556 "(1, 0, 0, 1), " + 1557 "(2, 0, 0, 1), " + 1558 "(3, 0, 0, 1), " + 1559 "(4, 0, 0, 1), " + 1560 "(5, 0, 0, 1), " + 1561 "(6, 0, 0, 1), " + 1562 "(0, 0, 1, 0), " + 1563 "(1, 0, 1, 0), " + 1564 "(2, 0, 1, 0), " + 1565 "(3, 0, 1, 0), " + 1566 "(4, 0, 1, 0), " + 1567 "(5, 0, 1, 0), " + 1568 "(6, 0, 1, 0), " + 1569 "(0, 0, 1, 1), " + 1570 "(1, 0, 1, 1), " + 1571 "(2, 0, 1, 1), " + 1572 "(3, 0, 1, 1), " + 1573 "(4, 0, 1, 1), " + 1574 "(5, 0, 1, 1), " + 1575 "(6, 0, 1, 1), " + 1576 "(0, 1, 0, 0), " + 1577 "(1, 1, 0, 0), " + 1578 "(2, 1, 0, 0), " + 1579 "(3, 1, 0, 0), " + 1580 "(4, 1, 0, 0), " + 1581 "(5, 1, 0, 0), " + 1582 "(6, 1, 0, 0), " + 1583 "(0, 1, 0, 1), " + 1584 "(1, 1, 0, 1), " + 1585 "(2, 1, 0, 1), " + 1586 "(3, 1, 0, 1), " + 1587 "(4, 1, 0, 1), " + 1588 "(5, 1, 0, 1), " + 1589 "(6, 1, 0, 1), " + 1590 "(0, 1, 1, 0), " + 1591 "(1, 1, 1, 0), " + 1592 "(2, 1, 1, 0), " + 1593 "(3, 1, 1, 0), " + 1594 "(4, 1, 1, 0), " + 1595 "(5, 1, 1, 0), " + 1596 "(6, 1, 1, 0), " + 1597 "(0, 1, 1, 1), " + 1598 "(1, 1, 1, 1), " + 1599 "(2, 1, 1, 1), " + 1600 "(3, 1, 1, 1), " + 1601 "(4, 1, 1, 1), " + 1602 "(5, 1, 1, 1), " + 1603 "(6, 1, 1, 1);", 1604 "insert into ranges values " + 1605 "(0, 2, 0, 0, 0), " + 1606 "(1, 3, 0, 0, 0), " + 1607 "(2, 4, 0, 0, 0), " + 1608 "(3, 5, 0, 0, 0), " + 1609 "(4, 6, 0, 0, 0), " + 1610 "(0, 2, 0, 0, 1), " + 1611 "(1, 3, 0, 0, 1), " + 1612 "(2, 4, 0, 0, 1), " + 1613 "(3, 5, 0, 0, 1), " + 1614 "(4, 6, 0, 0, 1), " + 1615 "(0, 2, 0, 1, 0), " + 1616 "(1, 3, 0, 1, 0), " + 1617 "(2, 4, 0, 1, 0), " + 1618 "(3, 5, 0, 1, 0), " + 1619 "(4, 6, 0, 1, 0), " + 1620 "(0, 2, 0, 1, 1), " + 1621 "(1, 3, 0, 1, 1), " + 1622 "(2, 4, 0, 1, 1), " + 1623 "(3, 5, 0, 1, 1), " + 1624 "(4, 6, 0, 1, 1), " + 1625 "(0, 2, 1, 0, 0), " + 1626 "(1, 3, 1, 0, 0), " + 1627 "(2, 4, 1, 0, 0), " + 1628 "(3, 5, 1, 0, 0), " + 1629 "(4, 6, 1, 0, 0), " + 1630 "(0, 2, 1, 0, 1), " + 1631 "(1, 3, 1, 0, 1), " + 1632 "(2, 4, 1, 0, 1), " + 1633 "(3, 5, 1, 0, 1), " + 1634 "(4, 6, 1, 0, 1), " + 1635 "(0, 2, 1, 1, 0), " + 1636 "(1, 3, 1, 1, 0), " + 1637 "(2, 4, 1, 1, 0), " + 1638 "(3, 5, 1, 1, 0), " + 1639 "(4, 6, 1, 1, 0), " + 1640 "(0, 2, 1, 1, 1), " + 1641 "(1, 3, 1, 1, 1), " + 1642 "(2, 4, 1, 1, 1), " + 1643 "(3, 5, 1, 1, 1), " + 1644 "(4, 6, 1, 1, 1); ", 1645 }, 1646 tests: []JoinPlanTest{ 1647 { 1648 // Test that a RangeHeapJoin won't be chosen over a LookupJoin with a multiple-column index. 1649 q: "select val, min, max, vals.filter1, vals.filter2, vals.filter3 from vals join ranges on val > min and val < max and vals.filter1 = ranges.filter1 and vals.filter2 = ranges.filter2 and vals.filter3 = ranges.filter3", 1650 types: []plan.JoinType{plan.JoinTypeLookup}, 1651 exp: []sql.Row{ 1652 {1, 0, 2, 0, 0, 0}, 1653 {2, 1, 3, 0, 0, 0}, 1654 {3, 2, 4, 0, 0, 0}, 1655 {4, 3, 5, 0, 0, 0}, 1656 {5, 4, 6, 0, 0, 0}, 1657 {1, 0, 2, 0, 0, 1}, 1658 {2, 1, 3, 0, 0, 1}, 1659 {3, 2, 4, 0, 0, 1}, 1660 {4, 3, 5, 0, 0, 1}, 1661 {5, 4, 6, 0, 0, 1}, 1662 {1, 0, 2, 0, 1, 0}, 1663 {2, 1, 3, 0, 1, 0}, 1664 {3, 2, 4, 0, 1, 0}, 1665 {4, 3, 5, 0, 1, 0}, 1666 {5, 4, 6, 0, 1, 0}, 1667 {1, 0, 2, 0, 1, 1}, 1668 {2, 1, 3, 0, 1, 1}, 1669 {3, 2, 4, 0, 1, 1}, 1670 {4, 3, 5, 0, 1, 1}, 1671 {5, 4, 6, 0, 1, 1}, 1672 {1, 0, 2, 1, 0, 0}, 1673 {2, 1, 3, 1, 0, 0}, 1674 {3, 2, 4, 1, 0, 0}, 1675 {4, 3, 5, 1, 0, 0}, 1676 {5, 4, 6, 1, 0, 0}, 1677 {1, 0, 2, 1, 0, 1}, 1678 {2, 1, 3, 1, 0, 1}, 1679 {3, 2, 4, 1, 0, 1}, 1680 {4, 3, 5, 1, 0, 1}, 1681 {5, 4, 6, 1, 0, 1}, 1682 {1, 0, 2, 1, 1, 0}, 1683 {2, 1, 3, 1, 1, 0}, 1684 {3, 2, 4, 1, 1, 0}, 1685 {4, 3, 5, 1, 1, 0}, 1686 {5, 4, 6, 1, 1, 0}, 1687 {1, 0, 2, 1, 1, 1}, 1688 {2, 1, 3, 1, 1, 1}, 1689 {3, 2, 4, 1, 1, 1}, 1690 {4, 3, 5, 1, 1, 1}, 1691 {5, 4, 6, 1, 1, 1}, 1692 }, 1693 }, 1694 }, 1695 }, 1696 } 1697 1698 func TestJoinPlanning(t *testing.T, harness Harness) { 1699 for _, tt := range JoinPlanningTests { 1700 t.Run(tt.name, func(t *testing.T) { 1701 harness.Setup([]setup.SetupScript{setup.MydbData[0], tt.setup}) 1702 e := mustNewEngine(t, harness) 1703 defer e.Close() 1704 for _, tt := range tt.tests { 1705 if tt.types != nil { 1706 evalJoinTypeTest(t, harness, e, tt.q, tt.types, tt.skipOld) 1707 } 1708 if tt.indexes != nil { 1709 evalIndexTest(t, harness, e, tt.q, tt.indexes, tt.skipOld) 1710 } 1711 if tt.mergeCompares != nil { 1712 evalMergeCmpTest(t, harness, e, tt) 1713 } 1714 if tt.exp != nil { 1715 evalJoinCorrectness(t, harness, e, tt.q, tt.q, tt.exp, tt.skipOld) 1716 } 1717 if tt.order != nil { 1718 evalJoinOrder(t, harness, e, tt.q, tt.order, tt.skipOld) 1719 } 1720 } 1721 }) 1722 } 1723 } 1724 1725 func evalJoinTypeTest(t *testing.T, harness Harness, e QueryEngine, query string, types []plan.JoinType, skipOld bool) { 1726 t.Run(query+" join types", func(t *testing.T) { 1727 if skipOld { 1728 t.Skip() 1729 } 1730 1731 ctx := NewContext(harness) 1732 ctx = ctx.WithQuery(query) 1733 1734 a, err := analyzeQuery(ctx, e, query) 1735 require.NoError(t, err) 1736 1737 jts := collectJoinTypes(a) 1738 var exp []string 1739 for _, t := range types { 1740 exp = append(exp, t.String()) 1741 } 1742 var cmp []string 1743 for _, t := range jts { 1744 cmp = append(cmp, t.String()) 1745 } 1746 require.Equal(t, exp, cmp, fmt.Sprintf("unexpected plan:\n%s", sql.DebugString(a))) 1747 }) 1748 } 1749 1750 func analyzeQuery(ctx *sql.Context, e QueryEngine, query string) (sql.Node, error) { 1751 parsed, err := planbuilder.Parse(ctx, e.EngineAnalyzer().Catalog, query) 1752 if err != nil { 1753 return nil, err 1754 } 1755 1756 return e.EngineAnalyzer().Analyze(ctx, parsed, nil) 1757 } 1758 1759 func evalMergeCmpTest(t *testing.T, harness Harness, e QueryEngine, tt JoinPlanTest) { 1760 hasMergeJoin := false 1761 for _, joinType := range tt.types { 1762 if joinType.IsMerge() { 1763 hasMergeJoin = true 1764 } 1765 } 1766 if !hasMergeJoin { 1767 return 1768 } 1769 t.Run(tt.q+"merge join compare", func(t *testing.T) { 1770 if tt.skipOld { 1771 t.Skip() 1772 } 1773 1774 ctx := NewContext(harness) 1775 ctx = ctx.WithQuery(tt.q) 1776 1777 a, err := analyzeQuery(ctx, e, tt.q) 1778 require.NoError(t, err) 1779 1780 // consider making this a string too 1781 compares := collectMergeCompares(a) 1782 var cmp []string 1783 for _, i := range compares { 1784 cmp = append(cmp, i.String()) 1785 } 1786 require.Equal(t, tt.mergeCompares, cmp, fmt.Sprintf("unexpected plan:\n%s", sql.DebugString(a))) 1787 }) 1788 } 1789 1790 func evalIndexTest(t *testing.T, harness Harness, e QueryEngine, q string, indexes []string, skip bool) { 1791 t.Run(q+" join indexes", func(t *testing.T) { 1792 if skip { 1793 t.Skip() 1794 } 1795 1796 ctx := NewContext(harness) 1797 ctx = ctx.WithQuery(q) 1798 1799 a, err := analyzeQuery(ctx, e, q) 1800 require.NoError(t, err) 1801 1802 idxs := collectIndexes(a) 1803 var exp []string 1804 for _, i := range indexes { 1805 exp = append(exp, i) 1806 } 1807 var cmp []string 1808 for _, i := range idxs { 1809 cmp = append(cmp, strings.ToLower(i.ID())) 1810 } 1811 require.Equal(t, exp, cmp, fmt.Sprintf("unexpected plan:\n%s", sql.DebugString(a))) 1812 }) 1813 } 1814 1815 func evalJoinCorrectness(t *testing.T, harness Harness, e QueryEngine, name, q string, exp []sql.Row, skipOld bool) { 1816 t.Run(name, func(t *testing.T) { 1817 ctx := NewContext(harness) 1818 ctx = ctx.WithQuery(q) 1819 1820 sch, iter, err := e.QueryWithBindings(ctx, q, nil, nil) 1821 require.NoError(t, err, "Unexpected error for query %s: %s", q, err) 1822 1823 rows, err := sql.RowIterToRows(ctx, iter) 1824 require.NoError(t, err, "Unexpected error for query %s: %s", q, err) 1825 1826 if exp != nil { 1827 checkResults(t, exp, nil, sch, rows, q, e) 1828 } 1829 1830 require.Equal(t, 0, ctx.Memory.NumCaches()) 1831 validateEngine(t, ctx, harness, e) 1832 }) 1833 } 1834 1835 func collectJoinTypes(n sql.Node) []plan.JoinType { 1836 var types []plan.JoinType 1837 transform.Inspect(n, func(n sql.Node) bool { 1838 if n == nil { 1839 return true 1840 } 1841 j, ok := n.(*plan.JoinNode) 1842 if ok { 1843 types = append(types, j.Op) 1844 } 1845 1846 if ex, ok := n.(sql.Expressioner); ok { 1847 for _, e := range ex.Expressions() { 1848 transform.InspectExpr(e, func(e sql.Expression) bool { 1849 sq, ok := e.(*plan.Subquery) 1850 if !ok { 1851 return false 1852 } 1853 types = append(types, collectJoinTypes(sq.Query)...) 1854 return false 1855 }) 1856 } 1857 } 1858 return true 1859 }) 1860 return types 1861 } 1862 1863 func collectMergeCompares(n sql.Node) []sql.Expression { 1864 var compares []sql.Expression 1865 transform.Inspect(n, func(n sql.Node) bool { 1866 if n == nil { 1867 return true 1868 } 1869 1870 if ex, ok := n.(sql.Expressioner); ok { 1871 for _, e := range ex.Expressions() { 1872 transform.InspectExpr(e, func(e sql.Expression) bool { 1873 sq, ok := e.(*plan.Subquery) 1874 if !ok { 1875 return false 1876 } 1877 compares = append(compares, collectMergeCompares(sq.Query)...) 1878 return false 1879 }) 1880 } 1881 } 1882 1883 join, ok := n.(*plan.JoinNode) 1884 if !ok { 1885 return true 1886 } 1887 if !join.Op.IsMerge() { 1888 return true 1889 } 1890 1891 compares = append(compares, expression.SplitConjunction(join.JoinCond())[0]) 1892 return true 1893 }) 1894 return compares 1895 } 1896 1897 func collectIndexes(n sql.Node) []sql.Index { 1898 var indexes []sql.Index 1899 transform.Inspect(n, func(n sql.Node) bool { 1900 if n == nil { 1901 return true 1902 } 1903 access, ok := n.(*plan.IndexedTableAccess) 1904 if ok { 1905 indexes = append(indexes, access.Index()) 1906 return true 1907 } 1908 1909 if ex, ok := n.(sql.Expressioner); ok { 1910 for _, e := range ex.Expressions() { 1911 transform.InspectExpr(e, func(e sql.Expression) bool { 1912 sq, ok := e.(*plan.Subquery) 1913 if !ok { 1914 return false 1915 } 1916 indexes = append(indexes, collectIndexes(sq.Query)...) 1917 return false 1918 }) 1919 } 1920 } 1921 return true 1922 }) 1923 return indexes 1924 } 1925 1926 func evalJoinOrder(t *testing.T, harness Harness, e QueryEngine, q string, exp [][]string, skipOld bool) { 1927 t.Run(q+" join order", func(t *testing.T) { 1928 ctx := NewContext(harness) 1929 ctx = ctx.WithQuery(q) 1930 1931 a, err := analyzeQuery(ctx, e, q) 1932 require.NoError(t, err) 1933 1934 cmp := collectJoinOrder(a) 1935 for _, expCand := range exp { 1936 if assert.ObjectsAreEqual(expCand, cmp) { 1937 return 1938 } 1939 } 1940 assert.Failf(t, "expected order %s found '%s'\ndetail:\n%s", fmt.Sprintf("%#v", exp), strings.Join(cmp, ","), sql.DebugString(a)) 1941 }) 1942 } 1943 1944 func collectJoinOrder(n sql.Node) []string { 1945 order := []string{} 1946 1947 switch n := n.(type) { 1948 case *plan.JoinNode: 1949 order = append(order, collectJoinOrder(n.Left())...) 1950 order = append(order, collectJoinOrder(n.Right())...) 1951 case plan.TableIdNode: 1952 order = append(order, n.Name()) 1953 default: 1954 children := n.Children() 1955 for _, c := range children { 1956 order = append(order, collectJoinOrder(c)...) 1957 } 1958 } 1959 1960 return order 1961 }