@@ -31,6 +31,7 @@ import (
31
31
type JoinPlanTest struct {
32
32
q string
33
33
types []plan.JoinType
34
+ indexes []string
34
35
exp []sql.Row
35
36
order []string
36
37
skipOld bool
@@ -438,14 +439,16 @@ HAVING count(v) >= 1)`,
438
439
q : `SELECT * FROM xy WHERE (
439
440
EXISTS (SELECT * FROM xy Alias1 WHERE Alias1.x = (xy.x + 1))
440
441
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 },
442
444
exp : []sql.Row {{0 , 2 }, {1 , 0 }},
443
445
},
444
446
{
445
447
q : `SELECT * FROM xy WHERE (
446
448
EXISTS (SELECT * FROM xy Alias1 WHERE Alias1.x = (xy.x + 1))
447
449
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 },
449
452
exp : []sql.Row {{0 , 2 }, {1 , 0 }},
450
453
},
451
454
{
@@ -890,8 +893,9 @@ join uv d on d.u = c.x`,
890
893
order : []string {"a" , "b" , "c" , "d" },
891
894
},
892
895
{
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 },
895
899
},
896
900
{
897
901
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`,
915
919
},
916
920
},
917
921
},
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
+ },
918
966
}
919
967
920
968
func TestJoinPlanning (t * testing.T , harness Harness ) {
@@ -927,6 +975,9 @@ func TestJoinPlanning(t *testing.T, harness Harness) {
927
975
if tt .types != nil {
928
976
evalJoinTypeTest (t , harness , e , tt )
929
977
}
978
+ if tt .indexes != nil {
979
+ evalIndexTest (t , harness , e , tt )
980
+ }
930
981
if tt .exp != nil {
931
982
evalJoinCorrectness (t , harness , e , tt .q , tt .q , tt .exp , tt .skipOld )
932
983
}
@@ -963,6 +1014,31 @@ func evalJoinTypeTest(t *testing.T, harness Harness, e *sqle.Engine, tt JoinPlan
963
1014
})
964
1015
}
965
1016
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
+
966
1042
func evalJoinCorrectness (t * testing.T , harness Harness , e * sqle.Engine , name , q string , exp []sql.Row , skipOld bool ) {
967
1043
t .Run (name , func (t * testing.T ) {
968
1044
if vh , ok := harness .(VersionedHarness ); (ok && vh .Version () == sql .VersionStable && skipOld ) || (! ok && skipOld ) {
@@ -1015,6 +1091,35 @@ func collectJoinTypes(n sql.Node) []plan.JoinType {
1015
1091
return types
1016
1092
}
1017
1093
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
+
1018
1123
func evalJoinOrder (t * testing.T , harness Harness , e * sqle.Engine , q string , exp []string , skipOld bool ) {
1019
1124
t .Run (q + " join order" , func (t * testing.T ) {
1020
1125
if vh , ok := harness .(VersionedHarness ); (ok && vh .Version () == sql .VersionStable && skipOld ) || (! ok && skipOld ) {
@@ -1061,6 +1166,9 @@ func TestJoinPlanningPrepared(t *testing.T, harness Harness) {
1061
1166
if tt .types != nil {
1062
1167
evalJoinTypeTestPrepared (t , harness , e , tt , tt .skipOld )
1063
1168
}
1169
+ if tt .indexes != nil {
1170
+ evalJoinIndexTestPrepared (t , harness , e , tt , tt .skipOld )
1171
+ }
1064
1172
if tt .exp != nil {
1065
1173
evalJoinCorrectnessPrepared (t , harness , e , tt .q , tt .q , tt .exp , tt .skipOld )
1066
1174
}
@@ -1112,6 +1220,46 @@ func evalJoinTypeTestPrepared(t *testing.T, harness Harness, e *sqle.Engine, tt
1112
1220
})
1113
1221
}
1114
1222
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
+
1115
1263
func evalJoinCorrectnessPrepared (t * testing.T , harness Harness , e * sqle.Engine , name , q string , exp []sql.Row , skipOld bool ) {
1116
1264
t .Run (q , func (t * testing.T ) {
1117
1265
if vh , ok := harness .(VersionedHarness ); (ok && vh .Version () == sql .VersionStable && skipOld ) || (! ok && skipOld ) {
0 commit comments