Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 44 additions & 1 deletion enginetest/join_op_tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -1977,15 +1977,58 @@ SELECT SUM(x) FROM xy WHERE x IN (
},
},
{
name: "where not exists",
name: "where exists and where not exists",
setup: [][]string{
setup.XyData[0],
{
"create table t(c varchar(500))",
"insert into t values ('a'),('a')",
"create table u(c0 int, c1 int, primary key(c0, c1))",
"insert into u values (1, 1),(2,2),(2,3)",
},
},
tests: []JoinOpTests{
{
Query: `select * from xy_hasnull x where not exists(select 1 from ab_hasnull a where a.b = x.y)`,
Expected: []sql.Row{{1, 0}, {3, nil}},
},
{
Query: "select x from xy where exists (select 1 from ab where ab.b = -1)",
Expected: []sql.Row{},
},
{
Query: "select x from xy where exists (select 1 from ab where ab.b = xy.y)",
Expected: []sql.Row{{0}, {2}},
},
{
Query: "select x from xy where not exists (select 1 from ab where ab.b = xy.y)",
Expected: []sql.Row{{1}, {3}},
},
{
Query: "select x from xy_hasnull where not exists(select 1 from ab_hasnull where ab_hasnull.b <> xy_hasnull.y)",
Expected: []sql.Row{{3}},
},
{
// TODO: this fails as a merge join. it seems related to https://github.com/dolthub/dolt/issues/9797
Skip: true,
Query: "select x from xy_hasnull_idx where exists(select 1 from rs where rs.s = xy_hasnull_idx.y)",
Expected: []sql.Row{{1}},
},
{
Query: "select x from xy_hasnull_idx where not exists(select 1 from rs where rs.s = xy_hasnull_idx.y)",
Expected: []sql.Row{{2}, {0}, {3}},
},
{
// https://github.com/dolthub/dolt/issues/9828
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);",
Expected: []sql.Row{{nil}, {nil}},
},
{
// https://github.com/dolthub/dolt/issues/9797
Skip: true,
Query: "select * from u where exists (select 1 from u as x where x.c0 = u.c0)",
Expected: []sql.Row{{1, 1}, {2, 2}, {2, 3}},
},
},
},
{
Expand Down
32 changes: 18 additions & 14 deletions enginetest/join_planning_tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -1943,24 +1943,28 @@ func evalIndexTest(t *testing.T, harness Harness, e QueryEngine, q string, index
})
}

func evalJoinCorrectness(t *testing.T, harness Harness, e QueryEngine, name, q string, exp []sql.Row, skipOld bool) {
t.Run(name, func(t *testing.T) {
ctx := NewContext(harness)
ctx = ctx.WithQuery(q)
func evalJoinCorrectness(t *testing.T, harness Harness, e QueryEngine, name, q string, exp []sql.Row, skip bool) {
if skip {
t.Skip()
} else {
t.Run(name, func(t *testing.T) {
ctx := NewContext(harness)
ctx = ctx.WithQuery(q)

sch, iter, _, err := e.QueryWithBindings(ctx, q, nil, nil, nil)
require.NoError(t, err, "Unexpected error for query %s: %s", q, err)
sch, iter, _, err := e.QueryWithBindings(ctx, q, nil, nil, nil)
require.NoError(t, err, "Unexpected error for query %s: %s", q, err)

rows, err := sql.RowIterToRows(ctx, iter)
require.NoError(t, err, "Unexpected error for query %s: %s", q, err)
rows, err := sql.RowIterToRows(ctx, iter)
require.NoError(t, err, "Unexpected error for query %s: %s", q, err)

if exp != nil {
CheckResults(ctx, t, harness, exp, nil, sch, rows, q, e)
}
if exp != nil {
CheckResults(ctx, t, harness, exp, nil, sch, rows, q, e)
}

require.Equal(t, 0, ctx.Memory.NumCaches())
validateEngine(t, ctx, harness, e)
})
require.Equal(t, 0, ctx.Memory.NumCaches())
validateEngine(t, ctx, harness, e)
})
}
}

func collectJoinTypes(n sql.Node) []plan.JoinType {
Expand Down
20 changes: 16 additions & 4 deletions sql/rowexec/join_iters.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ func newExistsIter(ctx *sql.Context, b sql.NodeExecBuilder, j *plan.JoinNode, ro
cond: j.Filter,
scopeLen: j.ScopeLen,
rowSize: rowSize,
nullRej: !(j.Filter != nil && plan.IsNullRejecting(j.Filter)),
nullRej: j.Filter != nil && plan.IsNullRejecting(j.Filter),
}, nil
}

Expand Down Expand Up @@ -303,10 +303,22 @@ func (i *existsIter) Next(ctx *sql.Context) (sql.Row, error) {
return nil, err
}
if plan.IsEmptyIter(rIter) {
if i.nullRej || i.typ.IsAnti() {
return nil, io.EOF
switch {
case i.typ.IsSemi():
// EXISTS with empty right is always false → skip this left row
nextState = esIncLeft
case i.typ.IsAnti():
if i.nullRej {
// Filter is null-rejecting: need to run condition once with nil right so NULL can propagate
// and row may be excluded
nextState = esCompare
} else {
// Filter is not null-rejecting: no matches possible, row passes
nextState = esRet
}
default:
nextState = esCompare
}
nextState = esCompare
} else {
nextState = esIncRight
}
Expand Down
Loading