Skip to content

Commit 133fc3d

Browse files
authored
Merge pull request #3211 from dolthub/angela/exists_iter
Do not return EOF in existsIter when right iter is empty
2 parents 34391f4 + 8720cc4 commit 133fc3d

File tree

3 files changed

+78
-19
lines changed

3 files changed

+78
-19
lines changed

enginetest/join_op_tests.go

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1977,15 +1977,58 @@ SELECT SUM(x) FROM xy WHERE x IN (
19771977
},
19781978
},
19791979
{
1980-
name: "where not exists",
1980+
name: "where exists and where not exists",
19811981
setup: [][]string{
19821982
setup.XyData[0],
1983+
{
1984+
"create table t(c varchar(500))",
1985+
"insert into t values ('a'),('a')",
1986+
"create table u(c0 int, c1 int, primary key(c0, c1))",
1987+
"insert into u values (1, 1),(2,2),(2,3)",
1988+
},
19831989
},
19841990
tests: []JoinOpTests{
19851991
{
19861992
Query: `select * from xy_hasnull x where not exists(select 1 from ab_hasnull a where a.b = x.y)`,
19871993
Expected: []sql.Row{{1, 0}, {3, nil}},
19881994
},
1995+
{
1996+
Query: "select x from xy where exists (select 1 from ab where ab.b = -1)",
1997+
Expected: []sql.Row{},
1998+
},
1999+
{
2000+
Query: "select x from xy where exists (select 1 from ab where ab.b = xy.y)",
2001+
Expected: []sql.Row{{0}, {2}},
2002+
},
2003+
{
2004+
Query: "select x from xy where not exists (select 1 from ab where ab.b = xy.y)",
2005+
Expected: []sql.Row{{1}, {3}},
2006+
},
2007+
{
2008+
Query: "select x from xy_hasnull where not exists(select 1 from ab_hasnull where ab_hasnull.b <> xy_hasnull.y)",
2009+
Expected: []sql.Row{{3}},
2010+
},
2011+
{
2012+
// TODO: this fails as a merge join. it seems related to https://github.com/dolthub/dolt/issues/9797
2013+
Skip: true,
2014+
Query: "select x from xy_hasnull_idx where exists(select 1 from rs where rs.s = xy_hasnull_idx.y)",
2015+
Expected: []sql.Row{{1}},
2016+
},
2017+
{
2018+
Query: "select x from xy_hasnull_idx where not exists(select 1 from rs where rs.s = xy_hasnull_idx.y)",
2019+
Expected: []sql.Row{{2}, {0}, {3}},
2020+
},
2021+
{
2022+
// https://github.com/dolthub/dolt/issues/9828
2023+
Query: "with v as (select 'a' as c where false) select null from t where not exists (select 1 from v where v.c <> t.c);",
2024+
Expected: []sql.Row{{nil}, {nil}},
2025+
},
2026+
{
2027+
// https://github.com/dolthub/dolt/issues/9797
2028+
Skip: true,
2029+
Query: "select * from u where exists (select 1 from u as x where x.c0 = u.c0)",
2030+
Expected: []sql.Row{{1, 1}, {2, 2}, {2, 3}},
2031+
},
19892032
},
19902033
},
19912034
{

enginetest/join_planning_tests.go

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1943,24 +1943,28 @@ func evalIndexTest(t *testing.T, harness Harness, e QueryEngine, q string, index
19431943
})
19441944
}
19451945

1946-
func evalJoinCorrectness(t *testing.T, harness Harness, e QueryEngine, name, q string, exp []sql.Row, skipOld bool) {
1947-
t.Run(name, func(t *testing.T) {
1948-
ctx := NewContext(harness)
1949-
ctx = ctx.WithQuery(q)
1946+
func evalJoinCorrectness(t *testing.T, harness Harness, e QueryEngine, name, q string, exp []sql.Row, skip bool) {
1947+
if skip {
1948+
t.Skip()
1949+
} else {
1950+
t.Run(name, func(t *testing.T) {
1951+
ctx := NewContext(harness)
1952+
ctx = ctx.WithQuery(q)
19501953

1951-
sch, iter, _, err := e.QueryWithBindings(ctx, q, nil, nil, nil)
1952-
require.NoError(t, err, "Unexpected error for query %s: %s", q, err)
1954+
sch, iter, _, err := e.QueryWithBindings(ctx, q, nil, nil, nil)
1955+
require.NoError(t, err, "Unexpected error for query %s: %s", q, err)
19531956

1954-
rows, err := sql.RowIterToRows(ctx, iter)
1955-
require.NoError(t, err, "Unexpected error for query %s: %s", q, err)
1957+
rows, err := sql.RowIterToRows(ctx, iter)
1958+
require.NoError(t, err, "Unexpected error for query %s: %s", q, err)
19561959

1957-
if exp != nil {
1958-
CheckResults(ctx, t, harness, exp, nil, sch, rows, q, e)
1959-
}
1960+
if exp != nil {
1961+
CheckResults(ctx, t, harness, exp, nil, sch, rows, q, e)
1962+
}
19601963

1961-
require.Equal(t, 0, ctx.Memory.NumCaches())
1962-
validateEngine(t, ctx, harness, e)
1963-
})
1964+
require.Equal(t, 0, ctx.Memory.NumCaches())
1965+
validateEngine(t, ctx, harness, e)
1966+
})
1967+
}
19641968
}
19651969

19661970
func collectJoinTypes(n sql.Node) []plan.JoinType {

sql/rowexec/join_iters.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ func newExistsIter(ctx *sql.Context, b sql.NodeExecBuilder, j *plan.JoinNode, ro
247247
cond: j.Filter,
248248
scopeLen: j.ScopeLen,
249249
rowSize: rowSize,
250-
nullRej: !(j.Filter != nil && plan.IsNullRejecting(j.Filter)),
250+
nullRej: j.Filter != nil && plan.IsNullRejecting(j.Filter),
251251
}, nil
252252
}
253253

@@ -303,10 +303,22 @@ func (i *existsIter) Next(ctx *sql.Context) (sql.Row, error) {
303303
return nil, err
304304
}
305305
if plan.IsEmptyIter(rIter) {
306-
if i.nullRej || i.typ.IsAnti() {
307-
return nil, io.EOF
306+
switch {
307+
case i.typ.IsSemi():
308+
// EXISTS with empty right is always false → skip this left row
309+
nextState = esIncLeft
310+
case i.typ.IsAnti():
311+
if i.nullRej {
312+
// Filter is null-rejecting: need to run condition once with nil right so NULL can propagate
313+
// and row may be excluded
314+
nextState = esCompare
315+
} else {
316+
// Filter is not null-rejecting: no matches possible, row passes
317+
nextState = esRet
318+
}
319+
default:
320+
nextState = esCompare
308321
}
309-
nextState = esCompare
310322
} else {
311323
nextState = esIncRight
312324
}

0 commit comments

Comments
 (0)