diff --git a/pkg/sql/opt/norm/prune_cols.go b/pkg/sql/opt/norm/prune_cols.go index fbe50cf671c9..365b08664bff 100644 --- a/pkg/sql/opt/norm/prune_cols.go +++ b/pkg/sql/opt/norm/prune_cols.go @@ -128,6 +128,9 @@ func (c *CustomFuncs) NeededMutationFetchCols( // It is possible to update a subset of families only for the primary // index, and only when key columns are not being updated. Otherwise, // all columns in the index must be fetched. + // TODO(andyk): It should be possible to not include columns that are + // being updated, since the existing value is not used. However, this + // would require execution support. if i == cat.PrimaryIndex && !keyCols.Intersects(updateCols) { addFamilyCols(updateCols) } else { diff --git a/pkg/sql/opt/optbuilder/mutation_builder.go b/pkg/sql/opt/optbuilder/mutation_builder.go index 7112bed63f38..0477311073ca 100644 --- a/pkg/sql/opt/optbuilder/mutation_builder.go +++ b/pkg/sql/opt/optbuilder/mutation_builder.go @@ -374,6 +374,10 @@ func (mb *mutationBuilder) addSynthesizedCols( // a constraint violation error if the value of the column is false. func (mb *mutationBuilder) addCheckConstraintCols() { if mb.tab.CheckCount() > 0 { + // Disambiguate names so that references in the constraint expression refer + // to the correct columns. + mb.disambiguateColumns() + mb.checkColList = make(opt.ColList, mb.tab.CheckCount()) projectionsScope := mb.outScope.replace() projectionsScope.appendColumnsFromScope(mb.outScope) @@ -396,6 +400,29 @@ func (mb *mutationBuilder) addCheckConstraintCols() { } } +// disambiguateColumns ranges over the scope and ensures that at most one column +// has each table column name, and that name refers to the column with the final +// value that the mutation applies. +func (mb *mutationBuilder) disambiguateColumns() { + for i, n := 0, mb.tab.DeletableColumnCount(); i < n; i++ { + colName := mb.tab.Column(i).ColName() + colID := mb.mapToReturnColID(i) + for i := range mb.outScope.cols { + col := &mb.outScope.cols[i] + if col.name == colName { + if col.id == colID { + // Use table name, not alias name, since computed column + // expressions will not reference aliases. + col.table = *mb.tab.Name() + } else { + // Clear name so that it will never match. + col.clearName() + } + } + } + } +} + // makeMutationPrivate builds a MutationPrivate struct containing the table and // column metadata needed for the mutation operator. func (mb *mutationBuilder) makeMutationPrivate(needResults bool) *memo.MutationPrivate { @@ -414,27 +441,41 @@ func (mb *mutationBuilder) makeMutationPrivate(needResults bool) *memo.MutationP // can be non-zero. private.ReturnCols = make(opt.ColList, mb.tab.DeletableColumnCount()) for i, n := 0, mb.tab.ColumnCount(); i < n; i++ { - // Map to columns in this order: upsert, update, fetch, insert. - switch { - case mb.upsertColList != nil && mb.upsertColList[i] != 0: - private.ReturnCols[i] = mb.upsertColList[i] + private.ReturnCols[i] = mb.mapToReturnColID(i) + } + } - case mb.updateColList != nil && mb.updateColList[i] != 0: - private.ReturnCols[i] = mb.updateColList[i] + return private +} - case mb.fetchColList != nil && mb.fetchColList[i] != 0: - private.ReturnCols[i] = mb.fetchColList[i] +// mapToReturnColID returns the ID of the input column that will provide the +// value for the corresponding return column. Columns take priority in this +// order: +// +// upsert, update, fetch, insert +// +// If an upsert column is available, then it already combines an update/fetch +// value with an insert value, so it takes priority. If an update column is +// available, then it overrides any fetch value. Finally, the relative priority +// of fetch and insert columns doesn't matter, since they're only used together +// in the upsert case where an upsert column would be available. +func (mb *mutationBuilder) mapToReturnColID(ord int) opt.ColumnID { + switch { + case mb.upsertColList != nil && mb.upsertColList[ord] != 0: + return mb.upsertColList[ord] - case mb.insertColList != nil && mb.insertColList[i] != 0: - private.ReturnCols[i] = mb.insertColList[i] + case mb.updateColList != nil && mb.updateColList[ord] != 0: + return mb.updateColList[ord] - default: - panic("could not find return column") - } - } - } + case mb.fetchColList != nil && mb.fetchColList[ord] != 0: + return mb.fetchColList[ord] - return private + case mb.insertColList != nil && mb.insertColList[ord] != 0: + return mb.insertColList[ord] + + default: + panic("could not find return column") + } } // buildReturning wraps the input expression with a Project operator that diff --git a/pkg/sql/opt/optbuilder/testdata/insert b/pkg/sql/opt/optbuilder/testdata/insert index c69117e1db43..3c059f409d5e 100644 --- a/pkg/sql/opt/optbuilder/testdata/insert +++ b/pkg/sql/opt/optbuilder/testdata/insert @@ -67,16 +67,18 @@ CREATE TABLE checks ( a INT PRIMARY KEY CHECK (a > 0), b INT, c INT, - CHECK (checks.b < c) + d INT AS (c + 1) STORED, + CHECK (checks.b < d) ) ---- TABLE checks ├── a int not null ├── b int ├── c int + ├── d int ├── INDEX primary │ └── a int not null - ├── CHECK (checks.b < c) + ├── CHECK (checks.b < d) └── CHECK (a > 0) # Unknown target table. @@ -1256,22 +1258,29 @@ INSERT INTO checks (a, b, c) VALUES (1, 2, 3) insert checks ├── columns: ├── insert-mapping: - │ ├── column1:4 => a:1 - │ ├── column2:5 => b:2 - │ └── column3:6 => c:3 - ├── check columns: check1:7(bool) check2:8(bool) + │ ├── column1:5 => a:1 + │ ├── column2:6 => b:2 + │ ├── column3:7 => c:3 + │ └── column8:8 => d:4 + ├── check columns: check1:9(bool) check2:10(bool) └── project - ├── columns: check1:7(bool) check2:8(bool) column1:4(int) column2:5(int) column3:6(int) - ├── values - │ ├── columns: column1:4(int) column2:5(int) column3:6(int) - │ └── tuple [type=tuple{int, int, int}] - │ ├── const: 1 [type=int] - │ ├── const: 2 [type=int] - │ └── const: 3 [type=int] + ├── columns: check1:9(bool) check2:10(bool) column1:5(int) column2:6(int) column3:7(int) column8:8(int) + ├── project + │ ├── columns: column8:8(int) column1:5(int) column2:6(int) column3:7(int) + │ ├── values + │ │ ├── columns: column1:5(int) column2:6(int) column3:7(int) + │ │ └── tuple [type=tuple{int, int, int}] + │ │ ├── const: 1 [type=int] + │ │ ├── const: 2 [type=int] + │ │ └── const: 3 [type=int] + │ └── projections + │ └── plus [type=int] + │ ├── variable: column3 [type=int] + │ └── const: 1 [type=int] └── projections ├── lt [type=bool] │ ├── variable: column2 [type=int] - │ └── variable: column3 [type=int] + │ └── variable: column8 [type=int] └── gt [type=bool] ├── variable: column1 [type=int] └── const: 0 [type=int] @@ -1283,20 +1292,27 @@ INSERT INTO checks SELECT a, b, c FROM abcde insert checks ├── columns: ├── insert-mapping: - │ ├── abcde.a:4 => checks.a:1 - │ ├── abcde.b:5 => checks.b:2 - │ └── abcde.c:6 => checks.c:3 - ├── check columns: check1:10(bool) check2:11(bool) + │ ├── abcde.a:5 => checks.a:1 + │ ├── abcde.b:6 => checks.b:2 + │ ├── abcde.c:7 => checks.c:3 + │ └── column11:11 => checks.d:4 + ├── check columns: check1:12(bool) check2:13(bool) └── project - ├── columns: check1:10(bool) check2:11(bool) abcde.a:4(int!null) abcde.b:5(int) abcde.c:6(int) + ├── columns: check1:12(bool) check2:13(bool) abcde.a:5(int!null) abcde.b:6(int) abcde.c:7(int) column11:11(int) ├── project - │ ├── columns: abcde.a:4(int!null) abcde.b:5(int) abcde.c:6(int) - │ └── scan abcde - │ └── columns: abcde.a:4(int!null) abcde.b:5(int) abcde.c:6(int) d:7(int) e:8(int) rowid:9(int!null) + │ ├── columns: column11:11(int) abcde.a:5(int!null) abcde.b:6(int) abcde.c:7(int) + │ ├── project + │ │ ├── columns: abcde.a:5(int!null) abcde.b:6(int) abcde.c:7(int) + │ │ └── scan abcde + │ │ └── columns: abcde.a:5(int!null) abcde.b:6(int) abcde.c:7(int) abcde.d:8(int) e:9(int) rowid:10(int!null) + │ └── projections + │ └── plus [type=int] + │ ├── variable: abcde.c [type=int] + │ └── const: 1 [type=int] └── projections ├── lt [type=bool] │ ├── variable: abcde.b [type=int] - │ └── variable: abcde.c [type=int] + │ └── variable: column11 [type=int] └── gt [type=bool] ├── variable: abcde.a [type=int] └── const: 0 [type=int] diff --git a/pkg/sql/opt/optbuilder/testdata/update b/pkg/sql/opt/optbuilder/testdata/update index 9ace72beb0db..530b49291801 100644 --- a/pkg/sql/opt/optbuilder/testdata/update +++ b/pkg/sql/opt/optbuilder/testdata/update @@ -65,16 +65,18 @@ CREATE TABLE checks ( a INT PRIMARY KEY CHECK (a > 0), b INT, c INT, - CHECK (checks.b < c) + d INT AS (c + 1) STORED, + CHECK (checks.b < d) ) ---- TABLE checks ├── a int not null ├── b int ├── c int + ├── d int ├── INDEX primary │ └── a int not null - ├── CHECK (checks.b < c) + ├── CHECK (checks.b < d) └── CHECK (a > 0) # ------------------------------------------------------------------------------ @@ -1363,28 +1365,35 @@ UPDATE checks SET a=1, b=2, c=3 ---- update checks ├── columns: - ├── fetch columns: a:4(int) b:5(int) c:6(int) + ├── fetch columns: a:5(int) b:6(int) c:7(int) d:8(int) ├── update-mapping: - │ ├── column7:7 => a:1 - │ ├── column8:8 => b:2 - │ └── column9:9 => c:3 - ├── check columns: check1:10(bool) check2:11(bool) + │ ├── column9:9 => a:1 + │ ├── column10:10 => b:2 + │ ├── column11:11 => c:3 + │ └── column12:12 => d:4 + ├── check columns: check1:13(bool) check2:14(bool) └── project - ├── columns: check1:10(bool) check2:11(bool) a:4(int!null) b:5(int) c:6(int) column7:7(int!null) column8:8(int!null) column9:9(int!null) + ├── columns: check1:13(bool) check2:14(bool) a:5(int!null) b:6(int) c:7(int) d:8(int) column9:9(int!null) column10:10(int!null) column11:11(int!null) column12:12(int) ├── project - │ ├── columns: column7:7(int!null) column8:8(int!null) column9:9(int!null) a:4(int!null) b:5(int) c:6(int) - │ ├── scan checks - │ │ └── columns: a:4(int!null) b:5(int) c:6(int) + │ ├── columns: column12:12(int) a:5(int!null) b:6(int) c:7(int) d:8(int) column9:9(int!null) column10:10(int!null) column11:11(int!null) + │ ├── project + │ │ ├── columns: column9:9(int!null) column10:10(int!null) column11:11(int!null) a:5(int!null) b:6(int) c:7(int) d:8(int) + │ │ ├── scan checks + │ │ │ └── columns: a:5(int!null) b:6(int) c:7(int) d:8(int) + │ │ └── projections + │ │ ├── const: 1 [type=int] + │ │ ├── const: 2 [type=int] + │ │ └── const: 3 [type=int] │ └── projections - │ ├── const: 1 [type=int] - │ ├── const: 2 [type=int] - │ └── const: 3 [type=int] + │ └── plus [type=int] + │ ├── variable: column11 [type=int] + │ └── const: 1 [type=int] └── projections ├── lt [type=bool] - │ ├── variable: column8 [type=int] - │ └── variable: column9 [type=int] + │ ├── variable: column10 [type=int] + │ └── variable: column12 [type=int] └── gt [type=bool] - ├── variable: column7 [type=int] + ├── variable: column9 [type=int] └── const: 0 [type=int] # Do not update columns for one of the constraints. @@ -1393,24 +1402,31 @@ UPDATE checks SET a=1 ---- update checks ├── columns: - ├── fetch columns: a:4(int) b:5(int) c:6(int) + ├── fetch columns: a:5(int) b:6(int) c:7(int) d:8(int) ├── update-mapping: - │ └── column7:7 => a:1 - ├── check columns: check1:8(bool) check2:9(bool) + │ ├── column9:9 => a:1 + │ └── column10:10 => d:4 + ├── check columns: check1:11(bool) check2:12(bool) └── project - ├── columns: check1:8(bool) check2:9(bool) a:4(int!null) b:5(int) c:6(int) column7:7(int!null) + ├── columns: check1:11(bool) check2:12(bool) a:5(int!null) b:6(int) c:7(int) d:8(int) column9:9(int!null) column10:10(int) ├── project - │ ├── columns: column7:7(int!null) a:4(int!null) b:5(int) c:6(int) - │ ├── scan checks - │ │ └── columns: a:4(int!null) b:5(int) c:6(int) + │ ├── columns: column10:10(int) a:5(int!null) b:6(int) c:7(int) d:8(int) column9:9(int!null) + │ ├── project + │ │ ├── columns: column9:9(int!null) a:5(int!null) b:6(int) c:7(int) d:8(int) + │ │ ├── scan checks + │ │ │ └── columns: a:5(int!null) b:6(int) c:7(int) d:8(int) + │ │ └── projections + │ │ └── const: 1 [type=int] │ └── projections - │ └── const: 1 [type=int] + │ └── plus [type=int] + │ ├── variable: c [type=int] + │ └── const: 1 [type=int] └── projections ├── lt [type=bool] │ ├── variable: b [type=int] - │ └── variable: c [type=int] + │ └── variable: column10 [type=int] └── gt [type=bool] - ├── variable: column7 [type=int] + ├── variable: column9 [type=int] └── const: 0 [type=int] # Update one column in constraint, but not the other. @@ -1419,22 +1435,29 @@ UPDATE checks SET b=2 ---- update checks ├── columns: - ├── fetch columns: a:4(int) b:5(int) c:6(int) + ├── fetch columns: a:5(int) b:6(int) c:7(int) d:8(int) ├── update-mapping: - │ └── column7:7 => b:2 - ├── check columns: check1:8(bool) check2:9(bool) + │ ├── column9:9 => b:2 + │ └── column10:10 => d:4 + ├── check columns: check1:11(bool) check2:12(bool) └── project - ├── columns: check1:8(bool) check2:9(bool) a:4(int!null) b:5(int) c:6(int) column7:7(int!null) + ├── columns: check1:11(bool) check2:12(bool) a:5(int!null) b:6(int) c:7(int) d:8(int) column9:9(int!null) column10:10(int) ├── project - │ ├── columns: column7:7(int!null) a:4(int!null) b:5(int) c:6(int) - │ ├── scan checks - │ │ └── columns: a:4(int!null) b:5(int) c:6(int) + │ ├── columns: column10:10(int) a:5(int!null) b:6(int) c:7(int) d:8(int) column9:9(int!null) + │ ├── project + │ │ ├── columns: column9:9(int!null) a:5(int!null) b:6(int) c:7(int) d:8(int) + │ │ ├── scan checks + │ │ │ └── columns: a:5(int!null) b:6(int) c:7(int) d:8(int) + │ │ └── projections + │ │ └── const: 2 [type=int] │ └── projections - │ └── const: 2 [type=int] + │ └── plus [type=int] + │ ├── variable: c [type=int] + │ └── const: 1 [type=int] └── projections ├── lt [type=bool] - │ ├── variable: column7 [type=int] - │ └── variable: c [type=int] + │ ├── variable: column9 [type=int] + │ └── variable: column10 [type=int] └── gt [type=bool] ├── variable: a [type=int] └── const: 0 [type=int] @@ -1445,34 +1468,41 @@ UPDATE checks SET (a, b)=(SELECT a, b FROM abcde WHERE abcde.a=checks.a) ---- update checks ├── columns: - ├── fetch columns: checks.a:4(int) checks.b:5(int) checks.c:6(int) + ├── fetch columns: checks.a:5(int) checks.b:6(int) checks.c:7(int) checks.d:8(int) ├── update-mapping: - │ ├── abcde.a:7 => checks.a:1 - │ └── abcde.b:8 => checks.b:2 - ├── check columns: check1:13(bool) check2:14(bool) + │ ├── abcde.a:9 => checks.a:1 + │ ├── abcde.b:10 => checks.b:2 + │ └── column15:15 => checks.d:4 + ├── check columns: check1:16(bool) check2:17(bool) └── project - ├── columns: check1:13(bool) check2:14(bool) checks.a:4(int!null) checks.b:5(int) checks.c:6(int) abcde.a:7(int) abcde.b:8(int) - ├── left-join-apply - │ ├── columns: checks.a:4(int!null) checks.b:5(int) checks.c:6(int) abcde.a:7(int) abcde.b:8(int) - │ ├── scan checks - │ │ └── columns: checks.a:4(int!null) checks.b:5(int) checks.c:6(int) - │ ├── max1-row - │ │ ├── columns: abcde.a:7(int!null) abcde.b:8(int) - │ │ └── project - │ │ ├── columns: abcde.a:7(int!null) abcde.b:8(int) - │ │ └── select - │ │ ├── columns: abcde.a:7(int!null) abcde.b:8(int) abcde.c:9(int) d:10(int) e:11(int) rowid:12(int!null) - │ │ ├── scan abcde - │ │ │ └── columns: abcde.a:7(int!null) abcde.b:8(int) abcde.c:9(int) d:10(int) e:11(int) rowid:12(int!null) - │ │ └── filters - │ │ └── eq [type=bool] - │ │ ├── variable: abcde.a [type=int] - │ │ └── variable: checks.a [type=int] - │ └── filters (true) + ├── columns: check1:16(bool) check2:17(bool) checks.a:5(int!null) checks.b:6(int) checks.c:7(int) checks.d:8(int) abcde.a:9(int) abcde.b:10(int) column15:15(int) + ├── project + │ ├── columns: column15:15(int) checks.a:5(int!null) checks.b:6(int) checks.c:7(int) checks.d:8(int) abcde.a:9(int) abcde.b:10(int) + │ ├── left-join-apply + │ │ ├── columns: checks.a:5(int!null) checks.b:6(int) checks.c:7(int) checks.d:8(int) abcde.a:9(int) abcde.b:10(int) + │ │ ├── scan checks + │ │ │ └── columns: checks.a:5(int!null) checks.b:6(int) checks.c:7(int) checks.d:8(int) + │ │ ├── max1-row + │ │ │ ├── columns: abcde.a:9(int!null) abcde.b:10(int) + │ │ │ └── project + │ │ │ ├── columns: abcde.a:9(int!null) abcde.b:10(int) + │ │ │ └── select + │ │ │ ├── columns: abcde.a:9(int!null) abcde.b:10(int) abcde.c:11(int) abcde.d:12(int) e:13(int) rowid:14(int!null) + │ │ │ ├── scan abcde + │ │ │ │ └── columns: abcde.a:9(int!null) abcde.b:10(int) abcde.c:11(int) abcde.d:12(int) e:13(int) rowid:14(int!null) + │ │ │ └── filters + │ │ │ └── eq [type=bool] + │ │ │ ├── variable: abcde.a [type=int] + │ │ │ └── variable: checks.a [type=int] + │ │ └── filters (true) + │ └── projections + │ └── plus [type=int] + │ ├── variable: checks.c [type=int] + │ └── const: 1 [type=int] └── projections ├── lt [type=bool] │ ├── variable: abcde.b [type=int] - │ └── variable: checks.c [type=int] + │ └── variable: column15 [type=int] └── gt [type=bool] ├── variable: abcde.a [type=int] └── const: 0 [type=int] diff --git a/pkg/sql/opt/optbuilder/testdata/upsert b/pkg/sql/opt/optbuilder/testdata/upsert index e12f44fe211e..169906064198 100644 --- a/pkg/sql/opt/optbuilder/testdata/upsert +++ b/pkg/sql/opt/optbuilder/testdata/upsert @@ -64,6 +64,20 @@ TABLE uv ├── u int not null └── v int not null +exec-ddl +CREATE TABLE noindex ( + x INT PRIMARY KEY, + y INT, + z INT +) +---- +TABLE noindex + ├── x int not null + ├── y int + ├── z int + └── INDEX primary + └── x int not null + exec-ddl CREATE TABLE mutation ( m INT PRIMARY KEY, @@ -87,16 +101,18 @@ CREATE TABLE checks ( a INT PRIMARY KEY CHECK (a > 0), b INT, c INT, - CHECK (checks.b < c) + d INT AS (c + 1) STORED, + CHECK (checks.b < d) ) ---- TABLE checks ├── a int not null ├── b int ├── c int + ├── d int ├── INDEX primary │ └── a int not null - ├── CHECK (checks.b < c) + ├── CHECK (checks.b < d) └── CHECK (a > 0) # ------------------------------------------------------------------------------ @@ -1310,91 +1326,78 @@ upsert xyz # Use explicitly specified column names with no secondary indexes present. # Upsert implemented with blind Puts is possible. build -UPSERT INTO checks (a, b, c) VALUES (1, 2, 3) +UPSERT INTO noindex (x, y, z) VALUES (1, 2, 3) ---- -upsert checks +upsert noindex ├── columns: ├── upsert-mapping: - │ ├── column1:4 => a:1 - │ ├── column2:5 => b:2 - │ └── column3:6 => c:3 - ├── check columns: check1:7(bool) check2:8(bool) - └── project - ├── columns: check1:7(bool) check2:8(bool) column1:4(int) column2:5(int) column3:6(int) - ├── values - │ ├── columns: column1:4(int) column2:5(int) column3:6(int) - │ └── tuple [type=tuple{int, int, int}] - │ ├── const: 1 [type=int] - │ ├── const: 2 [type=int] - │ └── const: 3 [type=int] - └── projections - ├── lt [type=bool] - │ ├── variable: column2 [type=int] - │ └── variable: column3 [type=int] - └── gt [type=bool] - ├── variable: column1 [type=int] - └── const: 0 [type=int] + │ ├── column1:4 => x:1 + │ ├── column2:5 => y:2 + │ └── column3:6 => z:3 + └── values + ├── columns: column1:4(int) column2:5(int) column3:6(int) + └── tuple [type=tuple{int, int, int}] + ├── const: 1 [type=int] + ├── const: 2 [type=int] + └── const: 3 [type=int] # Use subset of explicitly specified column names with no secondary indexes # present. Existing values of other columns need to be fetched to provide # update values for unspecified columns. build -UPSERT INTO checks (a, b) VALUES (1, 2) +UPSERT INTO checks (a, b, c) VALUES (1, 2, 3) ---- upsert checks ├── columns: - ├── canary column: 7 - ├── fetch columns: a:7(int) b:8(int) c:9(int) + ├── canary column: 9 + ├── fetch columns: a:9(int) b:10(int) c:11(int) d:12(int) ├── insert-mapping: - │ ├── column1:4 => a:1 - │ ├── column2:5 => b:2 - │ └── column6:6 => c:3 + │ ├── column1:5 => a:1 + │ ├── column2:6 => b:2 + │ ├── column3:7 => c:3 + │ └── column8:8 => d:4 ├── update-mapping: - │ └── column2:5 => b:2 - ├── check columns: check1:12(bool) check2:13(bool) + │ ├── column2:6 => b:2 + │ ├── column3:7 => c:3 + │ └── column8:8 => d:4 + ├── check columns: check1:14(bool) check2:15(bool) └── project - ├── columns: check1:12(bool) check2:13(bool) column1:4(int) column2:5(int) column6:6(int) a:7(int) b:8(int) c:9(int) upsert_a:10(int) upsert_c:11(int) + ├── columns: check1:14(bool) check2:15(bool) column1:5(int) column2:6(int) column3:7(int) column8:8(int) a:9(int) b:10(int) c:11(int) d:12(int) upsert_a:13(int) ├── project - │ ├── columns: upsert_a:10(int) upsert_c:11(int) column1:4(int) column2:5(int) column6:6(int) a:7(int) b:8(int) c:9(int) + │ ├── columns: upsert_a:13(int) column1:5(int) column2:6(int) column3:7(int) column8:8(int) a:9(int) b:10(int) c:11(int) d:12(int) │ ├── left-join - │ │ ├── columns: column1:4(int) column2:5(int) column6:6(int) a:7(int) b:8(int) c:9(int) + │ │ ├── columns: column1:5(int) column2:6(int) column3:7(int) column8:8(int) a:9(int) b:10(int) c:11(int) d:12(int) │ │ ├── project - │ │ │ ├── columns: column6:6(int) column1:4(int) column2:5(int) + │ │ │ ├── columns: column8:8(int) column1:5(int) column2:6(int) column3:7(int) │ │ │ ├── values - │ │ │ │ ├── columns: column1:4(int) column2:5(int) - │ │ │ │ └── tuple [type=tuple{int, int}] + │ │ │ │ ├── columns: column1:5(int) column2:6(int) column3:7(int) + │ │ │ │ └── tuple [type=tuple{int, int, int}] │ │ │ │ ├── const: 1 [type=int] - │ │ │ │ └── const: 2 [type=int] + │ │ │ │ ├── const: 2 [type=int] + │ │ │ │ └── const: 3 [type=int] │ │ │ └── projections - │ │ │ └── cast: INT8 [type=int] - │ │ │ └── null [type=unknown] + │ │ │ └── plus [type=int] + │ │ │ ├── variable: column3 [type=int] + │ │ │ └── const: 1 [type=int] │ │ ├── scan checks - │ │ │ └── columns: a:7(int!null) b:8(int) c:9(int) + │ │ │ └── columns: a:9(int!null) b:10(int) c:11(int) d:12(int) │ │ └── filters │ │ └── eq [type=bool] │ │ ├── variable: column1 [type=int] │ │ └── variable: a [type=int] │ └── projections - │ ├── case [type=int] - │ │ ├── true [type=bool] - │ │ ├── when [type=int] - │ │ │ ├── is [type=bool] - │ │ │ │ ├── variable: a [type=int] - │ │ │ │ └── null [type=unknown] - │ │ │ └── variable: column1 [type=int] - │ │ └── variable: a [type=int] │ └── case [type=int] │ ├── true [type=bool] │ ├── when [type=int] │ │ ├── is [type=bool] │ │ │ ├── variable: a [type=int] │ │ │ └── null [type=unknown] - │ │ └── variable: column6 [type=int] - │ └── variable: c [type=int] + │ │ └── variable: column1 [type=int] + │ └── variable: a [type=int] └── projections ├── lt [type=bool] │ ├── variable: column2 [type=int] - │ └── variable: upsert_c [type=int] + │ └── variable: column8 [type=int] └── gt [type=bool] ├── variable: upsert_a [type=int] └── const: 0 [type=int] @@ -1563,43 +1566,57 @@ INSERT INTO checks (a, b) VALUES (1, 2) ON CONFLICT (a) DO UPDATE SET b=3, c=4 ---- upsert checks ├── columns: - ├── canary column: 7 - ├── fetch columns: a:7(int) b:8(int) c:9(int) + ├── canary column: 9 + ├── fetch columns: a:9(int) b:10(int) c:11(int) d:12(int) ├── insert-mapping: - │ ├── column1:4 => a:1 - │ ├── column2:5 => b:2 - │ └── column6:6 => c:3 + │ ├── column1:5 => a:1 + │ ├── column2:6 => b:2 + │ ├── column7:7 => c:3 + │ └── column8:8 => d:4 ├── update-mapping: - │ ├── upsert_b:13 => b:2 - │ └── upsert_c:14 => c:3 - ├── check columns: check1:15(bool) check2:16(bool) + │ ├── upsert_b:17 => b:2 + │ ├── upsert_c:18 => c:3 + │ └── upsert_d:19 => d:4 + ├── check columns: check1:20(bool) check2:21(bool) └── project - ├── columns: check1:15(bool) check2:16(bool) column1:4(int) column2:5(int) column6:6(int) a:7(int) b:8(int) c:9(int) upsert_a:12(int) upsert_b:13(int) upsert_c:14(int) + ├── columns: check1:20(bool) check2:21(bool) column1:5(int) column2:6(int) column7:7(int) column8:8(int) a:9(int) b:10(int) c:11(int) d:12(int) upsert_a:16(int) upsert_b:17(int) upsert_c:18(int) upsert_d:19(int) ├── project - │ ├── columns: upsert_a:12(int) upsert_b:13(int) upsert_c:14(int) column1:4(int) column2:5(int) column6:6(int) a:7(int) b:8(int) c:9(int) + │ ├── columns: upsert_a:16(int) upsert_b:17(int) upsert_c:18(int) upsert_d:19(int) column1:5(int) column2:6(int) column7:7(int) column8:8(int) a:9(int) b:10(int) c:11(int) d:12(int) │ ├── project - │ │ ├── columns: column10:10(int!null) column11:11(int!null) column1:4(int) column2:5(int) column6:6(int) a:7(int) b:8(int) c:9(int) - │ │ ├── left-join - │ │ │ ├── columns: column1:4(int) column2:5(int) column6:6(int) a:7(int) b:8(int) c:9(int) - │ │ │ ├── project - │ │ │ │ ├── columns: column6:6(int) column1:4(int) column2:5(int) - │ │ │ │ ├── values - │ │ │ │ │ ├── columns: column1:4(int) column2:5(int) - │ │ │ │ │ └── tuple [type=tuple{int, int}] - │ │ │ │ │ ├── const: 1 [type=int] - │ │ │ │ │ └── const: 2 [type=int] - │ │ │ │ └── projections - │ │ │ │ └── cast: INT8 [type=int] - │ │ │ │ └── null [type=unknown] - │ │ │ ├── scan checks - │ │ │ │ └── columns: a:7(int!null) b:8(int) c:9(int) - │ │ │ └── filters - │ │ │ └── eq [type=bool] - │ │ │ ├── variable: column1 [type=int] - │ │ │ └── variable: a [type=int] + │ │ ├── columns: column15:15(int) column1:5(int) column2:6(int) column7:7(int) column8:8(int) a:9(int) b:10(int) c:11(int) d:12(int) column13:13(int!null) column14:14(int!null) + │ │ ├── project + │ │ │ ├── columns: column13:13(int!null) column14:14(int!null) column1:5(int) column2:6(int) column7:7(int) column8:8(int) a:9(int) b:10(int) c:11(int) d:12(int) + │ │ │ ├── left-join + │ │ │ │ ├── columns: column1:5(int) column2:6(int) column7:7(int) column8:8(int) a:9(int) b:10(int) c:11(int) d:12(int) + │ │ │ │ ├── project + │ │ │ │ │ ├── columns: column8:8(int) column1:5(int) column2:6(int) column7:7(int) + │ │ │ │ │ ├── project + │ │ │ │ │ │ ├── columns: column7:7(int) column1:5(int) column2:6(int) + │ │ │ │ │ │ ├── values + │ │ │ │ │ │ │ ├── columns: column1:5(int) column2:6(int) + │ │ │ │ │ │ │ └── tuple [type=tuple{int, int}] + │ │ │ │ │ │ │ ├── const: 1 [type=int] + │ │ │ │ │ │ │ └── const: 2 [type=int] + │ │ │ │ │ │ └── projections + │ │ │ │ │ │ └── cast: INT8 [type=int] + │ │ │ │ │ │ └── null [type=unknown] + │ │ │ │ │ └── projections + │ │ │ │ │ └── plus [type=int] + │ │ │ │ │ ├── variable: column7 [type=int] + │ │ │ │ │ └── const: 1 [type=int] + │ │ │ │ ├── scan checks + │ │ │ │ │ └── columns: a:9(int!null) b:10(int) c:11(int) d:12(int) + │ │ │ │ └── filters + │ │ │ │ └── eq [type=bool] + │ │ │ │ ├── variable: column1 [type=int] + │ │ │ │ └── variable: a [type=int] + │ │ │ └── projections + │ │ │ ├── const: 3 [type=int] + │ │ │ └── const: 4 [type=int] │ │ └── projections - │ │ ├── const: 3 [type=int] - │ │ └── const: 4 [type=int] + │ │ └── plus [type=int] + │ │ ├── variable: column14 [type=int] + │ │ └── const: 1 [type=int] │ └── projections │ ├── case [type=int] │ │ ├── true [type=bool] @@ -1616,19 +1633,27 @@ upsert checks │ │ │ │ ├── variable: a [type=int] │ │ │ │ └── null [type=unknown] │ │ │ └── variable: column2 [type=int] - │ │ └── variable: column10 [type=int] + │ │ └── variable: column13 [type=int] + │ ├── case [type=int] + │ │ ├── true [type=bool] + │ │ ├── when [type=int] + │ │ │ ├── is [type=bool] + │ │ │ │ ├── variable: a [type=int] + │ │ │ │ └── null [type=unknown] + │ │ │ └── variable: column7 [type=int] + │ │ └── variable: column14 [type=int] │ └── case [type=int] │ ├── true [type=bool] │ ├── when [type=int] │ │ ├── is [type=bool] │ │ │ ├── variable: a [type=int] │ │ │ └── null [type=unknown] - │ │ └── variable: column6 [type=int] - │ └── variable: column11 [type=int] + │ │ └── variable: column8 [type=int] + │ └── variable: column15 [type=int] └── projections ├── lt [type=bool] │ ├── variable: upsert_b [type=int] - │ └── variable: upsert_c [type=int] + │ └── variable: upsert_d [type=int] └── gt [type=bool] ├── variable: upsert_a [type=int] └── const: 0 [type=int] @@ -1640,30 +1665,37 @@ INSERT INTO checks (a, b) VALUES (1, 2) ON CONFLICT (a) DO NOTHING insert checks ├── columns: ├── insert-mapping: - │ ├── column1:4 => checks.a:1 - │ ├── column2:5 => checks.b:2 - │ └── column6:6 => checks.c:3 - ├── check columns: check1:10(bool) check2:11(bool) + │ ├── column1:5 => checks.a:1 + │ ├── column2:6 => checks.b:2 + │ ├── column7:7 => checks.c:3 + │ └── column8:8 => checks.d:4 + ├── check columns: check1:13(bool) check2:14(bool) └── project - ├── columns: check1:10(bool) check2:11(bool) column1:4(int) column2:5(int) column6:6(int) + ├── columns: check1:13(bool) check2:14(bool) column1:5(int) column2:6(int) column7:7(int) column8:8(int) ├── project - │ ├── columns: column1:4(int) column2:5(int) column6:6(int) + │ ├── columns: column1:5(int) column2:6(int) column7:7(int) column8:8(int) │ └── select - │ ├── columns: column1:4(int) column2:5(int) column6:6(int) checks_1.a:7(int) checks_1.b:8(int) checks_1.c:9(int) + │ ├── columns: column1:5(int) column2:6(int) column7:7(int) column8:8(int) checks_1.a:9(int) checks_1.b:10(int) checks_1.c:11(int) checks_1.d:12(int) │ ├── left-join - │ │ ├── columns: column1:4(int) column2:5(int) column6:6(int) checks_1.a:7(int) checks_1.b:8(int) checks_1.c:9(int) + │ │ ├── columns: column1:5(int) column2:6(int) column7:7(int) column8:8(int) checks_1.a:9(int) checks_1.b:10(int) checks_1.c:11(int) checks_1.d:12(int) │ │ ├── project - │ │ │ ├── columns: column6:6(int) column1:4(int) column2:5(int) - │ │ │ ├── values - │ │ │ │ ├── columns: column1:4(int) column2:5(int) - │ │ │ │ └── tuple [type=tuple{int, int}] - │ │ │ │ ├── const: 1 [type=int] - │ │ │ │ └── const: 2 [type=int] + │ │ │ ├── columns: column8:8(int) column1:5(int) column2:6(int) column7:7(int) + │ │ │ ├── project + │ │ │ │ ├── columns: column7:7(int) column1:5(int) column2:6(int) + │ │ │ │ ├── values + │ │ │ │ │ ├── columns: column1:5(int) column2:6(int) + │ │ │ │ │ └── tuple [type=tuple{int, int}] + │ │ │ │ │ ├── const: 1 [type=int] + │ │ │ │ │ └── const: 2 [type=int] + │ │ │ │ └── projections + │ │ │ │ └── cast: INT8 [type=int] + │ │ │ │ └── null [type=unknown] │ │ │ └── projections - │ │ │ └── cast: INT8 [type=int] - │ │ │ └── null [type=unknown] + │ │ │ └── plus [type=int] + │ │ │ ├── variable: column7 [type=int] + │ │ │ └── const: 1 [type=int] │ │ ├── scan checks_1 - │ │ │ └── columns: checks_1.a:7(int!null) checks_1.b:8(int) checks_1.c:9(int) + │ │ │ └── columns: checks_1.a:9(int!null) checks_1.b:10(int) checks_1.c:11(int) checks_1.d:12(int) │ │ └── filters │ │ └── eq [type=bool] │ │ ├── variable: column1 [type=int] @@ -1675,7 +1707,7 @@ insert checks └── projections ├── lt [type=bool] │ ├── variable: column2 [type=int] - │ └── variable: column6 [type=int] + │ └── variable: column8 [type=int] └── gt [type=bool] ├── variable: column1 [type=int] └── const: 0 [type=int] @@ -1686,37 +1718,51 @@ UPSERT INTO checks (a, b) VALUES (1, 2) ---- upsert checks ├── columns: - ├── canary column: 7 - ├── fetch columns: a:7(int) b:8(int) c:9(int) + ├── canary column: 9 + ├── fetch columns: a:9(int) b:10(int) c:11(int) d:12(int) ├── insert-mapping: - │ ├── column1:4 => a:1 - │ ├── column2:5 => b:2 - │ └── column6:6 => c:3 + │ ├── column1:5 => a:1 + │ ├── column2:6 => b:2 + │ ├── column7:7 => c:3 + │ └── column8:8 => d:4 ├── update-mapping: - │ └── column2:5 => b:2 - ├── check columns: check1:12(bool) check2:13(bool) + │ ├── column2:6 => b:2 + │ └── upsert_d:16 => d:4 + ├── check columns: check1:17(bool) check2:18(bool) └── project - ├── columns: check1:12(bool) check2:13(bool) column1:4(int) column2:5(int) column6:6(int) a:7(int) b:8(int) c:9(int) upsert_a:10(int) upsert_c:11(int) + ├── columns: check1:17(bool) check2:18(bool) column1:5(int) column2:6(int) column7:7(int) column8:8(int) a:9(int) b:10(int) c:11(int) d:12(int) upsert_a:14(int) upsert_c:15(int) upsert_d:16(int) ├── project - │ ├── columns: upsert_a:10(int) upsert_c:11(int) column1:4(int) column2:5(int) column6:6(int) a:7(int) b:8(int) c:9(int) - │ ├── left-join - │ │ ├── columns: column1:4(int) column2:5(int) column6:6(int) a:7(int) b:8(int) c:9(int) - │ │ ├── project - │ │ │ ├── columns: column6:6(int) column1:4(int) column2:5(int) - │ │ │ ├── values - │ │ │ │ ├── columns: column1:4(int) column2:5(int) - │ │ │ │ └── tuple [type=tuple{int, int}] - │ │ │ │ ├── const: 1 [type=int] - │ │ │ │ └── const: 2 [type=int] - │ │ │ └── projections - │ │ │ └── cast: INT8 [type=int] - │ │ │ └── null [type=unknown] - │ │ ├── scan checks - │ │ │ └── columns: a:7(int!null) b:8(int) c:9(int) - │ │ └── filters - │ │ └── eq [type=bool] - │ │ ├── variable: column1 [type=int] - │ │ └── variable: a [type=int] + │ ├── columns: upsert_a:14(int) upsert_c:15(int) upsert_d:16(int) column1:5(int) column2:6(int) column7:7(int) column8:8(int) a:9(int) b:10(int) c:11(int) d:12(int) + │ ├── project + │ │ ├── columns: column13:13(int) column1:5(int) column2:6(int) column7:7(int) column8:8(int) a:9(int) b:10(int) c:11(int) d:12(int) + │ │ ├── left-join + │ │ │ ├── columns: column1:5(int) column2:6(int) column7:7(int) column8:8(int) a:9(int) b:10(int) c:11(int) d:12(int) + │ │ │ ├── project + │ │ │ │ ├── columns: column8:8(int) column1:5(int) column2:6(int) column7:7(int) + │ │ │ │ ├── project + │ │ │ │ │ ├── columns: column7:7(int) column1:5(int) column2:6(int) + │ │ │ │ │ ├── values + │ │ │ │ │ │ ├── columns: column1:5(int) column2:6(int) + │ │ │ │ │ │ └── tuple [type=tuple{int, int}] + │ │ │ │ │ │ ├── const: 1 [type=int] + │ │ │ │ │ │ └── const: 2 [type=int] + │ │ │ │ │ └── projections + │ │ │ │ │ └── cast: INT8 [type=int] + │ │ │ │ │ └── null [type=unknown] + │ │ │ │ └── projections + │ │ │ │ └── plus [type=int] + │ │ │ │ ├── variable: column7 [type=int] + │ │ │ │ └── const: 1 [type=int] + │ │ │ ├── scan checks + │ │ │ │ └── columns: a:9(int!null) b:10(int) c:11(int) d:12(int) + │ │ │ └── filters + │ │ │ └── eq [type=bool] + │ │ │ ├── variable: column1 [type=int] + │ │ │ └── variable: a [type=int] + │ │ └── projections + │ │ └── plus [type=int] + │ │ ├── variable: c [type=int] + │ │ └── const: 1 [type=int] │ └── projections │ ├── case [type=int] │ │ ├── true [type=bool] @@ -1726,18 +1772,26 @@ upsert checks │ │ │ │ └── null [type=unknown] │ │ │ └── variable: column1 [type=int] │ │ └── variable: a [type=int] + │ ├── case [type=int] + │ │ ├── true [type=bool] + │ │ ├── when [type=int] + │ │ │ ├── is [type=bool] + │ │ │ │ ├── variable: a [type=int] + │ │ │ │ └── null [type=unknown] + │ │ │ └── variable: column7 [type=int] + │ │ └── variable: c [type=int] │ └── case [type=int] │ ├── true [type=bool] │ ├── when [type=int] │ │ ├── is [type=bool] │ │ │ ├── variable: a [type=int] │ │ │ └── null [type=unknown] - │ │ └── variable: column6 [type=int] - │ └── variable: c [type=int] + │ │ └── variable: column8 [type=int] + │ └── variable: column13 [type=int] └── projections ├── lt [type=bool] │ ├── variable: column2 [type=int] - │ └── variable: upsert_c [type=int] + │ └── variable: upsert_d [type=int] └── gt [type=bool] ├── variable: upsert_a [type=int] └── const: 0 [type=int] @@ -1750,53 +1804,67 @@ ON CONFLICT (a) DO UPDATE SET a=excluded.a, b=(SELECT x FROM xyz WHERE x=checks. ---- upsert checks ├── columns: - ├── canary column: 9 - ├── fetch columns: checks.a:9(int) checks.b:10(int) checks.c:11(int) + ├── canary column: 11 + ├── fetch columns: checks.a:11(int) checks.b:12(int) checks.c:13(int) d:14(int) ├── insert-mapping: - │ ├── abc.a:4 => checks.a:1 - │ ├── abc.b:5 => checks.b:2 - │ └── column8:8 => checks.c:3 + │ ├── abc.a:5 => checks.a:1 + │ ├── abc.b:6 => checks.b:2 + │ ├── column9:9 => checks.c:3 + │ └── column10:10 => d:4 ├── update-mapping: - │ ├── abc.a:4 => checks.a:1 - │ └── upsert_b:16 => checks.b:2 - ├── check columns: check1:18(bool) check2:19(bool) + │ ├── abc.a:5 => checks.a:1 + │ ├── upsert_b:20 => checks.b:2 + │ └── upsert_d:22 => d:4 + ├── check columns: check1:23(bool) check2:24(bool) └── project - ├── columns: check1:18(bool) check2:19(bool) abc.a:4(int!null) abc.b:5(int) column8:8(int) checks.a:9(int) checks.b:10(int) checks.c:11(int) upsert_b:16(int) upsert_c:17(int) + ├── columns: check1:23(bool) check2:24(bool) abc.a:5(int!null) abc.b:6(int) column9:9(int) column10:10(int) checks.a:11(int) checks.b:12(int) checks.c:13(int) d:14(int) upsert_b:20(int) upsert_c:21(int) upsert_d:22(int) ├── project - │ ├── columns: upsert_b:16(int) upsert_c:17(int) abc.a:4(int!null) abc.b:5(int) column8:8(int) checks.a:9(int) checks.b:10(int) checks.c:11(int) + │ ├── columns: upsert_b:20(int) upsert_c:21(int) upsert_d:22(int) abc.a:5(int!null) abc.b:6(int) column9:9(int) column10:10(int) checks.a:11(int) checks.b:12(int) checks.c:13(int) d:14(int) │ ├── project - │ │ ├── columns: column15:15(int) abc.a:4(int!null) abc.b:5(int) column8:8(int) checks.a:9(int) checks.b:10(int) checks.c:11(int) - │ │ ├── left-join - │ │ │ ├── columns: abc.a:4(int!null) abc.b:5(int) column8:8(int) checks.a:9(int) checks.b:10(int) checks.c:11(int) - │ │ │ ├── project - │ │ │ │ ├── columns: column8:8(int) abc.a:4(int!null) abc.b:5(int) + │ │ ├── columns: column19:19(int) abc.a:5(int!null) abc.b:6(int) column9:9(int) column10:10(int) checks.a:11(int) checks.b:12(int) checks.c:13(int) d:14(int) column18:18(int) + │ │ ├── project + │ │ │ ├── columns: column18:18(int) abc.a:5(int!null) abc.b:6(int) column9:9(int) column10:10(int) checks.a:11(int) checks.b:12(int) checks.c:13(int) d:14(int) + │ │ │ ├── left-join + │ │ │ │ ├── columns: abc.a:5(int!null) abc.b:6(int) column9:9(int) column10:10(int) checks.a:11(int) checks.b:12(int) checks.c:13(int) d:14(int) │ │ │ │ ├── project - │ │ │ │ │ ├── columns: abc.a:4(int!null) abc.b:5(int) - │ │ │ │ │ └── scan abc - │ │ │ │ │ └── columns: abc.a:4(int!null) abc.b:5(int) abc.c:6(int) rowid:7(int!null) - │ │ │ │ └── projections - │ │ │ │ └── cast: INT8 [type=int] - │ │ │ │ └── null [type=unknown] - │ │ │ ├── scan checks - │ │ │ │ └── columns: checks.a:9(int!null) checks.b:10(int) checks.c:11(int) - │ │ │ └── filters - │ │ │ └── eq [type=bool] - │ │ │ ├── variable: abc.a [type=int] - │ │ │ └── variable: checks.a [type=int] + │ │ │ │ │ ├── columns: column10:10(int) abc.a:5(int!null) abc.b:6(int) column9:9(int) + │ │ │ │ │ ├── project + │ │ │ │ │ │ ├── columns: column9:9(int) abc.a:5(int!null) abc.b:6(int) + │ │ │ │ │ │ ├── project + │ │ │ │ │ │ │ ├── columns: abc.a:5(int!null) abc.b:6(int) + │ │ │ │ │ │ │ └── scan abc + │ │ │ │ │ │ │ └── columns: abc.a:5(int!null) abc.b:6(int) abc.c:7(int) rowid:8(int!null) + │ │ │ │ │ │ └── projections + │ │ │ │ │ │ └── cast: INT8 [type=int] + │ │ │ │ │ │ └── null [type=unknown] + │ │ │ │ │ └── projections + │ │ │ │ │ └── plus [type=int] + │ │ │ │ │ ├── variable: column9 [type=int] + │ │ │ │ │ └── const: 1 [type=int] + │ │ │ │ ├── scan checks + │ │ │ │ │ └── columns: checks.a:11(int!null) checks.b:12(int) checks.c:13(int) d:14(int) + │ │ │ │ └── filters + │ │ │ │ └── eq [type=bool] + │ │ │ │ ├── variable: abc.a [type=int] + │ │ │ │ └── variable: checks.a [type=int] + │ │ │ └── projections + │ │ │ └── subquery [type=int] + │ │ │ └── max1-row + │ │ │ ├── columns: x:15(int!null) + │ │ │ └── project + │ │ │ ├── columns: x:15(int!null) + │ │ │ └── select + │ │ │ ├── columns: x:15(int!null) y:16(int) z:17(int) + │ │ │ ├── scan xyz + │ │ │ │ └── columns: x:15(int!null) y:16(int) z:17(int) + │ │ │ └── filters + │ │ │ └── eq [type=bool] + │ │ │ ├── variable: x [type=int] + │ │ │ └── variable: checks.a [type=int] │ │ └── projections - │ │ └── subquery [type=int] - │ │ └── max1-row - │ │ ├── columns: x:12(int!null) - │ │ └── project - │ │ ├── columns: x:12(int!null) - │ │ └── select - │ │ ├── columns: x:12(int!null) y:13(int) z:14(int) - │ │ ├── scan xyz - │ │ │ └── columns: x:12(int!null) y:13(int) z:14(int) - │ │ └── filters - │ │ └── eq [type=bool] - │ │ ├── variable: x [type=int] - │ │ └── variable: checks.a [type=int] + │ │ └── plus [type=int] + │ │ ├── variable: checks.c [type=int] + │ │ └── const: 1 [type=int] │ └── projections │ ├── case [type=int] │ │ ├── true [type=bool] @@ -1805,19 +1873,27 @@ upsert checks │ │ │ │ ├── variable: checks.a [type=int] │ │ │ │ └── null [type=unknown] │ │ │ └── variable: abc.b [type=int] - │ │ └── variable: column15 [type=int] + │ │ └── variable: column18 [type=int] + │ ├── case [type=int] + │ │ ├── true [type=bool] + │ │ ├── when [type=int] + │ │ │ ├── is [type=bool] + │ │ │ │ ├── variable: checks.a [type=int] + │ │ │ │ └── null [type=unknown] + │ │ │ └── variable: column9 [type=int] + │ │ └── variable: checks.c [type=int] │ └── case [type=int] │ ├── true [type=bool] │ ├── when [type=int] │ │ ├── is [type=bool] │ │ │ ├── variable: checks.a [type=int] │ │ │ └── null [type=unknown] - │ │ └── variable: column8 [type=int] - │ └── variable: checks.c [type=int] + │ │ └── variable: column10 [type=int] + │ └── variable: column19 [type=int] └── projections ├── lt [type=bool] │ ├── variable: upsert_b [type=int] - │ └── variable: upsert_c [type=int] + │ └── variable: upsert_d [type=int] └── gt [type=bool] ├── variable: abc.a [type=int] └── const: 0 [type=int] diff --git a/pkg/sql/opt/optbuilder/update.go b/pkg/sql/opt/optbuilder/update.go index efa4c7175407..cdbd00af75a2 100644 --- a/pkg/sql/opt/optbuilder/update.go +++ b/pkg/sql/opt/optbuilder/update.go @@ -294,31 +294,9 @@ func (mb *mutationBuilder) addComputedColsForUpdate() { mb.outScope.cols[i].mutation = false } - // Disambiguate any existing columns of the same name so that computed - // expressions will refer to the updated value rather than the old column - // containing the original value (or even the column containing a value to - // be inserted in case of upsert). - for i, n := 0, mb.tab.DeletableColumnCount(); i < n; i++ { - colName := mb.tab.Column(i).ColName() - colID := mb.fetchColList[i] - if mb.updateColList[i] != 0 { - colID = mb.updateColList[i] - } - - for i := range mb.outScope.cols { - col := &mb.outScope.cols[i] - if col.name == colName { - if col.id == colID { - // Use table name, not alias name, since computed column - // expressions will not reference aliases. - col.table = *mb.tab.Name() - } else { - // Clear name so that it will never match. - col.clearName() - } - } - } - } + // Disambiguate names so that references in the computed expression refer to + // the correct columns. + mb.disambiguateColumns() mb.addSynthesizedCols( mb.updateColList, diff --git a/pkg/sql/opt/testutils/testcat/create_table.go b/pkg/sql/opt/testutils/testcat/create_table.go index fa4fbb73fcc1..f0610b54d0e8 100644 --- a/pkg/sql/opt/testutils/testcat/create_table.go +++ b/pkg/sql/opt/testutils/testcat/create_table.go @@ -118,7 +118,7 @@ func (tc *Catalog) CreateTable(stmt *tree.CreateTable) *Table { } } - // Add the primary index and any check constraints. + // Add the primary index. if hasPrimaryIndex { for _, def := range stmt.Defs { switch def := def.(type) { @@ -132,15 +132,20 @@ func (tc *Catalog) CreateTable(stmt *tree.CreateTable) *Table { if def.PrimaryKey { tab.addIndex(&def.IndexTableDef, primaryIndex) } - - case *tree.CheckConstraintTableDef: - tab.Checks = append(tab.Checks, cat.CheckConstraint(tree.Serialize(def.Expr))) } } } else if !tab.IsVirtual { tab.addPrimaryColumnIndex("rowid") } + // Add check constraints. + for _, def := range stmt.Defs { + switch def := def.(type) { + case *tree.CheckConstraintTableDef: + tab.Checks = append(tab.Checks, cat.CheckConstraint(tree.Serialize(def.Expr))) + } + } + // Search for index and family definitions. for _, def := range stmt.Defs { switch def := def.(type) {