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  }