diff --git a/enginetest/queries/script_queries.go b/enginetest/queries/script_queries.go index d1dd30fd59..7cc540e713 100644 --- a/enginetest/queries/script_queries.go +++ b/enginetest/queries/script_queries.go @@ -14883,6 +14883,55 @@ select * from t1 except ( }, }, }, + { + // https://github.com/dolthub/dolt/issues/10600 + Name: "self-referential NOT IN subquery", + SetUpScript: []string{ + "CREATE TABLE bug_repro (id VARCHAR(32) PRIMARY KEY, status VARCHAR(16), name VARCHAR(32));", + "INSERT INTO bug_repro VALUES ('a', 'open', 'x'), ('b', 'open', 'y'), ('c', 'open', 'z');", + }, + Assertions: []ScriptTestAssertion{ + { + Query: "SELECT * FROM bug_repro WHERE id NOT IN (SELECT id FROM bug_repro WHERE status='open' LIMIT 1);", + Expected: []sql.Row{ + {"b", "open", "y"}, + {"c", "open", "z"}, + }, + }, + { + Query: "UPDATE bug_repro SET status='closed' WHERE id NOT IN (SELECT id FROM bug_repro WHERE status='open' LIMIT 1);", + Expected: []sql.Row{{NewUpdateResult(2, 2)}}, + }, + { + Query: "INSERT INTO bug_repro VALUES ('d', 'open', 'zz')", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "DELETE FROM bug_repro WHERE status='open' AND id NOT IN (SELECT id FROM bug_repro WHERE status='open' LIMIT 1);", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "select * from bug_repro", + Expected: []sql.Row{ + {"a", "open", "x"}, + {"b", "closed", "y"}, + {"c", "closed", "z"}, + }, + }, + { + Query: "UPDATE bug_repro SET status='delete this' WHERE id NOT IN (SELECT id FROM bug_repro WHERE status='keep this');", + Expected: []sql.Row{{NewUpdateResult(3, 3)}}, + }, + { + Query: "select * from bug_repro", + Expected: []sql.Row{ + {"a", "delete this", "x"}, + {"b", "delete this", "y"}, + {"c", "delete this", "z"}, + }, + }, + }, + }, } var SpatialScriptTests = []ScriptTest{ diff --git a/sql/analyzer/indexed_joins.go b/sql/analyzer/indexed_joins.go index 948e7393ab..b7cdb499e4 100644 --- a/sql/analyzer/indexed_joins.go +++ b/sql/analyzer/indexed_joins.go @@ -172,9 +172,13 @@ func replanJoin(ctx *sql.Context, n *plan.JoinNode, a *Analyzer, scope *plan.Sco return nil, err } - err = convertAntiToLeftJoin(m) - if err != nil { - return nil, err + // TODO: updateJoinIter is not able to handle left joins wrapped in project nodes, which is what an antijoin gets + // converted to. https://github.com/dolthub/dolt/issues/10614 + if !qFlags.IsSet(sql.QFlagUpdate) { + err = convertAntiToLeftJoin(m) + if err != nil { + return nil, err + } } err = addRightSemiJoins(ctx, m) diff --git a/sql/rowexec/join_iters.go b/sql/rowexec/join_iters.go index 1c9daca744..c269c69b23 100644 --- a/sql/rowexec/join_iters.go +++ b/sql/rowexec/join_iters.go @@ -159,7 +159,7 @@ func (i *joinState) makeLeftOuterNonMatchingResult() sql.Row { return resultRow } -// makeLeftOuterNonMatchingResult returns a new sql.Row representing a row from an OUTER RIGHT join where no match was made with the left child. +// makeRightOuterNonMatchingResult returns a new sql.Row representing a row from an OUTER RIGHT join where no match was made with the left child. func (i *joinState) makeRightOuterNonMatchingResult() sql.Row { resultRow := make(sql.Row, i.resultRowSize()) copy(resultRow, i.scopeColumns()) diff --git a/sql/rowexec/update.go b/sql/rowexec/update.go index 1d037e3a17..7a5474263e 100644 --- a/sql/rowexec/update.go +++ b/sql/rowexec/update.go @@ -209,12 +209,14 @@ func newUpdateIter( // done once. type updateJoinIter struct { updateSourceIter sql.RowIter - joinNode sql.Node - updaters map[string]sql.RowUpdater - caches map[string]sql.KeyValueCache - disposals map[string]sql.DisposeFunc - accumulator *updateJoinRowHandler - joinSchema sql.Schema + // TODO: naming this joinNode is confusing. It's not always a join node because it could be wrapped inside another + // node + joinNode sql.Node + updaters map[string]sql.RowUpdater + caches map[string]sql.KeyValueCache + disposals map[string]sql.DisposeFunc + accumulator *updateJoinRowHandler + joinSchema sql.Schema } var _ sql.RowIter = (*updateJoinIter)(nil) @@ -289,6 +291,7 @@ func (u *updateJoinIter) Next(ctx *sql.Context) (sql.Row, error) { } func toJoinNode(node sql.Node) *plan.JoinNode { + // TODO: rewrite this to use Inspect switch n := node.(type) { case *plan.JoinNode: return n @@ -348,6 +351,11 @@ func (u *updateJoinIter) shouldUpdateDirectionalJoin(ctx *sql.Context, joinRow, } // If the overall row fits the join condition it is fine (i.e. middle of the venn diagram). + // TODO: We shouldn't be evaluating the join condition on "joinRow". "joinRow" is not actually the row from the + // join node but rather the row from the updateSourceIter. The join node could be wrapped in a Project node and the + // indexes in the join condition would no longer match the correct columns. We also need to consider how to handle + // updateJoins where a LeftOuterJoin is filtered by a null right side. JoinCond could also be nil. + // https://github.com/dolthub/dolt/issues/10614 val, err := jn.JoinCond().Eval(ctx, joinRow) if err != nil { return true, err