diff --git a/pkg/sql/logictest/testdata/logic_test/fk_opt b/pkg/sql/logictest/testdata/logic_test/fk_opt index 9554ff5dab18..96c7dd2fa7f8 100644 --- a/pkg/sql/logictest/testdata/logic_test/fk_opt +++ b/pkg/sql/logictest/testdata/logic_test/fk_opt @@ -20,3 +20,19 @@ INSERT INTO child VALUES (1,1), (2,2), (3,3) statement ok INSERT INTO child VALUES (1,1), (2,2) + +# Use data from a different table as input. +statement ok +CREATE TABLE xy (x INT, y INT) + +statement ok +INSERT INTO xy VALUES (4, 4), (5, 5), (6, 6) + +statement error insert or update on table "child" violates foreign key constraint "fk_p_ref_parent"\nDETAIL: Key \(p\)=\(4\) is not present in table "parent"\. +INSERT INTO child SELECT x,y FROM xy + +statement ok +INSERT INTO parent SELECT x FROM xy + +statement ok +INSERT INTO child SELECT x,y FROM xy diff --git a/pkg/sql/opt/exec/execbuilder/builder.go b/pkg/sql/opt/exec/execbuilder/builder.go index 96c88044e6d3..18d21b4702cb 100644 --- a/pkg/sql/opt/exec/execbuilder/builder.go +++ b/pkg/sql/opt/exec/execbuilder/builder.go @@ -58,16 +58,6 @@ type Builder struct { withExprs []builtWithExpr } -// builtWithExpr is metadata regarding a With expression which has already been -// added to the set of subqueries for the query. -type builtWithExpr struct { - id opt.WithID - // outputCols maps the output ColumnIDs of the With expression to the ordinal - // positions they are output to. See execPlan.outputCols for more details. - outputCols opt.ColMap - bufferNode exec.Node -} - // New constructs an instance of the execution node builder using the // given factory to construct nodes. The Build method will build the execution // node tree from the given optimized expression tree. @@ -149,3 +139,21 @@ func (b *Builder) BuildScalar(ivh *tree.IndexedVarHelper) (tree.TypedExpr, error func (b *Builder) decorrelationError() error { return errors.Errorf("could not decorrelate subquery") } + +// builtWithExpr is metadata regarding a With expression which has already been +// added to the set of subqueries for the query. +type builtWithExpr struct { + id opt.WithID + // outputCols maps the output ColumnIDs of the With expression to the ordinal + // positions they are output to. See execPlan.outputCols for more details. + outputCols opt.ColMap + bufferNode exec.Node +} + +func (b *Builder) addBuiltWithExpr(id opt.WithID, outputCols opt.ColMap, bufferNode exec.Node) { + b.withExprs = append(b.withExprs, builtWithExpr{ + id: id, + outputCols: outputCols, + bufferNode: bufferNode, + }) +} diff --git a/pkg/sql/opt/exec/execbuilder/mutation.go b/pkg/sql/opt/exec/execbuilder/mutation.go index 0d52d6898fab..34134c029124 100644 --- a/pkg/sql/opt/exec/execbuilder/mutation.go +++ b/pkg/sql/opt/exec/execbuilder/mutation.go @@ -12,6 +12,7 @@ package execbuilder import ( "bytes" + "fmt" "github.com/cockroachdb/cockroach/pkg/sql/lex" "github.com/cockroachdb/cockroach/pkg/sql/opt" @@ -25,20 +26,38 @@ import ( "github.com/cockroachdb/errors" ) -func (b *Builder) buildInsert(ins *memo.InsertExpr) (execPlan, error) { - // Build the input query and ensure that the input columns that correspond to - // the table columns are projected. - input, err := b.buildRelational(ins.Input) +func (b *Builder) buildMutationInput( + inputExpr memo.RelExpr, colList opt.ColList, p *memo.MutationPrivate, +) (execPlan, error) { + input, err := b.buildRelational(inputExpr) if err != nil { return execPlan{}, err } + input, err = b.ensureColumns(input, colList, nil, inputExpr.ProvidedPhysical().Ordering) + if err != nil { + return execPlan{}, err + } + + if p.WithID != 0 { + label := fmt.Sprintf("buffer %d", p.WithID) + input.root, err = b.factory.ConstructBuffer(input.root, label) + if err != nil { + return execPlan{}, err + } + + b.addBuiltWithExpr(p.WithID, input.outputCols, input.root) + } + return input, nil +} + +func (b *Builder) buildInsert(ins *memo.InsertExpr) (execPlan, error) { // Construct list of columns that only contains columns that need to be // inserted (e.g. delete-only mutation columns don't need to be inserted). colList := make(opt.ColList, 0, len(ins.InsertCols)+len(ins.CheckCols)) colList = appendColsWhenPresent(colList, ins.InsertCols) colList = appendColsWhenPresent(colList, ins.CheckCols) - input, err = b.ensureColumns(input, colList, nil, ins.Input.ProvidedPhysical().Ordering) + input, err := b.buildMutationInput(ins.Input, colList, &ins.MutationPrivate) if err != nil { return execPlan{}, err } @@ -75,13 +94,6 @@ func (b *Builder) buildInsert(ins *memo.InsertExpr) (execPlan, error) { } func (b *Builder) buildUpdate(upd *memo.UpdateExpr) (execPlan, error) { - // Build the input query and ensure that the fetch and update columns are - // projected. - input, err := b.buildRelational(upd.Input) - if err != nil { - return execPlan{}, err - } - // Currently, the execution engine requires one input column for each fetch // and update expression, so use ensureColumns to map and reorder colums so // that they correspond to target table columns. For example: @@ -97,7 +109,8 @@ func (b *Builder) buildUpdate(upd *memo.UpdateExpr) (execPlan, error) { colList = appendColsWhenPresent(colList, upd.FetchCols) colList = appendColsWhenPresent(colList, upd.UpdateCols) colList = appendColsWhenPresent(colList, upd.CheckCols) - input, err = b.ensureColumns(input, colList, nil, upd.Input.ProvidedPhysical().Ordering) + + input, err := b.buildMutationInput(upd.Input, colList, &upd.MutationPrivate) if err != nil { return execPlan{}, err } @@ -130,13 +143,6 @@ func (b *Builder) buildUpdate(upd *memo.UpdateExpr) (execPlan, error) { } func (b *Builder) buildUpsert(ups *memo.UpsertExpr) (execPlan, error) { - // Build the input query and ensure that the insert, fetch, and update columns - // are projected. - input, err := b.buildRelational(ups.Input) - if err != nil { - return execPlan{}, err - } - // Currently, the execution engine requires one input column for each insert, // fetch, and update expression, so use ensureColumns to map and reorder // columns so that they correspond to target table columns. For example: @@ -164,7 +170,7 @@ func (b *Builder) buildUpsert(ups *memo.UpsertExpr) (execPlan, error) { colList = append(colList, ups.CanaryCol) } colList = appendColsWhenPresent(colList, ups.CheckCols) - input, err = b.ensureColumns(input, colList, nil, ups.Input.ProvidedPhysical().Ordering) + input, err := b.buildMutationInput(ups.Input, colList, &ups.MutationPrivate) if err != nil { return execPlan{}, err } @@ -212,19 +218,13 @@ func (b *Builder) buildDelete(del *memo.DeleteExpr) (execPlan, error) { return b.buildDeleteRange(del) } - // Build the input query and ensure that the fetch columns are projected. - input, err := b.buildRelational(del.Input) - if err != nil { - return execPlan{}, err - } - // Ensure that order of input columns matches order of target table columns. // // TODO(andyk): Using ensureColumns here can result in an extra Render. // Upgrade execution engine to not require this. colList := make(opt.ColList, 0, len(del.FetchCols)) colList = appendColsWhenPresent(colList, del.FetchCols) - input, err = b.ensureColumns(input, colList, nil, del.Input.ProvidedPhysical().Ordering) + input, err := b.buildMutationInput(del.Input, colList, &del.MutationPrivate) if err != nil { return execPlan{}, err } diff --git a/pkg/sql/opt/exec/execbuilder/relational.go b/pkg/sql/opt/exec/execbuilder/relational.go index aa9273d2cd74..ed1b879a84af 100644 --- a/pkg/sql/opt/exec/execbuilder/relational.go +++ b/pkg/sql/opt/exec/execbuilder/relational.go @@ -1373,11 +1373,7 @@ func (b *Builder) buildWith(with *memo.WithExpr) (execPlan, error) { Root: buffer, }) - b.withExprs = append(b.withExprs, builtWithExpr{ - id: with.ID, - outputCols: value.outputCols, - bufferNode: buffer, - }) + b.addBuiltWithExpr(with.ID, value.outputCols, buffer) return b.buildRelational(with.Input) } @@ -1405,27 +1401,38 @@ func (b *Builder) buildWithScan(withScan *memo.WithScanExpr) (execPlan, error) { if err != nil { return execPlan{}, err } + res := execPlan{root: node} - // The ColumnIDs from the With expression need to get remapped according to - // the mapping in the withScan to get the actual colMap for this expression. - var outputCols opt.ColMap - - referencedExpr := b.mem.WithExpr(withScan.ID) - if !referencedExpr.Relational().OutputCols.Equals(withScan.InCols.ToSet()) { - panic(errors.AssertionFailedf( - "columns being output from WITH do not match expected columns", - )) - } + if maxVal, _ := e.outputCols.MaxValue(); len(withScan.InCols) == maxVal+1 { + // We are outputting all columns. Just set up the map. - for i := range withScan.InCols { - idx, _ := e.outputCols.Get(int(withScan.InCols[i])) - outputCols.Set(int(withScan.OutCols[i]), idx) + // The ColumnIDs from the With expression need to get remapped according to + // the mapping in the withScan to get the actual colMap for this expression. + for i := range withScan.InCols { + idx, _ := e.outputCols.Get(int(withScan.InCols[i])) + res.outputCols.Set(int(withScan.OutCols[i]), idx) + } + } else { + // We need a projection. + cols := make([]exec.ColumnOrdinal, len(withScan.InCols)) + for i := range withScan.InCols { + col, ok := e.outputCols.Get(int(withScan.InCols[i])) + if !ok { + panic(errors.AssertionFailedf("column %d not in input", log.Safe(withScan.InCols[i]))) + } + cols[i] = exec.ColumnOrdinal(col) + res.outputCols.Set(int(withScan.OutCols[i]), i) + } + res.root, err = b.factory.ConstructSimpleProject( + res.root, cols, nil, /* colNames */ + exec.OutputOrdering(res.sqlOrdering(withScan.ProvidedPhysical().Ordering)), + ) + if err != nil { + return execPlan{}, err + } } + return res, nil - return execPlan{ - root: node, - outputCols: outputCols, - }, nil } func (b *Builder) buildProjectSet(projectSet *memo.ProjectSetExpr) (execPlan, error) { diff --git a/pkg/sql/opt/exec/execbuilder/testdata/fk_opt b/pkg/sql/opt/exec/execbuilder/testdata/fk_opt index 5727933ef2c2..84b6533a3e1d 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/fk_opt +++ b/pkg/sql/opt/exec/execbuilder/testdata/fk_opt @@ -9,22 +9,61 @@ CREATE TABLE parent (p INT PRIMARY KEY, other INT) statement ok CREATE TABLE child (c INT PRIMARY KEY, p INT NOT NULL REFERENCES parent(p)) -query TTT -EXPLAIN INSERT INTO child VALUES (1,1), (2,2) +query TTTTT +EXPLAIN (VERBOSE) INSERT INTO child VALUES (1,1), (2,2) ---- -root · · - ├── count · · - │ └── insert · · - │ │ into child(c, p) - │ │ strategy inserter - │ └── values · · - │ size 2 columns, 2 rows - └── postquery · · - └── errorIfRows · · - └── lookup-join · · - │ table parent@primary - │ type anti - │ equality (column2) = (p) - │ equality cols are key · - └── values · · -· size 1 column, 2 rows +root · · () · + ├── count · · () · + │ └── insert · · () · + │ │ into child(c, p) · · + │ │ strategy inserter · · + │ └── buffer node · · (column1, column2) · + │ │ label buffer 1 · · + │ └── values · · (column1, column2) · + │ size 2 columns, 2 rows · · + │ row 0, expr 0 1 · · + │ row 0, expr 1 1 · · + │ row 1, expr 0 2 · · + │ row 1, expr 1 2 · · + └── postquery · · () · + └── error if rows · · () · + └── lookup-join · · (column2) · + │ table parent@primary · · + │ type anti · · + │ equality (column2) = (p) · · + │ equality cols are key · · · + └── render · · (column2) · + │ render 0 column2 · · + └── scan buffer node · · (column1, column2) · +· label buffer 1 · · + +# Use data from a different table as input. +statement ok +CREATE TABLE xy (x INT, y INT) + +query TTTTT +EXPLAIN (VERBOSE) INSERT INTO child SELECT x,y FROM xy +---- +root · · () · + ├── count · · () · + │ └── insert · · () · + │ │ into child(c, p) · · + │ │ strategy inserter · · + │ └── buffer node · · (x, y) · + │ │ label buffer 1 · · + │ └── scan · · (x, y) · + │ table xy@primary · · + │ spans ALL · · + └── postquery · · () · + └── error if rows · · () · + └── hash-join · · (y) · + │ type anti · · + │ equality (y) = (p) · · + │ right cols are key · · · + ├── render · · (y) · + │ │ render 0 y · · + │ └── scan buffer node · · (x, y) · + │ label buffer 1 · · + └── scan · · (p) · +· table parent@primary · · +· spans ALL · · diff --git a/pkg/sql/opt/memo/expr_format.go b/pkg/sql/opt/memo/expr_format.go index 19fc8c1cabe1..17f204164dc0 100644 --- a/pkg/sql/opt/memo/expr_format.go +++ b/pkg/sql/opt/memo/expr_format.go @@ -361,8 +361,9 @@ func (f *ExprFmtCtx) formatRelational(e RelExpr, tp treeprinter.Node) { if len(colList) == 0 { tp.Child("columns: ") } - f.formatMutation(e, tp, "insert-mapping:", t.InsertCols, t.Table) + f.formatMutationCols(e, tp, "insert-mapping:", t.InsertCols, t.Table) f.formatColList(e, tp, "check columns:", t.CheckCols) + f.formatMutationWithID(tp, t.WithID) } case *UpdateExpr: @@ -371,8 +372,9 @@ func (f *ExprFmtCtx) formatRelational(e RelExpr, tp treeprinter.Node) { tp.Child("columns: ") } f.formatColList(e, tp, "fetch columns:", t.FetchCols) - f.formatMutation(e, tp, "update-mapping:", t.UpdateCols, t.Table) + f.formatMutationCols(e, tp, "update-mapping:", t.UpdateCols, t.Table) f.formatColList(e, tp, "check columns:", t.CheckCols) + f.formatMutationWithID(tp, t.WithID) } case *UpsertExpr: @@ -383,13 +385,14 @@ func (f *ExprFmtCtx) formatRelational(e RelExpr, tp treeprinter.Node) { if t.CanaryCol != 0 { tp.Childf("canary column: %d", t.CanaryCol) f.formatColList(e, tp, "fetch columns:", t.FetchCols) - f.formatMutation(e, tp, "insert-mapping:", t.InsertCols, t.Table) - f.formatMutation(e, tp, "update-mapping:", t.UpdateCols, t.Table) - f.formatMutation(e, tp, "return-mapping:", t.ReturnCols, t.Table) + f.formatMutationCols(e, tp, "insert-mapping:", t.InsertCols, t.Table) + f.formatMutationCols(e, tp, "update-mapping:", t.UpdateCols, t.Table) + f.formatMutationCols(e, tp, "return-mapping:", t.ReturnCols, t.Table) } else { - f.formatMutation(e, tp, "upsert-mapping:", t.InsertCols, t.Table) + f.formatMutationCols(e, tp, "upsert-mapping:", t.InsertCols, t.Table) } f.formatColList(e, tp, "check columns:", t.CheckCols) + f.formatMutationWithID(tp, t.WithID) } case *DeleteExpr: @@ -398,6 +401,7 @@ func (f *ExprFmtCtx) formatRelational(e RelExpr, tp treeprinter.Node) { tp.Child("columns: ") } f.formatColList(e, tp, "fetch columns:", t.FetchCols) + f.formatMutationWithID(tp, t.WithID) } case *WithScanExpr: @@ -810,13 +814,13 @@ func (f *ExprFmtCtx) formatColList( } } -// formatMutation adds a new treeprinter child for each non-zero column in the +// formatMutationCols adds a new treeprinter child for each non-zero column in the // given list. Each child shows how the column will be mutated, with the id of // the "before" and "after" columns, similar to this: // // a:1 => x:4 // -func (f *ExprFmtCtx) formatMutation( +func (f *ExprFmtCtx) formatMutationCols( nd RelExpr, tp treeprinter.Node, heading string, colList opt.ColList, tabID opt.TableID, ) { if len(colList) == 0 { @@ -835,6 +839,14 @@ func (f *ExprFmtCtx) formatMutation( } } +// formatMutationWithID shows the binding ID, if the mutation is buffering its +// input. +func (f *ExprFmtCtx) formatMutationWithID(tp treeprinter.Node, id opt.WithID) { + if id != 0 { + tp.Childf("input binding: &%d", id) + } +} + // formatCol outputs the specified column into the context's buffer using the // following format: // label:index(type) diff --git a/pkg/sql/opt/metadata.go b/pkg/sql/opt/metadata.go index 63de6d9d3941..bec015bb48ad 100644 --- a/pkg/sql/opt/metadata.go +++ b/pkg/sql/opt/metadata.go @@ -443,5 +443,6 @@ func (md *Metadata) AllViews() []cat.View { } // WithID uniquely identifies a With expression within the scope of a query. +// WithID=0 is reserved to mean "unknown expression". // See the comment for Metadata for more details on identifiers. type WithID uint64 diff --git a/pkg/sql/opt/ops/mutation.opt b/pkg/sql/opt/ops/mutation.opt index 5f7e26b31207..47082e072457 100644 --- a/pkg/sql/opt/ops/mutation.opt +++ b/pkg/sql/opt/ops/mutation.opt @@ -120,6 +120,11 @@ define MutationPrivate { # as part of online schema change). If no RETURNING clause was specified, # then ReturnCols is nil. ReturnCols ColList + + # Mutation operators can act similarly to a With operator: they buffer their + # input, making it accessible to FK queries. If this is not required, WithID + # is zero. + WithID WithID } # Update evaluates a relational input expression that fetches existing rows from diff --git a/pkg/sql/opt/optbuilder/mutation_builder.go b/pkg/sql/opt/optbuilder/mutation_builder.go index 6c53b8573bd2..5da96ff99741 100644 --- a/pkg/sql/opt/optbuilder/mutation_builder.go +++ b/pkg/sql/opt/optbuilder/mutation_builder.go @@ -124,6 +124,9 @@ type mutationBuilder struct { // checks contains foreign key check queries; see buildFKChecks. checks memo.FKChecksExpr + + // withID is nonzero if we need to buffer the input for FK checks. + withID opt.WithID } func (mb *mutationBuilder) init(b *Builder, op opt.Operator, tab cat.Table, alias tree.TableName) { @@ -583,6 +586,7 @@ func (mb *mutationBuilder) makeMutationPrivate(needResults bool) *memo.MutationP UpdateCols: makeColList(mb.updateOrds), CanaryCol: mb.canaryColID, CheckCols: makeColList(mb.checkOrds), + WithID: mb.withID, } if needResults { @@ -746,6 +750,16 @@ func (mb *mutationBuilder) buildFKChecks() { return } + if mb.tab.OutboundForeignKeyCount() == 0 { + return + } + + // TODO(radu): if the input is a VALUES with constant expressions, we don't + // need to buffer it. This could be a normalization rule, but it's probably + // more efficient if we did it in here (or we'd end up building the entire FK + // subtrees twice). + mb.withID = mb.b.factory.Memo().AddWithBinding(mb.outScope.expr) + for i, n := 0, mb.tab.OutboundForeignKeyCount(); i < n; i++ { fk := mb.tab.OutboundForeignKey(i) numCols := fk.ColumnCount() @@ -798,25 +812,20 @@ func (mb *mutationBuilder) buildFKChecks() { } } - // Build the join filters: - // (origin_a = referenced_a) AND (origin_b = referenced_b) AND ... - antiJoinFilters := make(memo.FiltersExpr, numCols) - for j := 0; j < numCols; j++ { - antiJoinFilters[j].Condition = mb.b.factory.ConstructEq( - mb.b.factory.ConstructVariable(inputCols[j]), - mb.b.factory.ConstructVariable(scanScope.cols[j].id), - ) + // Set up a WithRef; for this we have to synthesize new columns. + withRefCols := make(opt.ColList, numCols) + for i := 0; i < numCols; i++ { + c := mb.b.factory.Metadata().ColumnMeta(inputCols[i]) + withRefCols[i] = mb.md.AddColumn(c.Alias, c.Type) } - // TODO(radu): this is a major hack; we should be using a reference to a - // buffer instead of reusing the expression directly. This is for - // prototyping purposes (should work ok with constant inputs). Note that - // the column IDs in the FK check query aren't exposed by the ForeignKeys - // operator so using the same column IDs shouldn't cause issues. - left := mb.b.factory.ConstructProject( - mb.outScope.expr, memo.EmptyProjectionsExpr, inputCols.ToSet(), - ) - item.KeyCols = inputCols + left := mb.b.factory.ConstructWithScan(&memo.WithScanPrivate{ + ID: mb.withID, + InCols: inputCols, + OutCols: withRefCols, + }) + + item.KeyCols = withRefCols if notNullInputCols.Len() < numCols { // The columns we are inserting might have NULLs. These require special @@ -841,11 +850,11 @@ func (mb *mutationBuilder) buildFKChecks() { // Filter out any rows which have a NULL; build filters of the form // (a IS NOT NULL) AND (b IS NOT NULL) ... filters := make(memo.FiltersExpr, 0, numCols-notNullInputCols.Len()) - for _, col := range inputCols { - if !notNullInputCols.Contains(col) { + for i := range inputCols { + if !notNullInputCols.Contains(inputCols[i]) { filters = append(filters, memo.FiltersItem{ Condition: mb.b.factory.ConstructIsNot( - mb.b.factory.ConstructVariable(col), + mb.b.factory.ConstructVariable(withRefCols[i]), memo.NullSingleton, ), }) @@ -864,7 +873,7 @@ func (mb *mutationBuilder) buildFKChecks() { // Build a filter of the form // (a IS NOT NULL) OR (b IS NOT NULL) ... var condition opt.ScalarExpr - for _, col := range inputCols { + for _, col := range withRefCols { is := mb.b.factory.ConstructIsNot( mb.b.factory.ConstructVariable(col), memo.NullSingleton, @@ -882,6 +891,16 @@ func (mb *mutationBuilder) buildFKChecks() { } } + // Build the join filters: + // (origin_a = referenced_a) AND (origin_b = referenced_b) AND ... + antiJoinFilters := make(memo.FiltersExpr, numCols) + for j := 0; j < numCols; j++ { + antiJoinFilters[j].Condition = mb.b.factory.ConstructEq( + mb.b.factory.ConstructVariable(withRefCols[j]), + mb.b.factory.ConstructVariable(scanScope.cols[j].id), + ) + } + item.Check = mb.b.factory.ConstructAntiJoin( left, scanScope.expr, antiJoinFilters, &memo.JoinPrivate{}, ) diff --git a/pkg/sql/opt/optbuilder/testdata/fk-checks-insert b/pkg/sql/opt/optbuilder/testdata/fk-checks-insert index 390172b8722d..d6a5ab727b4c 100644 --- a/pkg/sql/opt/optbuilder/testdata/fk-checks-insert +++ b/pkg/sql/opt/optbuilder/testdata/fk-checks-insert @@ -14,6 +14,7 @@ insert child ├── insert-mapping: │ ├── column1:3 => c:1 │ └── column2:4 => child.p:2 + ├── input binding: &1 ├── values │ ├── columns: column1:3(int!null) column2:4(int!null) │ ├── tuple [type=tuple{int, int}] @@ -25,17 +26,11 @@ insert child └── f-k-checks └── f-k-checks-item: child(p) -> parent(p) └── anti-join (hash) - ├── columns: column2:4(int!null) - ├── project - │ ├── columns: column2:4(int!null) - │ └── values - │ ├── columns: column1:3(int!null) column2:4(int!null) - │ ├── tuple [type=tuple{int, int}] - │ │ ├── const: 100 [type=int] - │ │ └── const: 1 [type=int] - │ └── tuple [type=tuple{int, int}] - │ ├── const: 200 [type=int] - │ └── const: 1 [type=int] + ├── columns: column2:7(int!null) + ├── with-scan &1 + │ ├── columns: column2:7(int!null) + │ └── mapping: + │ └── column2:4(int) => column2:7(int) ├── scan t.public.parent │ └── columns: t.public.parent.p:5(int!null) └── filters @@ -43,6 +38,39 @@ insert child ├── variable: column2 [type=int] └── variable: t.public.parent.p [type=int] +# Use a non-constant input. +exec-ddl +CREATE TABLE xy (x INT, y INT) +---- + +build +INSERT INTO child SELECT x, y FROM xy +---- +insert child + ├── columns: + ├── insert-mapping: + │ ├── x:3 => c:1 + │ └── xy.y:4 => child.p:2 + ├── input binding: &1 + ├── project + │ ├── columns: x:3(int) xy.y:4(int) + │ └── scan xy + │ └── columns: x:3(int) xy.y:4(int) rowid:5(int!null) + └── f-k-checks + └── f-k-checks-item: child(p) -> parent(p) + └── anti-join (hash) + ├── columns: y:8(int) + ├── with-scan &1 + │ ├── columns: y:8(int) + │ └── mapping: + │ └── xy.y:4(int) => y:8(int) + ├── scan t.public.parent + │ └── columns: t.public.parent.p:6(int!null) + └── filters + └── eq [type=bool] + ├── variable: y [type=int] + └── variable: t.public.parent.p [type=int] + exec-ddl CREATE TABLE child_nullable (c INT PRIMARY KEY, p INT REFERENCES parent(p)); ---- @@ -57,6 +85,7 @@ insert child_nullable ├── insert-mapping: │ ├── column1:3 => c:1 │ └── column2:4 => child_nullable.p:2 + ├── input binding: &1 ├── values │ ├── columns: column1:3(int!null) column2:4(int) │ ├── tuple [type=tuple{int, int}] @@ -69,20 +98,13 @@ insert child_nullable └── f-k-checks └── f-k-checks-item: child_nullable(p) -> parent(p) └── anti-join (hash) - ├── columns: column2:4(int!null) + ├── columns: column2:7(int!null) ├── select - │ ├── columns: column2:4(int!null) - │ ├── project - │ │ ├── columns: column2:4(int) - │ │ └── values - │ │ ├── columns: column1:3(int!null) column2:4(int) - │ │ ├── tuple [type=tuple{int, int}] - │ │ │ ├── const: 100 [type=int] - │ │ │ └── const: 1 [type=int] - │ │ └── tuple [type=tuple{int, int}] - │ │ ├── const: 200 [type=int] - │ │ └── cast: INT8 [type=int] - │ │ └── null [type=unknown] + │ ├── columns: column2:7(int!null) + │ ├── with-scan &1 + │ │ ├── columns: column2:7(int) + │ │ └── mapping: + │ │ └── column2:4(int) => column2:7(int) │ └── filters │ └── is-not [type=bool] │ ├── variable: column2 [type=int] @@ -104,6 +126,7 @@ insert child_nullable ├── insert-mapping: │ ├── column1:3 => c:1 │ └── column2:4 => child_nullable.p:2 + ├── input binding: &1 ├── values │ ├── columns: column1:3(int!null) column2:4(int!null) │ ├── tuple [type=tuple{int, int}] @@ -115,17 +138,11 @@ insert child_nullable └── f-k-checks └── f-k-checks-item: child_nullable(p) -> parent(p) └── anti-join (hash) - ├── columns: column2:4(int!null) - ├── project - │ ├── columns: column2:4(int!null) - │ └── values - │ ├── columns: column1:3(int!null) column2:4(int!null) - │ ├── tuple [type=tuple{int, int}] - │ │ ├── const: 100 [type=int] - │ │ └── const: 1 [type=int] - │ └── tuple [type=tuple{int, int}] - │ ├── const: 200 [type=int] - │ └── const: 1 [type=int] + ├── columns: column2:7(int!null) + ├── with-scan &1 + │ ├── columns: column2:7(int!null) + │ └── mapping: + │ └── column2:4(int) => column2:7(int) ├── scan t.public.parent │ └── columns: t.public.parent.p:5(int!null) └── filters @@ -147,6 +164,7 @@ insert child_nullable_full ├── insert-mapping: │ ├── column1:3 => c:1 │ └── column2:4 => child_nullable_full.p:2 + ├── input binding: &1 ├── values │ ├── columns: column1:3(int!null) column2:4(int) │ ├── tuple [type=tuple{int, int}] @@ -159,20 +177,13 @@ insert child_nullable_full └── f-k-checks └── f-k-checks-item: child_nullable_full(p) -> parent(p) └── anti-join (hash) - ├── columns: column2:4(int!null) + ├── columns: column2:7(int!null) ├── select - │ ├── columns: column2:4(int!null) - │ ├── project - │ │ ├── columns: column2:4(int) - │ │ └── values - │ │ ├── columns: column1:3(int!null) column2:4(int) - │ │ ├── tuple [type=tuple{int, int}] - │ │ │ ├── const: 100 [type=int] - │ │ │ └── const: 1 [type=int] - │ │ └── tuple [type=tuple{int, int}] - │ │ ├── const: 200 [type=int] - │ │ └── cast: INT8 [type=int] - │ │ └── null [type=unknown] + │ ├── columns: column2:7(int!null) + │ ├── with-scan &1 + │ │ ├── columns: column2:7(int) + │ │ └── mapping: + │ │ └── column2:4(int) => column2:7(int) │ └── filters │ └── is-not [type=bool] │ ├── variable: column2 [type=int] @@ -208,6 +219,7 @@ insert multi_col_child │ ├── column2:6 => multi_col_child.p:2 │ ├── column3:7 => multi_col_child.q:3 │ └── column4:8 => multi_col_child.r:4 + ├── input binding: &1 ├── values │ ├── columns: column1:5(int!null) column2:6(int) column3:7(int) column4:8(int) │ └── tuple [type=tuple{int, int, int, int}] @@ -221,21 +233,15 @@ insert multi_col_child └── f-k-checks └── f-k-checks-item: multi_col_child(p,q,r) -> multi_col_parent(p,q,r) └── anti-join (hash) - ├── columns: column2:6(int!null) column3:7(int!null) column4:8(int!null) + ├── columns: column2:13(int!null) column3:14(int!null) column4:15(int!null) ├── select - │ ├── columns: column2:6(int!null) column3:7(int!null) column4:8(int!null) - │ ├── project - │ │ ├── columns: column2:6(int) column3:7(int) column4:8(int) - │ │ └── values - │ │ ├── columns: column1:5(int!null) column2:6(int) column3:7(int) column4:8(int) - │ │ └── tuple [type=tuple{int, int, int, int}] - │ │ ├── const: 4 [type=int] - │ │ ├── cast: INT8 [type=int] - │ │ │ └── null [type=unknown] - │ │ ├── cast: INT8 [type=int] - │ │ │ └── null [type=unknown] - │ │ └── cast: INT8 [type=int] - │ │ └── null [type=unknown] + │ ├── columns: column2:13(int!null) column3:14(int!null) column4:15(int!null) + │ ├── with-scan &1 + │ │ ├── columns: column2:13(int) column3:14(int) column4:15(int) + │ │ └── mapping: + │ │ ├── column2:6(int) => column2:13(int) + │ │ ├── column3:7(int) => column3:14(int) + │ │ └── column4:8(int) => column4:15(int) │ └── filters │ ├── is-not [type=bool] │ │ ├── variable: column2 [type=int] @@ -270,6 +276,7 @@ insert multi_col_child │ ├── column2:6 => multi_col_child.p:2 │ ├── column3:7 => multi_col_child.q:3 │ └── column4:8 => multi_col_child.r:4 + ├── input binding: &1 ├── values │ ├── columns: column1:5(int!null) column2:6(int) column3:7(int) column4:8(int!null) │ ├── tuple [type=tuple{int, int, int, int}] @@ -287,25 +294,15 @@ insert multi_col_child └── f-k-checks └── f-k-checks-item: multi_col_child(p,q,r) -> multi_col_parent(p,q,r) └── anti-join (hash) - ├── columns: column2:6(int!null) column3:7(int!null) column4:8(int!null) + ├── columns: column2:13(int!null) column3:14(int!null) column4:15(int!null) ├── select - │ ├── columns: column2:6(int!null) column3:7(int!null) column4:8(int!null) - │ ├── project - │ │ ├── columns: column2:6(int) column3:7(int) column4:8(int!null) - │ │ └── values - │ │ ├── columns: column1:5(int!null) column2:6(int) column3:7(int) column4:8(int!null) - │ │ ├── tuple [type=tuple{int, int, int, int}] - │ │ │ ├── const: 2 [type=int] - │ │ │ ├── cast: INT8 [type=int] - │ │ │ │ └── null [type=unknown] - │ │ │ ├── const: 20 [type=int] - │ │ │ └── const: 20 [type=int] - │ │ └── tuple [type=tuple{int, int, int, int}] - │ │ ├── const: 3 [type=int] - │ │ ├── const: 20 [type=int] - │ │ ├── cast: INT8 [type=int] - │ │ │ └── null [type=unknown] - │ │ └── const: 20 [type=int] + │ ├── columns: column2:13(int!null) column3:14(int!null) column4:15(int!null) + │ ├── with-scan &1 + │ │ ├── columns: column2:13(int) column3:14(int) column4:15(int!null) + │ │ └── mapping: + │ │ ├── column2:6(int) => column2:13(int) + │ │ ├── column3:7(int) => column3:14(int) + │ │ └── column4:8(int) => column4:15(int) │ └── filters │ ├── is-not [type=bool] │ │ ├── variable: column2 [type=int] @@ -337,6 +334,7 @@ insert multi_col_child │ ├── column2:6 => multi_col_child.p:2 │ ├── column3:7 => multi_col_child.q:3 │ └── column4:8 => multi_col_child.r:4 + ├── input binding: &1 ├── values │ ├── columns: column1:5(int!null) column2:6(int!null) column3:7(int!null) column4:8(int!null) │ └── tuple [type=tuple{int, int, int, int}] @@ -347,16 +345,13 @@ insert multi_col_child └── f-k-checks └── f-k-checks-item: multi_col_child(p,q,r) -> multi_col_parent(p,q,r) └── anti-join (hash) - ├── columns: column2:6(int!null) column3:7(int!null) column4:8(int!null) - ├── project - │ ├── columns: column2:6(int!null) column3:7(int!null) column4:8(int!null) - │ └── values - │ ├── columns: column1:5(int!null) column2:6(int!null) column3:7(int!null) column4:8(int!null) - │ └── tuple [type=tuple{int, int, int, int}] - │ ├── const: 1 [type=int] - │ ├── const: 10 [type=int] - │ ├── const: 10 [type=int] - │ └── const: 10 [type=int] + ├── columns: column2:13(int!null) column3:14(int!null) column4:15(int!null) + ├── with-scan &1 + │ ├── columns: column2:13(int!null) column3:14(int!null) column4:15(int!null) + │ └── mapping: + │ ├── column2:6(int) => column2:13(int) + │ ├── column3:7(int) => column3:14(int) + │ └── column4:8(int) => column4:15(int) ├── scan t.public.multi_col_parent │ └── columns: t.public.multi_col_parent.p:9(int!null) t.public.multi_col_parent.q:10(int!null) t.public.multi_col_parent.r:11(int!null) └── filters @@ -389,6 +384,7 @@ insert multi_col_child_full │ ├── column2:6 => multi_col_child_full.p:2 │ ├── column3:7 => multi_col_child_full.q:3 │ └── column4:8 => multi_col_child_full.r:4 + ├── input binding: &1 ├── values │ ├── columns: column1:5(int!null) column2:6(int) column3:7(int) column4:8(int) │ └── tuple [type=tuple{int, int, int, int}] @@ -402,21 +398,15 @@ insert multi_col_child_full └── f-k-checks └── f-k-checks-item: multi_col_child_full(p,q,r) -> multi_col_parent(p,q,r) └── anti-join (hash) - ├── columns: column2:6(int) column3:7(int) column4:8(int) + ├── columns: column2:13(int) column3:14(int) column4:15(int) ├── select - │ ├── columns: column2:6(int) column3:7(int) column4:8(int) - │ ├── project - │ │ ├── columns: column2:6(int) column3:7(int) column4:8(int) - │ │ └── values - │ │ ├── columns: column1:5(int!null) column2:6(int) column3:7(int) column4:8(int) - │ │ └── tuple [type=tuple{int, int, int, int}] - │ │ ├── const: 4 [type=int] - │ │ ├── cast: INT8 [type=int] - │ │ │ └── null [type=unknown] - │ │ ├── cast: INT8 [type=int] - │ │ │ └── null [type=unknown] - │ │ └── cast: INT8 [type=int] - │ │ └── null [type=unknown] + │ ├── columns: column2:13(int) column3:14(int) column4:15(int) + │ ├── with-scan &1 + │ │ ├── columns: column2:13(int) column3:14(int) column4:15(int) + │ │ └── mapping: + │ │ ├── column2:6(int) => column2:13(int) + │ │ ├── column3:7(int) => column3:14(int) + │ │ └── column4:8(int) => column4:15(int) │ └── filters │ └── or [type=bool] │ ├── or [type=bool] @@ -453,6 +443,7 @@ insert multi_col_child_full │ ├── column2:6 => multi_col_child_full.p:2 │ ├── column3:7 => multi_col_child_full.q:3 │ └── column4:8 => multi_col_child_full.r:4 + ├── input binding: &1 ├── values │ ├── columns: column1:5(int!null) column2:6(int) column3:7(int) column4:8(int!null) │ ├── tuple [type=tuple{int, int, int, int}] @@ -470,23 +461,13 @@ insert multi_col_child_full └── f-k-checks └── f-k-checks-item: multi_col_child_full(p,q,r) -> multi_col_parent(p,q,r) └── anti-join (hash) - ├── columns: column2:6(int) column3:7(int) column4:8(int!null) - ├── project - │ ├── columns: column2:6(int) column3:7(int) column4:8(int!null) - │ └── values - │ ├── columns: column1:5(int!null) column2:6(int) column3:7(int) column4:8(int!null) - │ ├── tuple [type=tuple{int, int, int, int}] - │ │ ├── const: 2 [type=int] - │ │ ├── cast: INT8 [type=int] - │ │ │ └── null [type=unknown] - │ │ ├── const: 20 [type=int] - │ │ └── const: 20 [type=int] - │ └── tuple [type=tuple{int, int, int, int}] - │ ├── const: 3 [type=int] - │ ├── const: 20 [type=int] - │ ├── cast: INT8 [type=int] - │ │ └── null [type=unknown] - │ └── const: 20 [type=int] + ├── columns: column2:13(int) column3:14(int) column4:15(int!null) + ├── with-scan &1 + │ ├── columns: column2:13(int) column3:14(int) column4:15(int!null) + │ └── mapping: + │ ├── column2:6(int) => column2:13(int) + │ ├── column3:7(int) => column3:14(int) + │ └── column4:8(int) => column4:15(int) ├── scan t.public.multi_col_parent │ └── columns: t.public.multi_col_parent.p:9(int!null) t.public.multi_col_parent.q:10(int!null) t.public.multi_col_parent.r:11(int!null) └── filters @@ -511,6 +492,7 @@ insert multi_col_child_full │ ├── column2:6 => multi_col_child_full.p:2 │ ├── column3:7 => multi_col_child_full.q:3 │ └── column4:8 => multi_col_child_full.r:4 + ├── input binding: &1 ├── values │ ├── columns: column1:5(int!null) column2:6(int!null) column3:7(int!null) column4:8(int!null) │ └── tuple [type=tuple{int, int, int, int}] @@ -521,16 +503,13 @@ insert multi_col_child_full └── f-k-checks └── f-k-checks-item: multi_col_child_full(p,q,r) -> multi_col_parent(p,q,r) └── anti-join (hash) - ├── columns: column2:6(int!null) column3:7(int!null) column4:8(int!null) - ├── project - │ ├── columns: column2:6(int!null) column3:7(int!null) column4:8(int!null) - │ └── values - │ ├── columns: column1:5(int!null) column2:6(int!null) column3:7(int!null) column4:8(int!null) - │ └── tuple [type=tuple{int, int, int, int}] - │ ├── const: 1 [type=int] - │ ├── const: 10 [type=int] - │ ├── const: 10 [type=int] - │ └── const: 10 [type=int] + ├── columns: column2:13(int!null) column3:14(int!null) column4:15(int!null) + ├── with-scan &1 + │ ├── columns: column2:13(int!null) column3:14(int!null) column4:15(int!null) + │ └── mapping: + │ ├── column2:6(int) => column2:13(int) + │ ├── column3:7(int) => column3:14(int) + │ └── column4:8(int) => column4:15(int) ├── scan t.public.multi_col_parent │ └── columns: t.public.multi_col_parent.p:9(int!null) t.public.multi_col_parent.q:10(int!null) t.public.multi_col_parent.r:11(int!null) └── filters @@ -573,6 +552,7 @@ insert multi_ref_child │ ├── column2:6 => multi_ref_child.a:2 │ ├── column3:7 => multi_ref_child.b:3 │ └── column4:8 => multi_ref_child.c:4 + ├── input binding: &1 ├── values │ ├── columns: column1:5(int!null) column2:6(int) column3:7(int) column4:8(int) │ └── tuple [type=tuple{int, int, int, int}] @@ -586,21 +566,13 @@ insert multi_ref_child └── f-k-checks ├── f-k-checks-item: multi_ref_child(a) -> multi_ref_parent_a(a) │ └── anti-join (hash) - │ ├── columns: column2:6(int!null) + │ ├── columns: column2:11(int!null) │ ├── select - │ │ ├── columns: column2:6(int!null) - │ │ ├── project - │ │ │ ├── columns: column2:6(int) - │ │ │ └── values - │ │ │ ├── columns: column1:5(int!null) column2:6(int) column3:7(int) column4:8(int) - │ │ │ └── tuple [type=tuple{int, int, int, int}] - │ │ │ ├── const: 1 [type=int] - │ │ │ ├── cast: INT8 [type=int] - │ │ │ │ └── null [type=unknown] - │ │ │ ├── cast: INT8 [type=int] - │ │ │ │ └── null [type=unknown] - │ │ │ └── cast: INT8 [type=int] - │ │ │ └── null [type=unknown] + │ │ ├── columns: column2:11(int!null) + │ │ ├── with-scan &1 + │ │ │ ├── columns: column2:11(int) + │ │ │ └── mapping: + │ │ │ └── column2:6(int) => column2:11(int) │ │ └── filters │ │ └── is-not [type=bool] │ │ ├── variable: column2 [type=int] @@ -613,21 +585,14 @@ insert multi_ref_child │ └── variable: t.public.multi_ref_parent_a.a [type=int] └── f-k-checks-item: multi_ref_child(b,c) -> multi_ref_parent_bc(b,c) └── anti-join (hash) - ├── columns: column3:7(int!null) column4:8(int!null) + ├── columns: column3:15(int!null) column4:16(int!null) ├── select - │ ├── columns: column3:7(int!null) column4:8(int!null) - │ ├── project - │ │ ├── columns: column3:7(int) column4:8(int) - │ │ └── values - │ │ ├── columns: column1:5(int!null) column2:6(int) column3:7(int) column4:8(int) - │ │ └── tuple [type=tuple{int, int, int, int}] - │ │ ├── const: 1 [type=int] - │ │ ├── cast: INT8 [type=int] - │ │ │ └── null [type=unknown] - │ │ ├── cast: INT8 [type=int] - │ │ │ └── null [type=unknown] - │ │ └── cast: INT8 [type=int] - │ │ └── null [type=unknown] + │ ├── columns: column3:15(int!null) column4:16(int!null) + │ ├── with-scan &1 + │ │ ├── columns: column3:15(int) column4:16(int) + │ │ └── mapping: + │ │ ├── column3:7(int) => column3:15(int) + │ │ └── column4:8(int) => column4:16(int) │ └── filters │ ├── is-not [type=bool] │ │ ├── variable: column3 [type=int] @@ -636,7 +601,7 @@ insert multi_ref_child │ ├── variable: column4 [type=int] │ └── null [type=unknown] ├── scan t.public.multi_ref_parent_bc - │ └── columns: t.public.multi_ref_parent_bc.b:11(int!null) t.public.multi_ref_parent_bc.c:12(int!null) + │ └── columns: t.public.multi_ref_parent_bc.b:12(int!null) t.public.multi_ref_parent_bc.c:13(int!null) └── filters ├── eq [type=bool] │ ├── variable: column3 [type=int] diff --git a/pkg/sql/walk.go b/pkg/sql/walk.go index 868b1e9e9d12..220025678fca 100644 --- a/pkg/sql/walk.go +++ b/pkg/sql/walk.go @@ -774,7 +774,7 @@ var planNodeNames = map[reflect.Type]string{ reflect.TypeOf(&dropTableNode{}): "drop table", reflect.TypeOf(&DropUserNode{}): "drop user/role", reflect.TypeOf(&dropViewNode{}): "drop view", - reflect.TypeOf(&errorIfRowsNode{}): "errorIfRows", + reflect.TypeOf(&errorIfRowsNode{}): "error if rows", reflect.TypeOf(&explainDistSQLNode{}): "explain distsql", reflect.TypeOf(&explainPlanNode{}): "explain plan", reflect.TypeOf(&filterNode{}): "filter",