@@ -31,6 +31,7 @@ import (
3131type  JoinPlanTest  struct  {
3232	q        string 
3333	types    []plan.JoinType 
34+ 	indexes  []string 
3435	exp      []sql.Row 
3536	order    []string 
3637	skipOld  bool 
@@ -438,14 +439,16 @@ HAVING count(v) >= 1)`,
438439				q : `SELECT * FROM xy WHERE ( 
439440      				EXISTS (SELECT * FROM xy Alias1 WHERE Alias1.x = (xy.x + 1)) 
440441      				AND EXISTS (SELECT * FROM uv Alias2 WHERE Alias2.u = (xy.x + 2)));` ,
441- 				types : []plan.JoinType {plan .JoinTypeSemiLookup , plan .JoinTypeSemiLookup },
442+ 				// These should both be JoinTypeSemiLookup, but for https://github.com/dolthub/go-mysql-server/issues/1893 
443+ 				types : []plan.JoinType {plan .JoinTypeSemiLookup , plan .JoinTypeMerge },
442444				exp :   []sql.Row {{0 , 2 }, {1 , 0 }},
443445			},
444446			{
445447				q : `SELECT * FROM xy WHERE ( 
446448      				EXISTS (SELECT * FROM xy Alias1 WHERE Alias1.x = (xy.x + 1)) 
447449      				AND EXISTS (SELECT * FROM uv Alias1 WHERE Alias1.u = (xy.x + 2)));` ,
448- 				types : []plan.JoinType {plan .JoinTypeSemiLookup , plan .JoinTypeSemiLookup },
450+ 				// These should both be JoinTypeSemiLookup, but for https://github.com/dolthub/go-mysql-server/issues/1893 
451+ 				types : []plan.JoinType {plan .JoinTypeSemiLookup , plan .JoinTypeMerge },
449452				exp :   []sql.Row {{0 , 2 }, {1 , 0 }},
450453			},
451454			{
@@ -890,8 +893,9 @@ join uv d on d.u = c.x`,
890893				order : []string {"a" , "b" , "c" , "d" },
891894			},
892895			{
893- 				q :     "select /*+ LOOKUP_JOIN(xy,scalarSubq0) */ 1 from xy where x not in (select u from uv)" ,
894- 				types : []plan.JoinType {plan .JoinTypeLeftOuterLookup },
896+ 				q : "select /*+ LOOKUP_JOIN(xy,scalarSubq0) */ 1 from xy where x not in (select u from uv)" ,
897+ 				// This should be JoinTypeSemiLookup, but for https://github.com/dolthub/go-mysql-server/issues/1894 
898+ 				types : []plan.JoinType {plan .JoinTypeAntiLookup },
895899			},
896900			{
897901				q :     "select /*+ ANTI_JOIN(xy,scalarSubq0) */ 1 from xy where x not in (select u from uv)" ,
@@ -915,6 +919,50 @@ join uv d on d.u = c.x`,
915919			},
916920		},
917921	},
922+ 	{
923+ 		// This is a regression test for https://github.com/dolthub/go-mysql-server/pull/1889. 
924+ 		// We should always prefer a more specific index over a less specific index for lookups. 
925+ 		name : "lookup join multiple indexes" ,
926+ 		setup : []string {
927+ 			"create table lhs (a int, b int, c int);" ,
928+ 			"create table rhs (a int, b int, c int, d int, index a_idx(a), index abcd_idx(a,b,c,d));" ,
929+ 			"insert into lhs values (0, 0, 0), (0, 0, 1), (0, 1, 1), (1, 1, 1);" ,
930+ 			"insert into rhs values "  + 
931+ 				"(0, 0, 0, 0),"  + 
932+ 				"(0, 0, 0, 1),"  + 
933+ 				"(0, 0, 1, 0),"  + 
934+ 				"(0, 0, 1, 1),"  + 
935+ 				"(0, 1, 0, 0),"  + 
936+ 				"(0, 1, 0, 1),"  + 
937+ 				"(0, 1, 1, 0),"  + 
938+ 				"(0, 1, 1, 1),"  + 
939+ 				"(1, 0, 0, 0),"  + 
940+ 				"(1, 0, 0, 1),"  + 
941+ 				"(1, 0, 1, 0),"  + 
942+ 				"(1, 0, 1, 1),"  + 
943+ 				"(1, 1, 0, 0),"  + 
944+ 				"(1, 1, 0, 1),"  + 
945+ 				"(1, 1, 1, 0),"  + 
946+ 				"(1, 1, 1, 1);" ,
947+ 		},
948+ 		tests : []JoinPlanTest {
949+ 			{
950+ 				q :       "select rhs.* from lhs left join rhs on lhs.a = rhs.a and lhs.b = rhs.b and lhs.c = rhs.c" ,
951+ 				types :   []plan.JoinType {plan .JoinTypeLeftOuterLookup },
952+ 				indexes : []string {"abcd_idx" },
953+ 				exp : []sql.Row {
954+ 					{0 , 0 , 0 , 0 },
955+ 					{0 , 0 , 0 , 1 },
956+ 					{0 , 0 , 1 , 0 },
957+ 					{0 , 0 , 1 , 1 },
958+ 					{0 , 1 , 1 , 0 },
959+ 					{0 , 1 , 1 , 1 },
960+ 					{1 , 1 , 1 , 0 },
961+ 					{1 , 1 , 1 , 1 },
962+ 				},
963+ 			},
964+ 		},
965+ 	},
918966}
919967
920968func  TestJoinPlanning (t  * testing.T , harness  Harness ) {
@@ -927,6 +975,9 @@ func TestJoinPlanning(t *testing.T, harness Harness) {
927975				if  tt .types  !=  nil  {
928976					evalJoinTypeTest (t , harness , e , tt )
929977				}
978+ 				if  tt .indexes  !=  nil  {
979+ 					evalIndexTest (t , harness , e , tt )
980+ 				}
930981				if  tt .exp  !=  nil  {
931982					evalJoinCorrectness (t , harness , e , tt .q , tt .q , tt .exp , tt .skipOld )
932983				}
@@ -963,6 +1014,31 @@ func evalJoinTypeTest(t *testing.T, harness Harness, e *sqle.Engine, tt JoinPlan
9631014	})
9641015}
9651016
1017+ func  evalIndexTest (t  * testing.T , harness  Harness , e  * sqle.Engine , tt  JoinPlanTest ) {
1018+ 	t .Run (tt .q + " join indexes" , func (t  * testing.T ) {
1019+ 		if  tt .skipOld  {
1020+ 			t .Skip ()
1021+ 		}
1022+ 
1023+ 		ctx  :=  NewContext (harness )
1024+ 		ctx  =  ctx .WithQuery (tt .q )
1025+ 
1026+ 		a , err  :=  e .AnalyzeQuery (ctx , tt .q )
1027+ 		require .NoError (t , err )
1028+ 
1029+ 		idxs  :=  collectIndexes (a )
1030+ 		var  exp  []string 
1031+ 		for  _ , i  :=  range  tt .indexes  {
1032+ 			exp  =  append (exp , i )
1033+ 		}
1034+ 		var  cmp  []string 
1035+ 		for  _ , i  :=  range  idxs  {
1036+ 			cmp  =  append (cmp , i .ID ())
1037+ 		}
1038+ 		require .Equal (t , exp , cmp , fmt .Sprintf ("unexpected plan:\n %s" , sql .DebugString (a )))
1039+ 	})
1040+ }
1041+ 
9661042func  evalJoinCorrectness (t  * testing.T , harness  Harness , e  * sqle.Engine , name , q  string , exp  []sql.Row , skipOld  bool ) {
9671043	t .Run (name , func (t  * testing.T ) {
9681044		if  vh , ok  :=  harness .(VersionedHarness ); (ok  &&  vh .Version () ==  sql .VersionStable  &&  skipOld ) ||  (! ok  &&  skipOld ) {
@@ -1015,6 +1091,35 @@ func collectJoinTypes(n sql.Node) []plan.JoinType {
10151091	return  types 
10161092}
10171093
1094+ func  collectIndexes (n  sql.Node ) []sql.Index  {
1095+ 	var  indexes  []sql.Index 
1096+ 	transform .Inspect (n , func (n  sql.Node ) bool  {
1097+ 		if  n  ==  nil  {
1098+ 			return  true 
1099+ 		}
1100+ 		access , ok  :=  n .(* plan.IndexedTableAccess )
1101+ 		if  ok  {
1102+ 			indexes  =  append (indexes , access .Index ())
1103+ 			return  true 
1104+ 		}
1105+ 
1106+ 		if  ex , ok  :=  n .(sql.Expressioner ); ok  {
1107+ 			for  _ , e  :=  range  ex .Expressions () {
1108+ 				transform .InspectExpr (e , func (e  sql.Expression ) bool  {
1109+ 					sq , ok  :=  e .(* plan.Subquery )
1110+ 					if  ! ok  {
1111+ 						return  false 
1112+ 					}
1113+ 					indexes  =  append (indexes , collectIndexes (sq .Query )... )
1114+ 					return  false 
1115+ 				})
1116+ 			}
1117+ 		}
1118+ 		return  true 
1119+ 	})
1120+ 	return  indexes 
1121+ }
1122+ 
10181123func  evalJoinOrder (t  * testing.T , harness  Harness , e  * sqle.Engine , q  string , exp  []string , skipOld  bool ) {
10191124	t .Run (q + " join order" , func (t  * testing.T ) {
10201125		if  vh , ok  :=  harness .(VersionedHarness ); (ok  &&  vh .Version () ==  sql .VersionStable  &&  skipOld ) ||  (! ok  &&  skipOld ) {
@@ -1061,6 +1166,9 @@ func TestJoinPlanningPrepared(t *testing.T, harness Harness) {
10611166				if  tt .types  !=  nil  {
10621167					evalJoinTypeTestPrepared (t , harness , e , tt , tt .skipOld )
10631168				}
1169+ 				if  tt .indexes  !=  nil  {
1170+ 					evalJoinIndexTestPrepared (t , harness , e , tt , tt .skipOld )
1171+ 				}
10641172				if  tt .exp  !=  nil  {
10651173					evalJoinCorrectnessPrepared (t , harness , e , tt .q , tt .q , tt .exp , tt .skipOld )
10661174				}
@@ -1112,6 +1220,46 @@ func evalJoinTypeTestPrepared(t *testing.T, harness Harness, e *sqle.Engine, tt
11121220	})
11131221}
11141222
1223+ func  evalJoinIndexTestPrepared (t  * testing.T , harness  Harness , e  * sqle.Engine , tt  JoinPlanTest , skipOld  bool ) {
1224+ 	t .Run (tt .q + " join indexes" , func (t  * testing.T ) {
1225+ 		if  vh , ok  :=  harness .(VersionedHarness ); (ok  &&  vh .Version () ==  sql .VersionStable  &&  skipOld ) ||  (! ok  &&  skipOld ) {
1226+ 			t .Skip ()
1227+ 		}
1228+ 
1229+ 		ctx  :=  NewContext (harness )
1230+ 		ctx  =  ctx .WithQuery (tt .q )
1231+ 
1232+ 		bindings , err  :=  injectBindVarsAndPrepare (t , ctx , e , tt .q )
1233+ 		require .NoError (t , err )
1234+ 
1235+ 		p , ok  :=  e .PreparedDataCache .GetCachedStmt (ctx .Session .ID (), tt .q )
1236+ 		require .True (t , ok , "prepared statement not found" )
1237+ 
1238+ 		if  len (bindings ) >  0  {
1239+ 			var  usedBindings  map [string ]bool 
1240+ 			p , usedBindings , err  =  plan .ApplyBindings (p , bindings )
1241+ 			require .NoError (t , err )
1242+ 			for  binding  :=  range  bindings  {
1243+ 				require .True (t , usedBindings [binding ], "unused binding %s" , binding )
1244+ 			}
1245+ 		}
1246+ 
1247+ 		a , _ , err  :=  e .Analyzer .AnalyzePrepared (ctx , p , nil )
1248+ 		require .NoError (t , err )
1249+ 
1250+ 		idxs  :=  collectIndexes (a )
1251+ 		var  exp  []string 
1252+ 		for  _ , i  :=  range  tt .indexes  {
1253+ 			exp  =  append (exp , i )
1254+ 		}
1255+ 		var  cmp  []string 
1256+ 		for  _ , i  :=  range  idxs  {
1257+ 			cmp  =  append (cmp , i .ID ())
1258+ 		}
1259+ 		require .Equal (t , exp , cmp , fmt .Sprintf ("unexpected plan:\n %s" , sql .DebugString (a )))
1260+ 	})
1261+ }
1262+ 
11151263func  evalJoinCorrectnessPrepared (t  * testing.T , harness  Harness , e  * sqle.Engine , name , q  string , exp  []sql.Row , skipOld  bool ) {
11161264	t .Run (q , func (t  * testing.T ) {
11171265		if  vh , ok  :=  harness .(VersionedHarness ); (ok  &&  vh .Version () ==  sql .VersionStable  &&  skipOld ) ||  (! ok  &&  skipOld ) {
0 commit comments