From b0a382a0ae28a01b12c52cfe82b7c5bea1b1753e Mon Sep 17 00:00:00 2001 From: Marcus Gartner Date: Wed, 12 Jan 2022 14:59:31 -0500 Subject: [PATCH] sql: add assignment casts for UPSERTs Assignment casts are now added to query plans for upserts, including `UPSERT`, `INSERT .. ON CONFLICT DO NOTHING`, and `INSERT .. ON CONFLICT DO UPDATE ..` statements. Assignment casts are a more general form of the logic for rounding decimal values, so the use of `round_decimal_values` in mutations is no longer needed. This logic has been removed. Fixes #67083 There is no release note because the behavior of upserts should not change with this commit. Release note: None --- pkg/sql/logictest/testdata/logic_test/cast | 452 +++++- pkg/sql/opt/optbuilder/fk_cascade.go | 4 +- pkg/sql/opt/optbuilder/insert.go | 47 +- pkg/sql/opt/optbuilder/mutation_builder.go | 168 -- .../optbuilder/testdata/fk-on-update-cascade | 329 +++- .../testdata/fk-on-update-set-default | 228 ++- pkg/sql/opt/optbuilder/testdata/upsert | 1403 +++++++++++++++-- pkg/sql/opt/optbuilder/update.go | 45 +- pkg/sql/opt/xform/testdata/external/tpce | 20 +- .../opt/xform/testdata/external/tpce-no-stats | 20 +- pkg/sql/opt/xform/testdata/external/trading | 104 +- .../xform/testdata/external/trading-mutation | 120 +- 12 files changed, 2382 insertions(+), 558 deletions(-) diff --git a/pkg/sql/logictest/testdata/logic_test/cast b/pkg/sql/logictest/testdata/logic_test/cast index b48918ad5e34..9ec19916297f 100644 --- a/pkg/sql/logictest/testdata/logic_test/cast +++ b/pkg/sql/logictest/testdata/logic_test/cast @@ -561,12 +561,420 @@ UPDATE assn_cast SET t = 3.2 statement error value type decimal doesn't match type timestamp of column "t" UPDATE assn_cast SET (i, t) = (1, 3.2) + +# Tests for assignment casts in UPSERTs. +subtest assignment_casts_upsert + +statement ok +CREATE TABLE assn_cast_upsert ( + k INT PRIMARY KEY, + c CHAR, + qc "char", + i2 INT2, + d DECIMAL(10, 0), + a DECIMAL(10, 0)[] +) + +statement error value too long for type CHAR +UPSERT INTO assn_cast_upsert (k, c) VALUES (1, 'abc') + +statement ok +UPSERT INTO assn_cast_upsert (k, c) VALUES (1, 'a') + +statement error value too long for type CHAR +UPSERT INTO assn_cast_upsert (k, c) VALUES (1, 'def') + +statement error value too long for type CHAR +UPSERT INTO assn_cast_upsert (k, c) VALUES ('1', 'def') + +statement error value too long for type CHAR +UPSERT INTO assn_cast_upsert (k, c) VALUES (1, 123) + +statement error value type string doesn't match type int of column \"k\" +UPSERT INTO assn_cast_upsert (k, c) VALUES ('1'::STRING, 'b') + +statement ok +UPSERT INTO assn_cast_upsert (k, c) VALUES (1, 'b') + +statement ok +UPSERT INTO assn_cast_upsert (k, c) VALUES ('1', 'c') + +statement ok +UPSERT INTO assn_cast_upsert (k, c) VALUES (1, NULL) + +statement ok +PREPARE upsert_c AS UPSERT INTO assn_cast_upsert (k, c) VALUES ($1, $2) + +statement error value too long for type CHAR +EXECUTE upsert_c(1, 'foo') + +statement error value too long for type CHAR +EXECUTE upsert_c(2, 'foo') + +statement error value too long for type CHAR +EXECUTE upsert_c(1, 'foo'::STRING) + +statement ok +EXECUTE upsert_c(1, ' ') + +statement ok +EXECUTE upsert_c(2, ' ') + +query IT +SELECT k, concat('"', c, '"') FROM assn_cast_upsert +---- +1 "" +2 "" + +statement ok +EXECUTE upsert_c(1, ' '::STRING) + +statement ok +EXECUTE upsert_c(3, ' '::STRING) + +query IT +SELECT k, concat('"', c, '"') FROM assn_cast_upsert +---- +1 "" +2 "" +3 "" + +statement ok +DELETE FROM assn_cast_upsert + +statement ok +UPSERT INTO assn_cast_upsert (k, qc) VALUES (1, 'a') + +query T +UPSERT INTO assn_cast_upsert (k, qc) VALUES (1, 'abc') RETURNING qc +---- +a + +# An integer to "char" cast converts the integer into the corresponding 7-bit +# ASCII character. Anything greater than 127 is out of range. +statement error \"char\" out of range +UPSERT INTO assn_cast_upsert (k, qc) VALUES (1, 1234) + +statement ok +PREPARE upsert_qc AS UPSERT INTO assn_cast_upsert (k, qc) VALUES ($1, $2) + +statement ok +EXECUTE upsert_qc(1, 'foo') + +query T +SELECT qc FROM assn_cast_upsert +---- +f + +statement ok +EXECUTE upsert_qc(1, 'bar'::STRING) + +query T +SELECT qc FROM assn_cast_upsert +---- +b + +statement error integer out of range for type int2 +UPSERT INTO assn_cast_upsert (k, i2) VALUES (1, 999999999) + +statement ok +PREPARE upsert_i2 AS UPSERT INTO assn_cast_upsert (k, i2) VALUES ($1, $2) + +statement error integer out of range for type int2 +EXECUTE upsert_i2(1, 99999999) + +query F +UPSERT INTO assn_cast_upsert (k, d) VALUES (1, 11.22) RETURNING d +---- +11 + +query F +UPSERT INTO assn_cast_upsert (k, d) VALUES (1, 11.22::DECIMAL(10, 0)) RETURNING d +---- +11 + +query F +UPSERT INTO assn_cast_upsert (k, d) VALUES (1, 11.22::DECIMAL(10, 2)) RETURNING d +---- +11 + +statement ok +PREPARE upsert_d AS UPSERT INTO assn_cast_upsert (k, d) VALUES ($1, $2) + +statement ok +EXECUTE upsert_d(1, 123.45) + +query F +SELECT d FROM assn_cast_upsert +---- +123 + +statement ok +PREPARE upsert_d2 AS UPSERT INTO assn_cast_upsert (k, d) VALUES (1, (SELECT * FROM (VALUES ($1::DECIMAL(10, 2))))) + +statement ok +EXECUTE upsert_d2(67.89) + +query F +SELECT d FROM assn_cast_upsert +---- +68 + +query T +UPSERT INTO assn_cast_upsert (k, a) VALUES (1, ARRAY[]) RETURNING a +---- +{} + +query T +UPSERT INTO assn_cast_upsert (k, a) VALUES (1, ARRAY[NULL]) RETURNING a +---- +{NULL} + +query T +UPSERT INTO assn_cast_upsert (k, a) VALUES (1, ARRAY[1.1]) RETURNING a +---- +{1} + +query T +UPSERT INTO assn_cast_upsert (k, a) VALUES (1, ARRAY[2.88, NULL, 15]) RETURNING a +---- +{3,NULL,15} + +query T +UPSERT INTO assn_cast_upsert (k, a) VALUES (1, ARRAY[3.99, NULL, 16]::DECIMAL(10, 2)[]) RETURNING a +---- +{4,NULL,16} + +query T +UPSERT INTO assn_cast_upsert (k, a) VALUES (1, ARRAY[5.55, 6.66::DECIMAL(10, 2)]) RETURNING a +---- +{6,7} + +statement ok +PREPARE upsert_a AS UPSERT INTO assn_cast_upsert (k, a) VALUES ($1, $2) + +statement ok +EXECUTE upsert_a(1, ARRAY[7.77, 8.88::DECIMAL(10, 2)]) + +query T +SELECT a FROM assn_cast_upsert +---- +{8,9} + +statement ok +PREPARE upsert_a2 AS UPSERT INTO assn_cast_upsert (k, a) VALUES ($1, ARRAY[$2]) + +statement ok +EXECUTE upsert_a2(1, 20.2) + +query T +SELECT a FROM assn_cast_upsert +---- +{20} + +statement ok +PREPARE upsert_a3 AS UPSERT INTO assn_cast_upsert (k, a) VALUES ($1, ARRAY[30.12, $2, 32.1]) + +statement ok +EXECUTE upsert_a3(1, 30.9) + +query T +SELECT a FROM assn_cast_upsert +---- +{30,31,32} + + +# Tests for assignment casts in INSERT .. ON CONFLICT .. DO NOTHING. +subtest assignment_casts_insert_do_nothing + +statement ok +CREATE TABLE assn_cast_do_nothing ( + k INT PRIMARY KEY, + d DECIMAL(10, 0) UNIQUE, + c CHAR UNIQUE +) + +statement error value too long for type CHAR +INSERT INTO assn_cast_do_nothing VALUES (1, 2.34, 'abc') ON CONFLICT DO NOTHING + +statement ok +INSERT INTO assn_cast_do_nothing VALUES (1, 2.34, 'a') ON CONFLICT DO NOTHING + +# Conflict with k. +statement ok +INSERT INTO assn_cast_do_nothing VALUES (1, 5.67, 'b') ON CONFLICT DO NOTHING + +# Conflict with d. +statement ok +INSERT INTO assn_cast_do_nothing VALUES (2, 2.34, 'b') ON CONFLICT DO NOTHING + +# Conflict with c. +statement ok +INSERT INTO assn_cast_do_nothing VALUES (2, 5.67, 'a') ON CONFLICT DO NOTHING + +statement ok +INSERT INTO assn_cast_do_nothing VALUES ('1', 2.34, 'a') ON CONFLICT (k) DO NOTHING + +statement ok +INSERT INTO assn_cast_do_nothing VALUES (1, 2.45, 'a') ON CONFLICT (d) DO NOTHING + +statement ok +INSERT INTO assn_cast_do_nothing VALUES (1, 2.45::DECIMAL(10, 2), 'a') ON CONFLICT (d) DO NOTHING + +statement ok +INSERT INTO assn_cast_do_nothing VALUES (1, 2.0, 'a') ON CONFLICT (d) DO NOTHING + +statement ok +INSERT INTO assn_cast_do_nothing VALUES (1, 2, 'a') ON CONFLICT (d) DO NOTHING + +query IRT +SELECT * FROM assn_cast_do_nothing +---- +1 2 a + +statement ok +PREPARE insert_do_nothing_d AS INSERT INTO assn_cast_do_nothing VALUES ($1, $2, $3) ON CONFLICT (d) DO NOTHING + +statement ok +EXECUTE insert_do_nothing_d(1, 2.45, 'a') + +statement ok +EXECUTE insert_do_nothing_d(1, 2.45::DECIMAL(10, 2), 'a') + +statement ok +EXECUTE insert_do_nothing_d(1, 2.0, 'a') + +statement ok +EXECUTE insert_do_nothing_d(1, 2, 'a') + +statement error duplicate key value violates unique constraint "assn_cast_do_nothing_pkey"\nDETAIL: Key \(k\)=\(1\) already exists\. +EXECUTE insert_do_nothing_d(1, 2.56, 'a') + +query IRT +SELECT * FROM assn_cast_do_nothing +---- +1 2 a + +statement ok +PREPARE insert_do_nothing_d2 AS INSERT INTO assn_cast_do_nothing VALUES ($1, $2::DECIMAL(10, 0), $3) ON CONFLICT (d) DO NOTHING + +statement ok +EXECUTE insert_do_nothing_d2(1, 2.45, 'a') + +statement ok +EXECUTE insert_do_nothing_d2(1, 2.45::DECIMAL(10, 2), 'a') + +statement ok +EXECUTE insert_do_nothing_d2(1, 2.0, 'a') + +statement ok +EXECUTE insert_do_nothing_d2(1, 2, 'a') + +statement error duplicate key value violates unique constraint "assn_cast_do_nothing_pkey"\nDETAIL: Key \(k\)=\(1\) already exists\. +EXECUTE insert_do_nothing_d2(1, 2.56, 'a') + +query IRT +SELECT * FROM assn_cast_do_nothing +---- +1 2 a + + +# Tests for assignment casts in INSERT .. ON CONFLICT .. DO UPDATE. +subtest assignment_casts_insert_do_update + +statement ok +CREATE TABLE assn_cast_do_update ( + k INT PRIMARY KEY, + d DECIMAL(10, 0) UNIQUE, + c CHAR UNIQUE +) + +statement error value too long for type CHAR +INSERT INTO assn_cast_do_update VALUES (1, 2.34, 'abc') ON CONFLICT (c) DO UPDATE SET c = 'b' + +statement ok +INSERT INTO assn_cast_do_update VALUES (1, 2.34, 'a') ON CONFLICT (c) DO UPDATE SET c = 'b' + +statement error value too long for type CHAR +INSERT INTO assn_cast_do_update VALUES (1, 2.34, 'a') ON CONFLICT (c) DO UPDATE SET c = 'abc' + +statement ok +INSERT INTO assn_cast_do_update VALUES (1, 2.34, 'a') ON CONFLICT (c) DO UPDATE SET c = 'b' + +query IRT +SELECT * FROM assn_cast_do_update +---- +1 2 b + +statement ok +PREPARE insert_do_update_c AS +INSERT INTO assn_cast_do_update VALUES (1, 2.34, $1) ON CONFLICT (c) DO UPDATE SET c = $2 + +statement error value too long for type CHAR +EXECUTE insert_do_update_c('b', 'abc') + +statement error value too long for type CHAR +EXECUTE insert_do_update_c('b', 'abc'::STRING) + +statement error duplicate key value violates unique constraint "assn_cast_do_update_pkey"\nDETAIL: Key \(k\)=\(1\) already exists\. +EXECUTE insert_do_update_c('c', 'abc') + +statement ok +EXECUTE insert_do_update_c('b', 'c') + +query IRT +SELECT * FROM assn_cast_do_update +---- +1 2 c + +query I +INSERT INTO assn_cast_do_update VALUES ('1', 2.34, 'a') +ON CONFLICT (k) DO UPDATE SET k = '2' +RETURNING k +---- +2 + +query R +INSERT INTO assn_cast_do_update VALUES (1, 2.45, 'a') +ON CONFLICT (d) DO UPDATE SET d = 3.56 +RETURNING d +---- +4 + +query R +INSERT INTO assn_cast_do_update VALUES (1, 3.56, 'a') +ON CONFLICT (d) DO UPDATE SET d = 5.12::DECIMAL(10, 2) +RETURNING d +---- +5 + +query IRT +SELECT * FROM assn_cast_do_update +---- +2 5 c + +statement ok +INSERT INTO assn_cast_do_update VALUES (3, 1.23, 'b') + +statement error duplicate key value violates unique constraint "assn_cast_do_update_c_key"\nDETAIL: Key \(c\)=\('c'\) already exists\. +INSERT INTO assn_cast_do_update VALUES (3, 10.12, 'b') +ON CONFLICT (c) DO UPDATE SET c = 'c' + +statement error duplicate key value violates unique constraint "assn_cast_do_update_d_key"\nDETAIL: Key \(d\)=\(5\) already exists\. +INSERT INTO assn_cast_do_update VALUES (3, 10.12, 'b') +ON CONFLICT (c) DO UPDATE SET d = 5.45 + +statement error duplicate key value violates unique constraint "assn_cast_do_update_d_key"\nDETAIL: Key \(d\)=\(5\) already exists\. +INSERT INTO assn_cast_do_update VALUES (3, 10.12, 'b') +ON CONFLICT (c) DO UPDATE SET d = 5.45::DECIMAL(10, 2) + + # Tests for assignment casts in cascading UPDATEs. subtest assignment_casts_update_cascade statement ok -CREATE TABLE assn_cast_p (p DECIMAL(10, 2) PRIMARY KEY); -INSERT INTO assn_cast_p VALUES (1.0); +CREATE TABLE assn_cast_p (p DECIMAL(10, 2) PRIMARY KEY, d DECIMAL(10, 2) UNIQUE); +INSERT INTO assn_cast_p VALUES (1.0, 10.0); # Test ON UPDATE CASCADE. statement ok @@ -584,6 +992,23 @@ SELECT * FROM assn_cast_c ---- 1 2 +statement ok +DROP TABLE assn_cast_c; +CREATE TABLE assn_cast_c (c INT PRIMARY KEY, d DECIMAL(10, 0) REFERENCES assn_cast_p(d) ON UPDATE CASCADE); +UPSERT INTO assn_cast_c VALUES (2, 10) + +statement error update on table "assn_cast_c" violates foreign key constraint "assn_cast_c_d_fkey" +UPSERT INTO assn_cast_p VALUES (2.0, 11.22) + +statement ok +UPSERT INTO assn_cast_p VALUES (2.0, 11.00) + +statement error update on table "assn_cast_c" violates foreign key constraint "assn_cast_c_d_fkey" +INSERT INTO assn_cast_p VALUES (2.0, 11.00) ON CONFLICT (d) DO UPDATE SET d = 12.99 + +statement ok +INSERT INTO assn_cast_p VALUES (2.0, 11.00) ON CONFLICT (d) DO UPDATE SET d = 12.0 + # Test ON UPDATE SET DEFAULT. statement ok DROP TABLE assn_cast_c; @@ -601,6 +1026,29 @@ SELECT * FROM assn_cast_c ---- 2 3 +statement ok +DROP TABLE assn_cast_c; +CREATE TABLE assn_cast_c (c INT PRIMARY KEY, d DECIMAL(10, 0) DEFAULT 3.1 REFERENCES assn_cast_p(d) ON UPDATE SET DEFAULT); +INSERT INTO assn_cast_c VALUES (2, 12) + +statement error update on table "assn_cast_c" violates foreign key constraint "assn_cast_c_d_fkey" +UPSERT INTO assn_cast_p VALUES (3.0, 3.4) + +statement ok +UPSERT INTO assn_cast_p VALUES (3.0, 3.0) + +statement error update on table "assn_cast_c" violates foreign key constraint "assn_cast_c_d_fkey" +INSERT INTO assn_cast_p VALUES (3.0, 1) ON CONFLICT (p) DO UPDATE SET d = 3.4 + +statement error update on table "assn_cast_c" violates foreign key constraint "assn_cast_c_d_fkey" +INSERT INTO assn_cast_p VALUES (4.0, 3.0) ON CONFLICT (d) DO UPDATE SET d = 3.4 + +statement ok +INSERT INTO assn_cast_p VALUES (3.0, 1) ON CONFLICT (p) DO UPDATE SET d = 3.0 + +statement ok +INSERT INTO assn_cast_p VALUES (4.0, 3.0) ON CONFLICT (d) DO UPDATE SET d = 3.0 + # Regression tests. subtest regressions diff --git a/pkg/sql/opt/optbuilder/fk_cascade.go b/pkg/sql/opt/optbuilder/fk_cascade.go index f7ca08262bf9..22df18aaea5c 100644 --- a/pkg/sql/opt/optbuilder/fk_cascade.go +++ b/pkg/sql/opt/optbuilder/fk_cascade.go @@ -458,7 +458,7 @@ func (cb *onDeleteSetBuilder) Build( updateExprs[i].Expr = tree.DefaultVal{} } } - mb.addUpdateCols(updateExprs, false /* isUpsert */) + mb.addUpdateCols(updateExprs) // TODO(radu): consider plumbing a flag to prevent building the FK check // against the parent we are cascading from. Need to investigate in which @@ -687,7 +687,7 @@ func (cb *onUpdateCascadeBuilder) Build( panic(errors.AssertionFailedf("unsupported action")) } } - mb.addUpdateCols(updateExprs, false /* isUpsert */) + mb.addUpdateCols(updateExprs) mb.buildUpdate(nil /* returning */) return mb.outScope.expr diff --git a/pkg/sql/opt/optbuilder/insert.go b/pkg/sql/opt/optbuilder/insert.go index cf76ab00f77a..504e45d0a9b7 100644 --- a/pkg/sql/opt/optbuilder/insert.go +++ b/pkg/sql/opt/optbuilder/insert.go @@ -259,7 +259,6 @@ func (b *Builder) buildInsert(ins *tree.Insert, inScope *scope) (outScope *scope // // INSERT INTO DEFAULT VALUES // - isUpsert := ins.OnConflict != nil && !ins.OnConflict.DoNothing if !ins.DefaultValues() { // Replace any DEFAULT expressions in the VALUES clause, if a VALUES clause // exists: @@ -268,15 +267,15 @@ func (b *Builder) buildInsert(ins *tree.Insert, inScope *scope) (outScope *scope // rows := mb.replaceDefaultExprs(ins.Rows) - mb.buildInputForInsert(inScope, rows, isUpsert) + mb.buildInputForInsert(inScope, rows) } else { - mb.buildInputForInsert(inScope, nil /* rows */, isUpsert) + mb.buildInputForInsert(inScope, nil /* rows */) } // Add default columns that were not explicitly specified by name or // implicitly targeted by input columns. Also add any computed columns. In // both cases, include columns undergoing mutations in the write-only state. - mb.addSynthesizedColsForInsert(isUpsert) + mb.addSynthesizedColsForInsert() var returning tree.ReturningExprs if resultsNeeded(ins.Returning) { @@ -317,7 +316,7 @@ func (b *Builder) buildInsert(ins *tree.Insert, inScope *scope) (outScope *scope // Add additional columns for computed expressions that may depend on any // updated columns, as well as mutation columns with default values. - mb.addSynthesizedColsForUpdate(true /* isUpsert */) + mb.addSynthesizedColsForUpdate() } // Build the final upsert statement, including any returned expressions. @@ -334,7 +333,7 @@ func (b *Builder) buildInsert(ins *tree.Insert, inScope *scope) (outScope *scope mb.addTargetColsForUpdate(ins.OnConflict.Exprs) // Build each of the SET expressions. - mb.addUpdateCols(ins.OnConflict.Exprs, true /* isUpsert */) + mb.addUpdateCols(ins.OnConflict.Exprs) // Build the final upsert statement, including any returned expressions. mb.buildUpsert(returning) @@ -558,9 +557,7 @@ func (mb *mutationBuilder) addTargetTableColsForInsert(maxCols int) { // buildInputForInsert constructs the memo group for the input expression and // constructs a new output scope containing that expression's output columns. -func (mb *mutationBuilder) buildInputForInsert( - inScope *scope, inputRows *tree.Select, isUpsert bool, -) { +func (mb *mutationBuilder) buildInputForInsert(inScope *scope, inputRows *tree.Select) { // Handle DEFAULT VALUES case by creating a single empty row as input. if inputRows == nil { mb.outScope = inScope.push() @@ -619,11 +616,6 @@ func (mb *mutationBuilder) buildInputForInsert( inCol := &mb.outScope.cols[i] ord := mb.tabID.ColumnOrdinal(mb.targetColList[i]) - if isUpsert { - // Type check the input column against the corresponding table column. - checkDatumTypeFitsColumnType(mb.tab.Column(ord), inCol.typ) - } - // Raise an error if the target column is a `GENERATED ALWAYS AS // IDENTITY` column. Such a column is not allowed to be explicitly // written to. @@ -645,10 +637,8 @@ func (mb *mutationBuilder) buildInputForInsert( mb.insertColIDs[ord] = inCol.id } - if !isUpsert { - // Add assignment casts for insert columns. - mb.addAssignmentCasts(mb.insertColIDs) - } + // Add assignment casts for insert columns. + mb.addAssignmentCasts(mb.insertColIDs) } // addSynthesizedColsForInsert wraps an Insert input expression with a Project @@ -656,7 +646,7 @@ func (mb *mutationBuilder) buildInputForInsert( // columns that are not yet part of the target column list. This includes all // write-only mutation columns, since they must always have default or computed // values. -func (mb *mutationBuilder) addSynthesizedColsForInsert(isUpsert bool) { +func (mb *mutationBuilder) addSynthesizedColsForInsert() { // Start by adding non-computed columns that have not already been explicitly // specified in the query. Do this before adding computed columns, since those // may depend on non-computed columns. @@ -666,25 +656,14 @@ func (mb *mutationBuilder) addSynthesizedColsForInsert(isUpsert bool) { false, /* applyOnUpdate */ ) - if isUpsert { - // Possibly round DECIMAL-related columns containing insertion values (whether - // synthesized or not). - mb.roundDecimalValues(mb.insertColIDs, false /* roundComputedCols */) - } else { - // Add assignment casts for default column values. - mb.addAssignmentCasts(mb.insertColIDs) - } + // Add assignment casts for default column values. + mb.addAssignmentCasts(mb.insertColIDs) // Now add all computed columns. mb.addSynthesizedComputedCols(mb.insertColIDs, false /* restrict */) - // Possibly round DECIMAL-related computed columns. - if isUpsert { - mb.roundDecimalValues(mb.insertColIDs, true /* roundComputedCols */) - } else { - // Add assignment casts for computed column values. - mb.addAssignmentCasts(mb.insertColIDs) - } + // Add assignment casts for computed column values. + mb.addAssignmentCasts(mb.insertColIDs) } // buildInsert constructs an Insert operator, possibly wrapped by a Project diff --git a/pkg/sql/opt/optbuilder/mutation_builder.go b/pkg/sql/opt/optbuilder/mutation_builder.go index caa2064fd904..a30ee7c96e24 100644 --- a/pkg/sql/opt/optbuilder/mutation_builder.go +++ b/pkg/sql/opt/optbuilder/mutation_builder.go @@ -24,7 +24,6 @@ import ( "github.com/cockroachdb/cockroach/pkg/sql/parser" "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" - "github.com/cockroachdb/cockroach/pkg/sql/sem/builtins" "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" "github.com/cockroachdb/cockroach/pkg/sql/sqlerrors" "github.com/cockroachdb/cockroach/pkg/sql/sqltelemetry" @@ -137,10 +136,6 @@ type mutationBuilder struct { // detect conflicts for UPSERT and INSERT ON CONFLICT statements. arbiters arbiterSet - // roundedDecimalCols is the set of columns that have already been rounded. - // Keeping this set avoids rounding the same column multiple times. - roundedDecimalCols opt.ColSet - // subqueries temporarily stores subqueries that were built during initial // analysis of SET expressions. They will be used later when the subqueries // are joined into larger LEFT OUTER JOIN expressions. @@ -721,148 +716,6 @@ func (mb *mutationBuilder) addSynthesizedComputedCols(colIDs opt.OptionalColList mb.outScope = pb.Finish() } -// roundDecimalValues wraps each DECIMAL-related column (including arrays of -// decimals) with a call to the crdb_internal.round_decimal_values function, if -// column values may need to be rounded. This is necessary when mutating table -// columns that have a limited scale (e.g. DECIMAL(10, 1)). Here is the PG docs -// description: -// -// http://www.postgresql.org/docs/9.5/static/datatype-numeric.html -// "If the scale of a value to be stored is greater than -// the declared scale of the column, the system will round the -// value to the specified number of fractional digits. Then, -// if the number of digits to the left of the decimal point -// exceeds the declared precision minus the declared scale, an -// error is raised." -// -// Note that this function only handles the rounding portion of that. The -// precision check is done by the execution engine. The rounding cannot be done -// there, since it needs to happen before check constraints are computed, and -// before UPSERT joins. -// -// If roundComputedCols is false, then don't wrap computed columns. If true, -// then only wrap computed columns. This is necessary because computed columns -// can depend on other columns mutated by the operation; it is necessary to -// first round those values, then evaluated the computed expression, and then -// round the result of the computation. -// -// roundDecimalValues will only round decimal columns that are part of the -// colIDs list (i.e. are not 0). If a column is rounded, then the list will be -// updated with the column ID of the new synthesized column. -func (mb *mutationBuilder) roundDecimalValues(colIDs opt.OptionalColList, roundComputedCols bool) { - var projectionsScope *scope - - for i, id := range colIDs { - if id == 0 { - // Column not mutated, so nothing to do. - continue - } - - // Include or exclude computed columns, depending on the value of - // roundComputedCols. - col := mb.tab.Column(i) - if col.IsComputed() != roundComputedCols { - continue - } - - // Check whether the target column's type may require rounding of the - // input value. - colType := col.DatumType() - precision, width := colType.Precision(), colType.Width() - if colType.Family() == types.ArrayFamily { - innerType := colType.ArrayContents() - if innerType.Family() == types.ArrayFamily { - panic(errors.AssertionFailedf("column type should never be a nested array")) - } - precision, width = innerType.Precision(), innerType.Width() - } - - props, overload := findRoundingFunction(colType, precision) - if props == nil { - continue - } - - // If column has already been rounded, then skip it. - if mb.roundedDecimalCols.Contains(id) { - continue - } - - private := &memo.FunctionPrivate{ - Name: "crdb_internal.round_decimal_values", - Typ: col.DatumType(), - Properties: props, - Overload: overload, - } - variable := mb.b.factory.ConstructVariable(id) - scale := mb.b.factory.ConstructConstVal(tree.NewDInt(tree.DInt(width)), types.Int) - fn := mb.b.factory.ConstructFunction(memo.ScalarListExpr{variable, scale}, private) - - // Lazily create new scope and update the scope column to be rounded. - if projectionsScope == nil { - projectionsScope = mb.outScope.replace() - projectionsScope.appendColumnsFromScope(mb.outScope) - } - scopeCol := projectionsScope.getColumn(id) - mb.b.populateSynthesizedColumn(scopeCol, fn) - - // Overwrite the input column ID with the new synthesized column ID. - colIDs[i] = scopeCol.id - mb.roundedDecimalCols.Add(scopeCol.id) - - // When building an UPDATE..FROM expression the projectionScope may have - // two columns with different names but the same ID. As a result, the - // scope column with the correct name (the name of the target column) - // may not be returned from projectionScope.getColumn. We set the name - // of the new scope column to the target column name to ensure it is - // in-scope when building CHECK constraint and partial index PUT - // expressions. See #61520. - // TODO(mgartner): Find a less brittle way to manage the scopes of - // mutations so that this isn't necessary. Ideally the scope produced by - // addUpdateColumns would not include columns in the FROM clause. Those - // columns are only in-scope in the RETURNING clause via - // mb.extraAccessibleCols. - scopeCol.name = scopeColName(mb.tab.Column(i).ColName()) - } - - if projectionsScope != nil { - mb.b.constructProjectForScope(mb.outScope, projectionsScope) - mb.outScope = projectionsScope - } -} - -// findRoundingFunction returns the builtin function overload needed to round -// input values. This is only necessary for DECIMAL or DECIMAL[] types that have -// limited precision, such as: -// -// DECIMAL(15, 1) -// DECIMAL(10, 3)[] -// -// If an input decimal value has more than the required number of fractional -// digits, it must be rounded before being inserted into these types. -// -// NOTE: CRDB does not allow nested array storage types, so only one level of -// array nesting needs to be checked. -func findRoundingFunction( - typ *types.T, precision int32, -) (*tree.FunctionProperties, *tree.Overload) { - if precision == 0 { - // Unlimited precision decimal target type never needs rounding. - return nil, nil - } - - props, overloads := builtins.GetBuiltinProperties("crdb_internal.round_decimal_values") - - if typ.Equivalent(types.Decimal) { - return props, &overloads[0] - } - if typ.Equivalent(types.DecimalArray) { - return props, &overloads[1] - } - - // Not DECIMAL or DECIMAL[]. - return nil, nil -} - // addCheckConstraintCols synthesizes a boolean output column for each check // constraint defined on the target table. The mutation operator will report a // constraint violation error if the value of the column is false. @@ -1369,27 +1222,6 @@ func resultsNeeded(r tree.ReturningClause) bool { } } -// checkDatumTypeFitsColumnType verifies that a given scalar value type is valid -// to be stored in a column of the given column type. -// -// For the purpose of this analysis, column type aliases are not considered to -// be different (eg. TEXT and VARCHAR will fit the same scalar type String). -// -// This is used by the UPDATE, INSERT and UPSERT code. -// TODO(mgartner): Remove this once assignment casts are fully supported. -func checkDatumTypeFitsColumnType(col *cat.Column, typ *types.T) { - if typ.Equivalent(col.DatumType()) { - return - } - - colName := string(col.ColName()) - err := pgerror.Newf(pgcode.DatatypeMismatch, - "value type %s doesn't match type %s of column %q", - typ, col.DatumType(), tree.ErrNameString(colName)) - err = errors.WithHint(err, "you will need to rewrite or cast the expression") - panic(err) -} - // addAssignmentCasts builds a projection that wraps columns in srcCols with // assignment casts when necessary so that the resulting columns have types // identical to their target column types. diff --git a/pkg/sql/opt/optbuilder/testdata/fk-on-update-cascade b/pkg/sql/opt/optbuilder/testdata/fk-on-update-cascade index 19a854ba08ee..61bd1ae7c7c6 100644 --- a/pkg/sql/opt/optbuilder/testdata/fk-on-update-cascade +++ b/pkg/sql/opt/optbuilder/testdata/fk-on-update-cascade @@ -734,7 +734,7 @@ root # Test a cascade to a child that requires an assignment cast because the # referencing column type is not identical to the referenced column type. exec-ddl -CREATE TABLE parent_assn_cast (p DECIMAL(10, 2) PRIMARY KEY) +CREATE TABLE parent_assn_cast (p DECIMAL(10, 2) PRIMARY KEY, p2 DECIMAL(10, 2) UNIQUE) ---- exec-ddl @@ -750,66 +750,249 @@ UPDATE parent_assn_cast SET p = 1.45 WHERE p > 1 root ├── update parent_assn_cast │ ├── columns: - │ ├── fetch columns: p:4 + │ ├── fetch columns: p:5 p2:6 │ ├── update-mapping: - │ │ └── p_cast:8 => p:1 + │ │ └── p_cast:10 => p:1 │ ├── input binding: &1 │ ├── cascades │ │ └── child_assn_cast_p_fkey │ └── project - │ ├── columns: p_cast:8!null p:4!null crdb_internal_mvcc_timestamp:5 tableoid:6 + │ ├── columns: p_cast:10!null p:5!null p2:6 crdb_internal_mvcc_timestamp:7 tableoid:8 │ ├── project - │ │ ├── columns: p_new:7!null p:4!null crdb_internal_mvcc_timestamp:5 tableoid:6 + │ │ ├── columns: p_new:9!null p:5!null p2:6 crdb_internal_mvcc_timestamp:7 tableoid:8 │ │ ├── select - │ │ │ ├── columns: p:4!null crdb_internal_mvcc_timestamp:5 tableoid:6 + │ │ │ ├── columns: p:5!null p2:6 crdb_internal_mvcc_timestamp:7 tableoid:8 │ │ │ ├── scan parent_assn_cast - │ │ │ │ └── columns: p:4!null crdb_internal_mvcc_timestamp:5 tableoid:6 + │ │ │ │ └── columns: p:5!null p2:6 crdb_internal_mvcc_timestamp:7 tableoid:8 │ │ │ └── filters - │ │ │ └── p:4 > 1 + │ │ │ └── p:5 > 1 │ │ └── projections - │ │ └── 1.45 [as=p_new:7] + │ │ └── 1.45 [as=p_new:9] │ └── projections - │ └── assignment-cast: DECIMAL(10,2) [as=p_cast:8] - │ └── p_new:7 + │ └── assignment-cast: DECIMAL(10,2) [as=p_cast:10] + │ └── p_new:9 └── cascade └── update child_assn_cast ├── columns: - ├── fetch columns: c:13 child_assn_cast.p:14 + ├── fetch columns: c:15 child_assn_cast.p:16 ├── update-mapping: - │ └── p_cast:19 => child_assn_cast.p:10 + │ └── p_cast:21 => child_assn_cast.p:12 ├── input binding: &2 ├── project - │ ├── columns: p_cast:19!null c:13!null child_assn_cast.p:14!null p_old:17!null p_new:18!null + │ ├── columns: p_cast:21!null c:15!null child_assn_cast.p:16!null p_old:19!null p_new:20!null │ ├── inner-join (hash) - │ │ ├── columns: c:13!null child_assn_cast.p:14!null p_old:17!null p_new:18!null + │ │ ├── columns: c:15!null child_assn_cast.p:16!null p_old:19!null p_new:20!null │ │ ├── scan child_assn_cast - │ │ │ └── columns: c:13!null child_assn_cast.p:14 + │ │ │ └── columns: c:15!null child_assn_cast.p:16 │ │ ├── select - │ │ │ ├── columns: p_old:17!null p_new:18!null + │ │ │ ├── columns: p_old:19!null p_new:20!null │ │ │ ├── with-scan &1 - │ │ │ │ ├── columns: p_old:17!null p_new:18!null + │ │ │ │ ├── columns: p_old:19!null p_new:20!null │ │ │ │ └── mapping: - │ │ │ │ ├── parent_assn_cast.p:4 => p_old:17 - │ │ │ │ └── p_cast:8 => p_new:18 + │ │ │ │ ├── parent_assn_cast.p:5 => p_old:19 + │ │ │ │ └── p_cast:10 => p_new:20 │ │ │ └── filters - │ │ │ └── p_old:17 IS DISTINCT FROM p_new:18 + │ │ │ └── p_old:19 IS DISTINCT FROM p_new:20 │ │ └── filters - │ │ └── child_assn_cast.p:14 = p_old:17 + │ │ └── child_assn_cast.p:16 = p_old:19 │ └── projections - │ └── assignment-cast: DECIMAL(10) [as=p_cast:19] - │ └── p_new:18 + │ └── assignment-cast: DECIMAL(10) [as=p_cast:21] + │ └── p_new:20 └── f-k-checks └── f-k-checks-item: child_assn_cast(p) -> parent_assn_cast(p) └── anti-join (hash) - ├── columns: p:20!null + ├── columns: p:22!null ├── with-scan &2 - │ ├── columns: p:20!null + │ ├── columns: p:22!null + │ └── mapping: + │ └── p_cast:21 => p:22 + ├── scan parent_assn_cast + │ └── columns: parent_assn_cast.p:23!null + └── filters + └── p:22 = parent_assn_cast.p:23 + +exec-ddl +CREATE TABLE child_assn_cast_p2 ( + c INT PRIMARY KEY, + p2 DECIMAL(10, 0) REFERENCES parent_assn_cast(p2) ON UPDATE CASCADE +) +---- + +build-cascades +UPSERT INTO parent_assn_cast (p, p2) VALUES (1.23, 4.56) +---- +root + ├── upsert parent_assn_cast + │ ├── columns: + │ ├── arbiter indexes: parent_assn_cast_pkey + │ ├── canary column: p:9 + │ ├── fetch columns: p:9 p2:10 + │ ├── insert-mapping: + │ │ ├── p_cast:7 => p:1 + │ │ └── p2_cast:8 => p2:2 + │ ├── update-mapping: + │ │ └── p2_cast:8 => p2:2 + │ ├── input binding: &1 + │ ├── cascades + │ │ └── child_assn_cast_p2_p2_fkey + │ └── project + │ ├── columns: upsert_p:13 p_cast:7!null p2_cast:8!null p:9 p2:10 crdb_internal_mvcc_timestamp:11 tableoid:12 + │ ├── left-join (hash) + │ │ ├── columns: p_cast:7!null p2_cast:8!null p:9 p2:10 crdb_internal_mvcc_timestamp:11 tableoid:12 + │ │ ├── ensure-upsert-distinct-on + │ │ │ ├── columns: p_cast:7!null p2_cast:8!null + │ │ │ ├── grouping columns: p_cast:7!null + │ │ │ ├── project + │ │ │ │ ├── columns: p_cast:7!null p2_cast:8!null + │ │ │ │ ├── values + │ │ │ │ │ ├── columns: column1:5!null column2:6!null + │ │ │ │ │ └── (1.23, 4.56) + │ │ │ │ └── projections + │ │ │ │ ├── assignment-cast: DECIMAL(10,2) [as=p_cast:7] + │ │ │ │ │ └── column1:5 + │ │ │ │ └── assignment-cast: DECIMAL(10,2) [as=p2_cast:8] + │ │ │ │ └── column2:6 + │ │ │ └── aggregations + │ │ │ └── first-agg [as=p2_cast:8] + │ │ │ └── p2_cast:8 + │ │ ├── scan parent_assn_cast + │ │ │ └── columns: p:9!null p2:10 crdb_internal_mvcc_timestamp:11 tableoid:12 + │ │ └── filters + │ │ └── p_cast:7 = p:9 + │ └── projections + │ └── CASE WHEN p:9 IS NULL THEN p_cast:7 ELSE p:9 END [as=upsert_p:13] + └── cascade + └── update child_assn_cast_p2 + ├── columns: + ├── fetch columns: c:18 child_assn_cast_p2.p2:19 + ├── update-mapping: + │ └── p2_cast:24 => child_assn_cast_p2.p2:15 + ├── input binding: &2 + ├── project + │ ├── columns: p2_cast:24!null c:18!null child_assn_cast_p2.p2:19!null p2_old:22!null p2_new:23!null + │ ├── inner-join (hash) + │ │ ├── columns: c:18!null child_assn_cast_p2.p2:19!null p2_old:22!null p2_new:23!null + │ │ ├── scan child_assn_cast_p2 + │ │ │ └── columns: c:18!null child_assn_cast_p2.p2:19 + │ │ ├── select + │ │ │ ├── columns: p2_old:22 p2_new:23!null + │ │ │ ├── with-scan &1 + │ │ │ │ ├── columns: p2_old:22 p2_new:23!null + │ │ │ │ └── mapping: + │ │ │ │ ├── parent_assn_cast.p2:10 => p2_old:22 + │ │ │ │ └── p2_cast:8 => p2_new:23 + │ │ │ └── filters + │ │ │ └── p2_old:22 IS DISTINCT FROM p2_new:23 + │ │ └── filters + │ │ └── child_assn_cast_p2.p2:19 = p2_old:22 + │ └── projections + │ └── assignment-cast: DECIMAL(10) [as=p2_cast:24] + │ └── p2_new:23 + └── f-k-checks + └── f-k-checks-item: child_assn_cast_p2(p2) -> parent_assn_cast(p2) + └── anti-join (hash) + ├── columns: p2:25!null + ├── with-scan &2 + │ ├── columns: p2:25!null │ └── mapping: - │ └── p_cast:19 => p:20 + │ └── p2_cast:24 => p2:25 ├── scan parent_assn_cast - │ └── columns: parent_assn_cast.p:21!null + │ └── columns: parent_assn_cast.p2:27 └── filters - └── p:20 = parent_assn_cast.p:21 + └── p2:25 = parent_assn_cast.p2:27 + +build-cascades +INSERT INTO parent_assn_cast (p, p2) VALUES (1.23, 4.56) ON CONFLICT (p) DO UPDATE SET p2 = 7.89 +---- +root + ├── upsert parent_assn_cast + │ ├── columns: + │ ├── arbiter indexes: parent_assn_cast_pkey + │ ├── canary column: p:9 + │ ├── fetch columns: p:9 p2:10 + │ ├── insert-mapping: + │ │ ├── p_cast:7 => p:1 + │ │ └── p2_cast:8 => p2:2 + │ ├── update-mapping: + │ │ └── upsert_p2:16 => p2:2 + │ ├── input binding: &1 + │ ├── cascades + │ │ └── child_assn_cast_p2_p2_fkey + │ └── project + │ ├── columns: upsert_p:15 upsert_p2:16!null p_cast:7!null p2_cast:8!null p:9 p2:10 crdb_internal_mvcc_timestamp:11 tableoid:12 p2_cast:14!null + │ ├── project + │ │ ├── columns: p2_cast:14!null p_cast:7!null p2_cast:8!null p:9 p2:10 crdb_internal_mvcc_timestamp:11 tableoid:12 + │ │ ├── project + │ │ │ ├── columns: p2_new:13!null p_cast:7!null p2_cast:8!null p:9 p2:10 crdb_internal_mvcc_timestamp:11 tableoid:12 + │ │ │ ├── left-join (hash) + │ │ │ │ ├── columns: p_cast:7!null p2_cast:8!null p:9 p2:10 crdb_internal_mvcc_timestamp:11 tableoid:12 + │ │ │ │ ├── ensure-upsert-distinct-on + │ │ │ │ │ ├── columns: p_cast:7!null p2_cast:8!null + │ │ │ │ │ ├── grouping columns: p_cast:7!null + │ │ │ │ │ ├── project + │ │ │ │ │ │ ├── columns: p_cast:7!null p2_cast:8!null + │ │ │ │ │ │ ├── values + │ │ │ │ │ │ │ ├── columns: column1:5!null column2:6!null + │ │ │ │ │ │ │ └── (1.23, 4.56) + │ │ │ │ │ │ └── projections + │ │ │ │ │ │ ├── assignment-cast: DECIMAL(10,2) [as=p_cast:7] + │ │ │ │ │ │ │ └── column1:5 + │ │ │ │ │ │ └── assignment-cast: DECIMAL(10,2) [as=p2_cast:8] + │ │ │ │ │ │ └── column2:6 + │ │ │ │ │ └── aggregations + │ │ │ │ │ └── first-agg [as=p2_cast:8] + │ │ │ │ │ └── p2_cast:8 + │ │ │ │ ├── scan parent_assn_cast + │ │ │ │ │ └── columns: p:9!null p2:10 crdb_internal_mvcc_timestamp:11 tableoid:12 + │ │ │ │ └── filters + │ │ │ │ └── p_cast:7 = p:9 + │ │ │ └── projections + │ │ │ └── 7.89 [as=p2_new:13] + │ │ └── projections + │ │ └── assignment-cast: DECIMAL(10,2) [as=p2_cast:14] + │ │ └── p2_new:13 + │ └── projections + │ ├── CASE WHEN p:9 IS NULL THEN p_cast:7 ELSE p:9 END [as=upsert_p:15] + │ └── CASE WHEN p:9 IS NULL THEN p2_cast:8 ELSE p2_cast:14 END [as=upsert_p2:16] + └── cascade + └── update child_assn_cast_p2 + ├── columns: + ├── fetch columns: c:21 child_assn_cast_p2.p2:22 + ├── update-mapping: + │ └── p2_cast:27 => child_assn_cast_p2.p2:18 + ├── input binding: &2 + ├── project + │ ├── columns: p2_cast:27!null c:21!null child_assn_cast_p2.p2:22!null p2_old:25!null p2_new:26!null + │ ├── inner-join (hash) + │ │ ├── columns: c:21!null child_assn_cast_p2.p2:22!null p2_old:25!null p2_new:26!null + │ │ ├── scan child_assn_cast_p2 + │ │ │ └── columns: c:21!null child_assn_cast_p2.p2:22 + │ │ ├── select + │ │ │ ├── columns: p2_old:25 p2_new:26!null + │ │ │ ├── with-scan &1 + │ │ │ │ ├── columns: p2_old:25 p2_new:26!null + │ │ │ │ └── mapping: + │ │ │ │ ├── parent_assn_cast.p2:10 => p2_old:25 + │ │ │ │ └── upsert_p2:16 => p2_new:26 + │ │ │ └── filters + │ │ │ └── p2_old:25 IS DISTINCT FROM p2_new:26 + │ │ └── filters + │ │ └── child_assn_cast_p2.p2:22 = p2_old:25 + │ └── projections + │ └── assignment-cast: DECIMAL(10) [as=p2_cast:27] + │ └── p2_new:26 + └── f-k-checks + └── f-k-checks-item: child_assn_cast_p2(p2) -> parent_assn_cast(p2) + └── anti-join (hash) + ├── columns: p2:28!null + ├── with-scan &2 + │ ├── columns: p2:28!null + │ └── mapping: + │ └── p2_cast:27 => p2:28 + ├── scan parent_assn_cast + │ └── columns: parent_assn_cast.p2:30 + └── filters + └── p2:28 = parent_assn_cast.p2:30 # Test a cascade to a child with a partial index. exec-ddl @@ -1403,3 +1586,91 @@ root └── eq [type=bool, outer=(19,20), constraints=(/19: (/NULL - ]; /20: (/NULL - ]), fd=(19)==(20), (20)==(19)] ├── variable: p:19 [type=int2] └── variable: t.public.parent_diff_type.p:20 [type=int] + +build-cascades +UPSERT INTO parent_diff_type VALUES (0) +---- +root + └── upsert parent_diff_type + ├── columns: + ├── upsert-mapping: + │ └── column1:4 => p:1 + └── values + ├── columns: column1:4!null + └── (0,) + +build-cascades +INSERT INTO parent_diff_type VALUES (0) ON CONFLICT (p) DO UPDATE SET p = 1 +---- +root + ├── upsert parent_diff_type + │ ├── columns: + │ ├── arbiter indexes: parent_diff_type_pkey + │ ├── canary column: p:5 + │ ├── fetch columns: p:5 + │ ├── insert-mapping: + │ │ └── column1:4 => p:1 + │ ├── update-mapping: + │ │ └── upsert_p:9 => p:1 + │ ├── input binding: &1 + │ ├── cascades + │ │ └── child_diff_type_p_fkey + │ └── project + │ ├── columns: upsert_p:9!null column1:4!null p:5 crdb_internal_mvcc_timestamp:6 tableoid:7 p_new:8!null + │ ├── project + │ │ ├── columns: p_new:8!null column1:4!null p:5 crdb_internal_mvcc_timestamp:6 tableoid:7 + │ │ ├── left-join (hash) + │ │ │ ├── columns: column1:4!null p:5 crdb_internal_mvcc_timestamp:6 tableoid:7 + │ │ │ ├── ensure-upsert-distinct-on + │ │ │ │ ├── columns: column1:4!null + │ │ │ │ ├── grouping columns: column1:4!null + │ │ │ │ └── values + │ │ │ │ ├── columns: column1:4!null + │ │ │ │ └── (0,) + │ │ │ ├── scan parent_diff_type + │ │ │ │ └── columns: p:5!null crdb_internal_mvcc_timestamp:6 tableoid:7 + │ │ │ └── filters + │ │ │ └── column1:4 = p:5 + │ │ └── projections + │ │ └── 1 [as=p_new:8] + │ └── projections + │ └── CASE WHEN p:5 IS NULL THEN column1:4 ELSE p_new:8 END [as=upsert_p:9] + └── cascade + └── update child_diff_type + ├── columns: + ├── fetch columns: c:14 child_diff_type.p:15 + ├── update-mapping: + │ └── p_cast:20 => child_diff_type.p:11 + ├── input binding: &2 + ├── project + │ ├── columns: p_cast:20!null c:14!null child_diff_type.p:15!null p_old:18!null p_new:19!null + │ ├── inner-join (hash) + │ │ ├── columns: c:14!null child_diff_type.p:15!null p_old:18!null p_new:19!null + │ │ ├── scan child_diff_type + │ │ │ └── columns: c:14!null child_diff_type.p:15 + │ │ ├── select + │ │ │ ├── columns: p_old:18 p_new:19!null + │ │ │ ├── with-scan &1 + │ │ │ │ ├── columns: p_old:18 p_new:19!null + │ │ │ │ └── mapping: + │ │ │ │ ├── parent_diff_type.p:5 => p_old:18 + │ │ │ │ └── upsert_p:9 => p_new:19 + │ │ │ └── filters + │ │ │ └── p_old:18 IS DISTINCT FROM p_new:19 + │ │ └── filters + │ │ └── child_diff_type.p:15 = p_old:18 + │ └── projections + │ └── assignment-cast: INT2 [as=p_cast:20] + │ └── p_new:19 + └── f-k-checks + └── f-k-checks-item: child_diff_type(p) -> parent_diff_type(p) + └── anti-join (hash) + ├── columns: p:21!null + ├── with-scan &2 + │ ├── columns: p:21!null + │ └── mapping: + │ └── p_cast:20 => p:21 + ├── scan parent_diff_type + │ └── columns: parent_diff_type.p:22!null + └── filters + └── p:21 = parent_diff_type.p:22 diff --git a/pkg/sql/opt/optbuilder/testdata/fk-on-update-set-default b/pkg/sql/opt/optbuilder/testdata/fk-on-update-set-default index d1b6ee113f6f..6a32ed363ebf 100644 --- a/pkg/sql/opt/optbuilder/testdata/fk-on-update-set-default +++ b/pkg/sql/opt/optbuilder/testdata/fk-on-update-set-default @@ -767,7 +767,7 @@ root # referencing column's DEFAULT expression type is not identical to the # referencing column type. exec-ddl -CREATE TABLE parent_assn_cast (p INT PRIMARY KEY) +CREATE TABLE parent_assn_cast (p INT PRIMARY KEY, p2 INT UNIQUE) ---- exec-ddl @@ -783,65 +783,237 @@ UPDATE parent_assn_cast SET p = 1 WHERE p > 1 root ├── update parent_assn_cast │ ├── columns: - │ ├── fetch columns: p:4 + │ ├── fetch columns: p:5 p2:6 │ ├── update-mapping: - │ │ └── p_new:7 => p:1 + │ │ └── p_new:9 => p:1 │ ├── input binding: &1 │ ├── cascades │ │ └── child_assn_cast_p_fkey │ └── project - │ ├── columns: p_new:7!null p:4!null crdb_internal_mvcc_timestamp:5 tableoid:6 + │ ├── columns: p_new:9!null p:5!null p2:6 crdb_internal_mvcc_timestamp:7 tableoid:8 │ ├── select - │ │ ├── columns: p:4!null crdb_internal_mvcc_timestamp:5 tableoid:6 + │ │ ├── columns: p:5!null p2:6 crdb_internal_mvcc_timestamp:7 tableoid:8 │ │ ├── scan parent_assn_cast - │ │ │ └── columns: p:4!null crdb_internal_mvcc_timestamp:5 tableoid:6 + │ │ │ └── columns: p:5!null p2:6 crdb_internal_mvcc_timestamp:7 tableoid:8 │ │ └── filters - │ │ └── p:4 > 1 + │ │ └── p:5 > 1 │ └── projections - │ └── 1 [as=p_new:7] + │ └── 1 [as=p_new:9] └── cascade └── update child_assn_cast ├── columns: - ├── fetch columns: c:12 child_assn_cast.p:13 + ├── fetch columns: c:14 child_assn_cast.p:15 ├── update-mapping: - │ └── p_cast:19 => child_assn_cast.p:9 + │ └── p_cast:21 => child_assn_cast.p:11 ├── input binding: &2 ├── project - │ ├── columns: p_cast:19!null c:12!null child_assn_cast.p:13!null p_old:16!null p_new:17!null + │ ├── columns: p_cast:21!null c:14!null child_assn_cast.p:15!null p_old:18!null p_new:19!null │ ├── project - │ │ ├── columns: p_new:18!null c:12!null child_assn_cast.p:13!null p_old:16!null p_new:17!null + │ │ ├── columns: p_new:20!null c:14!null child_assn_cast.p:15!null p_old:18!null p_new:19!null │ │ ├── inner-join (cross) - │ │ │ ├── columns: c:12!null child_assn_cast.p:13!null p_old:16!null p_new:17!null + │ │ │ ├── columns: c:14!null child_assn_cast.p:15!null p_old:18!null p_new:19!null │ │ │ ├── scan child_assn_cast - │ │ │ │ └── columns: c:12!null child_assn_cast.p:13 + │ │ │ │ └── columns: c:14!null child_assn_cast.p:15 │ │ │ ├── select - │ │ │ │ ├── columns: p_old:16!null p_new:17!null + │ │ │ │ ├── columns: p_old:18!null p_new:19!null │ │ │ │ ├── with-scan &1 - │ │ │ │ │ ├── columns: p_old:16!null p_new:17!null + │ │ │ │ │ ├── columns: p_old:18!null p_new:19!null │ │ │ │ │ └── mapping: - │ │ │ │ │ ├── parent_assn_cast.p:4 => p_old:16 - │ │ │ │ │ └── p_new:7 => p_new:17 + │ │ │ │ │ ├── parent_assn_cast.p:5 => p_old:18 + │ │ │ │ │ └── p_new:9 => p_new:19 │ │ │ │ └── filters - │ │ │ │ └── p_old:16 IS DISTINCT FROM p_new:17 + │ │ │ │ └── p_old:18 IS DISTINCT FROM p_new:19 │ │ │ └── filters - │ │ │ └── child_assn_cast.p:13 = p_old:16 + │ │ │ └── child_assn_cast.p:15 = p_old:18 │ │ └── projections - │ │ └── 1.45::DECIMAL(10,2) [as=p_new:18] + │ │ └── 1.45::DECIMAL(10,2) [as=p_new:20] │ └── projections - │ └── assignment-cast: DECIMAL(10) [as=p_cast:19] - │ └── p_new:18 + │ └── assignment-cast: DECIMAL(10) [as=p_cast:21] + │ └── p_new:20 └── f-k-checks └── f-k-checks-item: child_assn_cast(p) -> parent_assn_cast(p) └── anti-join (cross) - ├── columns: p:20!null + ├── columns: p:22!null + ├── with-scan &2 + │ ├── columns: p:22!null + │ └── mapping: + │ └── p_cast:21 => p:22 + ├── scan parent_assn_cast + │ └── columns: parent_assn_cast.p:23!null + └── filters + └── p:22 = parent_assn_cast.p:23 + +exec-ddl +CREATE TABLE child_assn_cast_p2 ( + c INT PRIMARY KEY, + p2 DECIMAL(10, 0) DEFAULT 1.45::DECIMAL(10, 2) REFERENCES parent_assn_cast(p2) ON UPDATE SET DEFAULT +) +---- + +build-cascades +UPSERT INTO parent_assn_cast (p, p2) VALUES (1, 2) +---- +root + ├── upsert parent_assn_cast + │ ├── columns: + │ ├── arbiter indexes: parent_assn_cast_pkey + │ ├── canary column: p:7 + │ ├── fetch columns: p:7 p2:8 + │ ├── insert-mapping: + │ │ ├── column1:5 => p:1 + │ │ └── column2:6 => p2:2 + │ ├── update-mapping: + │ │ └── column2:6 => p2:2 + │ ├── input binding: &1 + │ ├── cascades + │ │ └── child_assn_cast_p2_p2_fkey + │ └── project + │ ├── columns: upsert_p:11 column1:5!null column2:6!null p:7 p2:8 crdb_internal_mvcc_timestamp:9 tableoid:10 + │ ├── left-join (hash) + │ │ ├── columns: column1:5!null column2:6!null p:7 p2:8 crdb_internal_mvcc_timestamp:9 tableoid:10 + │ │ ├── ensure-upsert-distinct-on + │ │ │ ├── columns: column1:5!null column2:6!null + │ │ │ ├── grouping columns: column1:5!null + │ │ │ ├── values + │ │ │ │ ├── columns: column1:5!null column2:6!null + │ │ │ │ └── (1, 2) + │ │ │ └── aggregations + │ │ │ └── first-agg [as=column2:6] + │ │ │ └── column2:6 + │ │ ├── scan parent_assn_cast + │ │ │ └── columns: p:7!null p2:8 crdb_internal_mvcc_timestamp:9 tableoid:10 + │ │ └── filters + │ │ └── column1:5 = p:7 + │ └── projections + │ └── CASE WHEN p:7 IS NULL THEN column1:5 ELSE p:7 END [as=upsert_p:11] + └── cascade + └── update child_assn_cast_p2 + ├── columns: + ├── fetch columns: c:16 child_assn_cast_p2.p2:17 + ├── update-mapping: + │ └── p2_cast:23 => child_assn_cast_p2.p2:13 + ├── input binding: &2 + ├── project + │ ├── columns: p2_cast:23!null c:16!null child_assn_cast_p2.p2:17!null p2_old:20!null p2_new:21!null + │ ├── project + │ │ ├── columns: p2_new:22!null c:16!null child_assn_cast_p2.p2:17!null p2_old:20!null p2_new:21!null + │ │ ├── inner-join (cross) + │ │ │ ├── columns: c:16!null child_assn_cast_p2.p2:17!null p2_old:20!null p2_new:21!null + │ │ │ ├── scan child_assn_cast_p2 + │ │ │ │ └── columns: c:16!null child_assn_cast_p2.p2:17 + │ │ │ ├── select + │ │ │ │ ├── columns: p2_old:20 p2_new:21!null + │ │ │ │ ├── with-scan &1 + │ │ │ │ │ ├── columns: p2_old:20 p2_new:21!null + │ │ │ │ │ └── mapping: + │ │ │ │ │ ├── parent_assn_cast.p2:8 => p2_old:20 + │ │ │ │ │ └── column2:6 => p2_new:21 + │ │ │ │ └── filters + │ │ │ │ └── p2_old:20 IS DISTINCT FROM p2_new:21 + │ │ │ └── filters + │ │ │ └── child_assn_cast_p2.p2:17 = p2_old:20 + │ │ └── projections + │ │ └── 1.45::DECIMAL(10,2) [as=p2_new:22] + │ └── projections + │ └── assignment-cast: DECIMAL(10) [as=p2_cast:23] + │ └── p2_new:22 + └── f-k-checks + └── f-k-checks-item: child_assn_cast_p2(p2) -> parent_assn_cast(p2) + └── anti-join (cross) + ├── columns: p2:24!null + ├── with-scan &2 + │ ├── columns: p2:24!null + │ └── mapping: + │ └── p2_cast:23 => p2:24 + ├── scan parent_assn_cast + │ └── columns: parent_assn_cast.p2:26 + └── filters + └── p2:24 = parent_assn_cast.p2:26 + +build-cascades +INSERT INTO parent_assn_cast (p, p2) VALUES (1, 2) ON CONFLICT (p) DO UPDATE SET p2 = 3 +---- +root + ├── upsert parent_assn_cast + │ ├── columns: + │ ├── arbiter indexes: parent_assn_cast_pkey + │ ├── canary column: p:7 + │ ├── fetch columns: p:7 p2:8 + │ ├── insert-mapping: + │ │ ├── column1:5 => p:1 + │ │ └── column2:6 => p2:2 + │ ├── update-mapping: + │ │ └── upsert_p2:13 => p2:2 + │ ├── input binding: &1 + │ ├── cascades + │ │ └── child_assn_cast_p2_p2_fkey + │ └── project + │ ├── columns: upsert_p:12 upsert_p2:13!null column1:5!null column2:6!null p:7 p2:8 crdb_internal_mvcc_timestamp:9 tableoid:10 p2_new:11!null + │ ├── project + │ │ ├── columns: p2_new:11!null column1:5!null column2:6!null p:7 p2:8 crdb_internal_mvcc_timestamp:9 tableoid:10 + │ │ ├── left-join (hash) + │ │ │ ├── columns: column1:5!null column2:6!null p:7 p2:8 crdb_internal_mvcc_timestamp:9 tableoid:10 + │ │ │ ├── ensure-upsert-distinct-on + │ │ │ │ ├── columns: column1:5!null column2:6!null + │ │ │ │ ├── grouping columns: column1:5!null + │ │ │ │ ├── values + │ │ │ │ │ ├── columns: column1:5!null column2:6!null + │ │ │ │ │ └── (1, 2) + │ │ │ │ └── aggregations + │ │ │ │ └── first-agg [as=column2:6] + │ │ │ │ └── column2:6 + │ │ │ ├── scan parent_assn_cast + │ │ │ │ └── columns: p:7!null p2:8 crdb_internal_mvcc_timestamp:9 tableoid:10 + │ │ │ └── filters + │ │ │ └── column1:5 = p:7 + │ │ └── projections + │ │ └── 3 [as=p2_new:11] + │ └── projections + │ ├── CASE WHEN p:7 IS NULL THEN column1:5 ELSE p:7 END [as=upsert_p:12] + │ └── CASE WHEN p:7 IS NULL THEN column2:6 ELSE p2_new:11 END [as=upsert_p2:13] + └── cascade + └── update child_assn_cast_p2 + ├── columns: + ├── fetch columns: c:18 child_assn_cast_p2.p2:19 + ├── update-mapping: + │ └── p2_cast:25 => child_assn_cast_p2.p2:15 + ├── input binding: &2 + ├── project + │ ├── columns: p2_cast:25!null c:18!null child_assn_cast_p2.p2:19!null p2_old:22!null p2_new:23!null + │ ├── project + │ │ ├── columns: p2_new:24!null c:18!null child_assn_cast_p2.p2:19!null p2_old:22!null p2_new:23!null + │ │ ├── inner-join (cross) + │ │ │ ├── columns: c:18!null child_assn_cast_p2.p2:19!null p2_old:22!null p2_new:23!null + │ │ │ ├── scan child_assn_cast_p2 + │ │ │ │ └── columns: c:18!null child_assn_cast_p2.p2:19 + │ │ │ ├── select + │ │ │ │ ├── columns: p2_old:22 p2_new:23!null + │ │ │ │ ├── with-scan &1 + │ │ │ │ │ ├── columns: p2_old:22 p2_new:23!null + │ │ │ │ │ └── mapping: + │ │ │ │ │ ├── parent_assn_cast.p2:8 => p2_old:22 + │ │ │ │ │ └── upsert_p2:13 => p2_new:23 + │ │ │ │ └── filters + │ │ │ │ └── p2_old:22 IS DISTINCT FROM p2_new:23 + │ │ │ └── filters + │ │ │ └── child_assn_cast_p2.p2:19 = p2_old:22 + │ │ └── projections + │ │ └── 1.45::DECIMAL(10,2) [as=p2_new:24] + │ └── projections + │ └── assignment-cast: DECIMAL(10) [as=p2_cast:25] + │ └── p2_new:24 + └── f-k-checks + └── f-k-checks-item: child_assn_cast_p2(p2) -> parent_assn_cast(p2) + └── anti-join (cross) + ├── columns: p2:26!null ├── with-scan &2 - │ ├── columns: p:20!null + │ ├── columns: p2:26!null │ └── mapping: - │ └── p_cast:19 => p:20 + │ └── p2_cast:25 => p2:26 ├── scan parent_assn_cast - │ └── columns: parent_assn_cast.p:21!null + │ └── columns: parent_assn_cast.p2:28 └── filters - └── p:20 = parent_assn_cast.p:21 + └── p2:26 = parent_assn_cast.p2:28 # Test a cascade to a child with a partial index. exec-ddl diff --git a/pkg/sql/opt/optbuilder/testdata/upsert b/pkg/sql/opt/optbuilder/testdata/upsert index 592995c88ee0..e0edbbeaca0c 100644 --- a/pkg/sql/opt/optbuilder/testdata/upsert +++ b/pkg/sql/opt/optbuilder/testdata/upsert @@ -88,6 +88,28 @@ CREATE TABLE uniq ( ) ---- +exec-ddl +CREATE TABLE assn_cast ( + k INT PRIMARY KEY, + c CHAR, + qc "char", + i INT DEFAULT 10::INT2, + s STRING, + d DECIMAL(10, 0), + d_comp DECIMAL(10, 0) AS (d + 10.0) STORED +) +---- + +exec-ddl +CREATE TABLE assn_cast_on_update ( + k INT PRIMARY KEY, + i INT, + d DECIMAL(10, 1) ON UPDATE 1.23, + d2 DECIMAL(10, 1) ON UPDATE 1.23::DECIMAL(10, 2), + d_comp DECIMAL(10, 0) AS (d) STORED +) +---- + # ------------------------------------------------------------------------------ # Basic tests. # ------------------------------------------------------------------------------ @@ -1694,7 +1716,7 @@ upsert xyz └── CASE WHEN x:12 IS NULL THEN c:8 ELSE z:14 END [as=upsert_z:20] # ------------------------------------------------------------------------------ -# Test decimal column truncation. +# Test assignment casts. # ------------------------------------------------------------------------------ # Fast UPSERT case. @@ -1704,71 +1726,78 @@ UPSERT INTO decimals (a, b) VALUES (1.1, ARRAY[0.95]) upsert decimals ├── columns: ├── arbiter indexes: decimals_pkey - ├── canary column: decimals.a:15 - ├── fetch columns: decimals.a:15 decimals.b:16 c:17 d:18 + ├── canary column: a:15 + ├── fetch columns: a:15 b:16 c:17 d:18 ├── insert-mapping: - │ ├── a:10 => decimals.a:1 - │ ├── b:11 => decimals.b:2 - │ ├── c_default:12 => c:3 - │ └── d_comp:14 => d:4 + │ ├── a_cast:9 => a:1 + │ ├── b_cast:10 => b:2 + │ ├── c_cast:12 => c:3 + │ └── d_cast:14 => d:4 ├── update-mapping: - │ └── b:11 => decimals.b:2 + │ └── b_cast:10 => b:2 ├── check columns: check1:25 check2:26 └── project - ├── columns: check1:25 check2:26 a:10 b:11 c_default:12 d_comp:14 decimals.a:15 decimals.b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 d_comp:21 upsert_a:22 upsert_c:23 upsert_d:24 + ├── columns: check1:25 check2:26 a_cast:9!null b_cast:10 c_cast:12!null d_cast:14!null a:15 b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 d_comp:21 upsert_a:22 upsert_c:23 upsert_d:24 ├── project - │ ├── columns: upsert_a:22 upsert_c:23 upsert_d:24 a:10 b:11 c_default:12 d_comp:14 decimals.a:15 decimals.b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 d_comp:21 + │ ├── columns: upsert_a:22 upsert_c:23 upsert_d:24 a_cast:9!null b_cast:10 c_cast:12!null d_cast:14!null a:15 b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 d_comp:21 │ ├── project - │ │ ├── columns: d_comp:21 a:10 b:11 c_default:12 d_comp:14 decimals.a:15 decimals.b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 + │ │ ├── columns: d_comp:21 a_cast:9!null b_cast:10 c_cast:12!null d_cast:14!null a:15 b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 │ │ ├── left-join (hash) - │ │ │ ├── columns: a:10 b:11 c_default:12 d_comp:14 decimals.a:15 decimals.b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 + │ │ │ ├── columns: a_cast:9!null b_cast:10 c_cast:12!null d_cast:14!null a:15 b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 │ │ │ ├── ensure-upsert-distinct-on - │ │ │ │ ├── columns: a:10 b:11 c_default:12 d_comp:14 - │ │ │ │ ├── grouping columns: a:10 + │ │ │ │ ├── columns: a_cast:9!null b_cast:10 c_cast:12!null d_cast:14!null + │ │ │ │ ├── grouping columns: a_cast:9!null │ │ │ │ ├── project - │ │ │ │ │ ├── columns: d_comp:14 a:10 b:11 c_default:12 + │ │ │ │ │ ├── columns: d_cast:14!null a_cast:9!null b_cast:10 c_cast:12!null │ │ │ │ │ ├── project - │ │ │ │ │ │ ├── columns: d_comp:13 a:10 b:11 c_default:12 + │ │ │ │ │ │ ├── columns: d_comp:13!null a_cast:9!null b_cast:10 c_cast:12!null │ │ │ │ │ │ ├── project - │ │ │ │ │ │ │ ├── columns: a:10 b:11 c_default:12 + │ │ │ │ │ │ │ ├── columns: c_cast:12!null a_cast:9!null b_cast:10 │ │ │ │ │ │ │ ├── project - │ │ │ │ │ │ │ │ ├── columns: c_default:9!null column1:7!null column2:8 - │ │ │ │ │ │ │ │ ├── values - │ │ │ │ │ │ │ │ │ ├── columns: column1:7!null column2:8 - │ │ │ │ │ │ │ │ │ └── (1.1, ARRAY[0.95]) + │ │ │ │ │ │ │ │ ├── columns: c_default:11!null a_cast:9!null b_cast:10 + │ │ │ │ │ │ │ │ ├── project + │ │ │ │ │ │ │ │ │ ├── columns: a_cast:9!null b_cast:10 + │ │ │ │ │ │ │ │ │ ├── values + │ │ │ │ │ │ │ │ │ │ ├── columns: column1:7!null column2:8 + │ │ │ │ │ │ │ │ │ │ └── (1.1, ARRAY[0.95]) + │ │ │ │ │ │ │ │ │ └── projections + │ │ │ │ │ │ │ │ │ ├── assignment-cast: DECIMAL(10) [as=a_cast:9] + │ │ │ │ │ │ │ │ │ │ └── column1:7 + │ │ │ │ │ │ │ │ │ └── assignment-cast: DECIMAL(5,1)[] [as=b_cast:10] + │ │ │ │ │ │ │ │ │ └── column2:8 │ │ │ │ │ │ │ │ └── projections - │ │ │ │ │ │ │ │ └── 1.23 [as=c_default:9] + │ │ │ │ │ │ │ │ └── 1.23 [as=c_default:11] │ │ │ │ │ │ │ └── projections - │ │ │ │ │ │ │ ├── crdb_internal.round_decimal_values(column1:7, 0) [as=a:10] - │ │ │ │ │ │ │ ├── crdb_internal.round_decimal_values(column2:8, 1) [as=b:11] - │ │ │ │ │ │ │ └── crdb_internal.round_decimal_values(c_default:9, 1) [as=c_default:12] + │ │ │ │ │ │ │ └── assignment-cast: DECIMAL(10,1) [as=c_cast:12] + │ │ │ │ │ │ │ └── c_default:11 │ │ │ │ │ │ └── projections - │ │ │ │ │ │ └── a:10::DECIMAL + c_default:12::DECIMAL [as=d_comp:13] + │ │ │ │ │ │ └── a_cast:9::DECIMAL + c_cast:12::DECIMAL [as=d_comp:13] │ │ │ │ │ └── projections - │ │ │ │ │ └── crdb_internal.round_decimal_values(d_comp:13, 1) [as=d_comp:14] + │ │ │ │ │ └── assignment-cast: DECIMAL(10,1) [as=d_cast:14] + │ │ │ │ │ └── d_comp:13 │ │ │ │ └── aggregations - │ │ │ │ ├── first-agg [as=b:11] - │ │ │ │ │ └── b:11 - │ │ │ │ ├── first-agg [as=c_default:12] - │ │ │ │ │ └── c_default:12 - │ │ │ │ └── first-agg [as=d_comp:14] - │ │ │ │ └── d_comp:14 + │ │ │ │ ├── first-agg [as=b_cast:10] + │ │ │ │ │ └── b_cast:10 + │ │ │ │ ├── first-agg [as=c_cast:12] + │ │ │ │ │ └── c_cast:12 + │ │ │ │ └── first-agg [as=d_cast:14] + │ │ │ │ └── d_cast:14 │ │ │ ├── scan decimals - │ │ │ │ ├── columns: decimals.a:15!null decimals.b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 + │ │ │ │ ├── columns: a:15!null b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 │ │ │ │ └── computed column expressions │ │ │ │ └── d:18 - │ │ │ │ └── decimals.a:15::DECIMAL + c:17::DECIMAL + │ │ │ │ └── a:15::DECIMAL + c:17::DECIMAL │ │ │ └── filters - │ │ │ └── a:10 = decimals.a:15 + │ │ │ └── a_cast:9 = a:15 │ │ └── projections - │ │ └── decimals.a:15::DECIMAL + c:17::DECIMAL [as=d_comp:21] + │ │ └── a:15::DECIMAL + c:17::DECIMAL [as=d_comp:21] │ └── projections - │ ├── CASE WHEN decimals.a:15 IS NULL THEN a:10 ELSE decimals.a:15 END [as=upsert_a:22] - │ ├── CASE WHEN decimals.a:15 IS NULL THEN c_default:12 ELSE c:17 END [as=upsert_c:23] - │ └── CASE WHEN decimals.a:15 IS NULL THEN d_comp:14 ELSE d:18 END [as=upsert_d:24] + │ ├── CASE WHEN a:15 IS NULL THEN a_cast:9 ELSE a:15 END [as=upsert_a:22] + │ ├── CASE WHEN a:15 IS NULL THEN c_cast:12 ELSE c:17 END [as=upsert_c:23] + │ └── CASE WHEN a:15 IS NULL THEN d_cast:14 ELSE d:18 END [as=upsert_d:24] └── projections ├── round(upsert_a:22) = upsert_a:22 [as=check1:25] - └── b:11[0] > 1 [as=check2:26] + └── b_cast:10[0] > 1 [as=check2:26] # Regular UPSERT case. build @@ -1777,71 +1806,154 @@ UPSERT INTO decimals (a) VALUES (1.1) upsert decimals ├── columns: ├── arbiter indexes: decimals_pkey - ├── canary column: decimals.a:15 - ├── fetch columns: decimals.a:15 b:16 c:17 d:18 + ├── canary column: a:14 + ├── fetch columns: a:14 b:15 c:16 d:17 ├── insert-mapping: - │ ├── a:10 => decimals.a:1 - │ ├── b_default:11 => b:2 - │ ├── c_default:12 => c:3 - │ └── d_comp:14 => d:4 - ├── check columns: check1:26 check2:27 + │ ├── a_cast:8 => a:1 + │ ├── b_default:9 => b:2 + │ ├── c_cast:11 => c:3 + │ └── d_cast:13 => d:4 + ├── check columns: check1:25 check2:26 └── project - ├── columns: check1:26 check2:27 a:10 b_default:11 c_default:12 d_comp:14 decimals.a:15 b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 d_comp:21 upsert_a:22 upsert_b:23 upsert_c:24 upsert_d:25 + ├── columns: check1:25 check2:26 a_cast:8!null b_default:9 c_cast:11!null d_cast:13!null a:14 b:15 c:16 d:17 crdb_internal_mvcc_timestamp:18 tableoid:19 d_comp:20 upsert_a:21 upsert_b:22 upsert_c:23 upsert_d:24 ├── project - │ ├── columns: upsert_a:22 upsert_b:23 upsert_c:24 upsert_d:25 a:10 b_default:11 c_default:12 d_comp:14 decimals.a:15 b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 d_comp:21 + │ ├── columns: upsert_a:21 upsert_b:22 upsert_c:23 upsert_d:24 a_cast:8!null b_default:9 c_cast:11!null d_cast:13!null a:14 b:15 c:16 d:17 crdb_internal_mvcc_timestamp:18 tableoid:19 d_comp:20 │ ├── project - │ │ ├── columns: d_comp:21 a:10 b_default:11 c_default:12 d_comp:14 decimals.a:15 b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 + │ │ ├── columns: d_comp:20 a_cast:8!null b_default:9 c_cast:11!null d_cast:13!null a:14 b:15 c:16 d:17 crdb_internal_mvcc_timestamp:18 tableoid:19 │ │ ├── left-join (hash) - │ │ │ ├── columns: a:10 b_default:11 c_default:12 d_comp:14 decimals.a:15 b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 + │ │ │ ├── columns: a_cast:8!null b_default:9 c_cast:11!null d_cast:13!null a:14 b:15 c:16 d:17 crdb_internal_mvcc_timestamp:18 tableoid:19 │ │ │ ├── ensure-upsert-distinct-on - │ │ │ │ ├── columns: a:10 b_default:11 c_default:12 d_comp:14 - │ │ │ │ ├── grouping columns: a:10 + │ │ │ │ ├── columns: a_cast:8!null b_default:9 c_cast:11!null d_cast:13!null + │ │ │ │ ├── grouping columns: a_cast:8!null │ │ │ │ ├── project - │ │ │ │ │ ├── columns: d_comp:14 a:10 b_default:11 c_default:12 + │ │ │ │ │ ├── columns: d_cast:13!null a_cast:8!null b_default:9 c_cast:11!null │ │ │ │ │ ├── project - │ │ │ │ │ │ ├── columns: d_comp:13 a:10 b_default:11 c_default:12 + │ │ │ │ │ │ ├── columns: d_comp:12!null a_cast:8!null b_default:9 c_cast:11!null │ │ │ │ │ │ ├── project - │ │ │ │ │ │ │ ├── columns: a:10 b_default:11 c_default:12 + │ │ │ │ │ │ │ ├── columns: c_cast:11!null a_cast:8!null b_default:9 │ │ │ │ │ │ │ ├── project - │ │ │ │ │ │ │ │ ├── columns: b_default:8 c_default:9!null column1:7!null - │ │ │ │ │ │ │ │ ├── values - │ │ │ │ │ │ │ │ │ ├── columns: column1:7!null - │ │ │ │ │ │ │ │ │ └── (1.1,) + │ │ │ │ │ │ │ │ ├── columns: b_default:9 c_default:10!null a_cast:8!null + │ │ │ │ │ │ │ │ ├── project + │ │ │ │ │ │ │ │ │ ├── columns: a_cast:8!null + │ │ │ │ │ │ │ │ │ ├── values + │ │ │ │ │ │ │ │ │ │ ├── columns: column1:7!null + │ │ │ │ │ │ │ │ │ │ └── (1.1,) + │ │ │ │ │ │ │ │ │ └── projections + │ │ │ │ │ │ │ │ │ └── assignment-cast: DECIMAL(10) [as=a_cast:8] + │ │ │ │ │ │ │ │ │ └── column1:7 │ │ │ │ │ │ │ │ └── projections - │ │ │ │ │ │ │ │ ├── NULL::DECIMAL(5,1)[] [as=b_default:8] - │ │ │ │ │ │ │ │ └── 1.23 [as=c_default:9] + │ │ │ │ │ │ │ │ ├── NULL::DECIMAL(5,1)[] [as=b_default:9] + │ │ │ │ │ │ │ │ └── 1.23 [as=c_default:10] │ │ │ │ │ │ │ └── projections - │ │ │ │ │ │ │ ├── crdb_internal.round_decimal_values(column1:7, 0) [as=a:10] - │ │ │ │ │ │ │ ├── crdb_internal.round_decimal_values(b_default:8, 1) [as=b_default:11] - │ │ │ │ │ │ │ └── crdb_internal.round_decimal_values(c_default:9, 1) [as=c_default:12] + │ │ │ │ │ │ │ └── assignment-cast: DECIMAL(10,1) [as=c_cast:11] + │ │ │ │ │ │ │ └── c_default:10 │ │ │ │ │ │ └── projections - │ │ │ │ │ │ └── a:10::DECIMAL + c_default:12::DECIMAL [as=d_comp:13] + │ │ │ │ │ │ └── a_cast:8::DECIMAL + c_cast:11::DECIMAL [as=d_comp:12] │ │ │ │ │ └── projections - │ │ │ │ │ └── crdb_internal.round_decimal_values(d_comp:13, 1) [as=d_comp:14] + │ │ │ │ │ └── assignment-cast: DECIMAL(10,1) [as=d_cast:13] + │ │ │ │ │ └── d_comp:12 │ │ │ │ └── aggregations - │ │ │ │ ├── first-agg [as=b_default:11] - │ │ │ │ │ └── b_default:11 - │ │ │ │ ├── first-agg [as=c_default:12] - │ │ │ │ │ └── c_default:12 - │ │ │ │ └── first-agg [as=d_comp:14] - │ │ │ │ └── d_comp:14 + │ │ │ │ ├── first-agg [as=b_default:9] + │ │ │ │ │ └── b_default:9 + │ │ │ │ ├── first-agg [as=c_cast:11] + │ │ │ │ │ └── c_cast:11 + │ │ │ │ └── first-agg [as=d_cast:13] + │ │ │ │ └── d_cast:13 │ │ │ ├── scan decimals - │ │ │ │ ├── columns: decimals.a:15!null b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 + │ │ │ │ ├── columns: a:14!null b:15 c:16 d:17 crdb_internal_mvcc_timestamp:18 tableoid:19 │ │ │ │ └── computed column expressions - │ │ │ │ └── d:18 - │ │ │ │ └── decimals.a:15::DECIMAL + c:17::DECIMAL + │ │ │ │ └── d:17 + │ │ │ │ └── a:14::DECIMAL + c:16::DECIMAL + │ │ │ └── filters + │ │ │ └── a_cast:8 = a:14 + │ │ └── projections + │ │ └── a:14::DECIMAL + c:16::DECIMAL [as=d_comp:20] + │ └── projections + │ ├── CASE WHEN a:14 IS NULL THEN a_cast:8 ELSE a:14 END [as=upsert_a:21] + │ ├── CASE WHEN a:14 IS NULL THEN b_default:9 ELSE b:15 END [as=upsert_b:22] + │ ├── CASE WHEN a:14 IS NULL THEN c_cast:11 ELSE c:16 END [as=upsert_c:23] + │ └── CASE WHEN a:14 IS NULL THEN d_cast:13 ELSE d:17 END [as=upsert_d:24] + └── projections + ├── round(upsert_a:21) = upsert_a:21 [as=check1:25] + └── upsert_b:22[0] > 1 [as=check2:26] + +# Regular UPSERT case as a prepared statement. +assign-placeholders-build query-args=(1.1) +UPSERT INTO decimals (a) VALUES ($1) +---- +upsert decimals + ├── columns: + ├── arbiter indexes: decimals_pkey + ├── canary column: a:14 + ├── fetch columns: a:14 b:15 c:16 d:17 + ├── insert-mapping: + │ ├── a_cast:8 => a:1 + │ ├── b_default:9 => b:2 + │ ├── c_cast:11 => c:3 + │ └── d_cast:13 => d:4 + ├── check columns: check1:25 check2:26 + └── project + ├── columns: check1:25 check2:26 a_cast:8!null b_default:9 c_cast:11!null d_cast:13!null a:14 b:15 c:16 d:17 crdb_internal_mvcc_timestamp:18 tableoid:19 d_comp:20 upsert_a:21 upsert_b:22 upsert_c:23 upsert_d:24 + ├── project + │ ├── columns: upsert_a:21 upsert_b:22 upsert_c:23 upsert_d:24 a_cast:8!null b_default:9 c_cast:11!null d_cast:13!null a:14 b:15 c:16 d:17 crdb_internal_mvcc_timestamp:18 tableoid:19 d_comp:20 + │ ├── project + │ │ ├── columns: d_comp:20 a_cast:8!null b_default:9 c_cast:11!null d_cast:13!null a:14 b:15 c:16 d:17 crdb_internal_mvcc_timestamp:18 tableoid:19 + │ │ ├── left-join (hash) + │ │ │ ├── columns: a_cast:8!null b_default:9 c_cast:11!null d_cast:13!null a:14 b:15 c:16 d:17 crdb_internal_mvcc_timestamp:18 tableoid:19 + │ │ │ ├── ensure-upsert-distinct-on + │ │ │ │ ├── columns: a_cast:8!null b_default:9 c_cast:11!null d_cast:13!null + │ │ │ │ ├── grouping columns: a_cast:8!null + │ │ │ │ ├── project + │ │ │ │ │ ├── columns: d_cast:13!null a_cast:8!null b_default:9 c_cast:11!null + │ │ │ │ │ ├── project + │ │ │ │ │ │ ├── columns: d_comp:12!null a_cast:8!null b_default:9 c_cast:11!null + │ │ │ │ │ │ ├── project + │ │ │ │ │ │ │ ├── columns: c_cast:11!null a_cast:8!null b_default:9 + │ │ │ │ │ │ │ ├── project + │ │ │ │ │ │ │ │ ├── columns: b_default:9 c_default:10!null a_cast:8!null + │ │ │ │ │ │ │ │ ├── project + │ │ │ │ │ │ │ │ │ ├── columns: a_cast:8!null + │ │ │ │ │ │ │ │ │ ├── values + │ │ │ │ │ │ │ │ │ │ ├── columns: column1:7!null + │ │ │ │ │ │ │ │ │ │ └── (1.1,) + │ │ │ │ │ │ │ │ │ └── projections + │ │ │ │ │ │ │ │ │ └── assignment-cast: DECIMAL(10) [as=a_cast:8] + │ │ │ │ │ │ │ │ │ └── column1:7 + │ │ │ │ │ │ │ │ └── projections + │ │ │ │ │ │ │ │ ├── NULL::DECIMAL(5,1)[] [as=b_default:9] + │ │ │ │ │ │ │ │ └── 1.23 [as=c_default:10] + │ │ │ │ │ │ │ └── projections + │ │ │ │ │ │ │ └── assignment-cast: DECIMAL(10,1) [as=c_cast:11] + │ │ │ │ │ │ │ └── c_default:10 + │ │ │ │ │ │ └── projections + │ │ │ │ │ │ └── a_cast:8::DECIMAL + c_cast:11::DECIMAL [as=d_comp:12] + │ │ │ │ │ └── projections + │ │ │ │ │ └── assignment-cast: DECIMAL(10,1) [as=d_cast:13] + │ │ │ │ │ └── d_comp:12 + │ │ │ │ └── aggregations + │ │ │ │ ├── first-agg [as=b_default:9] + │ │ │ │ │ └── b_default:9 + │ │ │ │ ├── first-agg [as=c_cast:11] + │ │ │ │ │ └── c_cast:11 + │ │ │ │ └── first-agg [as=d_cast:13] + │ │ │ │ └── d_cast:13 + │ │ │ ├── scan decimals + │ │ │ │ ├── columns: a:14!null b:15 c:16 d:17 crdb_internal_mvcc_timestamp:18 tableoid:19 + │ │ │ │ └── computed column expressions + │ │ │ │ └── d:17 + │ │ │ │ └── a:14::DECIMAL + c:16::DECIMAL │ │ │ └── filters - │ │ │ └── a:10 = decimals.a:15 + │ │ │ └── a_cast:8 = a:14 │ │ └── projections - │ │ └── decimals.a:15::DECIMAL + c:17::DECIMAL [as=d_comp:21] + │ │ └── a:14::DECIMAL + c:16::DECIMAL [as=d_comp:20] │ └── projections - │ ├── CASE WHEN decimals.a:15 IS NULL THEN a:10 ELSE decimals.a:15 END [as=upsert_a:22] - │ ├── CASE WHEN decimals.a:15 IS NULL THEN b_default:11 ELSE b:16 END [as=upsert_b:23] - │ ├── CASE WHEN decimals.a:15 IS NULL THEN c_default:12 ELSE c:17 END [as=upsert_c:24] - │ └── CASE WHEN decimals.a:15 IS NULL THEN d_comp:14 ELSE d:18 END [as=upsert_d:25] + │ ├── CASE WHEN a:14 IS NULL THEN a_cast:8 ELSE a:14 END [as=upsert_a:21] + │ ├── CASE WHEN a:14 IS NULL THEN b_default:9 ELSE b:15 END [as=upsert_b:22] + │ ├── CASE WHEN a:14 IS NULL THEN c_cast:11 ELSE c:16 END [as=upsert_c:23] + │ └── CASE WHEN a:14 IS NULL THEN d_cast:13 ELSE d:17 END [as=upsert_d:24] └── projections - ├── round(upsert_a:22) = upsert_a:22 [as=check1:26] - └── upsert_b:23[0] > 1 [as=check2:27] + ├── round(upsert_a:21) = upsert_a:21 [as=check1:25] + └── upsert_b:22[0] > 1 [as=check2:26] # INSERT...ON CONFLICT case. build @@ -1852,81 +1964,1116 @@ DO UPDATE SET b=ARRAY[0.99] upsert decimals ├── columns: ├── arbiter indexes: decimals_pkey - ├── canary column: decimals.a:15 - ├── fetch columns: decimals.a:15 decimals.b:16 c:17 d:18 + ├── canary column: a:15 + ├── fetch columns: a:15 b:16 c:17 d:18 ├── insert-mapping: - │ ├── a:10 => decimals.a:1 - │ ├── b:11 => decimals.b:2 - │ ├── c_default:12 => c:3 - │ └── d_comp:14 => d:4 + │ ├── a_cast:9 => a:1 + │ ├── b_cast:10 => b:2 + │ ├── c_cast:12 => c:3 + │ └── d_cast:14 => d:4 + ├── update-mapping: + │ └── upsert_b:25 => b:2 + ├── check columns: check1:28 check2:29 + └── project + ├── columns: check1:28 check2:29 a_cast:9!null b_cast:10 c_cast:12!null d_cast:14!null a:15 b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 b_cast:22!null d_comp:23 upsert_a:24 upsert_b:25 upsert_c:26 upsert_d:27 + ├── project + │ ├── columns: upsert_a:24 upsert_b:25 upsert_c:26 upsert_d:27 a_cast:9!null b_cast:10 c_cast:12!null d_cast:14!null a:15 b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 b_cast:22!null d_comp:23 + │ ├── project + │ │ ├── columns: d_comp:23 a_cast:9!null b_cast:10 c_cast:12!null d_cast:14!null a:15 b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 b_cast:22!null + │ │ ├── project + │ │ │ ├── columns: b_cast:22!null a_cast:9!null b_cast:10 c_cast:12!null d_cast:14!null a:15 b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 + │ │ │ ├── project + │ │ │ │ ├── columns: b_new:21!null a_cast:9!null b_cast:10 c_cast:12!null d_cast:14!null a:15 b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 + │ │ │ │ ├── left-join (hash) + │ │ │ │ │ ├── columns: a_cast:9!null b_cast:10 c_cast:12!null d_cast:14!null a:15 b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 + │ │ │ │ │ ├── ensure-upsert-distinct-on + │ │ │ │ │ │ ├── columns: a_cast:9!null b_cast:10 c_cast:12!null d_cast:14!null + │ │ │ │ │ │ ├── grouping columns: a_cast:9!null + │ │ │ │ │ │ ├── project + │ │ │ │ │ │ │ ├── columns: d_cast:14!null a_cast:9!null b_cast:10 c_cast:12!null + │ │ │ │ │ │ │ ├── project + │ │ │ │ │ │ │ │ ├── columns: d_comp:13!null a_cast:9!null b_cast:10 c_cast:12!null + │ │ │ │ │ │ │ │ ├── project + │ │ │ │ │ │ │ │ │ ├── columns: c_cast:12!null a_cast:9!null b_cast:10 + │ │ │ │ │ │ │ │ │ ├── project + │ │ │ │ │ │ │ │ │ │ ├── columns: c_default:11!null a_cast:9!null b_cast:10 + │ │ │ │ │ │ │ │ │ │ ├── project + │ │ │ │ │ │ │ │ │ │ │ ├── columns: a_cast:9!null b_cast:10 + │ │ │ │ │ │ │ │ │ │ │ ├── values + │ │ │ │ │ │ │ │ │ │ │ │ ├── columns: column1:7!null column2:8 + │ │ │ │ │ │ │ │ │ │ │ │ └── (1.1, ARRAY[0.95]) + │ │ │ │ │ │ │ │ │ │ │ └── projections + │ │ │ │ │ │ │ │ │ │ │ ├── assignment-cast: DECIMAL(10) [as=a_cast:9] + │ │ │ │ │ │ │ │ │ │ │ │ └── column1:7 + │ │ │ │ │ │ │ │ │ │ │ └── assignment-cast: DECIMAL(5,1)[] [as=b_cast:10] + │ │ │ │ │ │ │ │ │ │ │ └── column2:8 + │ │ │ │ │ │ │ │ │ │ └── projections + │ │ │ │ │ │ │ │ │ │ └── 1.23 [as=c_default:11] + │ │ │ │ │ │ │ │ │ └── projections + │ │ │ │ │ │ │ │ │ └── assignment-cast: DECIMAL(10,1) [as=c_cast:12] + │ │ │ │ │ │ │ │ │ └── c_default:11 + │ │ │ │ │ │ │ │ └── projections + │ │ │ │ │ │ │ │ └── a_cast:9::DECIMAL + c_cast:12::DECIMAL [as=d_comp:13] + │ │ │ │ │ │ │ └── projections + │ │ │ │ │ │ │ └── assignment-cast: DECIMAL(10,1) [as=d_cast:14] + │ │ │ │ │ │ │ └── d_comp:13 + │ │ │ │ │ │ └── aggregations + │ │ │ │ │ │ ├── first-agg [as=b_cast:10] + │ │ │ │ │ │ │ └── b_cast:10 + │ │ │ │ │ │ ├── first-agg [as=c_cast:12] + │ │ │ │ │ │ │ └── c_cast:12 + │ │ │ │ │ │ └── first-agg [as=d_cast:14] + │ │ │ │ │ │ └── d_cast:14 + │ │ │ │ │ ├── scan decimals + │ │ │ │ │ │ ├── columns: a:15!null b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 + │ │ │ │ │ │ └── computed column expressions + │ │ │ │ │ │ └── d:18 + │ │ │ │ │ │ └── a:15::DECIMAL + c:17::DECIMAL + │ │ │ │ │ └── filters + │ │ │ │ │ └── a_cast:9 = a:15 + │ │ │ │ └── projections + │ │ │ │ └── ARRAY[0.99] [as=b_new:21] + │ │ │ └── projections + │ │ │ └── assignment-cast: DECIMAL(5,1)[] [as=b_cast:22] + │ │ │ └── b_new:21 + │ │ └── projections + │ │ └── a:15::DECIMAL + c:17::DECIMAL [as=d_comp:23] + │ └── projections + │ ├── CASE WHEN a:15 IS NULL THEN a_cast:9 ELSE a:15 END [as=upsert_a:24] + │ ├── CASE WHEN a:15 IS NULL THEN b_cast:10 ELSE b_cast:22 END [as=upsert_b:25] + │ ├── CASE WHEN a:15 IS NULL THEN c_cast:12 ELSE c:17 END [as=upsert_c:26] + │ └── CASE WHEN a:15 IS NULL THEN d_cast:14 ELSE d:18 END [as=upsert_d:27] + └── projections + ├── round(upsert_a:24) = upsert_a:24 [as=check1:28] + └── upsert_b:25[0] > 1 [as=check2:29] + +# INSERT...ON CONFLICT case as a prepared statement. +assign-placeholders-build query-args=(1.1, ARRAY[0.95], ARRAY[0.99]) +INSERT INTO decimals (a, b) VALUES ($1, $2) +ON CONFLICT (a) +DO UPDATE SET b=$3 +---- +upsert decimals + ├── columns: + ├── arbiter indexes: decimals_pkey + ├── canary column: a:15 + ├── fetch columns: a:15 b:16 c:17 d:18 + ├── insert-mapping: + │ ├── a_cast:9 => a:1 + │ ├── b_cast:10 => b:2 + │ ├── c_cast:12 => c:3 + │ └── d_cast:14 => d:4 ├── update-mapping: - │ └── upsert_b:25 => decimals.b:2 + │ └── upsert_b:25 => b:2 ├── check columns: check1:28 check2:29 └── project - ├── columns: check1:28 check2:29 a:10 b:11 c_default:12 d_comp:14 decimals.a:15 decimals.b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 b_new:22 d_comp:23 upsert_a:24 upsert_b:25 upsert_c:26 upsert_d:27 + ├── columns: check1:28 check2:29 a_cast:9!null b_cast:10!null c_cast:12!null d_cast:14!null a:15 b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 b_cast:22!null d_comp:23 upsert_a:24 upsert_b:25!null upsert_c:26 upsert_d:27 ├── project - │ ├── columns: upsert_a:24 upsert_b:25 upsert_c:26 upsert_d:27 a:10 b:11 c_default:12 d_comp:14 decimals.a:15 decimals.b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 b_new:22 d_comp:23 + │ ├── columns: upsert_a:24 upsert_b:25!null upsert_c:26 upsert_d:27 a_cast:9!null b_cast:10!null c_cast:12!null d_cast:14!null a:15 b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 b_cast:22!null d_comp:23 │ ├── project - │ │ ├── columns: d_comp:23 a:10 b:11 c_default:12 d_comp:14 decimals.a:15 decimals.b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 b_new:22 + │ │ ├── columns: d_comp:23 a_cast:9!null b_cast:10!null c_cast:12!null d_cast:14!null a:15 b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 b_cast:22!null │ │ ├── project - │ │ │ ├── columns: b_new:22 a:10 b:11 c_default:12 d_comp:14 decimals.a:15 decimals.b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 + │ │ │ ├── columns: b_cast:22!null a_cast:9!null b_cast:10!null c_cast:12!null d_cast:14!null a:15 b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 │ │ │ ├── project - │ │ │ │ ├── columns: b_new:21!null a:10 b:11 c_default:12 d_comp:14 decimals.a:15 decimals.b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 + │ │ │ │ ├── columns: b_new:21!null a_cast:9!null b_cast:10!null c_cast:12!null d_cast:14!null a:15 b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 │ │ │ │ ├── left-join (hash) - │ │ │ │ │ ├── columns: a:10 b:11 c_default:12 d_comp:14 decimals.a:15 decimals.b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 + │ │ │ │ │ ├── columns: a_cast:9!null b_cast:10!null c_cast:12!null d_cast:14!null a:15 b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 │ │ │ │ │ ├── ensure-upsert-distinct-on - │ │ │ │ │ │ ├── columns: a:10 b:11 c_default:12 d_comp:14 - │ │ │ │ │ │ ├── grouping columns: a:10 + │ │ │ │ │ │ ├── columns: a_cast:9!null b_cast:10!null c_cast:12!null d_cast:14!null + │ │ │ │ │ │ ├── grouping columns: a_cast:9!null │ │ │ │ │ │ ├── project - │ │ │ │ │ │ │ ├── columns: d_comp:14 a:10 b:11 c_default:12 + │ │ │ │ │ │ │ ├── columns: d_cast:14!null a_cast:9!null b_cast:10!null c_cast:12!null │ │ │ │ │ │ │ ├── project - │ │ │ │ │ │ │ │ ├── columns: d_comp:13 a:10 b:11 c_default:12 + │ │ │ │ │ │ │ │ ├── columns: d_comp:13!null a_cast:9!null b_cast:10!null c_cast:12!null │ │ │ │ │ │ │ │ ├── project - │ │ │ │ │ │ │ │ │ ├── columns: a:10 b:11 c_default:12 + │ │ │ │ │ │ │ │ │ ├── columns: c_cast:12!null a_cast:9!null b_cast:10!null │ │ │ │ │ │ │ │ │ ├── project - │ │ │ │ │ │ │ │ │ │ ├── columns: c_default:9!null column1:7!null column2:8 - │ │ │ │ │ │ │ │ │ │ ├── values - │ │ │ │ │ │ │ │ │ │ │ ├── columns: column1:7!null column2:8 - │ │ │ │ │ │ │ │ │ │ │ └── (1.1, ARRAY[0.95]) + │ │ │ │ │ │ │ │ │ │ ├── columns: c_default:11!null a_cast:9!null b_cast:10!null + │ │ │ │ │ │ │ │ │ │ ├── project + │ │ │ │ │ │ │ │ │ │ │ ├── columns: a_cast:9!null b_cast:10!null + │ │ │ │ │ │ │ │ │ │ │ ├── values + │ │ │ │ │ │ │ │ │ │ │ │ ├── columns: column1:7!null column2:8!null + │ │ │ │ │ │ │ │ │ │ │ │ └── (1.1, ARRAY[0.95]) + │ │ │ │ │ │ │ │ │ │ │ └── projections + │ │ │ │ │ │ │ │ │ │ │ ├── assignment-cast: DECIMAL(10) [as=a_cast:9] + │ │ │ │ │ │ │ │ │ │ │ │ └── column1:7 + │ │ │ │ │ │ │ │ │ │ │ └── assignment-cast: DECIMAL(5,1)[] [as=b_cast:10] + │ │ │ │ │ │ │ │ │ │ │ └── column2:8 │ │ │ │ │ │ │ │ │ │ └── projections - │ │ │ │ │ │ │ │ │ │ └── 1.23 [as=c_default:9] + │ │ │ │ │ │ │ │ │ │ └── 1.23 [as=c_default:11] │ │ │ │ │ │ │ │ │ └── projections - │ │ │ │ │ │ │ │ │ ├── crdb_internal.round_decimal_values(column1:7, 0) [as=a:10] - │ │ │ │ │ │ │ │ │ ├── crdb_internal.round_decimal_values(column2:8, 1) [as=b:11] - │ │ │ │ │ │ │ │ │ └── crdb_internal.round_decimal_values(c_default:9, 1) [as=c_default:12] + │ │ │ │ │ │ │ │ │ └── assignment-cast: DECIMAL(10,1) [as=c_cast:12] + │ │ │ │ │ │ │ │ │ └── c_default:11 │ │ │ │ │ │ │ │ └── projections - │ │ │ │ │ │ │ │ └── a:10::DECIMAL + c_default:12::DECIMAL [as=d_comp:13] + │ │ │ │ │ │ │ │ └── a_cast:9::DECIMAL + c_cast:12::DECIMAL [as=d_comp:13] │ │ │ │ │ │ │ └── projections - │ │ │ │ │ │ │ └── crdb_internal.round_decimal_values(d_comp:13, 1) [as=d_comp:14] + │ │ │ │ │ │ │ └── assignment-cast: DECIMAL(10,1) [as=d_cast:14] + │ │ │ │ │ │ │ └── d_comp:13 │ │ │ │ │ │ └── aggregations - │ │ │ │ │ │ ├── first-agg [as=b:11] - │ │ │ │ │ │ │ └── b:11 - │ │ │ │ │ │ ├── first-agg [as=c_default:12] - │ │ │ │ │ │ │ └── c_default:12 - │ │ │ │ │ │ └── first-agg [as=d_comp:14] - │ │ │ │ │ │ └── d_comp:14 + │ │ │ │ │ │ ├── first-agg [as=b_cast:10] + │ │ │ │ │ │ │ └── b_cast:10 + │ │ │ │ │ │ ├── first-agg [as=c_cast:12] + │ │ │ │ │ │ │ └── c_cast:12 + │ │ │ │ │ │ └── first-agg [as=d_cast:14] + │ │ │ │ │ │ └── d_cast:14 │ │ │ │ │ ├── scan decimals - │ │ │ │ │ │ ├── columns: decimals.a:15!null decimals.b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 + │ │ │ │ │ │ ├── columns: a:15!null b:16 c:17 d:18 crdb_internal_mvcc_timestamp:19 tableoid:20 │ │ │ │ │ │ └── computed column expressions │ │ │ │ │ │ └── d:18 - │ │ │ │ │ │ └── decimals.a:15::DECIMAL + c:17::DECIMAL + │ │ │ │ │ │ └── a:15::DECIMAL + c:17::DECIMAL │ │ │ │ │ └── filters - │ │ │ │ │ └── a:10 = decimals.a:15 + │ │ │ │ │ └── a_cast:9 = a:15 │ │ │ │ └── projections │ │ │ │ └── ARRAY[0.99] [as=b_new:21] │ │ │ └── projections - │ │ │ └── crdb_internal.round_decimal_values(b_new:21, 1) [as=b_new:22] + │ │ │ └── assignment-cast: DECIMAL(5,1)[] [as=b_cast:22] + │ │ │ └── b_new:21 │ │ └── projections - │ │ └── decimals.a:15::DECIMAL + c:17::DECIMAL [as=d_comp:23] + │ │ └── a:15::DECIMAL + c:17::DECIMAL [as=d_comp:23] │ └── projections - │ ├── CASE WHEN decimals.a:15 IS NULL THEN a:10 ELSE decimals.a:15 END [as=upsert_a:24] - │ ├── CASE WHEN decimals.a:15 IS NULL THEN b:11 ELSE b_new:22 END [as=upsert_b:25] - │ ├── CASE WHEN decimals.a:15 IS NULL THEN c_default:12 ELSE c:17 END [as=upsert_c:26] - │ └── CASE WHEN decimals.a:15 IS NULL THEN d_comp:14 ELSE d:18 END [as=upsert_d:27] + │ ├── CASE WHEN a:15 IS NULL THEN a_cast:9 ELSE a:15 END [as=upsert_a:24] + │ ├── CASE WHEN a:15 IS NULL THEN b_cast:10 ELSE b_cast:22 END [as=upsert_b:25] + │ ├── CASE WHEN a:15 IS NULL THEN c_cast:12 ELSE c:17 END [as=upsert_c:26] + │ └── CASE WHEN a:15 IS NULL THEN d_cast:14 ELSE d:18 END [as=upsert_d:27] └── projections ├── round(upsert_a:24) = upsert_a:24 [as=check1:28] └── upsert_b:25[0] > 1 [as=check2:29] +# Test standard upsert with some types that require assignment casts. +build +UPSERT INTO assn_cast (k, c, qc, i, s) VALUES (1.0::DECIMAL, ' ', 'foo', '1', 2) +---- +upsert assn_cast + ├── columns: + ├── arbiter indexes: assn_cast_pkey + ├── canary column: k:22 + ├── fetch columns: k:22 c:23 qc:24 i:25 s:26 d:27 d_comp:28 + ├── insert-mapping: + │ ├── k_cast:15 => k:1 + │ ├── c_cast:16 => c:2 + │ ├── qc_cast:17 => qc:3 + │ ├── column4:13 => i:4 + │ ├── s_cast:18 => s:5 + │ ├── d_default:19 => d:6 + │ └── d_comp_cast:21 => d_comp:7 + ├── update-mapping: + │ ├── c_cast:16 => c:2 + │ ├── qc_cast:17 => qc:3 + │ ├── column4:13 => i:4 + │ └── s_cast:18 => s:5 + └── project + ├── columns: upsert_k:32 upsert_d:33 upsert_d_comp:34 column4:13!null k_cast:15!null c_cast:16!null qc_cast:17!null s_cast:18!null d_default:19 d_comp_cast:21 k:22 c:23 qc:24 i:25 s:26 d:27 d_comp:28 crdb_internal_mvcc_timestamp:29 tableoid:30 d_comp_comp:31 + ├── project + │ ├── columns: d_comp_comp:31 column4:13!null k_cast:15!null c_cast:16!null qc_cast:17!null s_cast:18!null d_default:19 d_comp_cast:21 k:22 c:23 qc:24 i:25 s:26 d:27 d_comp:28 crdb_internal_mvcc_timestamp:29 tableoid:30 + │ ├── left-join (hash) + │ │ ├── columns: column4:13!null k_cast:15!null c_cast:16!null qc_cast:17!null s_cast:18!null d_default:19 d_comp_cast:21 k:22 c:23 qc:24 i:25 s:26 d:27 d_comp:28 crdb_internal_mvcc_timestamp:29 tableoid:30 + │ │ ├── ensure-upsert-distinct-on + │ │ │ ├── columns: column4:13!null k_cast:15!null c_cast:16!null qc_cast:17!null s_cast:18!null d_default:19 d_comp_cast:21 + │ │ │ ├── grouping columns: k_cast:15!null + │ │ │ ├── project + │ │ │ │ ├── columns: d_comp_cast:21 column4:13!null k_cast:15!null c_cast:16!null qc_cast:17!null s_cast:18!null d_default:19 + │ │ │ │ ├── project + │ │ │ │ │ ├── columns: d_comp_comp:20 column4:13!null k_cast:15!null c_cast:16!null qc_cast:17!null s_cast:18!null d_default:19 + │ │ │ │ │ ├── project + │ │ │ │ │ │ ├── columns: d_default:19 column4:13!null k_cast:15!null c_cast:16!null qc_cast:17!null s_cast:18!null + │ │ │ │ │ │ ├── project + │ │ │ │ │ │ │ ├── columns: k_cast:15!null c_cast:16!null qc_cast:17!null s_cast:18!null column4:13!null + │ │ │ │ │ │ │ ├── values + │ │ │ │ │ │ │ │ ├── columns: column1:10!null column2:11!null column3:12!null column4:13!null column5:14!null + │ │ │ │ │ │ │ │ └── (1.0, ' ', 'foo', 1, 2) + │ │ │ │ │ │ │ └── projections + │ │ │ │ │ │ │ ├── assignment-cast: INT8 [as=k_cast:15] + │ │ │ │ │ │ │ │ └── column1:10 + │ │ │ │ │ │ │ ├── assignment-cast: CHAR [as=c_cast:16] + │ │ │ │ │ │ │ │ └── column2:11 + │ │ │ │ │ │ │ ├── assignment-cast: "char" [as=qc_cast:17] + │ │ │ │ │ │ │ │ └── column3:12 + │ │ │ │ │ │ │ └── assignment-cast: STRING [as=s_cast:18] + │ │ │ │ │ │ │ └── column5:14 + │ │ │ │ │ │ └── projections + │ │ │ │ │ │ └── NULL::DECIMAL(10) [as=d_default:19] + │ │ │ │ │ └── projections + │ │ │ │ │ └── d_default:19::DECIMAL + 10.0 [as=d_comp_comp:20] + │ │ │ │ └── projections + │ │ │ │ └── assignment-cast: DECIMAL(10) [as=d_comp_cast:21] + │ │ │ │ └── d_comp_comp:20 + │ │ │ └── aggregations + │ │ │ ├── first-agg [as=c_cast:16] + │ │ │ │ └── c_cast:16 + │ │ │ ├── first-agg [as=qc_cast:17] + │ │ │ │ └── qc_cast:17 + │ │ │ ├── first-agg [as=column4:13] + │ │ │ │ └── column4:13 + │ │ │ ├── first-agg [as=s_cast:18] + │ │ │ │ └── s_cast:18 + │ │ │ ├── first-agg [as=d_default:19] + │ │ │ │ └── d_default:19 + │ │ │ └── first-agg [as=d_comp_cast:21] + │ │ │ └── d_comp_cast:21 + │ │ ├── scan assn_cast + │ │ │ ├── columns: k:22!null c:23 qc:24 i:25 s:26 d:27 d_comp:28 crdb_internal_mvcc_timestamp:29 tableoid:30 + │ │ │ └── computed column expressions + │ │ │ └── d_comp:28 + │ │ │ └── d:27::DECIMAL + 10.0 + │ │ └── filters + │ │ └── k_cast:15 = k:22 + │ └── projections + │ └── d:27::DECIMAL + 10.0 [as=d_comp_comp:31] + └── projections + ├── CASE WHEN k:22 IS NULL THEN k_cast:15 ELSE k:22 END [as=upsert_k:32] + ├── CASE WHEN k:22 IS NULL THEN d_default:19 ELSE d:27 END [as=upsert_d:33] + └── CASE WHEN k:22 IS NULL THEN d_comp_cast:21 ELSE d_comp:28 END [as=upsert_d_comp:34] + +# Test standard prepared upsert with some types that require assignment casts. +assign-placeholders-build query-args=(1.0, ' ', 'foo', '1') +UPSERT INTO assn_cast (k, c, qc, i) VALUES ($1::DECIMAL, $2, $3, $4) +---- +upsert assn_cast + ├── columns: + ├── arbiter indexes: assn_cast_pkey + ├── canary column: k:21 + ├── fetch columns: k:21 c:22 qc:23 i:24 s:25 d:26 d_comp:27 + ├── insert-mapping: + │ ├── k_cast:14 => k:1 + │ ├── c_cast:15 => c:2 + │ ├── qc_cast:16 => qc:3 + │ ├── column4:13 => i:4 + │ ├── s_default:17 => s:5 + │ ├── d_default:18 => d:6 + │ └── d_comp_cast:20 => d_comp:7 + ├── update-mapping: + │ ├── c_cast:15 => c:2 + │ ├── qc_cast:16 => qc:3 + │ └── column4:13 => i:4 + └── project + ├── columns: upsert_k:31 upsert_s:32 upsert_d:33 upsert_d_comp:34 column4:13!null k_cast:14!null c_cast:15!null qc_cast:16!null s_default:17 d_default:18 d_comp_cast:20 k:21 c:22 qc:23 i:24 s:25 d:26 d_comp:27 crdb_internal_mvcc_timestamp:28 tableoid:29 d_comp_comp:30 + ├── project + │ ├── columns: d_comp_comp:30 column4:13!null k_cast:14!null c_cast:15!null qc_cast:16!null s_default:17 d_default:18 d_comp_cast:20 k:21 c:22 qc:23 i:24 s:25 d:26 d_comp:27 crdb_internal_mvcc_timestamp:28 tableoid:29 + │ ├── left-join (hash) + │ │ ├── columns: column4:13!null k_cast:14!null c_cast:15!null qc_cast:16!null s_default:17 d_default:18 d_comp_cast:20 k:21 c:22 qc:23 i:24 s:25 d:26 d_comp:27 crdb_internal_mvcc_timestamp:28 tableoid:29 + │ │ ├── ensure-upsert-distinct-on + │ │ │ ├── columns: column4:13!null k_cast:14!null c_cast:15!null qc_cast:16!null s_default:17 d_default:18 d_comp_cast:20 + │ │ │ ├── grouping columns: k_cast:14!null + │ │ │ ├── project + │ │ │ │ ├── columns: d_comp_cast:20 column4:13!null k_cast:14!null c_cast:15!null qc_cast:16!null s_default:17 d_default:18 + │ │ │ │ ├── project + │ │ │ │ │ ├── columns: d_comp_comp:19 column4:13!null k_cast:14!null c_cast:15!null qc_cast:16!null s_default:17 d_default:18 + │ │ │ │ │ ├── project + │ │ │ │ │ │ ├── columns: s_default:17 d_default:18 column4:13!null k_cast:14!null c_cast:15!null qc_cast:16!null + │ │ │ │ │ │ ├── project + │ │ │ │ │ │ │ ├── columns: k_cast:14!null c_cast:15!null qc_cast:16!null column4:13!null + │ │ │ │ │ │ │ ├── values + │ │ │ │ │ │ │ │ ├── columns: column1:10!null column2:11!null column3:12!null column4:13!null + │ │ │ │ │ │ │ │ └── (1.0, ' ', 'foo', 1) + │ │ │ │ │ │ │ └── projections + │ │ │ │ │ │ │ ├── assignment-cast: INT8 [as=k_cast:14] + │ │ │ │ │ │ │ │ └── column1:10 + │ │ │ │ │ │ │ ├── assignment-cast: CHAR [as=c_cast:15] + │ │ │ │ │ │ │ │ └── column2:11 + │ │ │ │ │ │ │ └── assignment-cast: "char" [as=qc_cast:16] + │ │ │ │ │ │ │ └── column3:12 + │ │ │ │ │ │ └── projections + │ │ │ │ │ │ ├── NULL::STRING [as=s_default:17] + │ │ │ │ │ │ └── NULL::DECIMAL(10) [as=d_default:18] + │ │ │ │ │ └── projections + │ │ │ │ │ └── d_default:18::DECIMAL + 10.0 [as=d_comp_comp:19] + │ │ │ │ └── projections + │ │ │ │ └── assignment-cast: DECIMAL(10) [as=d_comp_cast:20] + │ │ │ │ └── d_comp_comp:19 + │ │ │ └── aggregations + │ │ │ ├── first-agg [as=c_cast:15] + │ │ │ │ └── c_cast:15 + │ │ │ ├── first-agg [as=qc_cast:16] + │ │ │ │ └── qc_cast:16 + │ │ │ ├── first-agg [as=column4:13] + │ │ │ │ └── column4:13 + │ │ │ ├── first-agg [as=s_default:17] + │ │ │ │ └── s_default:17 + │ │ │ ├── first-agg [as=d_default:18] + │ │ │ │ └── d_default:18 + │ │ │ └── first-agg [as=d_comp_cast:20] + │ │ │ └── d_comp_cast:20 + │ │ ├── scan assn_cast + │ │ │ ├── columns: k:21!null c:22 qc:23 i:24 s:25 d:26 d_comp:27 crdb_internal_mvcc_timestamp:28 tableoid:29 + │ │ │ └── computed column expressions + │ │ │ └── d_comp:27 + │ │ │ └── d:26::DECIMAL + 10.0 + │ │ └── filters + │ │ └── k_cast:14 = k:21 + │ └── projections + │ └── d:26::DECIMAL + 10.0 [as=d_comp_comp:30] + └── projections + ├── CASE WHEN k:21 IS NULL THEN k_cast:14 ELSE k:21 END [as=upsert_k:31] + ├── CASE WHEN k:21 IS NULL THEN s_default:17 ELSE s:25 END [as=upsert_s:32] + ├── CASE WHEN k:21 IS NULL THEN d_default:18 ELSE d:26 END [as=upsert_d:33] + └── CASE WHEN k:21 IS NULL THEN d_comp_cast:20 ELSE d_comp:27 END [as=upsert_d_comp:34] + +# Test standard insert-do-nothing with some types that require assignment casts. +build +INSERT INTO assn_cast (k, c, qc, i, s) VALUES (1.0::DECIMAL, ' ', 'foo', '1', 2) ON CONFLICT DO NOTHING +---- +insert assn_cast + ├── columns: + ├── arbiter indexes: assn_cast_pkey + ├── insert-mapping: + │ ├── k_cast:15 => k:1 + │ ├── c_cast:16 => c:2 + │ ├── qc_cast:17 => qc:3 + │ ├── column4:13 => i:4 + │ ├── s_cast:18 => s:5 + │ ├── d_default:19 => d:6 + │ └── d_comp_cast:21 => d_comp:7 + └── upsert-distinct-on + ├── columns: column4:13!null k_cast:15!null c_cast:16!null qc_cast:17!null s_cast:18!null d_default:19 d_comp_cast:21 + ├── grouping columns: k_cast:15!null + ├── anti-join (hash) + │ ├── columns: column4:13!null k_cast:15!null c_cast:16!null qc_cast:17!null s_cast:18!null d_default:19 d_comp_cast:21 + │ ├── project + │ │ ├── columns: d_comp_cast:21 column4:13!null k_cast:15!null c_cast:16!null qc_cast:17!null s_cast:18!null d_default:19 + │ │ ├── project + │ │ │ ├── columns: d_comp_comp:20 column4:13!null k_cast:15!null c_cast:16!null qc_cast:17!null s_cast:18!null d_default:19 + │ │ │ ├── project + │ │ │ │ ├── columns: d_default:19 column4:13!null k_cast:15!null c_cast:16!null qc_cast:17!null s_cast:18!null + │ │ │ │ ├── project + │ │ │ │ │ ├── columns: k_cast:15!null c_cast:16!null qc_cast:17!null s_cast:18!null column4:13!null + │ │ │ │ │ ├── values + │ │ │ │ │ │ ├── columns: column1:10!null column2:11!null column3:12!null column4:13!null column5:14!null + │ │ │ │ │ │ └── (1.0, ' ', 'foo', 1, 2) + │ │ │ │ │ └── projections + │ │ │ │ │ ├── assignment-cast: INT8 [as=k_cast:15] + │ │ │ │ │ │ └── column1:10 + │ │ │ │ │ ├── assignment-cast: CHAR [as=c_cast:16] + │ │ │ │ │ │ └── column2:11 + │ │ │ │ │ ├── assignment-cast: "char" [as=qc_cast:17] + │ │ │ │ │ │ └── column3:12 + │ │ │ │ │ └── assignment-cast: STRING [as=s_cast:18] + │ │ │ │ │ └── column5:14 + │ │ │ │ └── projections + │ │ │ │ └── NULL::DECIMAL(10) [as=d_default:19] + │ │ │ └── projections + │ │ │ └── d_default:19::DECIMAL + 10.0 [as=d_comp_comp:20] + │ │ └── projections + │ │ └── assignment-cast: DECIMAL(10) [as=d_comp_cast:21] + │ │ └── d_comp_comp:20 + │ ├── scan assn_cast + │ │ ├── columns: k:22!null c:23 qc:24 i:25 s:26 d:27 d_comp:28 + │ │ └── computed column expressions + │ │ └── d_comp:28 + │ │ └── d:27::DECIMAL + 10.0 + │ └── filters + │ └── k_cast:15 = k:22 + └── aggregations + ├── first-agg [as=c_cast:16] + │ └── c_cast:16 + ├── first-agg [as=qc_cast:17] + │ └── qc_cast:17 + ├── first-agg [as=column4:13] + │ └── column4:13 + ├── first-agg [as=s_cast:18] + │ └── s_cast:18 + ├── first-agg [as=d_default:19] + │ └── d_default:19 + └── first-agg [as=d_comp_cast:21] + └── d_comp_cast:21 + +# Test standard insert-do-update with some types that require assignment casts. +build +INSERT INTO assn_cast (k, c, qc, i, s) VALUES (1.0::DECIMAL, 'a', 'b', 1, 'c') +ON CONFLICT (k) DO UPDATE SET c = ' ', qc = 'foo', i = '1', s = 2 +---- +upsert assn_cast + ├── columns: + ├── arbiter indexes: assn_cast_pkey + ├── canary column: k:21 + ├── fetch columns: k:21 c:22 qc:23 i:24 s:25 d:26 d_comp:27 + ├── insert-mapping: + │ ├── k_cast:15 => k:1 + │ ├── c_cast:16 => c:2 + │ ├── qc_cast:17 => qc:3 + │ ├── column4:13 => i:4 + │ ├── column5:14 => s:5 + │ ├── d_default:18 => d:6 + │ └── d_comp_cast:20 => d_comp:7 + ├── update-mapping: + │ ├── upsert_c:39 => c:2 + │ ├── upsert_qc:40 => qc:3 + │ ├── upsert_i:41 => i:4 + │ └── upsert_s:42 => s:5 + └── project + ├── columns: upsert_k:38 upsert_c:39!null upsert_qc:40!null upsert_i:41!null upsert_s:42!null upsert_d:43 upsert_d_comp:44 column4:13!null column5:14!null k_cast:15!null c_cast:16!null qc_cast:17!null d_default:18 d_comp_cast:20 k:21 c:22 qc:23 i:24 s:25 d:26 d_comp:27 crdb_internal_mvcc_timestamp:28 tableoid:29 i_new:32!null c_cast:34!null qc_cast:35!null s_cast:36!null d_comp_comp:37 + ├── project + │ ├── columns: d_comp_comp:37 column4:13!null column5:14!null k_cast:15!null c_cast:16!null qc_cast:17!null d_default:18 d_comp_cast:20 k:21 c:22 qc:23 i:24 s:25 d:26 d_comp:27 crdb_internal_mvcc_timestamp:28 tableoid:29 i_new:32!null c_cast:34!null qc_cast:35!null s_cast:36!null + │ ├── project + │ │ ├── columns: c_cast:34!null qc_cast:35!null s_cast:36!null column4:13!null column5:14!null k_cast:15!null c_cast:16!null qc_cast:17!null d_default:18 d_comp_cast:20 k:21 c:22 qc:23 i:24 s:25 d:26 d_comp:27 crdb_internal_mvcc_timestamp:28 tableoid:29 i_new:32!null + │ │ ├── project + │ │ │ ├── columns: c_new:30!null qc_new:31!null i_new:32!null s_new:33!null column4:13!null column5:14!null k_cast:15!null c_cast:16!null qc_cast:17!null d_default:18 d_comp_cast:20 k:21 c:22 qc:23 i:24 s:25 d:26 d_comp:27 crdb_internal_mvcc_timestamp:28 tableoid:29 + │ │ │ ├── left-join (hash) + │ │ │ │ ├── columns: column4:13!null column5:14!null k_cast:15!null c_cast:16!null qc_cast:17!null d_default:18 d_comp_cast:20 k:21 c:22 qc:23 i:24 s:25 d:26 d_comp:27 crdb_internal_mvcc_timestamp:28 tableoid:29 + │ │ │ │ ├── ensure-upsert-distinct-on + │ │ │ │ │ ├── columns: column4:13!null column5:14!null k_cast:15!null c_cast:16!null qc_cast:17!null d_default:18 d_comp_cast:20 + │ │ │ │ │ ├── grouping columns: k_cast:15!null + │ │ │ │ │ ├── project + │ │ │ │ │ │ ├── columns: d_comp_cast:20 column4:13!null column5:14!null k_cast:15!null c_cast:16!null qc_cast:17!null d_default:18 + │ │ │ │ │ │ ├── project + │ │ │ │ │ │ │ ├── columns: d_comp_comp:19 column4:13!null column5:14!null k_cast:15!null c_cast:16!null qc_cast:17!null d_default:18 + │ │ │ │ │ │ │ ├── project + │ │ │ │ │ │ │ │ ├── columns: d_default:18 column4:13!null column5:14!null k_cast:15!null c_cast:16!null qc_cast:17!null + │ │ │ │ │ │ │ │ ├── project + │ │ │ │ │ │ │ │ │ ├── columns: k_cast:15!null c_cast:16!null qc_cast:17!null column4:13!null column5:14!null + │ │ │ │ │ │ │ │ │ ├── values + │ │ │ │ │ │ │ │ │ │ ├── columns: column1:10!null column2:11!null column3:12!null column4:13!null column5:14!null + │ │ │ │ │ │ │ │ │ │ └── (1.0, 'a', 'b', 1, 'c') + │ │ │ │ │ │ │ │ │ └── projections + │ │ │ │ │ │ │ │ │ ├── assignment-cast: INT8 [as=k_cast:15] + │ │ │ │ │ │ │ │ │ │ └── column1:10 + │ │ │ │ │ │ │ │ │ ├── assignment-cast: CHAR [as=c_cast:16] + │ │ │ │ │ │ │ │ │ │ └── column2:11 + │ │ │ │ │ │ │ │ │ └── assignment-cast: "char" [as=qc_cast:17] + │ │ │ │ │ │ │ │ │ └── column3:12 + │ │ │ │ │ │ │ │ └── projections + │ │ │ │ │ │ │ │ └── NULL::DECIMAL(10) [as=d_default:18] + │ │ │ │ │ │ │ └── projections + │ │ │ │ │ │ │ └── d_default:18::DECIMAL + 10.0 [as=d_comp_comp:19] + │ │ │ │ │ │ └── projections + │ │ │ │ │ │ └── assignment-cast: DECIMAL(10) [as=d_comp_cast:20] + │ │ │ │ │ │ └── d_comp_comp:19 + │ │ │ │ │ └── aggregations + │ │ │ │ │ ├── first-agg [as=c_cast:16] + │ │ │ │ │ │ └── c_cast:16 + │ │ │ │ │ ├── first-agg [as=qc_cast:17] + │ │ │ │ │ │ └── qc_cast:17 + │ │ │ │ │ ├── first-agg [as=column4:13] + │ │ │ │ │ │ └── column4:13 + │ │ │ │ │ ├── first-agg [as=column5:14] + │ │ │ │ │ │ └── column5:14 + │ │ │ │ │ ├── first-agg [as=d_default:18] + │ │ │ │ │ │ └── d_default:18 + │ │ │ │ │ └── first-agg [as=d_comp_cast:20] + │ │ │ │ │ └── d_comp_cast:20 + │ │ │ │ ├── scan assn_cast + │ │ │ │ │ ├── columns: k:21!null c:22 qc:23 i:24 s:25 d:26 d_comp:27 crdb_internal_mvcc_timestamp:28 tableoid:29 + │ │ │ │ │ └── computed column expressions + │ │ │ │ │ └── d_comp:27 + │ │ │ │ │ └── d:26::DECIMAL + 10.0 + │ │ │ │ └── filters + │ │ │ │ └── k_cast:15 = k:21 + │ │ │ └── projections + │ │ │ ├── ' ' [as=c_new:30] + │ │ │ ├── 'foo' [as=qc_new:31] + │ │ │ ├── 1 [as=i_new:32] + │ │ │ └── 2 [as=s_new:33] + │ │ └── projections + │ │ ├── assignment-cast: CHAR [as=c_cast:34] + │ │ │ └── c_new:30 + │ │ ├── assignment-cast: "char" [as=qc_cast:35] + │ │ │ └── qc_new:31 + │ │ └── assignment-cast: STRING [as=s_cast:36] + │ │ └── s_new:33 + │ └── projections + │ └── d:26::DECIMAL + 10.0 [as=d_comp_comp:37] + └── projections + ├── CASE WHEN k:21 IS NULL THEN k_cast:15 ELSE k:21 END [as=upsert_k:38] + ├── CASE WHEN k:21 IS NULL THEN c_cast:16 ELSE c_cast:34 END [as=upsert_c:39] + ├── CASE WHEN k:21 IS NULL THEN qc_cast:17 ELSE qc_cast:35 END [as=upsert_qc:40] + ├── CASE WHEN k:21 IS NULL THEN column4:13 ELSE i_new:32 END [as=upsert_i:41] + ├── CASE WHEN k:21 IS NULL THEN column5:14 ELSE s_cast:36 END [as=upsert_s:42] + ├── CASE WHEN k:21 IS NULL THEN d_default:18 ELSE d:26 END [as=upsert_d:43] + └── CASE WHEN k:21 IS NULL THEN d_comp_cast:20 ELSE d_comp:27 END [as=upsert_d_comp:44] + +# Test upsert to DEFAULT that requires an assignment cast. +build +UPSERT INTO assn_cast (k, i) VALUES (1, DEFAULT) +---- +upsert assn_cast + ├── columns: + ├── arbiter indexes: assn_cast_pkey + ├── canary column: k:19 + ├── fetch columns: k:19 c:20 qc:21 i:22 s:23 d:24 d_comp:25 + ├── insert-mapping: + │ ├── column1:10 => k:1 + │ ├── c_default:13 => c:2 + │ ├── qc_default:14 => qc:3 + │ ├── i_cast:12 => i:4 + │ ├── s_default:15 => s:5 + │ ├── d_default:16 => d:6 + │ └── d_comp_cast:18 => d_comp:7 + ├── update-mapping: + │ └── i_cast:12 => i:4 + └── project + ├── columns: upsert_k:29 upsert_c:30 upsert_qc:31 upsert_s:32 upsert_d:33 upsert_d_comp:34 column1:10!null i_cast:12 c_default:13 qc_default:14 s_default:15 d_default:16 d_comp_cast:18 k:19 c:20 qc:21 i:22 s:23 d:24 d_comp:25 crdb_internal_mvcc_timestamp:26 tableoid:27 d_comp_comp:28 + ├── project + │ ├── columns: d_comp_comp:28 column1:10!null i_cast:12 c_default:13 qc_default:14 s_default:15 d_default:16 d_comp_cast:18 k:19 c:20 qc:21 i:22 s:23 d:24 d_comp:25 crdb_internal_mvcc_timestamp:26 tableoid:27 + │ ├── left-join (hash) + │ │ ├── columns: column1:10!null i_cast:12 c_default:13 qc_default:14 s_default:15 d_default:16 d_comp_cast:18 k:19 c:20 qc:21 i:22 s:23 d:24 d_comp:25 crdb_internal_mvcc_timestamp:26 tableoid:27 + │ │ ├── ensure-upsert-distinct-on + │ │ │ ├── columns: column1:10!null i_cast:12 c_default:13 qc_default:14 s_default:15 d_default:16 d_comp_cast:18 + │ │ │ ├── grouping columns: column1:10!null + │ │ │ ├── project + │ │ │ │ ├── columns: d_comp_cast:18 column1:10!null i_cast:12 c_default:13 qc_default:14 s_default:15 d_default:16 + │ │ │ │ ├── project + │ │ │ │ │ ├── columns: d_comp_comp:17 column1:10!null i_cast:12 c_default:13 qc_default:14 s_default:15 d_default:16 + │ │ │ │ │ ├── project + │ │ │ │ │ │ ├── columns: c_default:13 qc_default:14 s_default:15 d_default:16 column1:10!null i_cast:12 + │ │ │ │ │ │ ├── project + │ │ │ │ │ │ │ ├── columns: i_cast:12 column1:10!null + │ │ │ │ │ │ │ ├── values + │ │ │ │ │ │ │ │ ├── columns: column1:10!null column2:11 + │ │ │ │ │ │ │ │ └── (1, 10::INT2) + │ │ │ │ │ │ │ └── projections + │ │ │ │ │ │ │ └── assignment-cast: INT8 [as=i_cast:12] + │ │ │ │ │ │ │ └── column2:11 + │ │ │ │ │ │ └── projections + │ │ │ │ │ │ ├── NULL::CHAR [as=c_default:13] + │ │ │ │ │ │ ├── NULL::"char" [as=qc_default:14] + │ │ │ │ │ │ ├── NULL::STRING [as=s_default:15] + │ │ │ │ │ │ └── NULL::DECIMAL(10) [as=d_default:16] + │ │ │ │ │ └── projections + │ │ │ │ │ └── d_default:16::DECIMAL + 10.0 [as=d_comp_comp:17] + │ │ │ │ └── projections + │ │ │ │ └── assignment-cast: DECIMAL(10) [as=d_comp_cast:18] + │ │ │ │ └── d_comp_comp:17 + │ │ │ └── aggregations + │ │ │ ├── first-agg [as=i_cast:12] + │ │ │ │ └── i_cast:12 + │ │ │ ├── first-agg [as=c_default:13] + │ │ │ │ └── c_default:13 + │ │ │ ├── first-agg [as=qc_default:14] + │ │ │ │ └── qc_default:14 + │ │ │ ├── first-agg [as=s_default:15] + │ │ │ │ └── s_default:15 + │ │ │ ├── first-agg [as=d_default:16] + │ │ │ │ └── d_default:16 + │ │ │ └── first-agg [as=d_comp_cast:18] + │ │ │ └── d_comp_cast:18 + │ │ ├── scan assn_cast + │ │ │ ├── columns: k:19!null c:20 qc:21 i:22 s:23 d:24 d_comp:25 crdb_internal_mvcc_timestamp:26 tableoid:27 + │ │ │ └── computed column expressions + │ │ │ └── d_comp:25 + │ │ │ └── d:24::DECIMAL + 10.0 + │ │ └── filters + │ │ └── column1:10 = k:19 + │ └── projections + │ └── d:24::DECIMAL + 10.0 [as=d_comp_comp:28] + └── projections + ├── CASE WHEN k:19 IS NULL THEN column1:10 ELSE k:19 END [as=upsert_k:29] + ├── CASE WHEN k:19 IS NULL THEN c_default:13 ELSE c:20 END [as=upsert_c:30] + ├── CASE WHEN k:19 IS NULL THEN qc_default:14 ELSE qc:21 END [as=upsert_qc:31] + ├── CASE WHEN k:19 IS NULL THEN s_default:15 ELSE s:23 END [as=upsert_s:32] + ├── CASE WHEN k:19 IS NULL THEN d_default:16 ELSE d:24 END [as=upsert_d:33] + └── CASE WHEN k:19 IS NULL THEN d_comp_cast:18 ELSE d_comp:25 END [as=upsert_d_comp:34] + +# Test insert-do-update to DEFAULT that requires an assignment cast. +build +INSERT INTO assn_cast (k, i) VALUES (1, 2) ON CONFLICT (k) DO UPDATE SET i = DEFAULT +---- +upsert assn_cast + ├── columns: + ├── arbiter indexes: assn_cast_pkey + ├── canary column: k:18 + ├── fetch columns: k:18 c:19 qc:20 i:21 s:22 d:23 d_comp:24 + ├── insert-mapping: + │ ├── column1:10 => k:1 + │ ├── c_default:12 => c:2 + │ ├── qc_default:13 => qc:3 + │ ├── column2:11 => i:4 + │ ├── s_default:14 => s:5 + │ ├── d_default:15 => d:6 + │ └── d_comp_cast:17 => d_comp:7 + ├── update-mapping: + │ └── upsert_i:33 => i:4 + └── project + ├── columns: upsert_k:30 upsert_c:31 upsert_qc:32 upsert_i:33!null upsert_s:34 upsert_d:35 upsert_d_comp:36 column1:10!null column2:11!null c_default:12 qc_default:13 s_default:14 d_default:15 d_comp_cast:17 k:18 c:19 qc:20 i:21 s:22 d:23 d_comp:24 crdb_internal_mvcc_timestamp:25 tableoid:26 i_cast:28!null d_comp_comp:29 + ├── project + │ ├── columns: d_comp_comp:29 column1:10!null column2:11!null c_default:12 qc_default:13 s_default:14 d_default:15 d_comp_cast:17 k:18 c:19 qc:20 i:21 s:22 d:23 d_comp:24 crdb_internal_mvcc_timestamp:25 tableoid:26 i_cast:28!null + │ ├── project + │ │ ├── columns: i_cast:28!null column1:10!null column2:11!null c_default:12 qc_default:13 s_default:14 d_default:15 d_comp_cast:17 k:18 c:19 qc:20 i:21 s:22 d:23 d_comp:24 crdb_internal_mvcc_timestamp:25 tableoid:26 + │ │ ├── project + │ │ │ ├── columns: i_new:27!null column1:10!null column2:11!null c_default:12 qc_default:13 s_default:14 d_default:15 d_comp_cast:17 k:18 c:19 qc:20 i:21 s:22 d:23 d_comp:24 crdb_internal_mvcc_timestamp:25 tableoid:26 + │ │ │ ├── left-join (hash) + │ │ │ │ ├── columns: column1:10!null column2:11!null c_default:12 qc_default:13 s_default:14 d_default:15 d_comp_cast:17 k:18 c:19 qc:20 i:21 s:22 d:23 d_comp:24 crdb_internal_mvcc_timestamp:25 tableoid:26 + │ │ │ │ ├── ensure-upsert-distinct-on + │ │ │ │ │ ├── columns: column1:10!null column2:11!null c_default:12 qc_default:13 s_default:14 d_default:15 d_comp_cast:17 + │ │ │ │ │ ├── grouping columns: column1:10!null + │ │ │ │ │ ├── project + │ │ │ │ │ │ ├── columns: d_comp_cast:17 column1:10!null column2:11!null c_default:12 qc_default:13 s_default:14 d_default:15 + │ │ │ │ │ │ ├── project + │ │ │ │ │ │ │ ├── columns: d_comp_comp:16 column1:10!null column2:11!null c_default:12 qc_default:13 s_default:14 d_default:15 + │ │ │ │ │ │ │ ├── project + │ │ │ │ │ │ │ │ ├── columns: c_default:12 qc_default:13 s_default:14 d_default:15 column1:10!null column2:11!null + │ │ │ │ │ │ │ │ ├── values + │ │ │ │ │ │ │ │ │ ├── columns: column1:10!null column2:11!null + │ │ │ │ │ │ │ │ │ └── (1, 2) + │ │ │ │ │ │ │ │ └── projections + │ │ │ │ │ │ │ │ ├── NULL::CHAR [as=c_default:12] + │ │ │ │ │ │ │ │ ├── NULL::"char" [as=qc_default:13] + │ │ │ │ │ │ │ │ ├── NULL::STRING [as=s_default:14] + │ │ │ │ │ │ │ │ └── NULL::DECIMAL(10) [as=d_default:15] + │ │ │ │ │ │ │ └── projections + │ │ │ │ │ │ │ └── d_default:15::DECIMAL + 10.0 [as=d_comp_comp:16] + │ │ │ │ │ │ └── projections + │ │ │ │ │ │ └── assignment-cast: DECIMAL(10) [as=d_comp_cast:17] + │ │ │ │ │ │ └── d_comp_comp:16 + │ │ │ │ │ └── aggregations + │ │ │ │ │ ├── first-agg [as=column2:11] + │ │ │ │ │ │ └── column2:11 + │ │ │ │ │ ├── first-agg [as=c_default:12] + │ │ │ │ │ │ └── c_default:12 + │ │ │ │ │ ├── first-agg [as=qc_default:13] + │ │ │ │ │ │ └── qc_default:13 + │ │ │ │ │ ├── first-agg [as=s_default:14] + │ │ │ │ │ │ └── s_default:14 + │ │ │ │ │ ├── first-agg [as=d_default:15] + │ │ │ │ │ │ └── d_default:15 + │ │ │ │ │ └── first-agg [as=d_comp_cast:17] + │ │ │ │ │ └── d_comp_cast:17 + │ │ │ │ ├── scan assn_cast + │ │ │ │ │ ├── columns: k:18!null c:19 qc:20 i:21 s:22 d:23 d_comp:24 crdb_internal_mvcc_timestamp:25 tableoid:26 + │ │ │ │ │ └── computed column expressions + │ │ │ │ │ └── d_comp:24 + │ │ │ │ │ └── d:23::DECIMAL + 10.0 + │ │ │ │ └── filters + │ │ │ │ └── column1:10 = k:18 + │ │ │ └── projections + │ │ │ └── 10::INT2 [as=i_new:27] + │ │ └── projections + │ │ └── assignment-cast: INT8 [as=i_cast:28] + │ │ └── i_new:27 + │ └── projections + │ └── d:23::DECIMAL + 10.0 [as=d_comp_comp:29] + └── projections + ├── CASE WHEN k:18 IS NULL THEN column1:10 ELSE k:18 END [as=upsert_k:30] + ├── CASE WHEN k:18 IS NULL THEN c_default:12 ELSE c:19 END [as=upsert_c:31] + ├── CASE WHEN k:18 IS NULL THEN qc_default:13 ELSE qc:20 END [as=upsert_qc:32] + ├── CASE WHEN k:18 IS NULL THEN column2:11 ELSE i_cast:28 END [as=upsert_i:33] + ├── CASE WHEN k:18 IS NULL THEN s_default:14 ELSE s:22 END [as=upsert_s:34] + ├── CASE WHEN k:18 IS NULL THEN d_default:15 ELSE d:23 END [as=upsert_d:35] + └── CASE WHEN k:18 IS NULL THEN d_comp_cast:17 ELSE d_comp:24 END [as=upsert_d_comp:36] + +# Test upsert to a column that requires an assignment cast and a computed column +# that depends on the new value. +build +UPSERT INTO assn_cast (k, d) VALUES (1, 1.45::DECIMAL(10, 2)) +---- +upsert assn_cast + ├── columns: + ├── arbiter indexes: assn_cast_pkey + ├── canary column: k:20 + ├── fetch columns: k:20 c:21 qc:22 i:23 s:24 d:25 d_comp:26 + ├── insert-mapping: + │ ├── column1:10 => k:1 + │ ├── c_default:13 => c:2 + │ ├── qc_default:14 => qc:3 + │ ├── i_cast:17 => i:4 + │ ├── s_default:16 => s:5 + │ ├── d_cast:12 => d:6 + │ └── d_comp_cast:19 => d_comp:7 + ├── update-mapping: + │ ├── d_cast:12 => d:6 + │ └── d_comp_cast:19 => d_comp:7 + └── project + ├── columns: upsert_k:29 upsert_c:30 upsert_qc:31 upsert_i:32 upsert_s:33 column1:10!null d_cast:12 c_default:13 qc_default:14 s_default:16 i_cast:17!null d_comp_cast:19 k:20 c:21 qc:22 i:23 s:24 d:25 d_comp:26 crdb_internal_mvcc_timestamp:27 tableoid:28 + ├── left-join (hash) + │ ├── columns: column1:10!null d_cast:12 c_default:13 qc_default:14 s_default:16 i_cast:17!null d_comp_cast:19 k:20 c:21 qc:22 i:23 s:24 d:25 d_comp:26 crdb_internal_mvcc_timestamp:27 tableoid:28 + │ ├── ensure-upsert-distinct-on + │ │ ├── columns: column1:10!null d_cast:12 c_default:13 qc_default:14 s_default:16 i_cast:17!null d_comp_cast:19 + │ │ ├── grouping columns: column1:10!null + │ │ ├── project + │ │ │ ├── columns: d_comp_cast:19 column1:10!null d_cast:12 c_default:13 qc_default:14 s_default:16 i_cast:17!null + │ │ │ ├── project + │ │ │ │ ├── columns: d_comp_comp:18 column1:10!null d_cast:12 c_default:13 qc_default:14 s_default:16 i_cast:17!null + │ │ │ │ ├── project + │ │ │ │ │ ├── columns: i_cast:17!null column1:10!null d_cast:12 c_default:13 qc_default:14 s_default:16 + │ │ │ │ │ ├── project + │ │ │ │ │ │ ├── columns: c_default:13 qc_default:14 i_default:15!null s_default:16 column1:10!null d_cast:12 + │ │ │ │ │ │ ├── project + │ │ │ │ │ │ │ ├── columns: d_cast:12 column1:10!null + │ │ │ │ │ │ │ ├── values + │ │ │ │ │ │ │ │ ├── columns: column1:10!null column2:11 + │ │ │ │ │ │ │ │ └── (1, 1.45::DECIMAL(10,2)) + │ │ │ │ │ │ │ └── projections + │ │ │ │ │ │ │ └── assignment-cast: DECIMAL(10) [as=d_cast:12] + │ │ │ │ │ │ │ └── column2:11 + │ │ │ │ │ │ └── projections + │ │ │ │ │ │ ├── NULL::CHAR [as=c_default:13] + │ │ │ │ │ │ ├── NULL::"char" [as=qc_default:14] + │ │ │ │ │ │ ├── 10::INT2 [as=i_default:15] + │ │ │ │ │ │ └── NULL::STRING [as=s_default:16] + │ │ │ │ │ └── projections + │ │ │ │ │ └── assignment-cast: INT8 [as=i_cast:17] + │ │ │ │ │ └── i_default:15 + │ │ │ │ └── projections + │ │ │ │ └── d_cast:12::DECIMAL + 10.0 [as=d_comp_comp:18] + │ │ │ └── projections + │ │ │ └── assignment-cast: DECIMAL(10) [as=d_comp_cast:19] + │ │ │ └── d_comp_comp:18 + │ │ └── aggregations + │ │ ├── first-agg [as=d_cast:12] + │ │ │ └── d_cast:12 + │ │ ├── first-agg [as=c_default:13] + │ │ │ └── c_default:13 + │ │ ├── first-agg [as=qc_default:14] + │ │ │ └── qc_default:14 + │ │ ├── first-agg [as=i_cast:17] + │ │ │ └── i_cast:17 + │ │ ├── first-agg [as=s_default:16] + │ │ │ └── s_default:16 + │ │ └── first-agg [as=d_comp_cast:19] + │ │ └── d_comp_cast:19 + │ ├── scan assn_cast + │ │ ├── columns: k:20!null c:21 qc:22 i:23 s:24 d:25 d_comp:26 crdb_internal_mvcc_timestamp:27 tableoid:28 + │ │ └── computed column expressions + │ │ └── d_comp:26 + │ │ └── d:25::DECIMAL + 10.0 + │ └── filters + │ └── column1:10 = k:20 + └── projections + ├── CASE WHEN k:20 IS NULL THEN column1:10 ELSE k:20 END [as=upsert_k:29] + ├── CASE WHEN k:20 IS NULL THEN c_default:13 ELSE c:21 END [as=upsert_c:30] + ├── CASE WHEN k:20 IS NULL THEN qc_default:14 ELSE qc:22 END [as=upsert_qc:31] + ├── CASE WHEN k:20 IS NULL THEN i_cast:17 ELSE i:23 END [as=upsert_i:32] + └── CASE WHEN k:20 IS NULL THEN s_default:16 ELSE s:24 END [as=upsert_s:33] + +# Test prepared upsert to a column that requires an assignment cast and a +# computed column that depends on the new value. +assign-placeholders-build query-args=(1.45::DECIMAL(10, 2)) +UPSERT INTO assn_cast (k, d) VALUES (1, $1) +---- +upsert assn_cast + ├── columns: + ├── arbiter indexes: assn_cast_pkey + ├── canary column: k:20 + ├── fetch columns: k:20 c:21 qc:22 i:23 s:24 d:25 d_comp:26 + ├── insert-mapping: + │ ├── column1:10 => k:1 + │ ├── c_default:13 => c:2 + │ ├── qc_default:14 => qc:3 + │ ├── i_cast:17 => i:4 + │ ├── s_default:16 => s:5 + │ ├── d_cast:12 => d:6 + │ └── d_comp_cast:19 => d_comp:7 + ├── update-mapping: + │ ├── d_cast:12 => d:6 + │ └── d_comp_cast:19 => d_comp:7 + └── project + ├── columns: upsert_k:29 upsert_c:30 upsert_qc:31 upsert_i:32 upsert_s:33 column1:10!null d_cast:12!null c_default:13 qc_default:14 s_default:16 i_cast:17!null d_comp_cast:19!null k:20 c:21 qc:22 i:23 s:24 d:25 d_comp:26 crdb_internal_mvcc_timestamp:27 tableoid:28 + ├── left-join (hash) + │ ├── columns: column1:10!null d_cast:12!null c_default:13 qc_default:14 s_default:16 i_cast:17!null d_comp_cast:19!null k:20 c:21 qc:22 i:23 s:24 d:25 d_comp:26 crdb_internal_mvcc_timestamp:27 tableoid:28 + │ ├── ensure-upsert-distinct-on + │ │ ├── columns: column1:10!null d_cast:12!null c_default:13 qc_default:14 s_default:16 i_cast:17!null d_comp_cast:19!null + │ │ ├── grouping columns: column1:10!null + │ │ ├── project + │ │ │ ├── columns: d_comp_cast:19!null column1:10!null d_cast:12!null c_default:13 qc_default:14 s_default:16 i_cast:17!null + │ │ │ ├── project + │ │ │ │ ├── columns: d_comp_comp:18!null column1:10!null d_cast:12!null c_default:13 qc_default:14 s_default:16 i_cast:17!null + │ │ │ │ ├── project + │ │ │ │ │ ├── columns: i_cast:17!null column1:10!null d_cast:12!null c_default:13 qc_default:14 s_default:16 + │ │ │ │ │ ├── project + │ │ │ │ │ │ ├── columns: c_default:13 qc_default:14 i_default:15!null s_default:16 column1:10!null d_cast:12!null + │ │ │ │ │ │ ├── project + │ │ │ │ │ │ │ ├── columns: d_cast:12!null column1:10!null + │ │ │ │ │ │ │ ├── values + │ │ │ │ │ │ │ │ ├── columns: column1:10!null column2:11!null + │ │ │ │ │ │ │ │ └── (1, 1.45) + │ │ │ │ │ │ │ └── projections + │ │ │ │ │ │ │ └── assignment-cast: DECIMAL(10) [as=d_cast:12] + │ │ │ │ │ │ │ └── column2:11 + │ │ │ │ │ │ └── projections + │ │ │ │ │ │ ├── NULL::CHAR [as=c_default:13] + │ │ │ │ │ │ ├── NULL::"char" [as=qc_default:14] + │ │ │ │ │ │ ├── 10::INT2 [as=i_default:15] + │ │ │ │ │ │ └── NULL::STRING [as=s_default:16] + │ │ │ │ │ └── projections + │ │ │ │ │ └── assignment-cast: INT8 [as=i_cast:17] + │ │ │ │ │ └── i_default:15 + │ │ │ │ └── projections + │ │ │ │ └── d_cast:12::DECIMAL + 10.0 [as=d_comp_comp:18] + │ │ │ └── projections + │ │ │ └── assignment-cast: DECIMAL(10) [as=d_comp_cast:19] + │ │ │ └── d_comp_comp:18 + │ │ └── aggregations + │ │ ├── first-agg [as=d_cast:12] + │ │ │ └── d_cast:12 + │ │ ├── first-agg [as=c_default:13] + │ │ │ └── c_default:13 + │ │ ├── first-agg [as=qc_default:14] + │ │ │ └── qc_default:14 + │ │ ├── first-agg [as=i_cast:17] + │ │ │ └── i_cast:17 + │ │ ├── first-agg [as=s_default:16] + │ │ │ └── s_default:16 + │ │ └── first-agg [as=d_comp_cast:19] + │ │ └── d_comp_cast:19 + │ ├── scan assn_cast + │ │ ├── columns: k:20!null c:21 qc:22 i:23 s:24 d:25 d_comp:26 crdb_internal_mvcc_timestamp:27 tableoid:28 + │ │ └── computed column expressions + │ │ └── d_comp:26 + │ │ └── d:25::DECIMAL + 10.0 + │ └── filters + │ └── column1:10 = k:20 + └── projections + ├── CASE WHEN k:20 IS NULL THEN column1:10 ELSE k:20 END [as=upsert_k:29] + ├── CASE WHEN k:20 IS NULL THEN c_default:13 ELSE c:21 END [as=upsert_c:30] + ├── CASE WHEN k:20 IS NULL THEN qc_default:14 ELSE qc:22 END [as=upsert_qc:31] + ├── CASE WHEN k:20 IS NULL THEN i_cast:17 ELSE i:23 END [as=upsert_i:32] + └── CASE WHEN k:20 IS NULL THEN s_default:16 ELSE s:24 END [as=upsert_s:33] + +# Test insert-do-nothing to a column that requires an assignment cast and a +# computed column that depends on the new value. +build +INSERT INTO assn_cast (k, d) VALUES (1, 1.45::DECIMAL(10, 2)) ON CONFLICT DO NOTHING +---- +insert assn_cast + ├── columns: + ├── arbiter indexes: assn_cast_pkey + ├── insert-mapping: + │ ├── column1:10 => k:1 + │ ├── c_default:13 => c:2 + │ ├── qc_default:14 => qc:3 + │ ├── i_cast:17 => i:4 + │ ├── s_default:16 => s:5 + │ ├── d_cast:12 => d:6 + │ └── d_comp_cast:19 => d_comp:7 + └── upsert-distinct-on + ├── columns: column1:10!null d_cast:12 c_default:13 qc_default:14 s_default:16 i_cast:17!null d_comp_cast:19 + ├── grouping columns: column1:10!null + ├── anti-join (hash) + │ ├── columns: column1:10!null d_cast:12 c_default:13 qc_default:14 s_default:16 i_cast:17!null d_comp_cast:19 + │ ├── project + │ │ ├── columns: d_comp_cast:19 column1:10!null d_cast:12 c_default:13 qc_default:14 s_default:16 i_cast:17!null + │ │ ├── project + │ │ │ ├── columns: d_comp_comp:18 column1:10!null d_cast:12 c_default:13 qc_default:14 s_default:16 i_cast:17!null + │ │ │ ├── project + │ │ │ │ ├── columns: i_cast:17!null column1:10!null d_cast:12 c_default:13 qc_default:14 s_default:16 + │ │ │ │ ├── project + │ │ │ │ │ ├── columns: c_default:13 qc_default:14 i_default:15!null s_default:16 column1:10!null d_cast:12 + │ │ │ │ │ ├── project + │ │ │ │ │ │ ├── columns: d_cast:12 column1:10!null + │ │ │ │ │ │ ├── values + │ │ │ │ │ │ │ ├── columns: column1:10!null column2:11 + │ │ │ │ │ │ │ └── (1, 1.45::DECIMAL(10,2)) + │ │ │ │ │ │ └── projections + │ │ │ │ │ │ └── assignment-cast: DECIMAL(10) [as=d_cast:12] + │ │ │ │ │ │ └── column2:11 + │ │ │ │ │ └── projections + │ │ │ │ │ ├── NULL::CHAR [as=c_default:13] + │ │ │ │ │ ├── NULL::"char" [as=qc_default:14] + │ │ │ │ │ ├── 10::INT2 [as=i_default:15] + │ │ │ │ │ └── NULL::STRING [as=s_default:16] + │ │ │ │ └── projections + │ │ │ │ └── assignment-cast: INT8 [as=i_cast:17] + │ │ │ │ └── i_default:15 + │ │ │ └── projections + │ │ │ └── d_cast:12::DECIMAL + 10.0 [as=d_comp_comp:18] + │ │ └── projections + │ │ └── assignment-cast: DECIMAL(10) [as=d_comp_cast:19] + │ │ └── d_comp_comp:18 + │ ├── scan assn_cast + │ │ ├── columns: k:20!null c:21 qc:22 i:23 s:24 d:25 d_comp:26 + │ │ └── computed column expressions + │ │ └── d_comp:26 + │ │ └── d:25::DECIMAL + 10.0 + │ └── filters + │ └── column1:10 = k:20 + └── aggregations + ├── first-agg [as=d_cast:12] + │ └── d_cast:12 + ├── first-agg [as=c_default:13] + │ └── c_default:13 + ├── first-agg [as=qc_default:14] + │ └── qc_default:14 + ├── first-agg [as=i_cast:17] + │ └── i_cast:17 + ├── first-agg [as=s_default:16] + │ └── s_default:16 + └── first-agg [as=d_comp_cast:19] + └── d_comp_cast:19 + +# Test insert-do-update to a column that requires an assignment cast and a +# computed column that depends on the new value. +build +INSERT INTO assn_cast (k, d) VALUES (1, 1.45) ON CONFLICT (k) DO UPDATE SET d = 2.67::DECIMAL(10, 2) +---- +upsert assn_cast + ├── columns: + ├── arbiter indexes: assn_cast_pkey + ├── canary column: k:20 + ├── fetch columns: k:20 c:21 qc:22 i:23 s:24 d:25 d_comp:26 + ├── insert-mapping: + │ ├── column1:10 => k:1 + │ ├── c_default:13 => c:2 + │ ├── qc_default:14 => qc:3 + │ ├── i_cast:17 => i:4 + │ ├── s_default:16 => s:5 + │ ├── d_cast:12 => d:6 + │ └── d_comp_cast:19 => d_comp:7 + ├── update-mapping: + │ ├── upsert_d:38 => d:6 + │ └── upsert_d_comp:39 => d_comp:7 + └── project + ├── columns: upsert_k:33 upsert_c:34 upsert_qc:35 upsert_i:36 upsert_s:37 upsert_d:38!null upsert_d_comp:39!null column1:10!null d_cast:12!null c_default:13 qc_default:14 s_default:16 i_cast:17!null d_comp_cast:19!null k:20 c:21 qc:22 i:23 s:24 d:25 d_comp:26 crdb_internal_mvcc_timestamp:27 tableoid:28 d_cast:30!null d_comp_cast:32!null + ├── project + │ ├── columns: d_comp_cast:32!null column1:10!null d_cast:12!null c_default:13 qc_default:14 s_default:16 i_cast:17!null d_comp_cast:19!null k:20 c:21 qc:22 i:23 s:24 d:25 d_comp:26 crdb_internal_mvcc_timestamp:27 tableoid:28 d_cast:30!null + │ ├── project + │ │ ├── columns: d_comp_comp:31!null column1:10!null d_cast:12!null c_default:13 qc_default:14 s_default:16 i_cast:17!null d_comp_cast:19!null k:20 c:21 qc:22 i:23 s:24 d:25 d_comp:26 crdb_internal_mvcc_timestamp:27 tableoid:28 d_cast:30!null + │ │ ├── project + │ │ │ ├── columns: d_cast:30!null column1:10!null d_cast:12!null c_default:13 qc_default:14 s_default:16 i_cast:17!null d_comp_cast:19!null k:20 c:21 qc:22 i:23 s:24 d:25 d_comp:26 crdb_internal_mvcc_timestamp:27 tableoid:28 + │ │ │ ├── project + │ │ │ │ ├── columns: d_new:29!null column1:10!null d_cast:12!null c_default:13 qc_default:14 s_default:16 i_cast:17!null d_comp_cast:19!null k:20 c:21 qc:22 i:23 s:24 d:25 d_comp:26 crdb_internal_mvcc_timestamp:27 tableoid:28 + │ │ │ │ ├── left-join (hash) + │ │ │ │ │ ├── columns: column1:10!null d_cast:12!null c_default:13 qc_default:14 s_default:16 i_cast:17!null d_comp_cast:19!null k:20 c:21 qc:22 i:23 s:24 d:25 d_comp:26 crdb_internal_mvcc_timestamp:27 tableoid:28 + │ │ │ │ │ ├── ensure-upsert-distinct-on + │ │ │ │ │ │ ├── columns: column1:10!null d_cast:12!null c_default:13 qc_default:14 s_default:16 i_cast:17!null d_comp_cast:19!null + │ │ │ │ │ │ ├── grouping columns: column1:10!null + │ │ │ │ │ │ ├── project + │ │ │ │ │ │ │ ├── columns: d_comp_cast:19!null column1:10!null d_cast:12!null c_default:13 qc_default:14 s_default:16 i_cast:17!null + │ │ │ │ │ │ │ ├── project + │ │ │ │ │ │ │ │ ├── columns: d_comp_comp:18!null column1:10!null d_cast:12!null c_default:13 qc_default:14 s_default:16 i_cast:17!null + │ │ │ │ │ │ │ │ ├── project + │ │ │ │ │ │ │ │ │ ├── columns: i_cast:17!null column1:10!null d_cast:12!null c_default:13 qc_default:14 s_default:16 + │ │ │ │ │ │ │ │ │ ├── project + │ │ │ │ │ │ │ │ │ │ ├── columns: c_default:13 qc_default:14 i_default:15!null s_default:16 column1:10!null d_cast:12!null + │ │ │ │ │ │ │ │ │ │ ├── project + │ │ │ │ │ │ │ │ │ │ │ ├── columns: d_cast:12!null column1:10!null + │ │ │ │ │ │ │ │ │ │ │ ├── values + │ │ │ │ │ │ │ │ │ │ │ │ ├── columns: column1:10!null column2:11!null + │ │ │ │ │ │ │ │ │ │ │ │ └── (1, 1.45) + │ │ │ │ │ │ │ │ │ │ │ └── projections + │ │ │ │ │ │ │ │ │ │ │ └── assignment-cast: DECIMAL(10) [as=d_cast:12] + │ │ │ │ │ │ │ │ │ │ │ └── column2:11 + │ │ │ │ │ │ │ │ │ │ └── projections + │ │ │ │ │ │ │ │ │ │ ├── NULL::CHAR [as=c_default:13] + │ │ │ │ │ │ │ │ │ │ ├── NULL::"char" [as=qc_default:14] + │ │ │ │ │ │ │ │ │ │ ├── 10::INT2 [as=i_default:15] + │ │ │ │ │ │ │ │ │ │ └── NULL::STRING [as=s_default:16] + │ │ │ │ │ │ │ │ │ └── projections + │ │ │ │ │ │ │ │ │ └── assignment-cast: INT8 [as=i_cast:17] + │ │ │ │ │ │ │ │ │ └── i_default:15 + │ │ │ │ │ │ │ │ └── projections + │ │ │ │ │ │ │ │ └── d_cast:12::DECIMAL + 10.0 [as=d_comp_comp:18] + │ │ │ │ │ │ │ └── projections + │ │ │ │ │ │ │ └── assignment-cast: DECIMAL(10) [as=d_comp_cast:19] + │ │ │ │ │ │ │ └── d_comp_comp:18 + │ │ │ │ │ │ └── aggregations + │ │ │ │ │ │ ├── first-agg [as=d_cast:12] + │ │ │ │ │ │ │ └── d_cast:12 + │ │ │ │ │ │ ├── first-agg [as=c_default:13] + │ │ │ │ │ │ │ └── c_default:13 + │ │ │ │ │ │ ├── first-agg [as=qc_default:14] + │ │ │ │ │ │ │ └── qc_default:14 + │ │ │ │ │ │ ├── first-agg [as=i_cast:17] + │ │ │ │ │ │ │ └── i_cast:17 + │ │ │ │ │ │ ├── first-agg [as=s_default:16] + │ │ │ │ │ │ │ └── s_default:16 + │ │ │ │ │ │ └── first-agg [as=d_comp_cast:19] + │ │ │ │ │ │ └── d_comp_cast:19 + │ │ │ │ │ ├── scan assn_cast + │ │ │ │ │ │ ├── columns: k:20!null c:21 qc:22 i:23 s:24 d:25 d_comp:26 crdb_internal_mvcc_timestamp:27 tableoid:28 + │ │ │ │ │ │ └── computed column expressions + │ │ │ │ │ │ └── d_comp:26 + │ │ │ │ │ │ └── d:25::DECIMAL + 10.0 + │ │ │ │ │ └── filters + │ │ │ │ │ └── column1:10 = k:20 + │ │ │ │ └── projections + │ │ │ │ └── 2.67::DECIMAL(10,2) [as=d_new:29] + │ │ │ └── projections + │ │ │ └── assignment-cast: DECIMAL(10) [as=d_cast:30] + │ │ │ └── d_new:29 + │ │ └── projections + │ │ └── d_cast:30::DECIMAL + 10.0 [as=d_comp_comp:31] + │ └── projections + │ └── assignment-cast: DECIMAL(10) [as=d_comp_cast:32] + │ └── d_comp_comp:31 + └── projections + ├── CASE WHEN k:20 IS NULL THEN column1:10 ELSE k:20 END [as=upsert_k:33] + ├── CASE WHEN k:20 IS NULL THEN c_default:13 ELSE c:21 END [as=upsert_c:34] + ├── CASE WHEN k:20 IS NULL THEN qc_default:14 ELSE qc:22 END [as=upsert_qc:35] + ├── CASE WHEN k:20 IS NULL THEN i_cast:17 ELSE i:23 END [as=upsert_i:36] + ├── CASE WHEN k:20 IS NULL THEN s_default:16 ELSE s:24 END [as=upsert_s:37] + ├── CASE WHEN k:20 IS NULL THEN d_cast:12 ELSE d_cast:30 END [as=upsert_d:38] + └── CASE WHEN k:20 IS NULL THEN d_comp_cast:19 ELSE d_comp_cast:32 END [as=upsert_d_comp:39] + +# Test ON UPDATE columns that require assignment casts. +build +INSERT INTO assn_cast_on_update (k, i) VALUES (1, 2) ON CONFLICT (k) DO UPDATE SET i = 3 +---- +upsert assn_cast_on_update + ├── columns: + ├── arbiter indexes: assn_cast_on_update_pkey + ├── canary column: k:12 + ├── fetch columns: k:12 i:13 d:14 d2:15 d_comp:16 + ├── insert-mapping: + │ ├── column1:8 => k:1 + │ ├── column2:9 => i:2 + │ ├── d_default:10 => d:3 + │ ├── d_default:10 => d2:4 + │ └── d_comp_cast:11 => d_comp:5 + ├── update-mapping: + │ ├── upsert_i:26 => i:2 + │ ├── upsert_d:27 => d:3 + │ ├── upsert_d2:28 => d2:4 + │ └── upsert_d_comp:29 => d_comp:5 + └── project + ├── columns: upsert_k:25 upsert_i:26!null upsert_d:27 upsert_d2:28 upsert_d_comp:29 column1:8!null column2:9!null d_default:10 d_comp_cast:11 k:12 i:13 d:14 d2:15 d_comp:16 crdb_internal_mvcc_timestamp:17 tableoid:18 i_new:19!null d_cast:22!null d2_cast:23!null d_comp_cast:24!null + ├── project + │ ├── columns: d_comp_cast:24!null column1:8!null column2:9!null d_default:10 d_comp_cast:11 k:12 i:13 d:14 d2:15 d_comp:16 crdb_internal_mvcc_timestamp:17 tableoid:18 i_new:19!null d_cast:22!null d2_cast:23!null + │ ├── project + │ │ ├── columns: d_cast:22!null d2_cast:23!null column1:8!null column2:9!null d_default:10 d_comp_cast:11 k:12 i:13 d:14 d2:15 d_comp:16 crdb_internal_mvcc_timestamp:17 tableoid:18 i_new:19!null + │ │ ├── project + │ │ │ ├── columns: d_on_update:20!null d2_on_update:21!null column1:8!null column2:9!null d_default:10 d_comp_cast:11 k:12 i:13 d:14 d2:15 d_comp:16 crdb_internal_mvcc_timestamp:17 tableoid:18 i_new:19!null + │ │ │ ├── project + │ │ │ │ ├── columns: i_new:19!null column1:8!null column2:9!null d_default:10 d_comp_cast:11 k:12 i:13 d:14 d2:15 d_comp:16 crdb_internal_mvcc_timestamp:17 tableoid:18 + │ │ │ │ ├── left-join (hash) + │ │ │ │ │ ├── columns: column1:8!null column2:9!null d_default:10 d_comp_cast:11 k:12 i:13 d:14 d2:15 d_comp:16 crdb_internal_mvcc_timestamp:17 tableoid:18 + │ │ │ │ │ ├── ensure-upsert-distinct-on + │ │ │ │ │ │ ├── columns: column1:8!null column2:9!null d_default:10 d_comp_cast:11 + │ │ │ │ │ │ ├── grouping columns: column1:8!null + │ │ │ │ │ │ ├── project + │ │ │ │ │ │ │ ├── columns: d_comp_cast:11 column1:8!null column2:9!null d_default:10 + │ │ │ │ │ │ │ ├── project + │ │ │ │ │ │ │ │ ├── columns: d_default:10 column1:8!null column2:9!null + │ │ │ │ │ │ │ │ ├── values + │ │ │ │ │ │ │ │ │ ├── columns: column1:8!null column2:9!null + │ │ │ │ │ │ │ │ │ └── (1, 2) + │ │ │ │ │ │ │ │ └── projections + │ │ │ │ │ │ │ │ └── NULL::DECIMAL(10,1) [as=d_default:10] + │ │ │ │ │ │ │ └── projections + │ │ │ │ │ │ │ └── assignment-cast: DECIMAL(10) [as=d_comp_cast:11] + │ │ │ │ │ │ │ └── d_default:10 + │ │ │ │ │ │ └── aggregations + │ │ │ │ │ │ ├── first-agg [as=column2:9] + │ │ │ │ │ │ │ └── column2:9 + │ │ │ │ │ │ ├── first-agg [as=d_default:10] + │ │ │ │ │ │ │ └── d_default:10 + │ │ │ │ │ │ └── first-agg [as=d_comp_cast:11] + │ │ │ │ │ │ └── d_comp_cast:11 + │ │ │ │ │ ├── scan assn_cast_on_update + │ │ │ │ │ │ ├── columns: k:12!null i:13 d:14 d2:15 d_comp:16 crdb_internal_mvcc_timestamp:17 tableoid:18 + │ │ │ │ │ │ └── computed column expressions + │ │ │ │ │ │ └── d_comp:16 + │ │ │ │ │ │ └── d:14 + │ │ │ │ │ └── filters + │ │ │ │ │ └── column1:8 = k:12 + │ │ │ │ └── projections + │ │ │ │ └── 3 [as=i_new:19] + │ │ │ └── projections + │ │ │ ├── 1.23 [as=d_on_update:20] + │ │ │ └── 1.23::DECIMAL(10,2) [as=d2_on_update:21] + │ │ └── projections + │ │ ├── assignment-cast: DECIMAL(10,1) [as=d_cast:22] + │ │ │ └── d_on_update:20 + │ │ └── assignment-cast: DECIMAL(10,1) [as=d2_cast:23] + │ │ └── d2_on_update:21 + │ └── projections + │ └── assignment-cast: DECIMAL(10) [as=d_comp_cast:24] + │ └── d_cast:22 + └── projections + ├── CASE WHEN k:12 IS NULL THEN column1:8 ELSE k:12 END [as=upsert_k:25] + ├── CASE WHEN k:12 IS NULL THEN column2:9 ELSE i_new:19 END [as=upsert_i:26] + ├── CASE WHEN k:12 IS NULL THEN d_default:10 ELSE d_cast:22 END [as=upsert_d:27] + ├── CASE WHEN k:12 IS NULL THEN d_default:10 ELSE d2_cast:23 END [as=upsert_d2:28] + └── CASE WHEN k:12 IS NULL THEN d_comp_cast:11 ELSE d_comp_cast:24 END [as=upsert_d_comp:29] + + # Regression test for #67100. Do not prune check columns for UPSERTs even if the # expression does not reference any mutating columns. diff --git a/pkg/sql/opt/optbuilder/update.go b/pkg/sql/opt/optbuilder/update.go index 66927222f36c..5386fa8f2cb8 100644 --- a/pkg/sql/opt/optbuilder/update.go +++ b/pkg/sql/opt/optbuilder/update.go @@ -104,7 +104,7 @@ func (b *Builder) buildUpdate(upd *tree.Update, inScope *scope) (outScope *scope mb.addTargetColsForUpdate(upd.Exprs) // Build each of the SET expressions. - mb.addUpdateCols(upd.Exprs, false /* isUpsert */) + mb.addUpdateCols(upd.Exprs) // Build the final update statement, including any returned expressions. if resultsNeeded(upd.Returning) { @@ -180,7 +180,7 @@ func (mb *mutationBuilder) addTargetColsForUpdate(exprs tree.UpdateExprs) { // Multiple subqueries result in multiple left joins successively wrapping the // input. A final Project operator is built if any single-column or tuple SET // expressions are present. -func (mb *mutationBuilder) addUpdateCols(exprs tree.UpdateExprs, isUpsert bool) { +func (mb *mutationBuilder) addUpdateCols(exprs tree.UpdateExprs) { // SET expressions should reject aggregates, generators, etc. scalarProps := &mb.b.semaCtx.Properties defer scalarProps.Restore(*scalarProps) @@ -221,12 +221,6 @@ func (mb *mutationBuilder) addUpdateCols(exprs tree.UpdateExprs, isUpsert bool) scopeCol := projectionsScope.addColumn(colName, texpr) mb.b.buildScalar(texpr, inScope, projectionsScope, scopeCol, nil) - if isUpsert { - // Type check the input expression against the corresponding table - // column. - checkDatumTypeFitsColumnType(targetCol, scopeCol.typ) - } - // Add the column ID to the list of columns to update. mb.updateColIDs[ord] = scopeCol.id } @@ -246,11 +240,6 @@ func (mb *mutationBuilder) addUpdateCols(exprs tree.UpdateExprs, isUpsert bool) ord := mb.tabID.ColumnOrdinal(mb.targetColList[n]) targetCol := mb.tab.Column(ord) subqueryScope.cols[i].name = scopeColName(targetCol.ColName()) - if isUpsert { - // Type check the input expression against the corresponding table - // column. - checkDatumTypeFitsColumnType(targetCol, subqueryScope.cols[i].typ) - } // Add the column ID to the list of columns to update. mb.updateColIDs[ord] = subqueryScope.cols[i].id @@ -291,20 +280,18 @@ func (mb *mutationBuilder) addUpdateCols(exprs tree.UpdateExprs, isUpsert bool) mb.b.constructProjectForScope(mb.outScope, projectionsScope) mb.outScope = projectionsScope - if !isUpsert { - // Add assignment casts for update columns. - mb.addAssignmentCasts(mb.updateColIDs) - } + // Add assignment casts for update columns. + mb.addAssignmentCasts(mb.updateColIDs) // Add additional columns for computed expressions that may depend on the // updated columns. - mb.addSynthesizedColsForUpdate(isUpsert) + mb.addSynthesizedColsForUpdate() } // addSynthesizedColsForUpdate wraps an Update input expression with a Project // operator containing any computed columns that need to be updated. This // includes write-only mutation columns that are computed. -func (mb *mutationBuilder) addSynthesizedColsForUpdate(isUpsert bool) { +func (mb *mutationBuilder) addSynthesizedColsForUpdate() { // Allow mutation columns to be referenced by other computed mutation // columns (otherwise the scope will raise an error if a mutation column // is referenced). These do not need to be set back to true again because @@ -323,15 +310,8 @@ func (mb *mutationBuilder) addSynthesizedColsForUpdate(isUpsert bool) { true, /* applyOnUpdate */ ) - if isUpsert { - // Possibly round DECIMAL-related columns containing update values. Do - // this before evaluating computed expressions, since those may depend on - // the inserted columns. - mb.roundDecimalValues(mb.updateColIDs, false /* roundComputedCols */) - } else { - // Add assignment casts for default column values. - mb.addAssignmentCasts(mb.updateColIDs) - } + // Add assignment casts for default column values. + mb.addAssignmentCasts(mb.updateColIDs) // Disambiguate names so that references in the computed expression refer to // the correct columns. @@ -340,13 +320,8 @@ func (mb *mutationBuilder) addSynthesizedColsForUpdate(isUpsert bool) { // Add all computed columns in case their values have changed. mb.addSynthesizedComputedCols(mb.updateColIDs, true /* restrict */) - if isUpsert { - // Possibly round DECIMAL-related computed columns. - mb.roundDecimalValues(mb.updateColIDs, true /* roundComputedCols */) - } else { - // Add assignment casts for computed column values. - mb.addAssignmentCasts(mb.updateColIDs) - } + // Add assignment casts for computed column values. + mb.addAssignmentCasts(mb.updateColIDs) } // buildUpdate constructs an Update operator, possibly wrapped by a Project diff --git a/pkg/sql/opt/xform/testdata/external/tpce b/pkg/sql/opt/xform/testdata/external/tpce index 0b17af9fe311..ae2c7a5cb80c 100644 --- a/pkg/sql/opt/xform/testdata/external/tpce +++ b/pkg/sql/opt/xform/testdata/external/tpce @@ -6181,12 +6181,12 @@ upsert trade_history ├── upsert-mapping: │ ├── column1:6 => trade_history.th_t_id:1 │ ├── column2:7 => th_dts:2 - │ └── column3:8 => trade_history.th_st_id:3 + │ └── th_st_id_cast:9 => trade_history.th_st_id:3 ├── input binding: &1 ├── cardinality: [0 - 0] ├── volatile, mutations ├── values - │ ├── columns: column1:6!null column2:7!null column3:8!null + │ ├── columns: column1:6!null column2:7!null th_st_id_cast:9!null │ ├── cardinality: [3 - 3] │ ├── (0, '2020-06-17 22:27:42.148484', 'SBMT') │ ├── (0, '2020-06-20 22:27:42.148484', 'PNDG') @@ -6194,26 +6194,26 @@ upsert trade_history └── f-k-checks ├── f-k-checks-item: trade_history(th_t_id) -> trade(t_id) │ └── anti-join (lookup trade) - │ ├── columns: th_t_id:9!null - │ ├── key columns: [9] = [10] + │ ├── columns: th_t_id:10!null + │ ├── key columns: [10] = [11] │ ├── lookup columns are key │ ├── cardinality: [0 - 3] │ ├── with-scan &1 - │ │ ├── columns: th_t_id:9!null + │ │ ├── columns: th_t_id:10!null │ │ ├── mapping: - │ │ │ └── column1:6 => th_t_id:9 + │ │ │ └── column1:6 => th_t_id:10 │ │ └── cardinality: [3 - 3] │ └── filters (true) └── f-k-checks-item: trade_history(th_st_id) -> status_type(st_id) └── anti-join (lookup status_type) - ├── columns: th_st_id:27!null - ├── key columns: [27] = [28] + ├── columns: th_st_id:28!null + ├── key columns: [28] = [29] ├── lookup columns are key ├── cardinality: [0 - 3] ├── with-scan &1 - │ ├── columns: th_st_id:27!null + │ ├── columns: th_st_id:28!null │ ├── mapping: - │ │ └── column3:8 => th_st_id:27 + │ │ └── th_st_id_cast:9 => th_st_id:28 │ └── cardinality: [3 - 3] └── filters (true) diff --git a/pkg/sql/opt/xform/testdata/external/tpce-no-stats b/pkg/sql/opt/xform/testdata/external/tpce-no-stats index d12c296839eb..bcd1815a077f 100644 --- a/pkg/sql/opt/xform/testdata/external/tpce-no-stats +++ b/pkg/sql/opt/xform/testdata/external/tpce-no-stats @@ -6204,12 +6204,12 @@ upsert trade_history ├── upsert-mapping: │ ├── column1:6 => trade_history.th_t_id:1 │ ├── column2:7 => th_dts:2 - │ └── column3:8 => trade_history.th_st_id:3 + │ └── th_st_id_cast:9 => trade_history.th_st_id:3 ├── input binding: &1 ├── cardinality: [0 - 0] ├── volatile, mutations ├── values - │ ├── columns: column1:6!null column2:7!null column3:8!null + │ ├── columns: column1:6!null column2:7!null th_st_id_cast:9!null │ ├── cardinality: [3 - 3] │ ├── (0, '2020-06-17 22:27:42.148484', 'SBMT') │ ├── (0, '2020-06-20 22:27:42.148484', 'PNDG') @@ -6217,26 +6217,26 @@ upsert trade_history └── f-k-checks ├── f-k-checks-item: trade_history(th_t_id) -> trade(t_id) │ └── anti-join (lookup trade) - │ ├── columns: th_t_id:9!null - │ ├── key columns: [9] = [10] + │ ├── columns: th_t_id:10!null + │ ├── key columns: [10] = [11] │ ├── lookup columns are key │ ├── cardinality: [0 - 3] │ ├── with-scan &1 - │ │ ├── columns: th_t_id:9!null + │ │ ├── columns: th_t_id:10!null │ │ ├── mapping: - │ │ │ └── column1:6 => th_t_id:9 + │ │ │ └── column1:6 => th_t_id:10 │ │ └── cardinality: [3 - 3] │ └── filters (true) └── f-k-checks-item: trade_history(th_st_id) -> status_type(st_id) └── anti-join (lookup status_type) - ├── columns: th_st_id:27!null - ├── key columns: [27] = [28] + ├── columns: th_st_id:28!null + ├── key columns: [28] = [29] ├── lookup columns are key ├── cardinality: [0 - 3] ├── with-scan &1 - │ ├── columns: th_st_id:27!null + │ ├── columns: th_st_id:28!null │ ├── mapping: - │ │ └── column3:8 => th_st_id:27 + │ │ └── th_st_id_cast:9 => th_st_id:28 │ └── cardinality: [3 - 3] └── filters (true) diff --git a/pkg/sql/opt/xform/testdata/external/trading b/pkg/sql/opt/xform/testdata/external/trading index ca67b237c730..da73c646874e 100644 --- a/pkg/sql/opt/xform/testdata/external/trading +++ b/pkg/sql/opt/xform/testdata/external/trading @@ -1267,42 +1267,42 @@ VALUES (1, FALSE, '2020-03-01', 'the-account', 'the-customer', '70F03EB1-4F58-4C upsert transactions ├── columns: ├── arbiter indexes: transactionsprimarykey - ├── canary column: dealerid:17 - ├── fetch columns: dealerid:17 isbuy:18 date:19 accountname:20 customername:21 operationid:22 version:23 + ├── canary column: dealerid:19 + ├── fetch columns: dealerid:19 isbuy:20 date:21 accountname:22 customername:23 operationid:24 version:25 ├── insert-mapping: │ ├── column1:10 => dealerid:1 │ ├── column2:11 => isbuy:2 │ ├── column3:12 => date:3 - │ ├── column4:13 => accountname:4 - │ ├── column5:14 => customername:5 + │ ├── accountname_cast:16 => accountname:4 + │ ├── customername_cast:17 => customername:5 │ ├── column6:15 => operationid:6 - │ └── version_default:16 => version:7 + │ └── version_default:18 => version:7 ├── update-mapping: - │ ├── column4:13 => accountname:4 - │ ├── column5:14 => customername:5 + │ ├── accountname_cast:16 => accountname:4 + │ ├── customername_cast:17 => customername:5 │ └── column6:15 => operationid:6 ├── cardinality: [0 - 0] ├── volatile, mutations └── left-join (cross) - ├── columns: column1:10!null column2:11!null column3:12!null column4:13!null column5:14!null column6:15!null version_default:16 dealerid:17 isbuy:18 date:19 accountname:20 customername:21 operationid:22 version:23 + ├── columns: column1:10!null column2:11!null column3:12!null column6:15!null accountname_cast:16!null customername_cast:17!null version_default:18 dealerid:19 isbuy:20 date:21 accountname:22 customername:23 operationid:24 version:25 ├── cardinality: [1 - 1] ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one) ├── volatile ├── key: () - ├── fd: ()-->(10-23) + ├── fd: ()-->(10-12,15-25) ├── values - │ ├── columns: column1:10!null column2:11!null column3:12!null column4:13!null column5:14!null column6:15!null version_default:16 + │ ├── columns: column1:10!null column2:11!null column3:12!null column6:15!null accountname_cast:16!null customername_cast:17!null version_default:18 │ ├── cardinality: [1 - 1] │ ├── volatile │ ├── key: () - │ ├── fd: ()-->(10-16) - │ └── (1, false, '2020-03-01 00:00:00+00:00', 'the-account', 'the-customer', '70f03eb1-4f58-4c26-b72d-c524a9d537dd', cluster_logical_timestamp()) + │ ├── fd: ()-->(10-12,15-18) + │ └── (1, false, '2020-03-01 00:00:00+00:00', '70f03eb1-4f58-4c26-b72d-c524a9d537dd', 'the-account', 'the-customer', cluster_logical_timestamp()) ├── scan transactions - │ ├── columns: dealerid:17!null isbuy:18!null date:19!null accountname:20!null customername:21!null operationid:22 version:23!null - │ ├── constraint: /17/18/19: [/1/false/'2020-03-01 00:00:00+00:00' - /1/false/'2020-03-01 00:00:00+00:00'] + │ ├── columns: dealerid:19!null isbuy:20!null date:21!null accountname:22!null customername:23!null operationid:24 version:25!null + │ ├── constraint: /19/20/21: [/1/false/'2020-03-01 00:00:00+00:00' - /1/false/'2020-03-01 00:00:00+00:00'] │ ├── cardinality: [0 - 1] │ ├── key: () - │ └── fd: ()-->(17-23) + │ └── fd: ()-->(19-25) └── filters (true) # Insert structured data (c=CardId, q=Quantity, s=SellPrice, b=BuyPrice) @@ -1323,69 +1323,69 @@ FROM updates upsert transactiondetails ├── columns: ├── arbiter indexes: detailsprimarykey - ├── canary column: transactiondetails.dealerid:23 - ├── fetch columns: transactiondetails.dealerid:23 transactiondetails.isbuy:24 transactiondetails.transactiondate:25 transactiondetails.cardid:26 quantity:27 transactiondetails.sellprice:28 transactiondetails.buyprice:29 transactiondetails.version:30 + ├── canary column: transactiondetails.dealerid:21 + ├── fetch columns: transactiondetails.dealerid:21 transactiondetails.isbuy:22 transactiondetails.transactiondate:23 transactiondetails.cardid:24 quantity:25 sellprice:26 buyprice:27 transactiondetails.version:28 ├── insert-mapping: │ ├── "?column?":13 => transactiondetails.dealerid:2 │ ├── bool:14 => transactiondetails.isbuy:3 │ ├── current_timestamp:15 => transactiondetails.transactiondate:4 │ ├── int8:16 => transactiondetails.cardid:5 │ ├── int8:17 => quantity:6 - │ ├── sellprice:21 => transactiondetails.sellprice:7 - │ ├── buyprice:22 => transactiondetails.buyprice:8 + │ ├── numeric:18 => sellprice:7 + │ ├── numeric:19 => buyprice:8 │ └── version_default:20 => transactiondetails.version:9 ├── update-mapping: - │ ├── sellprice:21 => transactiondetails.sellprice:7 - │ └── buyprice:22 => transactiondetails.buyprice:8 + │ ├── numeric:18 => sellprice:7 + │ └── numeric:19 => buyprice:8 ├── input binding: &2 ├── cardinality: [0 - 0] ├── volatile, mutations ├── project - │ ├── columns: upsert_dealerid:33 upsert_isbuy:34 upsert_transactiondate:35 upsert_cardid:36 "?column?":13!null bool:14!null current_timestamp:15!null int8:16!null int8:17!null version_default:20 sellprice:21 buyprice:22 transactiondetails.dealerid:23 transactiondetails.isbuy:24 transactiondetails.transactiondate:25 transactiondetails.cardid:26 quantity:27 transactiondetails.sellprice:28 transactiondetails.buyprice:29 transactiondetails.version:30 + │ ├── columns: upsert_dealerid:31 upsert_isbuy:32 upsert_transactiondate:33 upsert_cardid:34 "?column?":13!null bool:14!null current_timestamp:15!null int8:16!null int8:17!null numeric:18!null numeric:19!null version_default:20 transactiondetails.dealerid:21 transactiondetails.isbuy:22 transactiondetails.transactiondate:23 transactiondetails.cardid:24 quantity:25 sellprice:26 buyprice:27 transactiondetails.version:28 │ ├── cardinality: [1 - 2] │ ├── volatile │ ├── key: (16,17) - │ ├── fd: ()-->(13-15), (16,17)-->(20-30), (23-27)-->(28-30), (23)-->(33), (23,24)-->(34), (23,25)-->(35), (16,23,26)-->(36) + │ ├── fd: ()-->(13-15), (16,17)-->(18-28), (21-25)-->(26-28), (21)-->(31), (21,22)-->(32), (21,23)-->(33), (16,21,24)-->(34) │ ├── left-join (lookup transactiondetails) - │ │ ├── columns: "?column?":13!null bool:14!null current_timestamp:15!null int8:16!null int8:17!null version_default:20 sellprice:21 buyprice:22 transactiondetails.dealerid:23 transactiondetails.isbuy:24 transactiondetails.transactiondate:25 transactiondetails.cardid:26 quantity:27 transactiondetails.sellprice:28 transactiondetails.buyprice:29 transactiondetails.version:30 - │ │ ├── key columns: [13 14 15 16 17] = [23 24 25 26 27] + │ │ ├── columns: "?column?":13!null bool:14!null current_timestamp:15!null int8:16!null int8:17!null numeric:18!null numeric:19!null version_default:20 transactiondetails.dealerid:21 transactiondetails.isbuy:22 transactiondetails.transactiondate:23 transactiondetails.cardid:24 quantity:25 sellprice:26 buyprice:27 transactiondetails.version:28 + │ │ ├── key columns: [13 14 15 16 17] = [21 22 23 24 25] │ │ ├── lookup columns are key │ │ ├── cardinality: [1 - 2] │ │ ├── volatile │ │ ├── key: (16,17) - │ │ ├── fd: ()-->(13-15), (16,17)-->(20-30), (23-27)-->(28-30) + │ │ ├── fd: ()-->(13-15), (16,17)-->(18-28), (21-25)-->(26-28) │ │ ├── ensure-upsert-distinct-on - │ │ │ ├── columns: "?column?":13!null bool:14!null current_timestamp:15!null int8:16!null int8:17!null version_default:20 sellprice:21 buyprice:22 + │ │ │ ├── columns: "?column?":13!null bool:14!null current_timestamp:15!null int8:16!null int8:17!null numeric:18!null numeric:19!null version_default:20 │ │ │ ├── grouping columns: int8:16!null int8:17!null │ │ │ ├── error: "UPSERT or INSERT...ON CONFLICT command cannot affect row a second time" │ │ │ ├── cardinality: [1 - 2] │ │ │ ├── volatile │ │ │ ├── key: (16,17) - │ │ │ ├── fd: ()-->(13-15), (16,17)-->(13-15,20-22) + │ │ │ ├── fd: ()-->(13-15), (16,17)-->(13-15,18-20) │ │ │ ├── project - │ │ │ │ ├── columns: sellprice:21 buyprice:22 version_default:20 "?column?":13!null bool:14!null current_timestamp:15!null int8:16!null int8:17!null + │ │ │ │ ├── columns: version_default:20 "?column?":13!null bool:14!null current_timestamp:15!null int8:16!null int8:17!null numeric:18!null numeric:19!null │ │ │ │ ├── cardinality: [2 - 2] │ │ │ │ ├── volatile │ │ │ │ ├── fd: ()-->(13-15) │ │ │ │ ├── values - │ │ │ │ │ ├── columns: detail_b:60!null detail_c:61!null detail_q:62!null detail_s:63!null + │ │ │ │ │ ├── columns: detail_b:58!null detail_c:59!null detail_q:60!null detail_s:61!null │ │ │ │ │ ├── cardinality: [2 - 2] │ │ │ │ │ ├── ('2.29', '49833', '4', '2.89') │ │ │ │ │ └── ('17.59', '29483', '2', '18.93') │ │ │ │ └── projections - │ │ │ │ ├── crdb_internal.round_decimal_values(detail_s:63::STRING::DECIMAL(10,4), 4) [as=sellprice:21, outer=(63), immutable] - │ │ │ │ ├── crdb_internal.round_decimal_values(detail_b:60::STRING::DECIMAL(10,4), 4) [as=buyprice:22, outer=(60), immutable] │ │ │ │ ├── cluster_logical_timestamp() [as=version_default:20, volatile] │ │ │ │ ├── 1 [as="?column?":13] │ │ │ │ ├── false [as=bool:14] │ │ │ │ ├── '2017-05-10 13:00:00+00:00' [as=current_timestamp:15] - │ │ │ │ ├── detail_c:61::STRING::INT8 [as=int8:16, outer=(61), immutable] - │ │ │ │ └── detail_q:62::STRING::INT8 [as=int8:17, outer=(62), immutable] + │ │ │ │ ├── detail_c:59::STRING::INT8 [as=int8:16, outer=(59), immutable] + │ │ │ │ ├── detail_q:60::STRING::INT8 [as=int8:17, outer=(60), immutable] + │ │ │ │ ├── detail_s:61::STRING::DECIMAL(10,4) [as=numeric:18, outer=(61), immutable] + │ │ │ │ └── detail_b:58::STRING::DECIMAL(10,4) [as=numeric:19, outer=(58), immutable] │ │ │ └── aggregations - │ │ │ ├── first-agg [as=sellprice:21, outer=(21)] - │ │ │ │ └── sellprice:21 - │ │ │ ├── first-agg [as=buyprice:22, outer=(22)] - │ │ │ │ └── buyprice:22 + │ │ │ ├── first-agg [as=numeric:18, outer=(18)] + │ │ │ │ └── numeric:18 + │ │ │ ├── first-agg [as=numeric:19, outer=(19)] + │ │ │ │ └── numeric:19 │ │ │ ├── first-agg [as=version_default:20, outer=(20)] │ │ │ │ └── version_default:20 │ │ │ ├── const-agg [as="?column?":13, outer=(13)] @@ -1396,35 +1396,35 @@ upsert transactiondetails │ │ │ └── current_timestamp:15 │ │ └── filters (true) │ └── projections - │ ├── CASE WHEN transactiondetails.dealerid:23 IS NULL THEN "?column?":13 ELSE transactiondetails.dealerid:23 END [as=upsert_dealerid:33, outer=(13,23)] - │ ├── CASE WHEN transactiondetails.dealerid:23 IS NULL THEN bool:14 ELSE transactiondetails.isbuy:24 END [as=upsert_isbuy:34, outer=(14,23,24)] - │ ├── CASE WHEN transactiondetails.dealerid:23 IS NULL THEN current_timestamp:15 ELSE transactiondetails.transactiondate:25 END [as=upsert_transactiondate:35, outer=(15,23,25)] - │ └── CASE WHEN transactiondetails.dealerid:23 IS NULL THEN int8:16 ELSE transactiondetails.cardid:26 END [as=upsert_cardid:36, outer=(16,23,26)] + │ ├── CASE WHEN transactiondetails.dealerid:21 IS NULL THEN "?column?":13 ELSE transactiondetails.dealerid:21 END [as=upsert_dealerid:31, outer=(13,21)] + │ ├── CASE WHEN transactiondetails.dealerid:21 IS NULL THEN bool:14 ELSE transactiondetails.isbuy:22 END [as=upsert_isbuy:32, outer=(14,21,22)] + │ ├── CASE WHEN transactiondetails.dealerid:21 IS NULL THEN current_timestamp:15 ELSE transactiondetails.transactiondate:23 END [as=upsert_transactiondate:33, outer=(15,21,23)] + │ └── CASE WHEN transactiondetails.dealerid:21 IS NULL THEN int8:16 ELSE transactiondetails.cardid:24 END [as=upsert_cardid:34, outer=(16,21,24)] └── f-k-checks ├── f-k-checks-item: transactiondetails(dealerid,isbuy,transactiondate) -> transactions(dealerid,isbuy,date) │ └── anti-join (lookup transactions) - │ ├── columns: dealerid:39 isbuy:40 transactiondate:41 - │ ├── key columns: [39 40 41] = [42 43 44] + │ ├── columns: dealerid:37 isbuy:38 transactiondate:39 + │ ├── key columns: [37 38 39] = [40 41 42] │ ├── lookup columns are key │ ├── cardinality: [0 - 2] │ ├── with-scan &2 - │ │ ├── columns: dealerid:39 isbuy:40 transactiondate:41 + │ │ ├── columns: dealerid:37 isbuy:38 transactiondate:39 │ │ ├── mapping: - │ │ │ ├── upsert_dealerid:33 => dealerid:39 - │ │ │ ├── upsert_isbuy:34 => isbuy:40 - │ │ │ └── upsert_transactiondate:35 => transactiondate:41 + │ │ │ ├── upsert_dealerid:31 => dealerid:37 + │ │ │ ├── upsert_isbuy:32 => isbuy:38 + │ │ │ └── upsert_transactiondate:33 => transactiondate:39 │ │ └── cardinality: [1 - 2] │ └── filters (true) └── f-k-checks-item: transactiondetails(cardid) -> cards(id) └── anti-join (lookup cards) - ├── columns: cardid:51 - ├── key columns: [51] = [52] + ├── columns: cardid:49 + ├── key columns: [49] = [50] ├── lookup columns are key ├── cardinality: [0 - 2] ├── with-scan &2 - │ ├── columns: cardid:51 + │ ├── columns: cardid:49 │ ├── mapping: - │ │ └── upsert_cardid:36 => cardid:51 + │ │ └── upsert_cardid:34 => cardid:49 │ └── cardinality: [1 - 2] └── filters (true) diff --git a/pkg/sql/opt/xform/testdata/external/trading-mutation b/pkg/sql/opt/xform/testdata/external/trading-mutation index 1a04ce2ec3d7..eb6218e4ceec 100644 --- a/pkg/sql/opt/xform/testdata/external/trading-mutation +++ b/pkg/sql/opt/xform/testdata/external/trading-mutation @@ -1272,44 +1272,44 @@ VALUES (1, FALSE, '2020-03-01', 'the-account', 'the-customer', '70F03EB1-4F58-4C upsert transactions ├── columns: ├── arbiter indexes: transactionsprimarykey - ├── canary column: dealerid:20 - ├── fetch columns: dealerid:20 isbuy:21 date:22 accountname:23 customername:24 operationid:25 version:26 olddate:27 extra:28 + ├── canary column: dealerid:22 + ├── fetch columns: dealerid:22 isbuy:23 date:24 accountname:25 customername:26 operationid:27 version:28 olddate:29 extra:30 ├── insert-mapping: │ ├── column1:12 => dealerid:1 │ ├── column2:13 => isbuy:2 │ ├── column3:14 => date:3 - │ ├── column4:15 => accountname:4 - │ ├── column5:16 => customername:5 + │ ├── accountname_cast:18 => accountname:4 + │ ├── customername_cast:19 => customername:5 │ ├── column6:17 => operationid:6 - │ ├── version_default:18 => version:7 - │ └── olddate_default:19 => olddate:8 + │ ├── version_default:20 => version:7 + │ └── olddate_default:21 => olddate:8 ├── update-mapping: - │ ├── column4:15 => accountname:4 - │ ├── column5:16 => customername:5 + │ ├── accountname_cast:18 => accountname:4 + │ ├── customername_cast:19 => customername:5 │ ├── column6:17 => operationid:6 - │ └── olddate_default:19 => olddate:8 + │ └── olddate_default:21 => olddate:8 ├── cardinality: [0 - 0] ├── volatile, mutations └── left-join (cross) - ├── columns: column1:12!null column2:13!null column3:14!null column4:15!null column5:16!null column6:17!null version_default:18 olddate_default:19!null dealerid:20 isbuy:21 date:22 accountname:23 customername:24 operationid:25 version:26 olddate:27 extra:28 + ├── columns: column1:12!null column2:13!null column3:14!null column6:17!null accountname_cast:18!null customername_cast:19!null version_default:20 olddate_default:21!null dealerid:22 isbuy:23 date:24 accountname:25 customername:26 operationid:27 version:28 olddate:29 extra:30 ├── cardinality: [1 - 1] ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one) ├── volatile ├── key: () - ├── fd: ()-->(12-28) + ├── fd: ()-->(12-14,17-30) ├── values - │ ├── columns: column1:12!null column2:13!null column3:14!null column4:15!null column5:16!null column6:17!null version_default:18 olddate_default:19!null + │ ├── columns: column1:12!null column2:13!null column3:14!null column6:17!null accountname_cast:18!null customername_cast:19!null version_default:20 olddate_default:21!null │ ├── cardinality: [1 - 1] │ ├── volatile │ ├── key: () - │ ├── fd: ()-->(12-19) - │ └── (1, false, '2020-03-01 00:00:00+00:00', 'the-account', 'the-customer', '70f03eb1-4f58-4c26-b72d-c524a9d537dd', cluster_logical_timestamp(), '0001-01-01 00:00:00') + │ ├── fd: ()-->(12-14,17-21) + │ └── (1, false, '2020-03-01 00:00:00+00:00', '70f03eb1-4f58-4c26-b72d-c524a9d537dd', 'the-account', 'the-customer', cluster_logical_timestamp(), '0001-01-01 00:00:00') ├── scan transactions - │ ├── columns: dealerid:20!null isbuy:21!null date:22!null accountname:23!null customername:24!null operationid:25 version:26!null olddate:27 extra:28 - │ ├── constraint: /20/21/22: [/1/false/'2020-03-01 00:00:00+00:00' - /1/false/'2020-03-01 00:00:00+00:00'] + │ ├── columns: dealerid:22!null isbuy:23!null date:24!null accountname:25!null customername:26!null operationid:27 version:28!null olddate:29 extra:30 + │ ├── constraint: /22/23/24: [/1/false/'2020-03-01 00:00:00+00:00' - /1/false/'2020-03-01 00:00:00+00:00'] │ ├── cardinality: [0 - 1] │ ├── key: () - │ └── fd: ()-->(20-28) + │ └── fd: ()-->(22-30) └── filters (true) # Insert structured data (c=CardId, q=Quantity, s=SellPrice, b=BuyPrice) @@ -1330,76 +1330,76 @@ FROM updates upsert transactiondetails ├── columns: ├── arbiter indexes: detailsprimarykey - ├── canary column: transactiondetails.dealerid:27 - ├── fetch columns: transactiondetails.dealerid:27 transactiondetails.isbuy:28 transactiondetails.transactiondate:29 transactiondetails.cardid:30 quantity:31 transactiondetails.sellprice:32 transactiondetails.buyprice:33 transactiondetails.version:34 discount:35 transactiondetails.extra:36 + ├── canary column: transactiondetails.dealerid:25 + ├── fetch columns: transactiondetails.dealerid:25 transactiondetails.isbuy:26 transactiondetails.transactiondate:27 transactiondetails.cardid:28 quantity:29 sellprice:30 buyprice:31 transactiondetails.version:32 discount:33 transactiondetails.extra:34 ├── insert-mapping: │ ├── "?column?":15 => transactiondetails.dealerid:2 │ ├── bool:16 => transactiondetails.isbuy:3 │ ├── current_timestamp:17 => transactiondetails.transactiondate:4 │ ├── int8:18 => transactiondetails.cardid:5 │ ├── int8:19 => quantity:6 - │ ├── sellprice:24 => transactiondetails.sellprice:7 - │ ├── buyprice:25 => transactiondetails.buyprice:8 + │ ├── numeric:20 => sellprice:7 + │ ├── numeric:21 => buyprice:8 │ ├── version_default:22 => transactiondetails.version:9 - │ └── discount_default:26 => discount:10 + │ └── discount_cast:24 => discount:10 ├── update-mapping: - │ ├── sellprice:24 => transactiondetails.sellprice:7 - │ ├── buyprice:25 => transactiondetails.buyprice:8 - │ └── discount_default:26 => discount:10 + │ ├── numeric:20 => sellprice:7 + │ ├── numeric:21 => buyprice:8 + │ └── discount_cast:24 => discount:10 ├── input binding: &2 ├── cardinality: [0 - 0] ├── volatile, mutations ├── project - │ ├── columns: upsert_dealerid:39 upsert_isbuy:40 upsert_transactiondate:41 upsert_cardid:42 "?column?":15!null bool:16!null current_timestamp:17!null int8:18!null int8:19!null version_default:22 sellprice:24 buyprice:25 discount_default:26!null transactiondetails.dealerid:27 transactiondetails.isbuy:28 transactiondetails.transactiondate:29 transactiondetails.cardid:30 quantity:31 transactiondetails.sellprice:32 transactiondetails.buyprice:33 transactiondetails.version:34 discount:35 transactiondetails.extra:36 + │ ├── columns: upsert_dealerid:37 upsert_isbuy:38 upsert_transactiondate:39 upsert_cardid:40 "?column?":15!null bool:16!null current_timestamp:17!null int8:18!null int8:19!null numeric:20!null numeric:21!null version_default:22 discount_cast:24!null transactiondetails.dealerid:25 transactiondetails.isbuy:26 transactiondetails.transactiondate:27 transactiondetails.cardid:28 quantity:29 sellprice:30 buyprice:31 transactiondetails.version:32 discount:33 transactiondetails.extra:34 │ ├── cardinality: [1 - 2] │ ├── volatile │ ├── key: (18,19) - │ ├── fd: ()-->(15-17,26), (18,19)-->(22,24,25,27-36), (27-31)-->(32-36), (27)-->(39), (27,28)-->(40), (27,29)-->(41), (18,27,30)-->(42) + │ ├── fd: ()-->(15-17,24), (18,19)-->(20-22,25-34), (25-29)-->(30-34), (25)-->(37), (25,26)-->(38), (25,27)-->(39), (18,25,28)-->(40) │ ├── left-join (lookup transactiondetails) - │ │ ├── columns: "?column?":15!null bool:16!null current_timestamp:17!null int8:18!null int8:19!null version_default:22 sellprice:24 buyprice:25 discount_default:26!null transactiondetails.dealerid:27 transactiondetails.isbuy:28 transactiondetails.transactiondate:29 transactiondetails.cardid:30 quantity:31 transactiondetails.sellprice:32 transactiondetails.buyprice:33 transactiondetails.version:34 discount:35 transactiondetails.extra:36 - │ │ ├── key columns: [15 16 17 18 19] = [27 28 29 30 31] + │ │ ├── columns: "?column?":15!null bool:16!null current_timestamp:17!null int8:18!null int8:19!null numeric:20!null numeric:21!null version_default:22 discount_cast:24!null transactiondetails.dealerid:25 transactiondetails.isbuy:26 transactiondetails.transactiondate:27 transactiondetails.cardid:28 quantity:29 sellprice:30 buyprice:31 transactiondetails.version:32 discount:33 transactiondetails.extra:34 + │ │ ├── key columns: [15 16 17 18 19] = [25 26 27 28 29] │ │ ├── lookup columns are key │ │ ├── cardinality: [1 - 2] │ │ ├── volatile │ │ ├── key: (18,19) - │ │ ├── fd: ()-->(15-17,26), (18,19)-->(22,24,25,27-36), (27-31)-->(32-36) + │ │ ├── fd: ()-->(15-17,24), (18,19)-->(20-22,25-34), (25-29)-->(30-34) │ │ ├── ensure-upsert-distinct-on - │ │ │ ├── columns: "?column?":15!null bool:16!null current_timestamp:17!null int8:18!null int8:19!null version_default:22 sellprice:24 buyprice:25 discount_default:26!null + │ │ │ ├── columns: "?column?":15!null bool:16!null current_timestamp:17!null int8:18!null int8:19!null numeric:20!null numeric:21!null version_default:22 discount_cast:24!null │ │ │ ├── grouping columns: int8:18!null int8:19!null │ │ │ ├── error: "UPSERT or INSERT...ON CONFLICT command cannot affect row a second time" │ │ │ ├── cardinality: [1 - 2] │ │ │ ├── volatile │ │ │ ├── key: (18,19) - │ │ │ ├── fd: ()-->(15-17,26), (18,19)-->(15-17,22,24-26) + │ │ │ ├── fd: ()-->(15-17,24), (18,19)-->(15-17,20-22,24) │ │ │ ├── project - │ │ │ │ ├── columns: sellprice:24 buyprice:25 discount_default:26!null version_default:22 "?column?":15!null bool:16!null current_timestamp:17!null int8:18!null int8:19!null + │ │ │ │ ├── columns: discount_cast:24!null version_default:22 "?column?":15!null bool:16!null current_timestamp:17!null int8:18!null int8:19!null numeric:20!null numeric:21!null │ │ │ │ ├── cardinality: [2 - 2] │ │ │ │ ├── volatile - │ │ │ │ ├── fd: ()-->(15-17,26) + │ │ │ │ ├── fd: ()-->(15-17,24) │ │ │ │ ├── values - │ │ │ │ │ ├── columns: detail_b:68!null detail_c:69!null detail_q:70!null detail_s:71!null + │ │ │ │ │ ├── columns: detail_b:66!null detail_c:67!null detail_q:68!null detail_s:69!null │ │ │ │ │ ├── cardinality: [2 - 2] │ │ │ │ │ ├── ('2.29', '49833', '4', '2.89') │ │ │ │ │ └── ('17.59', '29483', '2', '18.93') │ │ │ │ └── projections - │ │ │ │ ├── crdb_internal.round_decimal_values(detail_s:71::STRING::DECIMAL(10,4), 4) [as=sellprice:24, outer=(71), immutable] - │ │ │ │ ├── crdb_internal.round_decimal_values(detail_b:68::STRING::DECIMAL(10,4), 4) [as=buyprice:25, outer=(68), immutable] - │ │ │ │ ├── 0.0000 [as=discount_default:26] + │ │ │ │ ├── 0.0000 [as=discount_cast:24] │ │ │ │ ├── cluster_logical_timestamp() [as=version_default:22, volatile] │ │ │ │ ├── 1 [as="?column?":15] │ │ │ │ ├── false [as=bool:16] │ │ │ │ ├── '2017-05-10 13:00:00+00:00' [as=current_timestamp:17] - │ │ │ │ ├── detail_c:69::STRING::INT8 [as=int8:18, outer=(69), immutable] - │ │ │ │ └── detail_q:70::STRING::INT8 [as=int8:19, outer=(70), immutable] + │ │ │ │ ├── detail_c:67::STRING::INT8 [as=int8:18, outer=(67), immutable] + │ │ │ │ ├── detail_q:68::STRING::INT8 [as=int8:19, outer=(68), immutable] + │ │ │ │ ├── detail_s:69::STRING::DECIMAL(10,4) [as=numeric:20, outer=(69), immutable] + │ │ │ │ └── detail_b:66::STRING::DECIMAL(10,4) [as=numeric:21, outer=(66), immutable] │ │ │ └── aggregations - │ │ │ ├── first-agg [as=sellprice:24, outer=(24)] - │ │ │ │ └── sellprice:24 - │ │ │ ├── first-agg [as=buyprice:25, outer=(25)] - │ │ │ │ └── buyprice:25 + │ │ │ ├── first-agg [as=numeric:20, outer=(20)] + │ │ │ │ └── numeric:20 + │ │ │ ├── first-agg [as=numeric:21, outer=(21)] + │ │ │ │ └── numeric:21 │ │ │ ├── first-agg [as=version_default:22, outer=(22)] │ │ │ │ └── version_default:22 - │ │ │ ├── first-agg [as=discount_default:26, outer=(26)] - │ │ │ │ └── discount_default:26 + │ │ │ ├── first-agg [as=discount_cast:24, outer=(24)] + │ │ │ │ └── discount_cast:24 │ │ │ ├── const-agg [as="?column?":15, outer=(15)] │ │ │ │ └── "?column?":15 │ │ │ ├── const-agg [as=bool:16, outer=(16)] @@ -1408,35 +1408,35 @@ upsert transactiondetails │ │ │ └── current_timestamp:17 │ │ └── filters (true) │ └── projections - │ ├── CASE WHEN transactiondetails.dealerid:27 IS NULL THEN "?column?":15 ELSE transactiondetails.dealerid:27 END [as=upsert_dealerid:39, outer=(15,27)] - │ ├── CASE WHEN transactiondetails.dealerid:27 IS NULL THEN bool:16 ELSE transactiondetails.isbuy:28 END [as=upsert_isbuy:40, outer=(16,27,28)] - │ ├── CASE WHEN transactiondetails.dealerid:27 IS NULL THEN current_timestamp:17 ELSE transactiondetails.transactiondate:29 END [as=upsert_transactiondate:41, outer=(17,27,29)] - │ └── CASE WHEN transactiondetails.dealerid:27 IS NULL THEN int8:18 ELSE transactiondetails.cardid:30 END [as=upsert_cardid:42, outer=(18,27,30)] + │ ├── CASE WHEN transactiondetails.dealerid:25 IS NULL THEN "?column?":15 ELSE transactiondetails.dealerid:25 END [as=upsert_dealerid:37, outer=(15,25)] + │ ├── CASE WHEN transactiondetails.dealerid:25 IS NULL THEN bool:16 ELSE transactiondetails.isbuy:26 END [as=upsert_isbuy:38, outer=(16,25,26)] + │ ├── CASE WHEN transactiondetails.dealerid:25 IS NULL THEN current_timestamp:17 ELSE transactiondetails.transactiondate:27 END [as=upsert_transactiondate:39, outer=(17,25,27)] + │ └── CASE WHEN transactiondetails.dealerid:25 IS NULL THEN int8:18 ELSE transactiondetails.cardid:28 END [as=upsert_cardid:40, outer=(18,25,28)] └── f-k-checks ├── f-k-checks-item: transactiondetails(dealerid,isbuy,transactiondate) -> transactions(dealerid,isbuy,date) │ └── anti-join (lookup transactions) - │ ├── columns: dealerid:45 isbuy:46 transactiondate:47 - │ ├── key columns: [45 46 47] = [48 49 50] + │ ├── columns: dealerid:43 isbuy:44 transactiondate:45 + │ ├── key columns: [43 44 45] = [46 47 48] │ ├── lookup columns are key │ ├── cardinality: [0 - 2] │ ├── with-scan &2 - │ │ ├── columns: dealerid:45 isbuy:46 transactiondate:47 + │ │ ├── columns: dealerid:43 isbuy:44 transactiondate:45 │ │ ├── mapping: - │ │ │ ├── upsert_dealerid:39 => dealerid:45 - │ │ │ ├── upsert_isbuy:40 => isbuy:46 - │ │ │ └── upsert_transactiondate:41 => transactiondate:47 + │ │ │ ├── upsert_dealerid:37 => dealerid:43 + │ │ │ ├── upsert_isbuy:38 => isbuy:44 + │ │ │ └── upsert_transactiondate:39 => transactiondate:45 │ │ └── cardinality: [1 - 2] │ └── filters (true) └── f-k-checks-item: transactiondetails(cardid) -> cards(id) └── anti-join (lookup cards) - ├── columns: cardid:59 - ├── key columns: [59] = [60] + ├── columns: cardid:57 + ├── key columns: [57] = [58] ├── lookup columns are key ├── cardinality: [0 - 2] ├── with-scan &2 - │ ├── columns: cardid:59 + │ ├── columns: cardid:57 │ ├── mapping: - │ │ └── upsert_cardid:42 => cardid:59 + │ │ └── upsert_cardid:40 => cardid:57 │ └── cardinality: [1 - 2] └── filters (true)