diff --git a/pkg/ccl/logictestccl/testdata/logic_test/regional_by_row b/pkg/ccl/logictestccl/testdata/logic_test/regional_by_row index 9ef7a550f5a7..48d610bc11c1 100644 --- a/pkg/ccl/logictestccl/testdata/logic_test/regional_by_row +++ b/pkg/ccl/logictestccl/testdata/logic_test/regional_by_row @@ -299,7 +299,7 @@ vectorized: true │ │ │ └── • lookup join (semi) │ │ table: regional_by_row_table@primary -│ │ equality: (lookup_join_const_col_@24, column1) = (crdb_region,pk) +│ │ equality: (lookup_join_const_col_@22, column1) = (crdb_region,pk) │ │ equality cols are key │ │ pred: column15 != crdb_region │ │ @@ -317,7 +317,7 @@ vectorized: true │ │ │ └── • lookup join (semi) │ │ table: regional_by_row_table@regional_by_row_table_b_key -│ │ equality: (lookup_join_const_col_@36, column4) = (crdb_region,b) +│ │ equality: (lookup_join_const_col_@37, column4) = (crdb_region,b) │ │ equality cols are key │ │ pred: (column1 != pk) OR (column15 != crdb_region) │ │ @@ -335,7 +335,7 @@ vectorized: true │ └── • lookup join (semi) │ table: regional_by_row_table@new_idx - │ equality: (lookup_join_const_col_@49, column3, column4) = (crdb_region,a,b) + │ equality: (lookup_join_const_col_@52, column3, column4) = (crdb_region,a,b) │ equality cols are key │ pred: (column1 != pk) OR (column15 != crdb_region) │ @@ -385,7 +385,7 @@ vectorized: true │ │ │ └── • lookup join (semi) │ │ table: regional_by_row_table@primary -│ │ equality: (lookup_join_const_col_@35, upsert_pk) = (crdb_region,pk) +│ │ equality: (lookup_join_const_col_@33, upsert_pk) = (crdb_region,pk) │ │ equality cols are key │ │ pred: column1 != crdb_region │ │ @@ -403,7 +403,7 @@ vectorized: true │ │ │ └── • lookup join (semi) │ │ table: regional_by_row_table@regional_by_row_table_b_key -│ │ equality: (lookup_join_const_col_@47, column5) = (crdb_region,b) +│ │ equality: (lookup_join_const_col_@48, column5) = (crdb_region,b) │ │ equality cols are key │ │ pred: (upsert_pk != pk) OR (column1 != crdb_region) │ │ @@ -421,7 +421,7 @@ vectorized: true │ └── • lookup join (semi) │ table: regional_by_row_table@new_idx - │ equality: (lookup_join_const_col_@60, column4, column5) = (crdb_region,a,b) + │ equality: (lookup_join_const_col_@63, column4, column5) = (crdb_region,a,b) │ equality cols are key │ pred: (upsert_pk != pk) OR (column1 != crdb_region) │ diff --git a/pkg/sql/logictest/testdata/logic_test/unique b/pkg/sql/logictest/testdata/logic_test/unique index 67de2a6d8781..acf32539420c 100644 --- a/pkg/sql/logictest/testdata/logic_test/unique +++ b/pkg/sql/logictest/testdata/logic_test/unique @@ -251,6 +251,49 @@ us-west foo 1 1 eu-west bar 2 2 +# Insert some non-null data into a table with a partial unique without index +# constraint. +statement ok +INSERT INTO uniq_partial VALUES (1, 1), (1, -1), (2, 2) + +# Partial unique constraint violation. +statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_a"\nDETAIL: Key \(a\)=\(1\) already exists\. +INSERT INTO uniq_partial VALUES (1, 3) + +# No partial unique constraint violation because b <= 0. +statement ok +INSERT INTO uniq_partial VALUES (1, -3) + +# Attempt to insert conflicting keys twice in the same statement. +statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_a"\nDETAIL: Key \(a\)=\(3\) already exists\. +INSERT INTO uniq_partial VALUES (3, 3), (3, 4) + +# Attempt to insert one conflicting key and one non-conflicting key in the same +# statement. +statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_a"\nDETAIL: Key \(a\)=\(1\) already exists\. +INSERT INTO uniq_partial VALUES (1, 3), (3, 3) + +# Insert some rows with NULL keys. +statement ok +INSERT INTO uniq_partial VALUES (NULL, 5), (5, 5), (NULL, 5) + +# Insert with non-constant input. +statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_a"\nDETAIL: Key \(a\)=\(1\) already exists\. +INSERT INTO uniq_partial SELECT w, x FROM other + +query II colnames,rowsort +SELECT * FROM uniq_partial +---- +a b +1 1 +1 -1 +1 -3 +2 2 +5 5 +NULL 5 +NULL 5 + + # -- Tests with UPDATE -- subtest Update diff --git a/pkg/sql/opt/exec/execbuilder/testdata/unique b/pkg/sql/opt/exec/execbuilder/testdata/unique index 54c4f639c181..67bcf33fd5e2 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/unique +++ b/pkg/sql/opt/exec/execbuilder/testdata/unique @@ -71,6 +71,41 @@ CREATE TABLE uniq_fk_child ( FAMILY (c) ) +statement ok +CREATE TABLE uniq_partial ( + k INT PRIMARY KEY, + a INT, + b INT, + UNIQUE WITHOUT INDEX (a) WHERE b > 0, + UNIQUE WITHOUT INDEX (b) WHERE b > 0, + FAMILY (k), + FAMILY (a), + FAMILY (b) +) + +statement ok +CREATE TABLE uniq_partial_overlaps_pk ( + k INT PRIMARY KEY, + a INT, + b INT, + UNIQUE WITHOUT INDEX (k) WHERE b > 0, + UNIQUE WITHOUT INDEX (k, a) WHERE b > 0, + FAMILY (k), + FAMILY (a), + FAMILY (b) +) + +statement ok +CREATE TABLE uniq_partial_hidden_pk ( + a INT, + b INT, + c INT, + UNIQUE WITHOUT INDEX (b) WHERE c > 0, + FAMILY (a), + FAMILY (b), + FAMILY (c) +) + statement ok CREATE TYPE region AS ENUM ('us-east', 'us-west', 'eu-west') @@ -92,6 +127,19 @@ CREATE TABLE uniq_enum ( FAMILY (j) ) +statement ok +CREATE TABLE uniq_partial_enum ( + r region DEFAULT CASE (random()*3)::int WHEN 0 THEN 'us-east' WHEN 1 THEN 'us-west' ELSE 'eu-west' END, + i INT, + s STRING, + PRIMARY KEY (r, i), + UNIQUE WITHOUT INDEX (i) WHERE s IN ('foo', 'bar', 'baz'), + INDEX (r, i) WHERE s IN ('foo', 'bar', 'baz'), + FAMILY (r), + FAMILY (s), + FAMILY (i) +) + statement ok CREATE TABLE other (k INT, v INT, w INT NOT NULL, x INT, y INT) @@ -148,7 +196,7 @@ vectorized: true └── • scan buffer label: buffer 1 -# No need to plan checks for w since it's aways null. +# No need to plan checks for w since it's always null. # We still plan checks for x,y since neither column is null in all rows. query T EXPLAIN INSERT INTO uniq VALUES (4, 4, NULL, NULL, 1), (5, 5, NULL, 2, NULL) @@ -263,7 +311,7 @@ vectorized: true │ │ columns: () │ │ │ └── • hash join (right semi) -│ │ columns: (column3, column1) +│ │ columns: (column1, column2, column3, column4, column5) │ │ estimated row count: 0 (missing stats) │ │ equality: (w) = (column3) │ │ right cols are key @@ -275,13 +323,10 @@ vectorized: true │ │ table: uniq@primary │ │ spans: FULL SCAN │ │ -│ └── • project -│ │ columns: (column3, column1) -│ │ estimated row count: 0 (missing stats) -│ │ -│ └── • scan buffer -│ columns: (column1, column2, column3, column4, column5) -│ label: buffer 1 +│ └── • scan buffer +│ columns: (column1, column2, column3, column4, column5) +│ estimated row count: 0 (missing stats) +│ label: buffer 1 │ └── • constraint-check │ @@ -289,7 +334,7 @@ vectorized: true │ columns: () │ └── • hash join (right semi) - │ columns: (column4, column5, column1) + │ columns: (column1, column2, column3, column4, column5) │ estimated row count: 0 (missing stats) │ equality: (x, y) = (column4, column5) │ right cols are key @@ -301,13 +346,10 @@ vectorized: true │ table: uniq@primary │ spans: FULL SCAN │ - └── • project - │ columns: (column4, column5, column1) - │ estimated row count: 0 (missing stats) - │ - └── • scan buffer - columns: (column1, column2, column3, column4, column5) - label: buffer 1 + └── • scan buffer + columns: (column1, column2, column3, column4, column5) + estimated row count: 0 (missing stats) + label: buffer 1 # Insert with non-constant input. query T @@ -660,29 +702,29 @@ vectorized: true │ │ columns: () │ │ │ └── • project -│ │ columns: (column3, column1) +│ │ columns: (column1, column2, column3, column4) │ │ estimated row count: 1 (missing stats) │ │ │ └── • lookup join (semi) -│ │ columns: ("lookup_join_const_col_@14", column3, column1) +│ │ columns: ("lookup_join_const_col_@12", column1, column2, column3, column4) │ │ table: uniq_enum@primary -│ │ equality: (lookup_join_const_col_@14, column3) = (r,i) +│ │ equality: (lookup_join_const_col_@12, column3) = (r,i) │ │ equality cols are key │ │ pred: column1 != r │ │ │ └── • cross join (inner) -│ │ columns: ("lookup_join_const_col_@14", column3, column1) +│ │ columns: ("lookup_join_const_col_@12", column1, column2, column3, column4) │ │ estimated row count: 6 │ │ │ ├── • values -│ │ columns: ("lookup_join_const_col_@14") +│ │ columns: ("lookup_join_const_col_@12") │ │ size: 1 column, 3 rows │ │ row 0, expr 0: 'us-east' │ │ row 1, expr 0: 'us-west' │ │ row 2, expr 0: 'eu-west' │ │ │ └── • project -│ │ columns: (column3, column1) +│ │ columns: (column1, column2, column3, column4) │ │ estimated row count: 2 │ │ │ └── • scan buffer @@ -695,29 +737,29 @@ vectorized: true │ columns: () │ └── • project - │ columns: (column2, column4, column1, column3) + │ columns: (column1, column2, column3, column4) │ estimated row count: 1 (missing stats) │ └── • lookup join (semi) - │ columns: ("lookup_join_const_col_@24", column2, column4, column1, column3) + │ columns: ("lookup_join_const_col_@22", column1, column2, column3, column4) │ table: uniq_enum@uniq_enum_r_s_j_key - │ equality: (lookup_join_const_col_@24, column2, column4) = (r,s,j) + │ equality: (lookup_join_const_col_@22, column2, column4) = (r,s,j) │ equality cols are key │ pred: (column1 != r) OR (column3 != i) │ └── • cross join (inner) - │ columns: ("lookup_join_const_col_@24", column2, column4, column1, column3) + │ columns: ("lookup_join_const_col_@22", column1, column2, column3, column4) │ estimated row count: 6 │ ├── • values - │ columns: ("lookup_join_const_col_@24") + │ columns: ("lookup_join_const_col_@22") │ size: 1 column, 3 rows │ row 0, expr 0: 'us-east' │ row 1, expr 0: 'us-west' │ row 2, expr 0: 'eu-west' │ └── • project - │ columns: (column2, column4, column1, column3) + │ columns: (column1, column2, column3, column4) │ estimated row count: 2 │ └── • scan buffer @@ -775,29 +817,29 @@ vectorized: true │ columns: () │ └── • project - │ columns: (column2, column9) + │ columns: (column9, column1, column2, column10) │ estimated row count: 1 (missing stats) │ └── • lookup join (semi) - │ columns: ("lookup_join_const_col_@14", column2, column9) + │ columns: ("lookup_join_const_col_@12", column9, column1, column2, column10) │ table: uniq_enum@primary - │ equality: (lookup_join_const_col_@14, column2) = (r,i) + │ equality: (lookup_join_const_col_@12, column2) = (r,i) │ equality cols are key │ pred: column9 != r │ └── • cross join (inner) - │ columns: ("lookup_join_const_col_@14", column2, column9) + │ columns: ("lookup_join_const_col_@12", column9, column1, column2, column10) │ estimated row count: 6 │ ├── • values - │ columns: ("lookup_join_const_col_@14") + │ columns: ("lookup_join_const_col_@12") │ size: 1 column, 3 rows │ row 0, expr 0: 'us-east' │ row 1, expr 0: 'us-west' │ row 2, expr 0: 'eu-west' │ └── • project - │ columns: (column2, column9) + │ columns: (column9, column1, column2, column10) │ estimated row count: 2 │ └── • scan buffer @@ -891,29 +933,29 @@ vectorized: true │ │ columns: () │ │ │ └── • project -│ │ columns: (column3, column1) +│ │ columns: (column1, column2, column3, column4) │ │ estimated row count: 0 (missing stats) │ │ │ └── • lookup join (semi) -│ │ columns: ("lookup_join_const_col_@38", column3, column1) +│ │ columns: ("lookup_join_const_col_@36", column1, column2, column3, column4) │ │ table: uniq_enum@primary -│ │ equality: (lookup_join_const_col_@38, column3) = (r,i) +│ │ equality: (lookup_join_const_col_@36, column3) = (r,i) │ │ equality cols are key │ │ pred: column1 != r │ │ │ └── • cross join (inner) -│ │ columns: ("lookup_join_const_col_@38", column3, column1) +│ │ columns: ("lookup_join_const_col_@36", column1, column2, column3, column4) │ │ estimated row count: 0 (missing stats) │ │ │ ├── • values -│ │ columns: ("lookup_join_const_col_@38") +│ │ columns: ("lookup_join_const_col_@36") │ │ size: 1 column, 3 rows │ │ row 0, expr 0: 'us-east' │ │ row 1, expr 0: 'us-west' │ │ row 2, expr 0: 'eu-west' │ │ │ └── • project -│ │ columns: (column3, column1) +│ │ columns: (column1, column2, column3, column4) │ │ estimated row count: 0 (missing stats) │ │ │ └── • scan buffer @@ -926,35 +968,337 @@ vectorized: true │ columns: () │ └── • project - │ columns: (column2, column4, column1, column3) + │ columns: (column1, column2, column3, column4) │ estimated row count: 0 (missing stats) │ └── • lookup join (semi) - │ columns: ("lookup_join_const_col_@48", column2, column4, column1, column3) + │ columns: ("lookup_join_const_col_@46", column1, column2, column3, column4) │ table: uniq_enum@uniq_enum_r_s_j_key - │ equality: (lookup_join_const_col_@48, column2, column4) = (r,s,j) + │ equality: (lookup_join_const_col_@46, column2, column4) = (r,s,j) │ equality cols are key │ pred: (column1 != r) OR (column3 != i) │ └── • cross join (inner) - │ columns: ("lookup_join_const_col_@48", column2, column4, column1, column3) + │ columns: ("lookup_join_const_col_@46", column1, column2, column3, column4) │ estimated row count: 0 (missing stats) │ ├── • values - │ columns: ("lookup_join_const_col_@48") + │ columns: ("lookup_join_const_col_@46") │ size: 1 column, 3 rows │ row 0, expr 0: 'us-east' │ row 1, expr 0: 'us-west' │ row 2, expr 0: 'eu-west' │ └── • project - │ columns: (column2, column4, column1, column3) + │ columns: (column1, column2, column3, column4) │ estimated row count: 0 (missing stats) │ └── • scan buffer columns: (column1, column2, column3, column4, check1) label: buffer 1 +# None of the inserted values have nulls. +query T +EXPLAIN INSERT INTO uniq_partial VALUES (1, 1, 1), (2, 2, 2) +---- +distribution: local +vectorized: true +· +• root +│ +├── • insert +│ │ into: uniq_partial(k, a, b) +│ │ +│ └── • buffer +│ │ label: buffer 1 +│ │ +│ └── • values +│ size: 3 columns, 2 rows +│ +├── • constraint-check +│ │ +│ └── • error if rows +│ │ +│ └── • hash join (right semi) +│ │ equality: (a) = (column2) +│ │ pred: column1 != k +│ │ +│ ├── • filter +│ │ │ filter: b > 0 +│ │ │ +│ │ └── • scan +│ │ missing stats +│ │ table: uniq_partial@primary +│ │ spans: FULL SCAN +│ │ +│ └── • filter +│ │ filter: column3 > 0 +│ │ +│ └── • scan buffer +│ label: buffer 1 +│ +└── • constraint-check + │ + └── • error if rows + │ + └── • hash join (right semi) + │ equality: (b) = (column3) + │ pred: column1 != k + │ + ├── • filter + │ │ filter: b > 0 + │ │ + │ └── • scan + │ missing stats + │ table: uniq_partial@primary + │ spans: FULL SCAN + │ + └── • filter + │ filter: column3 > 0 + │ + └── • scan buffer + label: buffer 1 + +# No need to plan checks for a since it's always null. +query T +EXPLAIN INSERT INTO uniq_partial VALUES (1, NULL, 1), (2, NULL, 2) +---- +distribution: local +vectorized: true +· +• root +│ +├── • insert +│ │ into: uniq_partial(k, a, b) +│ │ +│ └── • buffer +│ │ label: buffer 1 +│ │ +│ └── • values +│ size: 3 columns, 2 rows +│ +└── • constraint-check + │ + └── • error if rows + │ + └── • hash join (right semi) + │ equality: (b) = (column3) + │ pred: column1 != k + │ + ├── • filter + │ │ filter: b > 0 + │ │ + │ └── • scan + │ missing stats + │ table: uniq_partial@primary + │ spans: FULL SCAN + │ + └── • filter + │ filter: column3 > 0 + │ + └── • scan buffer + label: buffer 1 + +# Insert with non-constant input. +query T +EXPLAIN INSERT INTO uniq_partial SELECT k, v, w FROM other +---- +distribution: local +vectorized: true +· +• root +│ +├── • insert +│ │ into: uniq_partial(k, a, b) +│ │ +│ └── • buffer +│ │ label: buffer 1 +│ │ +│ └── • scan +│ missing stats +│ table: other@primary +│ spans: FULL SCAN +│ +├── • constraint-check +│ │ +│ └── • error if rows +│ │ +│ └── • hash join (right semi) +│ │ equality: (a) = (v) +│ │ pred: k != k +│ │ +│ ├── • filter +│ │ │ filter: b > 0 +│ │ │ +│ │ └── • scan +│ │ missing stats +│ │ table: uniq_partial@primary +│ │ spans: FULL SCAN +│ │ +│ └── • filter +│ │ filter: w > 0 +│ │ +│ └── • scan buffer +│ label: buffer 1 +│ +└── • constraint-check + │ + └── • error if rows + │ + └── • hash join (right semi) + │ equality: (b) = (w) + │ pred: k != k + │ + ├── • filter + │ │ filter: b > 0 + │ │ + │ └── • scan + │ missing stats + │ table: uniq_partial@primary + │ spans: FULL SCAN + │ + └── • filter + │ filter: w > 0 + │ + └── • scan buffer + label: buffer 1 + +# No need to build uniqueness checks when the primary key columns are a subset +# of the partial unique constraint columns. +query T +EXPLAIN INSERT INTO uniq_partial_overlaps_pk VALUES (1, 1, 1), (2, 2, 2) +---- +distribution: local +vectorized: true +· +• insert fast path + into: uniq_partial_overlaps_pk(k, a, b) + auto commit + size: 3 columns, 2 rows + +# Insert with non-constant input. +# Add inequality filters for the hidden primary key column. +query T +EXPLAIN INSERT INTO uniq_partial_hidden_pk SELECT k, v, x FROM other +---- +distribution: local +vectorized: true +· +• root +│ +├── • insert +│ │ into: uniq_partial_hidden_pk(a, b, c, rowid) +│ │ +│ └── • buffer +│ │ label: buffer 1 +│ │ +│ └── • render +│ │ +│ └── • scan +│ missing stats +│ table: other@primary +│ spans: FULL SCAN +│ +└── • constraint-check + │ + └── • error if rows + │ + └── • hash join (semi) + │ equality: (v) = (b) + │ pred: column15 != rowid + │ + ├── • filter + │ │ filter: x > 0 + │ │ + │ └── • scan buffer + │ label: buffer 1 + │ + └── • filter + │ filter: c > 0 + │ + └── • scan + missing stats + table: uniq_partial_hidden_pk@primary + spans: FULL SCAN + +# Test that we use the partial index when available for the insert checks. +query T +EXPLAIN (VERBOSE) INSERT INTO uniq_partial_enum VALUES ('us-west', 1, 'foo'), ('us-east', 2, 'bar') +---- +distribution: local +vectorized: true +· +• root +│ columns: () +│ +├── • insert +│ │ columns: () +│ │ estimated row count: 0 (missing stats) +│ │ into: uniq_partial_enum(r, i, s) +│ │ +│ └── • buffer +│ │ columns: (column1, column2, column3, check1, partial_index_put1) +│ │ label: buffer 1 +│ │ +│ └── • render +│ │ columns: (column1, column2, column3, check1, partial_index_put1) +│ │ estimated row count: 2 +│ │ render partial_index_put1: column3 IN ('bar', 'baz', 'foo') +│ │ render check1: column1 IN ('us-east', 'us-west', 'eu-west') +│ │ render column1: column1 +│ │ render column2: column2 +│ │ render column3: column3 +│ │ +│ └── • values +│ columns: (column1, column2, column3) +│ size: 3 columns, 2 rows +│ row 0, expr 0: 'us-west' +│ row 0, expr 1: 1 +│ row 0, expr 2: 'foo' +│ row 1, expr 0: 'us-east' +│ row 1, expr 1: 2 +│ row 1, expr 2: 'bar' +│ +└── • constraint-check + │ + └── • error if rows + │ columns: () + │ + └── • project + │ columns: (column1, column2, column3) + │ estimated row count: 1 (missing stats) + │ + └── • lookup join (semi) + │ columns: ("lookup_join_const_col_@11", column1, column2, column3) + │ table: uniq_partial_enum@uniq_partial_enum_r_i_idx (partial index) + │ equality: (lookup_join_const_col_@11, column2) = (r,i) + │ equality cols are key + │ pred: column1 != r + │ + └── • cross join (inner) + │ columns: ("lookup_join_const_col_@11", column1, column2, column3) + │ estimated row count: 6 + │ + ├── • values + │ columns: ("lookup_join_const_col_@11") + │ size: 1 column, 3 rows + │ row 0, expr 0: 'us-east' + │ row 1, expr 0: 'us-west' + │ row 2, expr 0: 'eu-west' + │ + └── • filter + │ columns: (column1, column2, column3) + │ estimated row count: 2 + │ filter: column3 IN ('bar', 'baz', 'foo') + │ + └── • project + │ columns: (column1, column2, column3) + │ estimated row count: 2 + │ + └── • scan buffer + columns: (column1, column2, column3, check1, partial_index_put1) + label: buffer 1 + # -- Tests with UPDATE -- subtest Update @@ -1014,7 +1358,7 @@ vectorized: true table: uniq@primary spans: FULL SCAN -# No need to plan checks for x,y since x is aways null. +# No need to plan checks for x,y since x is always null. # Also update the primary key. query T EXPLAIN UPDATE uniq SET k = 1, w = 2, x = NULL @@ -1420,22 +1764,22 @@ vectorized: true │ │ columns: () │ │ │ └── • project -│ │ columns: (i_new, r_new) +│ │ columns: (r_new, s_new, i_new, j) │ │ estimated row count: 3 (missing stats) │ │ │ └── • lookup join (semi) -│ │ columns: (i_new, r_new, "lookup_join_const_col_@19") +│ │ columns: (r_new, s_new, i_new, j, "lookup_join_const_col_@17") │ │ table: uniq_enum@primary -│ │ equality: (lookup_join_const_col_@19, i_new) = (r,i) +│ │ equality: (lookup_join_const_col_@17, i_new) = (r,i) │ │ equality cols are key │ │ pred: r_new != r │ │ │ └── • cross join (inner) -│ │ columns: (i_new, r_new, "lookup_join_const_col_@19") +│ │ columns: (r_new, s_new, i_new, j, "lookup_join_const_col_@17") │ │ estimated row count: 30 (missing stats) │ │ │ ├── • project -│ │ │ columns: (i_new, r_new) +│ │ │ columns: (r_new, s_new, i_new, j) │ │ │ estimated row count: 10 (missing stats) │ │ │ │ │ └── • scan buffer @@ -1443,7 +1787,7 @@ vectorized: true │ │ label: buffer 1 │ │ │ └── • values -│ columns: ("lookup_join_const_col_@19") +│ columns: ("lookup_join_const_col_@17") │ size: 1 column, 3 rows │ row 0, expr 0: 'us-east' │ row 1, expr 0: 'us-west' @@ -1455,22 +1799,22 @@ vectorized: true │ columns: () │ └── • project - │ columns: (s_new, j, r_new, i_new) + │ columns: (r_new, s_new, i_new, j) │ estimated row count: 3 (missing stats) │ └── • lookup join (semi) - │ columns: (s_new, j, r_new, i_new, "lookup_join_const_col_@29") + │ columns: (r_new, s_new, i_new, j, "lookup_join_const_col_@27") │ table: uniq_enum@uniq_enum_r_s_j_key - │ equality: (lookup_join_const_col_@29, s_new, j) = (r,s,j) + │ equality: (lookup_join_const_col_@27, s_new, j) = (r,s,j) │ equality cols are key │ pred: (r_new != r) OR (i_new != i) │ └── • cross join (inner) - │ columns: (s_new, j, r_new, i_new, "lookup_join_const_col_@29") + │ columns: (r_new, s_new, i_new, j, "lookup_join_const_col_@27") │ estimated row count: 30 (missing stats) │ ├── • project - │ │ columns: (s_new, j, r_new, i_new) + │ │ columns: (r_new, s_new, i_new, j) │ │ estimated row count: 10 (missing stats) │ │ │ └── • scan buffer @@ -1478,7 +1822,7 @@ vectorized: true │ label: buffer 1 │ └── • values - columns: ("lookup_join_const_col_@29") + columns: ("lookup_join_const_col_@27") size: 1 column, 3 rows row 0, expr 0: 'us-east' row 1, expr 0: 'us-west' @@ -1608,7 +1952,8 @@ vectorized: true └── • scan buffer label: buffer 1 -# TODO(rytaft): No need to plan checks for w since it's aways NULL (see #58300). +# TODO(rytaft): No need to plan checks for w since it's always NULL (see +# #58300). query T EXPLAIN UPSERT INTO uniq (k, w, x) VALUES (1, NULL, 1), (2, NULL, NULL) ---- @@ -2230,29 +2575,29 @@ vectorized: true │ │ columns: () │ │ │ └── • project -│ │ columns: (upsert_i, upsert_r) +│ │ columns: (upsert_r, column2, upsert_i, column4) │ │ estimated row count: 1 (missing stats) │ │ │ └── • lookup join (semi) -│ │ columns: ("lookup_join_const_col_@22", upsert_i, upsert_r) +│ │ columns: ("lookup_join_const_col_@20", upsert_r, column2, upsert_i, column4) │ │ table: uniq_enum@primary -│ │ equality: (lookup_join_const_col_@22, upsert_i) = (r,i) +│ │ equality: (lookup_join_const_col_@20, upsert_i) = (r,i) │ │ equality cols are key │ │ pred: upsert_r != r │ │ │ └── • cross join (inner) -│ │ columns: ("lookup_join_const_col_@22", upsert_i, upsert_r) +│ │ columns: ("lookup_join_const_col_@20", upsert_r, column2, upsert_i, column4) │ │ estimated row count: 6 (missing stats) │ │ │ ├── • values -│ │ columns: ("lookup_join_const_col_@22") +│ │ columns: ("lookup_join_const_col_@20") │ │ size: 1 column, 3 rows │ │ row 0, expr 0: 'us-east' │ │ row 1, expr 0: 'us-west' │ │ row 2, expr 0: 'eu-west' │ │ │ └── • project -│ │ columns: (upsert_i, upsert_r) +│ │ columns: (upsert_r, column2, upsert_i, column4) │ │ estimated row count: 2 (missing stats) │ │ │ └── • scan buffer @@ -2265,29 +2610,29 @@ vectorized: true │ columns: () │ └── • project - │ columns: (column2, column4, upsert_r, upsert_i) + │ columns: (upsert_r, column2, upsert_i, column4) │ estimated row count: 1 (missing stats) │ └── • lookup join (semi) - │ columns: ("lookup_join_const_col_@32", column2, column4, upsert_r, upsert_i) + │ columns: ("lookup_join_const_col_@30", upsert_r, column2, upsert_i, column4) │ table: uniq_enum@uniq_enum_r_s_j_key - │ equality: (lookup_join_const_col_@32, column2, column4) = (r,s,j) + │ equality: (lookup_join_const_col_@30, column2, column4) = (r,s,j) │ equality cols are key │ pred: (upsert_r != r) OR (upsert_i != i) │ └── • cross join (inner) - │ columns: ("lookup_join_const_col_@32", column2, column4, upsert_r, upsert_i) + │ columns: ("lookup_join_const_col_@30", upsert_r, column2, upsert_i, column4) │ estimated row count: 6 (missing stats) │ ├── • values - │ columns: ("lookup_join_const_col_@32") + │ columns: ("lookup_join_const_col_@30") │ size: 1 column, 3 rows │ row 0, expr 0: 'us-east' │ row 1, expr 0: 'us-west' │ row 2, expr 0: 'eu-west' │ └── • project - │ columns: (column2, column4, upsert_r, upsert_i) + │ columns: (upsert_r, column2, upsert_i, column4) │ estimated row count: 2 (missing stats) │ └── • scan buffer @@ -2382,29 +2727,29 @@ vectorized: true │ │ columns: () │ │ │ └── • project -│ │ columns: (upsert_i, upsert_r) +│ │ columns: (upsert_r, upsert_s, upsert_i, upsert_j) │ │ estimated row count: 1 (missing stats) │ │ │ └── • lookup join (semi) -│ │ columns: ("lookup_join_const_col_@25", upsert_i, upsert_r) +│ │ columns: ("lookup_join_const_col_@23", upsert_r, upsert_s, upsert_i, upsert_j) │ │ table: uniq_enum@primary -│ │ equality: (lookup_join_const_col_@25, upsert_i) = (r,i) +│ │ equality: (lookup_join_const_col_@23, upsert_i) = (r,i) │ │ equality cols are key │ │ pred: upsert_r != r │ │ │ └── • cross join (inner) -│ │ columns: ("lookup_join_const_col_@25", upsert_i, upsert_r) +│ │ columns: ("lookup_join_const_col_@23", upsert_r, upsert_s, upsert_i, upsert_j) │ │ estimated row count: 6 (missing stats) │ │ │ ├── • values -│ │ columns: ("lookup_join_const_col_@25") +│ │ columns: ("lookup_join_const_col_@23") │ │ size: 1 column, 3 rows │ │ row 0, expr 0: 'us-east' │ │ row 1, expr 0: 'us-west' │ │ row 2, expr 0: 'eu-west' │ │ │ └── • project -│ │ columns: (upsert_i, upsert_r) +│ │ columns: (upsert_r, upsert_s, upsert_i, upsert_j) │ │ estimated row count: 2 (missing stats) │ │ │ └── • scan buffer @@ -2417,29 +2762,29 @@ vectorized: true │ columns: () │ └── • project - │ columns: (upsert_s, upsert_j, upsert_r, upsert_i) + │ columns: (upsert_r, upsert_s, upsert_i, upsert_j) │ estimated row count: 1 (missing stats) │ └── • lookup join (semi) - │ columns: ("lookup_join_const_col_@35", upsert_s, upsert_j, upsert_r, upsert_i) + │ columns: ("lookup_join_const_col_@33", upsert_r, upsert_s, upsert_i, upsert_j) │ table: uniq_enum@uniq_enum_r_s_j_key - │ equality: (lookup_join_const_col_@35, upsert_s, upsert_j) = (r,s,j) + │ equality: (lookup_join_const_col_@33, upsert_s, upsert_j) = (r,s,j) │ equality cols are key │ pred: (upsert_r != r) OR (upsert_i != i) │ └── • cross join (inner) - │ columns: ("lookup_join_const_col_@35", upsert_s, upsert_j, upsert_r, upsert_i) + │ columns: ("lookup_join_const_col_@33", upsert_r, upsert_s, upsert_i, upsert_j) │ estimated row count: 6 (missing stats) │ ├── • values - │ columns: ("lookup_join_const_col_@35") + │ columns: ("lookup_join_const_col_@33") │ size: 1 column, 3 rows │ row 0, expr 0: 'us-east' │ row 1, expr 0: 'us-west' │ row 2, expr 0: 'eu-west' │ └── • project - │ columns: (upsert_s, upsert_j, upsert_r, upsert_i) + │ columns: (upsert_r, upsert_s, upsert_i, upsert_j) │ estimated row count: 2 (missing stats) │ └── • scan buffer diff --git a/pkg/sql/opt/norm/testdata/rules/prune_cols b/pkg/sql/opt/norm/testdata/rules/prune_cols index aee982723767..922dfec7bffd 100644 --- a/pkg/sql/opt/norm/testdata/rules/prune_cols +++ b/pkg/sql/opt/norm/testdata/rules/prune_cols @@ -3591,19 +3591,19 @@ update uniq ├── cardinality: [0 - 0] ├── volatile, mutations ├── project - │ ├── columns: w_new:15!null x_new:16!null uniq.k:8!null uniq.w:10 uniq.x:11 uniq.y:12 + │ ├── columns: w_new:15!null x_new:16!null uniq.k:8!null uniq.v:9 uniq.w:10 uniq.x:11 uniq.y:12 uniq.z:13 │ ├── cardinality: [0 - 1] │ ├── key: () - │ ├── fd: ()-->(8,10-12,15,16) + │ ├── fd: ()-->(8-13,15,16) │ ├── select - │ │ ├── columns: uniq.k:8!null uniq.w:10 uniq.x:11 uniq.y:12 + │ │ ├── columns: uniq.k:8!null uniq.v:9 uniq.w:10 uniq.x:11 uniq.y:12 uniq.z:13 │ │ ├── cardinality: [0 - 1] │ │ ├── key: () - │ │ ├── fd: ()-->(8,10-12) + │ │ ├── fd: ()-->(8-13) │ │ ├── scan uniq - │ │ │ ├── columns: uniq.k:8!null uniq.w:10 uniq.x:11 uniq.y:12 + │ │ │ ├── columns: uniq.k:8!null uniq.v:9 uniq.w:10 uniq.x:11 uniq.y:12 uniq.z:13 │ │ │ ├── key: (8) - │ │ │ └── fd: (8)-->(10-12) + │ │ │ └── fd: (8)-->(9-13), (13)~~>(8-12) │ │ └── filters │ │ └── uniq.k:8 = 3 [outer=(8), constraints=(/8: [/3 - /3]; tight), fd=()-->(8)] │ └── projections @@ -3612,48 +3612,55 @@ update uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: w:17!null k:18!null + │ ├── columns: k:24!null v:25 w:26!null x:27!null y:28 z:29 │ ├── cardinality: [0 - 1] │ ├── key: () - │ ├── fd: ()-->(17,18) + │ ├── fd: ()-->(24-29) │ ├── with-scan &1 - │ │ ├── columns: w:17!null k:18!null + │ │ ├── columns: k:24!null v:25 w:26!null x:27!null y:28 z:29 │ │ ├── mapping: - │ │ │ ├── w_new:15 => w:17 - │ │ │ └── uniq.k:8 => k:18 + │ │ │ ├── uniq.k:8 => k:24 + │ │ │ ├── uniq.v:9 => v:25 + │ │ │ ├── w_new:15 => w:26 + │ │ │ ├── x_new:16 => x:27 + │ │ │ ├── uniq.y:12 => y:28 + │ │ │ └── uniq.z:13 => z:29 │ │ ├── cardinality: [0 - 1] │ │ ├── key: () - │ │ └── fd: ()-->(17,18) + │ │ └── fd: ()-->(24-29) │ ├── scan uniq - │ │ ├── columns: uniq.k:19!null uniq.w:21 - │ │ ├── key: (19) - │ │ └── fd: (19)-->(21) + │ │ ├── columns: uniq.k:17!null uniq.w:19 + │ │ ├── key: (17) + │ │ └── fd: (17)-->(19) │ └── filters - │ ├── w:17 = uniq.w:21 [outer=(17,21), constraints=(/17: (/NULL - ]; /21: (/NULL - ]), fd=(17)==(21), (21)==(17)] - │ └── k:18 != uniq.k:19 [outer=(18,19), constraints=(/18: (/NULL - ]; /19: (/NULL - ])] + │ ├── w:26 = uniq.w:19 [outer=(19,26), constraints=(/19: (/NULL - ]; /26: (/NULL - ]), fd=(19)==(26), (26)==(19)] + │ └── k:24 != uniq.k:17 [outer=(17,24), constraints=(/17: (/NULL - ]; /24: (/NULL - ])] └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: x:26!null y:27 k:28!null + ├── columns: k:37!null v:38 w:39!null x:40!null y:41 z:42 ├── cardinality: [0 - 1] ├── key: () - ├── fd: ()-->(26-28) + ├── fd: ()-->(37-42) ├── with-scan &1 - │ ├── columns: x:26!null y:27 k:28!null + │ ├── columns: k:37!null v:38 w:39!null x:40!null y:41 z:42 │ ├── mapping: - │ │ ├── x_new:16 => x:26 - │ │ ├── uniq.y:12 => y:27 - │ │ └── uniq.k:8 => k:28 + │ │ ├── uniq.k:8 => k:37 + │ │ ├── uniq.v:9 => v:38 + │ │ ├── w_new:15 => w:39 + │ │ ├── x_new:16 => x:40 + │ │ ├── uniq.y:12 => y:41 + │ │ └── uniq.z:13 => z:42 │ ├── cardinality: [0 - 1] │ ├── key: () - │ └── fd: ()-->(26-28) + │ └── fd: ()-->(37-42) ├── scan uniq - │ ├── columns: uniq.k:29!null uniq.x:32 uniq.y:33 - │ ├── key: (29) - │ └── fd: (29)-->(32,33) + │ ├── columns: uniq.k:30!null uniq.x:33 uniq.y:34 + │ ├── key: (30) + │ └── fd: (30)-->(33,34) └── filters - ├── x:26 = uniq.x:32 [outer=(26,32), constraints=(/26: (/NULL - ]; /32: (/NULL - ]), fd=(26)==(32), (32)==(26)] - ├── y:27 = uniq.y:33 [outer=(27,33), constraints=(/27: (/NULL - ]; /33: (/NULL - ]), fd=(27)==(33), (33)==(27)] - └── k:28 != uniq.k:29 [outer=(28,29), constraints=(/28: (/NULL - ]; /29: (/NULL - ])] + ├── x:40 = uniq.x:33 [outer=(33,40), constraints=(/33: (/NULL - ]; /40: (/NULL - ]), fd=(33)==(40), (40)==(33)] + ├── y:41 = uniq.y:34 [outer=(34,41), constraints=(/34: (/NULL - ]; /41: (/NULL - ]), fd=(34)==(41), (41)==(34)] + └── k:37 != uniq.k:30 [outer=(30,37), constraints=(/30: (/NULL - ]; /37: (/NULL - ])] # Do not prune columns that are needed for foreign key checks or cascades. norm expect=PruneMutationInputCols @@ -3669,7 +3676,7 @@ upsert uniq_fk_parent │ ├── column2:8 => uniq_fk_parent.a:2 │ ├── column9:9 => uniq_fk_parent.b:3 │ ├── column9:9 => uniq_fk_parent.c:4 - │ └── column9:9 => d:5 + │ └── column9:9 => uniq_fk_parent.d:5 ├── update-mapping: │ └── upsert_c:20 => uniq_fk_parent.c:4 ├── input binding: &1 @@ -3678,16 +3685,16 @@ upsert uniq_fk_parent ├── cardinality: [0 - 0] ├── volatile, mutations ├── project - │ ├── columns: upsert_k:17 upsert_a:18 upsert_b:19 upsert_c:20 column1:7!null column2:8!null column9:9 uniq_fk_parent.k:10 uniq_fk_parent.b:12 uniq_fk_parent.c:13 + │ ├── columns: upsert_k:17 upsert_a:18 upsert_b:19 upsert_c:20 upsert_d:21 column1:7!null column2:8!null column9:9 uniq_fk_parent.k:10 uniq_fk_parent.b:12 uniq_fk_parent.c:13 │ ├── cardinality: [1 - 1] │ ├── key: () - │ ├── fd: ()-->(7-10,12,13,17-20) + │ ├── fd: ()-->(7-10,12,13,17-21) │ ├── left-join (cross) - │ │ ├── columns: column1:7!null column2:8!null column9:9 uniq_fk_parent.k:10 uniq_fk_parent.a:11 uniq_fk_parent.b:12 uniq_fk_parent.c:13 + │ │ ├── columns: column1:7!null column2:8!null column9:9 uniq_fk_parent.k:10 uniq_fk_parent.a:11 uniq_fk_parent.b:12 uniq_fk_parent.c:13 uniq_fk_parent.d:14 │ │ ├── cardinality: [1 - 1] │ │ ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one) │ │ ├── key: () - │ │ ├── fd: ()-->(7-13) + │ │ ├── fd: ()-->(7-14) │ │ ├── values │ │ │ ├── columns: column1:7!null column2:8!null column9:9 │ │ │ ├── cardinality: [1 - 1] @@ -3695,14 +3702,14 @@ upsert uniq_fk_parent │ │ │ ├── fd: ()-->(7-9) │ │ │ └── (2, 1, NULL) │ │ ├── select - │ │ │ ├── columns: uniq_fk_parent.k:10!null uniq_fk_parent.a:11 uniq_fk_parent.b:12 uniq_fk_parent.c:13 + │ │ │ ├── columns: uniq_fk_parent.k:10!null uniq_fk_parent.a:11 uniq_fk_parent.b:12 uniq_fk_parent.c:13 uniq_fk_parent.d:14 │ │ │ ├── cardinality: [0 - 1] │ │ │ ├── key: () - │ │ │ ├── fd: ()-->(10-13) + │ │ │ ├── fd: ()-->(10-14) │ │ │ ├── scan uniq_fk_parent - │ │ │ │ ├── columns: uniq_fk_parent.k:10!null uniq_fk_parent.a:11 uniq_fk_parent.b:12 uniq_fk_parent.c:13 + │ │ │ │ ├── columns: uniq_fk_parent.k:10!null uniq_fk_parent.a:11 uniq_fk_parent.b:12 uniq_fk_parent.c:13 uniq_fk_parent.d:14 │ │ │ │ ├── key: (10) - │ │ │ │ └── fd: (10)-->(11-13) + │ │ │ │ └── fd: (10)-->(11-14) │ │ │ └── filters │ │ │ └── uniq_fk_parent.k:10 = 2 [outer=(10), constraints=(/10: [/2 - /2]; tight), fd=()-->(10)] │ │ └── filters (true) @@ -3710,52 +3717,58 @@ upsert uniq_fk_parent │ ├── CASE WHEN uniq_fk_parent.k:10 IS NULL THEN column1:7 ELSE uniq_fk_parent.k:10 END [as=upsert_k:17, outer=(7,10)] │ ├── CASE WHEN uniq_fk_parent.k:10 IS NULL THEN column2:8 ELSE uniq_fk_parent.a:11 END [as=upsert_a:18, outer=(8,10,11)] │ ├── CASE WHEN uniq_fk_parent.k:10 IS NULL THEN column9:9 ELSE uniq_fk_parent.b:12 END [as=upsert_b:19, outer=(9,10,12)] - │ └── CASE WHEN uniq_fk_parent.k:10 IS NULL THEN column9:9 ELSE 1 END [as=upsert_c:20, outer=(9,10)] + │ ├── CASE WHEN uniq_fk_parent.k:10 IS NULL THEN column9:9 ELSE 1 END [as=upsert_c:20, outer=(9,10)] + │ └── CASE WHEN uniq_fk_parent.k:10 IS NULL THEN column9:9 ELSE uniq_fk_parent.d:14 END [as=upsert_d:21, outer=(9,10,14)] └── unique-checks ├── unique-checks-item: uniq_fk_parent(a) │ └── semi-join (hash) - │ ├── columns: a:22 k:23 + │ ├── columns: k:28 a:29 b:30 c:31 d:32 │ ├── cardinality: [0 - 1] │ ├── key: () - │ ├── fd: ()-->(22,23) + │ ├── fd: ()-->(28-32) │ ├── with-scan &1 - │ │ ├── columns: a:22 k:23 + │ │ ├── columns: k:28 a:29 b:30 c:31 d:32 │ │ ├── mapping: - │ │ │ ├── upsert_a:18 => a:22 - │ │ │ └── upsert_k:17 => k:23 + │ │ │ ├── upsert_k:17 => k:28 + │ │ │ ├── upsert_a:18 => a:29 + │ │ │ ├── upsert_b:19 => b:30 + │ │ │ ├── upsert_c:20 => c:31 + │ │ │ └── upsert_d:21 => d:32 │ │ ├── cardinality: [1 - 1] │ │ ├── key: () - │ │ └── fd: ()-->(22,23) + │ │ └── fd: ()-->(28-32) │ ├── scan uniq_fk_parent - │ │ ├── columns: uniq_fk_parent.k:24!null uniq_fk_parent.a:25 - │ │ ├── key: (24) - │ │ └── fd: (24)-->(25) + │ │ ├── columns: uniq_fk_parent.k:22!null uniq_fk_parent.a:23 + │ │ ├── key: (22) + │ │ └── fd: (22)-->(23) │ └── filters - │ ├── a:22 = uniq_fk_parent.a:25 [outer=(22,25), constraints=(/22: (/NULL - ]; /25: (/NULL - ]), fd=(22)==(25), (25)==(22)] - │ └── k:23 != uniq_fk_parent.k:24 [outer=(23,24), constraints=(/23: (/NULL - ]; /24: (/NULL - ])] + │ ├── a:29 = uniq_fk_parent.a:23 [outer=(23,29), constraints=(/23: (/NULL - ]; /29: (/NULL - ]), fd=(23)==(29), (29)==(23)] + │ └── k:28 != uniq_fk_parent.k:22 [outer=(22,28), constraints=(/22: (/NULL - ]; /28: (/NULL - ])] └── unique-checks-item: uniq_fk_parent(b,c) └── semi-join (hash) - ├── columns: b:30 c:31 k:32 + ├── columns: k:39 a:40 b:41 c:42 d:43 ├── cardinality: [0 - 1] ├── key: () - ├── fd: ()-->(30-32) + ├── fd: ()-->(39-43) ├── with-scan &1 - │ ├── columns: b:30 c:31 k:32 + │ ├── columns: k:39 a:40 b:41 c:42 d:43 │ ├── mapping: - │ │ ├── upsert_b:19 => b:30 - │ │ ├── upsert_c:20 => c:31 - │ │ └── upsert_k:17 => k:32 + │ │ ├── upsert_k:17 => k:39 + │ │ ├── upsert_a:18 => a:40 + │ │ ├── upsert_b:19 => b:41 + │ │ ├── upsert_c:20 => c:42 + │ │ └── upsert_d:21 => d:43 │ ├── cardinality: [1 - 1] │ ├── key: () - │ └── fd: ()-->(30-32) + │ └── fd: ()-->(39-43) ├── scan uniq_fk_parent │ ├── columns: uniq_fk_parent.k:33!null uniq_fk_parent.b:35 uniq_fk_parent.c:36 │ ├── key: (33) │ └── fd: (33)-->(35,36) └── filters - ├── b:30 = uniq_fk_parent.b:35 [outer=(30,35), constraints=(/30: (/NULL - ]; /35: (/NULL - ]), fd=(30)==(35), (35)==(30)] - ├── c:31 = uniq_fk_parent.c:36 [outer=(31,36), constraints=(/31: (/NULL - ]; /36: (/NULL - ]), fd=(31)==(36), (36)==(31)] - └── k:32 != uniq_fk_parent.k:33 [outer=(32,33), constraints=(/32: (/NULL - ]; /33: (/NULL - ])] + ├── b:41 = uniq_fk_parent.b:35 [outer=(35,41), constraints=(/35: (/NULL - ]; /41: (/NULL - ]), fd=(35)==(41), (41)==(35)] + ├── c:42 = uniq_fk_parent.c:36 [outer=(36,42), constraints=(/36: (/NULL - ]; /42: (/NULL - ]), fd=(36)==(42), (42)==(36)] + └── k:39 != uniq_fk_parent.k:33 [outer=(33,39), constraints=(/33: (/NULL - ]; /39: (/NULL - ])] # Prune inbound foreign key columns when they are not updated. norm expect=PruneMutationInputCols @@ -3765,25 +3778,25 @@ upsert uniq_fk_parent ├── columns: ├── arbiter indexes: primary ├── canary column: uniq_fk_parent.k:9 - ├── fetch columns: uniq_fk_parent.k:9 d:13 + ├── fetch columns: uniq_fk_parent.k:9 uniq_fk_parent.d:13 ├── insert-mapping: │ ├── column1:7 => uniq_fk_parent.k:1 │ ├── column8:8 => uniq_fk_parent.a:2 │ ├── column8:8 => uniq_fk_parent.b:3 │ ├── column8:8 => uniq_fk_parent.c:4 - │ └── column8:8 => d:5 + │ └── column8:8 => uniq_fk_parent.d:5 ├── update-mapping: - │ └── upsert_d:20 => d:5 + │ └── upsert_d:20 => uniq_fk_parent.d:5 ├── input binding: &1 ├── cardinality: [0 - 0] ├── volatile, mutations ├── project - │ ├── columns: upsert_k:16 upsert_a:17 upsert_b:18 upsert_c:19 upsert_d:20 column1:7!null column8:8 uniq_fk_parent.k:9 d:13 + │ ├── columns: upsert_k:16 upsert_a:17 upsert_b:18 upsert_c:19 upsert_d:20 column1:7!null column8:8 uniq_fk_parent.k:9 uniq_fk_parent.d:13 │ ├── cardinality: [1 - 1] │ ├── key: () │ ├── fd: ()-->(7-9,13,16-20) │ ├── left-join (cross) - │ │ ├── columns: column1:7!null column8:8 uniq_fk_parent.k:9 uniq_fk_parent.a:10 uniq_fk_parent.b:11 uniq_fk_parent.c:12 d:13 + │ │ ├── columns: column1:7!null column8:8 uniq_fk_parent.k:9 uniq_fk_parent.a:10 uniq_fk_parent.b:11 uniq_fk_parent.c:12 uniq_fk_parent.d:13 │ │ ├── cardinality: [1 - 1] │ │ ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one) │ │ ├── key: () @@ -3795,12 +3808,12 @@ upsert uniq_fk_parent │ │ │ ├── fd: ()-->(7,8) │ │ │ └── (1, NULL) │ │ ├── select - │ │ │ ├── columns: uniq_fk_parent.k:9!null uniq_fk_parent.a:10 uniq_fk_parent.b:11 uniq_fk_parent.c:12 d:13 + │ │ │ ├── columns: uniq_fk_parent.k:9!null uniq_fk_parent.a:10 uniq_fk_parent.b:11 uniq_fk_parent.c:12 uniq_fk_parent.d:13 │ │ │ ├── cardinality: [0 - 1] │ │ │ ├── key: () │ │ │ ├── fd: ()-->(9-13) │ │ │ ├── scan uniq_fk_parent - │ │ │ │ ├── columns: uniq_fk_parent.k:9!null uniq_fk_parent.a:10 uniq_fk_parent.b:11 uniq_fk_parent.c:12 d:13 + │ │ │ │ ├── columns: uniq_fk_parent.k:9!null uniq_fk_parent.a:10 uniq_fk_parent.b:11 uniq_fk_parent.c:12 uniq_fk_parent.d:13 │ │ │ │ ├── key: (9) │ │ │ │ └── fd: (9)-->(10-13) │ │ │ └── filters @@ -3815,48 +3828,53 @@ upsert uniq_fk_parent └── unique-checks ├── unique-checks-item: uniq_fk_parent(a) │ └── semi-join (hash) - │ ├── columns: a:21 k:22 + │ ├── columns: k:27 a:28 b:29 c:30 d:31 │ ├── cardinality: [0 - 1] │ ├── key: () - │ ├── fd: ()-->(21,22) + │ ├── fd: ()-->(27-31) │ ├── with-scan &1 - │ │ ├── columns: a:21 k:22 + │ │ ├── columns: k:27 a:28 b:29 c:30 d:31 │ │ ├── mapping: - │ │ │ ├── upsert_a:17 => a:21 - │ │ │ └── upsert_k:16 => k:22 + │ │ │ ├── upsert_k:16 => k:27 + │ │ │ ├── upsert_a:17 => a:28 + │ │ │ ├── upsert_b:18 => b:29 + │ │ │ ├── upsert_c:19 => c:30 + │ │ │ └── upsert_d:20 => d:31 │ │ ├── cardinality: [1 - 1] │ │ ├── key: () - │ │ └── fd: ()-->(21,22) + │ │ └── fd: ()-->(27-31) │ ├── scan uniq_fk_parent - │ │ ├── columns: uniq_fk_parent.k:23!null uniq_fk_parent.a:24 - │ │ ├── key: (23) - │ │ └── fd: (23)-->(24) + │ │ ├── columns: uniq_fk_parent.k:21!null uniq_fk_parent.a:22 + │ │ ├── key: (21) + │ │ └── fd: (21)-->(22) │ └── filters - │ ├── a:21 = uniq_fk_parent.a:24 [outer=(21,24), constraints=(/21: (/NULL - ]; /24: (/NULL - ]), fd=(21)==(24), (24)==(21)] - │ └── k:22 != uniq_fk_parent.k:23 [outer=(22,23), constraints=(/22: (/NULL - ]; /23: (/NULL - ])] + │ ├── a:28 = uniq_fk_parent.a:22 [outer=(22,28), constraints=(/22: (/NULL - ]; /28: (/NULL - ]), fd=(22)==(28), (28)==(22)] + │ └── k:27 != uniq_fk_parent.k:21 [outer=(21,27), constraints=(/21: (/NULL - ]; /27: (/NULL - ])] └── unique-checks-item: uniq_fk_parent(b,c) └── semi-join (hash) - ├── columns: b:29 c:30 k:31 + ├── columns: k:38 a:39 b:40 c:41 d:42 ├── cardinality: [0 - 1] ├── key: () - ├── fd: ()-->(29-31) + ├── fd: ()-->(38-42) ├── with-scan &1 - │ ├── columns: b:29 c:30 k:31 + │ ├── columns: k:38 a:39 b:40 c:41 d:42 │ ├── mapping: - │ │ ├── upsert_b:18 => b:29 - │ │ ├── upsert_c:19 => c:30 - │ │ └── upsert_k:16 => k:31 + │ │ ├── upsert_k:16 => k:38 + │ │ ├── upsert_a:17 => a:39 + │ │ ├── upsert_b:18 => b:40 + │ │ ├── upsert_c:19 => c:41 + │ │ └── upsert_d:20 => d:42 │ ├── cardinality: [1 - 1] │ ├── key: () - │ └── fd: ()-->(29-31) + │ └── fd: ()-->(38-42) ├── scan uniq_fk_parent │ ├── columns: uniq_fk_parent.k:32!null uniq_fk_parent.b:34 uniq_fk_parent.c:35 │ ├── key: (32) │ └── fd: (32)-->(34,35) └── filters - ├── b:29 = uniq_fk_parent.b:34 [outer=(29,34), constraints=(/29: (/NULL - ]; /34: (/NULL - ]), fd=(29)==(34), (34)==(29)] - ├── c:30 = uniq_fk_parent.c:35 [outer=(30,35), constraints=(/30: (/NULL - ]; /35: (/NULL - ]), fd=(30)==(35), (35)==(30)] - └── k:31 != uniq_fk_parent.k:32 [outer=(31,32), constraints=(/31: (/NULL - ]; /32: (/NULL - ])] + ├── b:40 = uniq_fk_parent.b:34 [outer=(34,40), constraints=(/34: (/NULL - ]; /40: (/NULL - ]), fd=(34)==(40), (40)==(34)] + ├── c:41 = uniq_fk_parent.c:35 [outer=(35,41), constraints=(/35: (/NULL - ]; /41: (/NULL - ]), fd=(35)==(41), (41)==(35)] + └── k:38 != uniq_fk_parent.k:32 [outer=(32,38), constraints=(/32: (/NULL - ]; /38: (/NULL - ])] # Do not prune columns that are needed for foreign key checks or cascades. norm expect=PruneMutationInputCols diff --git a/pkg/sql/opt/optbuilder/mutation_builder.go b/pkg/sql/opt/optbuilder/mutation_builder.go index ad4c6320ae17..7fbb810b04fd 100644 --- a/pkg/sql/opt/optbuilder/mutation_builder.go +++ b/pkg/sql/opt/optbuilder/mutation_builder.go @@ -159,6 +159,11 @@ type mutationBuilder struct { // reuse. parsedIndexExprs []tree.Expr + // parsedUniqueConstraintExprs is a cached set of parsed partial unique + // constraint predicate expressions from the table schema. These are parsed + // once and cached for reuse. + parsedUniqueConstraintExprs []tree.Expr + // uniqueChecks contains unique check queries; see buildUnique* methods. uniqueChecks memo.UniqueChecksExpr @@ -1193,6 +1198,35 @@ func (mb *mutationBuilder) parsePartialIndexPredicateExpr(idx cat.IndexOrdinal) return expr } +// parseUniqueConstraintPredicateExpr parses the predicate of the given partial +// unique constraint and caches it for reuse. This function panics if the unique +// constraint at the given ordinal is not partial. +func (mb *mutationBuilder) parseUniqueConstraintPredicateExpr(idx cat.UniqueOrdinal) tree.Expr { + uniqueConstraint := mb.tab.Unique(idx) + + predStr, isPartial := uniqueConstraint.Predicate() + if !isPartial { + panic(errors.AssertionFailedf("unique constraint at ordinal %d is not a partial unique constraint", idx)) + } + + if mb.parsedUniqueConstraintExprs == nil { + mb.parsedUniqueConstraintExprs = make([]tree.Expr, mb.tab.UniqueCount()) + } + + // Return expression from the cache, if it was already parsed previously. + if mb.parsedUniqueConstraintExprs[idx] != nil { + return mb.parsedUniqueConstraintExprs[idx] + } + + expr, err := parser.ParseExpr(predStr) + if err != nil { + panic(err) + } + + mb.parsedUniqueConstraintExprs[idx] = expr + return expr +} + // getIndexLaxKeyOrdinals returns the ordinals of all lax key columns in the // given index. A column's ordinal is the ordered position of that column in the // owning table. @@ -1314,7 +1348,10 @@ func (mb *mutationBuilder) buildCheckInputScan( } // Synthesize a new output column for the input column, using the name - // of the column in the underlying table. + // of the column in the underlying table. The table's column names are + // used because partial unique constraint checks must filter the + // WithScan rows with a predicate expression that references the table's + // columns. tableCol := mb.b.factory.Metadata().Table(mb.tabID).Column(tabOrd) outCol := mb.md.AddColumn(string(tableCol.ColName()), tableCol.DatumType()) withScanScope.cols[i] = scopeColumn{ diff --git a/pkg/sql/opt/optbuilder/mutation_builder_unique.go b/pkg/sql/opt/optbuilder/mutation_builder_unique.go index cc30ee2bdc42..f42bcceb5c25 100644 --- a/pkg/sql/opt/optbuilder/mutation_builder_unique.go +++ b/pkg/sql/opt/optbuilder/mutation_builder_unique.go @@ -17,6 +17,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/sql/opt/memo" "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" "github.com/cockroachdb/cockroach/pkg/sql/sqltelemetry" + "github.com/cockroachdb/cockroach/pkg/sql/types" "github.com/cockroachdb/cockroach/pkg/util" ) @@ -129,12 +130,11 @@ type uniqueCheckHelper struct { // uniqueOrdinals are the table ordinals of the unique columns in the table // that is being mutated. They correspond 1-to-1 to the columns in the // UniqueConstraint. - uniqueOrdinals []int + uniqueOrdinals util.FastIntSet - // uniqueAndPrimaryKeyOrdinals includes all the ordinals from uniqueOrdinals, - // plus the ordinals from any primary key columns that are not already - // included in uniqueOrdinals. - uniqueAndPrimaryKeyOrdinals []int + // primaryKeyOrdinals includes the ordinals from any primary key columns + // that are not included in uniqueOrdinals. + primaryKeyOrdinals util.FastIntSet } // init initializes the helper with a unique constraint. @@ -150,15 +150,18 @@ func (h *uniqueCheckHelper) init(mb *mutationBuilder, uniqueOrdinal int) bool { uniqueOrdinal: uniqueOrdinal, } - uniqueCount := h.unique.ColumnCount() - var uniqueOrds util.FastIntSet - for i := 0; i < uniqueCount; i++ { + for i, n := 0, h.unique.ColumnCount(); i < n; i++ { uniqueOrds.Add(h.unique.ColumnOrdinal(mb.tab, i)) } // Find the primary key columns that are not part of the unique constraint. // If there aren't any, we don't need a check. + // TODO(mgartner): We also don't need a check if there exists a unique index + // with columns that are a subset of the unique constraint columns. + // Similarly, we don't need a check for a partial unique constraint if there + // exists a non-partial unique constraint with columns that are a subset of + // the partial unique constrain columns. primaryOrds := getIndexLaxKeyOrdinals(mb.tab.Index(cat.PrimaryIndex)) primaryOrds.DifferenceWith(uniqueOrds) if primaryOrds.Empty() { @@ -167,13 +170,13 @@ func (h *uniqueCheckHelper) init(mb *mutationBuilder, uniqueOrdinal int) bool { return false } - h.uniqueAndPrimaryKeyOrdinals = append(uniqueOrds.Ordered(), primaryOrds.Ordered()...) - h.uniqueOrdinals = h.uniqueAndPrimaryKeyOrdinals[:uniqueCount] + h.uniqueOrdinals = uniqueOrds + h.primaryKeyOrdinals = primaryOrds // Check if we are setting NULL values for the unique columns, like when this // mutation is the result of a SET NULL cascade action. numNullCols := 0 - for _, tabOrd := range h.uniqueOrdinals { + for tabOrd, ok := h.uniqueOrdinals.Next(0); ok; tabOrd, ok = h.uniqueOrdinals.Next(tabOrd + 1) { colID := mb.mapToReturnColID(tabOrd) if memo.OutputColumnIsAlwaysNull(mb.outScope.expr, colID) { numNullCols++ @@ -189,26 +192,31 @@ func (h *uniqueCheckHelper) init(mb *mutationBuilder, uniqueOrdinal int) bool { // table. The input to the insertion check will be produced from the input to // the mutation operator. func (h *uniqueCheckHelper) buildInsertionCheck() memo.UniqueChecksItem { - withScanScope, _ := h.mb.buildCheckInputScan( - checkInputScanNewVals, h.uniqueAndPrimaryKeyOrdinals, - ) - - numCols := len(withScanScope.cols) f := h.mb.b.factory // Build a self semi-join, with the new values on the left and the // existing values on the right. + scanScope, ordinals := h.buildTableScan() - scanScope, _ := h.buildTableScan() + withScanScope, _ := h.mb.buildCheckInputScan( + checkInputScanNewVals, ordinals, + ) // Build the join filters: // (new_a = existing_a) AND (new_b = existing_b) AND ... // - // Set the capacity to len(h.uniqueOrdinals)+1 since we'll have an equality + // Set the capacity to h.uniqueOrdinals.Len()+1 since we'll have an equality // condition for each column in the unique constraint, plus one additional - // condition to prevent rows from matching themselves (see below). - semiJoinFilters := make(memo.FiltersExpr, 0, len(h.uniqueOrdinals)+1) - for i := 0; i < len(h.uniqueOrdinals); i++ { + // condition to prevent rows from matching themselves (see below). If the + // constraint is partial, add 2 to account for filtering both the WithScan + // and the Scan by the partial unique constraint predicate. + numFilters := h.uniqueOrdinals.Len() + 1 + _, isPartial := h.unique.Predicate() + if isPartial { + numFilters += 2 + } + semiJoinFilters := make(memo.FiltersExpr, 0, numFilters) + for i, ok := h.uniqueOrdinals.Next(0); ok; i, ok = h.uniqueOrdinals.Next(i + 1) { semiJoinFilters = append(semiJoinFilters, f.ConstructFiltersItem( f.ConstructEq( f.ConstructVariable(withScanScope.cols[i].id), @@ -217,12 +225,29 @@ func (h *uniqueCheckHelper) buildInsertionCheck() memo.UniqueChecksItem { )) } + // If the unique constraint is partial, we need to filter out inserted rows + // that don't satisfy the predicate. We also need to make sure that rows do + // not match existing rows in the the table that do not satisfy the + // predicate. So we add the predicate as a filter on both the WithScan + // columns and the Scan columns. + if isPartial { + pred := h.mb.parseUniqueConstraintPredicateExpr(h.uniqueOrdinal) + + typedPred := withScanScope.resolveAndRequireType(pred, types.Bool) + withScanPred := h.mb.b.buildScalar(typedPred, withScanScope, nil, nil, nil) + semiJoinFilters = append(semiJoinFilters, f.ConstructFiltersItem(withScanPred)) + + typedPred = scanScope.resolveAndRequireType(pred, types.Bool) + scanPred := h.mb.b.buildScalar(typedPred, scanScope, nil, nil, nil) + semiJoinFilters = append(semiJoinFilters, f.ConstructFiltersItem(scanPred)) + } + // We need to prevent rows from matching themselves in the semi join. We can // do this by adding another filter that uses the primary keys to check if // two rows are identical: // (new_pk1 != existing_pk1) OR (new_pk2 != existing_pk2) OR ... var pkFilter opt.ScalarExpr - for i := len(h.uniqueOrdinals); i < numCols; i++ { + for i, ok := h.primaryKeyOrdinals.Next(0); ok; i, ok = h.primaryKeyOrdinals.Next(i + 1) { pkFilterLocal := f.ConstructNe( f.ConstructVariable(withScanScope.cols[i].id), f.ConstructVariable(scanScope.cols[i].id), @@ -237,26 +262,38 @@ func (h *uniqueCheckHelper) buildInsertionCheck() memo.UniqueChecksItem { semiJoin := f.ConstructSemiJoin(withScanScope.expr, scanScope.expr, semiJoinFilters, memo.EmptyJoinPrivate) + // Collect the key columns that will be shown in the error message if there + // is a duplicate key violation resulting from this uniqueness check. + keyCols := make(opt.ColList, 0, h.uniqueOrdinals.Len()) + for i, ok := h.uniqueOrdinals.Next(0); ok; i, ok = h.uniqueOrdinals.Next(i + 1) { + keyCols = append(keyCols, withScanScope.cols[i].id) + } + return f.ConstructUniqueChecksItem(semiJoin, &memo.UniqueChecksItemPrivate{ Table: h.mb.tabID, CheckOrdinal: h.uniqueOrdinal, - // uniqueOrdinals is always a prefix of uniqueAndPrimaryKeyOrdinals, - // which maps 1-to-1 to the columns in withScanScope.cols. The remaining - // columns are primary key columns and should not be included in the - // KeyCols. - KeyCols: withScanScope.colList()[:len(h.uniqueOrdinals)], - OpName: h.mb.opName, + KeyCols: keyCols, + OpName: h.mb.opName, }) } -// buildTableScan builds a Scan of the table. -func (h *uniqueCheckHelper) buildTableScan() (outScope *scope, tabMeta *opt.TableMeta) { - tabMeta = h.mb.b.addTable(h.mb.tab, tree.NewUnqualifiedTableName(h.mb.tab.Name())) +// buildTableScan builds a Scan of the table. The ordinals of the columns +// scanned are also returned. +func (h *uniqueCheckHelper) buildTableScan() (outScope *scope, ordinals []int) { + tabMeta := h.mb.b.addTable(h.mb.tab, tree.NewUnqualifiedTableName(h.mb.tab.Name())) + // TODO(mgartner): Include mutation columns once schema changes are + // transactional. + ordinals = tableOrdinals(tabMeta.Table, columnKinds{ + includeMutations: false, + includeSystem: false, + includeVirtualInverted: false, + includeVirtualComputed: true, + }) return h.mb.b.buildScan( tabMeta, - h.uniqueAndPrimaryKeyOrdinals, + ordinals, nil, /* indexFlags */ noRowLocking, h.mb.b.allocScope(), - ), tabMeta + ), ordinals } diff --git a/pkg/sql/opt/optbuilder/testdata/unique-checks-insert b/pkg/sql/opt/optbuilder/testdata/unique-checks-insert index 0c2f6c71f29b..55dce437f057 100644 --- a/pkg/sql/opt/optbuilder/testdata/unique-checks-insert +++ b/pkg/sql/opt/optbuilder/testdata/unique-checks-insert @@ -17,7 +17,7 @@ insert uniq ├── columns: ├── insert-mapping: │ ├── column1:7 => uniq.k:1 - │ ├── column2:8 => v:2 + │ ├── column2:8 => uniq.v:2 │ ├── column3:9 => uniq.w:3 │ ├── column4:10 => uniq.x:4 │ └── column5:11 => uniq.y:5 @@ -29,32 +29,37 @@ insert uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: w:12!null k:13!null + │ ├── columns: k:18!null v:19!null w:20!null x:21!null y:22!null │ ├── with-scan &1 - │ │ ├── columns: w:12!null k:13!null + │ │ ├── columns: k:18!null v:19!null w:20!null x:21!null y:22!null │ │ └── mapping: - │ │ ├── column3:9 => w:12 - │ │ └── column1:7 => k:13 + │ │ ├── column1:7 => k:18 + │ │ ├── column2:8 => v:19 + │ │ ├── column3:9 => w:20 + │ │ ├── column4:10 => x:21 + │ │ └── column5:11 => y:22 │ ├── scan uniq - │ │ └── columns: uniq.k:14!null uniq.w:16 + │ │ └── columns: uniq.k:12!null uniq.v:13 uniq.w:14 uniq.x:15 uniq.y:16 │ └── filters - │ ├── w:12 = uniq.w:16 - │ └── k:13 != uniq.k:14 + │ ├── w:20 = uniq.w:14 + │ └── k:18 != uniq.k:12 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: x:20!null y:21!null k:22!null + ├── columns: k:29!null v:30!null w:31!null x:32!null y:33!null ├── with-scan &1 - │ ├── columns: x:20!null y:21!null k:22!null + │ ├── columns: k:29!null v:30!null w:31!null x:32!null y:33!null │ └── mapping: - │ ├── column4:10 => x:20 - │ ├── column5:11 => y:21 - │ └── column1:7 => k:22 + │ ├── column1:7 => k:29 + │ ├── column2:8 => v:30 + │ ├── column3:9 => w:31 + │ ├── column4:10 => x:32 + │ └── column5:11 => y:33 ├── scan uniq - │ └── columns: uniq.k:23!null uniq.x:26 uniq.y:27 + │ └── columns: uniq.k:23!null uniq.v:24 uniq.w:25 uniq.x:26 uniq.y:27 └── filters - ├── x:20 = uniq.x:26 - ├── y:21 = uniq.y:27 - └── k:22 != uniq.k:23 + ├── x:32 = uniq.x:26 + ├── y:33 = uniq.y:27 + └── k:29 != uniq.k:23 # Some of the inserted values have nulls. build @@ -64,7 +69,7 @@ insert uniq ├── columns: ├── insert-mapping: │ ├── column1:7 => uniq.k:1 - │ ├── column2:8 => v:2 + │ ├── column2:8 => uniq.v:2 │ ├── column3:9 => uniq.w:3 │ ├── column4:10 => uniq.x:4 │ └── column5:11 => uniq.y:5 @@ -77,34 +82,39 @@ insert uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: w:12 k:13!null + │ ├── columns: k:18!null v:19 w:20 x:21 y:22!null │ ├── with-scan &1 - │ │ ├── columns: w:12 k:13!null + │ │ ├── columns: k:18!null v:19 w:20 x:21 y:22!null │ │ └── mapping: - │ │ ├── column3:9 => w:12 - │ │ └── column1:7 => k:13 + │ │ ├── column1:7 => k:18 + │ │ ├── column2:8 => v:19 + │ │ ├── column3:9 => w:20 + │ │ ├── column4:10 => x:21 + │ │ └── column5:11 => y:22 │ ├── scan uniq - │ │ └── columns: uniq.k:14!null uniq.w:16 + │ │ └── columns: uniq.k:12!null uniq.v:13 uniq.w:14 uniq.x:15 uniq.y:16 │ └── filters - │ ├── w:12 = uniq.w:16 - │ └── k:13 != uniq.k:14 + │ ├── w:20 = uniq.w:14 + │ └── k:18 != uniq.k:12 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: x:20 y:21!null k:22!null + ├── columns: k:29!null v:30 w:31 x:32 y:33!null ├── with-scan &1 - │ ├── columns: x:20 y:21!null k:22!null + │ ├── columns: k:29!null v:30 w:31 x:32 y:33!null │ └── mapping: - │ ├── column4:10 => x:20 - │ ├── column5:11 => y:21 - │ └── column1:7 => k:22 + │ ├── column1:7 => k:29 + │ ├── column2:8 => v:30 + │ ├── column3:9 => w:31 + │ ├── column4:10 => x:32 + │ └── column5:11 => y:33 ├── scan uniq - │ └── columns: uniq.k:23!null uniq.x:26 uniq.y:27 + │ └── columns: uniq.k:23!null uniq.v:24 uniq.w:25 uniq.x:26 uniq.y:27 └── filters - ├── x:20 = uniq.x:26 - ├── y:21 = uniq.y:27 - └── k:22 != uniq.k:23 + ├── x:32 = uniq.x:26 + ├── y:33 = uniq.y:27 + └── k:29 != uniq.k:23 -# No need to plan checks for w since it's aways null. +# No need to plan checks for w since it's always null. build INSERT INTO uniq VALUES (1, 1, NULL, 1, 1), (2, 2, NULL, 2, 2) ---- @@ -112,8 +122,8 @@ insert uniq ├── columns: ├── insert-mapping: │ ├── column1:7 => uniq.k:1 - │ ├── column2:8 => v:2 - │ ├── column3:9 => w:3 + │ ├── column2:8 => uniq.v:2 + │ ├── column3:9 => uniq.w:3 │ ├── column4:10 => uniq.x:4 │ └── column5:11 => uniq.y:5 ├── input binding: &1 @@ -124,21 +134,23 @@ insert uniq └── unique-checks └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: x:12!null y:13!null k:14!null + ├── columns: k:18!null v:19!null w:20 x:21!null y:22!null ├── with-scan &1 - │ ├── columns: x:12!null y:13!null k:14!null + │ ├── columns: k:18!null v:19!null w:20 x:21!null y:22!null │ └── mapping: - │ ├── column4:10 => x:12 - │ ├── column5:11 => y:13 - │ └── column1:7 => k:14 + │ ├── column1:7 => k:18 + │ ├── column2:8 => v:19 + │ ├── column3:9 => w:20 + │ ├── column4:10 => x:21 + │ └── column5:11 => y:22 ├── scan uniq - │ └── columns: uniq.k:15!null uniq.x:18 uniq.y:19 + │ └── columns: uniq.k:12!null uniq.v:13 uniq.w:14 uniq.x:15 uniq.y:16 └── filters - ├── x:12 = uniq.x:18 - ├── y:13 = uniq.y:19 - └── k:14 != uniq.k:15 + ├── x:21 = uniq.x:15 + ├── y:22 = uniq.y:16 + └── k:18 != uniq.k:12 -# No need to plan checks for x,y since x is aways null. +# No need to plan checks for x,y since x is always null. build INSERT INTO uniq VALUES (1, 1, 1, NULL, 1), (2, 2, NULL, NULL, 2) ---- @@ -146,10 +158,10 @@ insert uniq ├── columns: ├── insert-mapping: │ ├── column1:7 => uniq.k:1 - │ ├── column2:8 => v:2 + │ ├── column2:8 => uniq.v:2 │ ├── column3:9 => uniq.w:3 - │ ├── column4:10 => x:4 - │ └── column5:11 => y:5 + │ ├── column4:10 => uniq.x:4 + │ └── column5:11 => uniq.y:5 ├── input binding: &1 ├── values │ ├── columns: column1:7!null column2:8!null column3:9 column4:10 column5:11!null @@ -158,19 +170,22 @@ insert uniq └── unique-checks └── unique-checks-item: uniq(w) └── semi-join (hash) - ├── columns: w:12 k:13!null + ├── columns: k:18!null v:19!null w:20 x:21 y:22!null ├── with-scan &1 - │ ├── columns: w:12 k:13!null + │ ├── columns: k:18!null v:19!null w:20 x:21 y:22!null │ └── mapping: - │ ├── column3:9 => w:12 - │ └── column1:7 => k:13 + │ ├── column1:7 => k:18 + │ ├── column2:8 => v:19 + │ ├── column3:9 => w:20 + │ ├── column4:10 => x:21 + │ └── column5:11 => y:22 ├── scan uniq - │ └── columns: uniq.k:14!null uniq.w:16 + │ └── columns: uniq.k:12!null uniq.v:13 uniq.w:14 uniq.x:15 uniq.y:16 └── filters - ├── w:12 = uniq.w:16 - └── k:13 != uniq.k:14 + ├── w:20 = uniq.w:14 + └── k:18 != uniq.k:12 -# No need to plan checks for x,y since y is aways null. +# No need to plan checks for x,y since y is always null. build INSERT INTO uniq VALUES (1, 1, 1, 1, NULL), (2, 2, 2, 2, NULL) ---- @@ -178,10 +193,10 @@ insert uniq ├── columns: ├── insert-mapping: │ ├── column1:7 => uniq.k:1 - │ ├── column2:8 => v:2 + │ ├── column2:8 => uniq.v:2 │ ├── column3:9 => uniq.w:3 - │ ├── column4:10 => x:4 - │ └── column5:11 => y:5 + │ ├── column4:10 => uniq.x:4 + │ └── column5:11 => uniq.y:5 ├── input binding: &1 ├── values │ ├── columns: column1:7!null column2:8!null column3:9!null column4:10!null column5:11 @@ -190,19 +205,22 @@ insert uniq └── unique-checks └── unique-checks-item: uniq(w) └── semi-join (hash) - ├── columns: w:12!null k:13!null + ├── columns: k:18!null v:19!null w:20!null x:21!null y:22 ├── with-scan &1 - │ ├── columns: w:12!null k:13!null + │ ├── columns: k:18!null v:19!null w:20!null x:21!null y:22 │ └── mapping: - │ ├── column3:9 => w:12 - │ └── column1:7 => k:13 + │ ├── column1:7 => k:18 + │ ├── column2:8 => v:19 + │ ├── column3:9 => w:20 + │ ├── column4:10 => x:21 + │ └── column5:11 => y:22 ├── scan uniq - │ └── columns: uniq.k:14!null uniq.w:16 + │ └── columns: uniq.k:12!null uniq.v:13 uniq.w:14 uniq.x:15 uniq.y:16 └── filters - ├── w:12 = uniq.w:16 - └── k:13 != uniq.k:14 + ├── w:20 = uniq.w:14 + └── k:18 != uniq.k:12 -# No need to plan any checks, since w, x and y are aways null. +# No need to plan any checks, since w, x and y are always null. build INSERT INTO uniq VALUES (1, 1, NULL, NULL, NULL), (2, 2, NULL, NULL, NULL) ---- @@ -232,7 +250,7 @@ insert uniq ├── arbiter constraints: unique_w unique_x_y ├── insert-mapping: │ ├── column1:7 => uniq.k:1 - │ ├── column2:8 => v:2 + │ ├── column2:8 => uniq.v:2 │ ├── column3:9 => uniq.w:3 │ ├── column4:10 => uniq.x:4 │ └── column5:11 => uniq.y:5 @@ -261,19 +279,19 @@ insert uniq │ │ │ │ │ │ │ │ │ ├── columns: column1:7!null column2:8!null column3:9!null column4:10!null column5:11!null │ │ │ │ │ │ │ │ │ └── (1, 2, 3, 4, 5) │ │ │ │ │ │ │ │ ├── scan uniq - │ │ │ │ │ │ │ │ │ └── columns: uniq.k:12!null v:13 uniq.w:14 uniq.x:15 uniq.y:16 + │ │ │ │ │ │ │ │ │ └── columns: uniq.k:12!null uniq.v:13 uniq.w:14 uniq.x:15 uniq.y:16 │ │ │ │ │ │ │ │ └── filters │ │ │ │ │ │ │ │ └── column1:7 = uniq.k:12 │ │ │ │ │ │ │ ├── scan uniq - │ │ │ │ │ │ │ │ └── columns: uniq.k:18!null v:19 uniq.w:20 uniq.x:21 uniq.y:22 + │ │ │ │ │ │ │ │ └── columns: uniq.k:18!null uniq.v:19 uniq.w:20 uniq.x:21 uniq.y:22 │ │ │ │ │ │ │ └── filters - │ │ │ │ │ │ │ └── column2:8 = v:19 + │ │ │ │ │ │ │ └── column2:8 = uniq.v:19 │ │ │ │ │ │ ├── scan uniq - │ │ │ │ │ │ │ └── columns: uniq.k:24!null v:25 uniq.w:26 uniq.x:27 uniq.y:28 + │ │ │ │ │ │ │ └── columns: uniq.k:24!null uniq.v:25 uniq.w:26 uniq.x:27 uniq.y:28 │ │ │ │ │ │ └── filters │ │ │ │ │ │ └── column3:9 = uniq.w:26 │ │ │ │ │ ├── scan uniq - │ │ │ │ │ │ └── columns: uniq.k:30!null v:31 uniq.w:32 uniq.x:33 uniq.y:34 + │ │ │ │ │ │ └── columns: uniq.k:30!null uniq.v:31 uniq.w:32 uniq.x:33 uniq.y:34 │ │ │ │ │ └── filters │ │ │ │ │ ├── column4:10 = uniq.x:33 │ │ │ │ │ └── column5:11 = uniq.y:34 @@ -314,45 +332,50 @@ insert uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: w:36!null k:37!null + │ ├── columns: k:42!null v:43!null w:44!null x:45!null y:46!null │ ├── with-scan &1 - │ │ ├── columns: w:36!null k:37!null + │ │ ├── columns: k:42!null v:43!null w:44!null x:45!null y:46!null │ │ └── mapping: - │ │ ├── column3:9 => w:36 - │ │ └── column1:7 => k:37 + │ │ ├── column1:7 => k:42 + │ │ ├── column2:8 => v:43 + │ │ ├── column3:9 => w:44 + │ │ ├── column4:10 => x:45 + │ │ └── column5:11 => y:46 │ ├── scan uniq - │ │ └── columns: uniq.k:38!null uniq.w:40 + │ │ └── columns: uniq.k:36!null uniq.v:37 uniq.w:38 uniq.x:39 uniq.y:40 │ └── filters - │ ├── w:36 = uniq.w:40 - │ └── k:37 != uniq.k:38 + │ ├── w:44 = uniq.w:38 + │ └── k:42 != uniq.k:36 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: x:44!null y:45!null k:46!null + ├── columns: k:53!null v:54!null w:55!null x:56!null y:57!null ├── with-scan &1 - │ ├── columns: x:44!null y:45!null k:46!null + │ ├── columns: k:53!null v:54!null w:55!null x:56!null y:57!null │ └── mapping: - │ ├── column4:10 => x:44 - │ ├── column5:11 => y:45 - │ └── column1:7 => k:46 + │ ├── column1:7 => k:53 + │ ├── column2:8 => v:54 + │ ├── column3:9 => w:55 + │ ├── column4:10 => x:56 + │ └── column5:11 => y:57 ├── scan uniq - │ └── columns: uniq.k:47!null uniq.x:50 uniq.y:51 + │ └── columns: uniq.k:47!null uniq.v:48 uniq.w:49 uniq.x:50 uniq.y:51 └── filters - ├── x:44 = uniq.x:50 - ├── y:45 = uniq.y:51 - └── k:46 != uniq.k:47 + ├── x:56 = uniq.x:50 + ├── y:57 = uniq.y:51 + └── k:53 != uniq.k:47 # On conflict clause references unique without index constraint. # TODO(rytaft): we should be able to remove the unique check for w in this case # (see #59119). build -INSERT INTO uniq VALUES (1, 2, 3, 4, 5) ON CONFLICT (w) DO NOTHING +INSERT INTO UNIQ VALUES (1, 2, 3, 4, 5) ON CONFLICT (W) DO NOTHING ---- insert uniq ├── columns: ├── arbiter constraints: unique_w ├── insert-mapping: │ ├── column1:7 => uniq.k:1 - │ ├── column2:8 => v:2 + │ ├── column2:8 => uniq.v:2 │ ├── column3:9 => uniq.w:3 │ ├── column4:10 => uniq.x:4 │ └── column5:11 => uniq.y:5 @@ -366,7 +389,7 @@ insert uniq │ │ │ ├── columns: column1:7!null column2:8!null column3:9!null column4:10!null column5:11!null │ │ │ └── (1, 2, 3, 4, 5) │ │ ├── scan uniq - │ │ │ └── columns: uniq.k:12!null v:13 uniq.w:14 uniq.x:15 uniq.y:16 + │ │ │ └── columns: uniq.k:12!null uniq.v:13 uniq.w:14 uniq.x:15 uniq.y:16 │ │ └── filters │ │ └── column3:9 = uniq.w:14 │ └── aggregations @@ -381,32 +404,37 @@ insert uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: w:18!null k:19!null + │ ├── columns: k:24!null v:25!null w:26!null x:27!null y:28!null │ ├── with-scan &1 - │ │ ├── columns: w:18!null k:19!null + │ │ ├── columns: k:24!null v:25!null w:26!null x:27!null y:28!null │ │ └── mapping: - │ │ ├── column3:9 => w:18 - │ │ └── column1:7 => k:19 + │ │ ├── column1:7 => k:24 + │ │ ├── column2:8 => v:25 + │ │ ├── column3:9 => w:26 + │ │ ├── column4:10 => x:27 + │ │ └── column5:11 => y:28 │ ├── scan uniq - │ │ └── columns: uniq.k:20!null uniq.w:22 + │ │ └── columns: uniq.k:18!null uniq.v:19 uniq.w:20 uniq.x:21 uniq.y:22 │ └── filters - │ ├── w:18 = uniq.w:22 - │ └── k:19 != uniq.k:20 + │ ├── w:26 = uniq.w:20 + │ └── k:24 != uniq.k:18 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: x:26!null y:27!null k:28!null + ├── columns: k:35!null v:36!null w:37!null x:38!null y:39!null ├── with-scan &1 - │ ├── columns: x:26!null y:27!null k:28!null + │ ├── columns: k:35!null v:36!null w:37!null x:38!null y:39!null │ └── mapping: - │ ├── column4:10 => x:26 - │ ├── column5:11 => y:27 - │ └── column1:7 => k:28 + │ ├── column1:7 => k:35 + │ ├── column2:8 => v:36 + │ ├── column3:9 => w:37 + │ ├── column4:10 => x:38 + │ └── column5:11 => y:39 ├── scan uniq - │ └── columns: uniq.k:29!null uniq.x:32 uniq.y:33 + │ └── columns: uniq.k:29!null uniq.v:30 uniq.w:31 uniq.x:32 uniq.y:33 └── filters - ├── x:26 = uniq.x:32 - ├── y:27 = uniq.y:33 - └── k:28 != uniq.k:29 + ├── x:38 = uniq.x:32 + ├── y:39 = uniq.y:33 + └── k:35 != uniq.k:29 exec-ddl CREATE TABLE other (k INT, v INT, w INT NOT NULL, x INT, y INT) @@ -432,32 +460,37 @@ insert uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: w:14!null k:15 + │ ├── columns: k:20 v:21 w:22!null x:23 y:24 │ ├── with-scan &1 - │ │ ├── columns: w:14!null k:15 + │ │ ├── columns: k:20 v:21 w:22!null x:23 y:24 │ │ └── mapping: - │ │ ├── other.w:9 => w:14 - │ │ └── other.k:7 => k:15 + │ │ ├── other.k:7 => k:20 + │ │ ├── other.v:8 => v:21 + │ │ ├── other.w:9 => w:22 + │ │ ├── other.x:10 => x:23 + │ │ └── other.y:11 => y:24 │ ├── scan uniq - │ │ └── columns: uniq.k:16!null uniq.w:18 + │ │ └── columns: uniq.k:14!null uniq.v:15 uniq.w:16 uniq.x:17 uniq.y:18 │ └── filters - │ ├── w:14 = uniq.w:18 - │ └── k:15 != uniq.k:16 + │ ├── w:22 = uniq.w:16 + │ └── k:20 != uniq.k:14 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: x:22 y:23 k:24 + ├── columns: k:31 v:32 w:33!null x:34 y:35 ├── with-scan &1 - │ ├── columns: x:22 y:23 k:24 + │ ├── columns: k:31 v:32 w:33!null x:34 y:35 │ └── mapping: - │ ├── other.x:10 => x:22 - │ ├── other.y:11 => y:23 - │ └── other.k:7 => k:24 + │ ├── other.k:7 => k:31 + │ ├── other.v:8 => v:32 + │ ├── other.w:9 => w:33 + │ ├── other.x:10 => x:34 + │ └── other.y:11 => y:35 ├── scan uniq - │ └── columns: uniq.k:25!null uniq.x:28 uniq.y:29 + │ └── columns: uniq.k:25!null uniq.v:26 uniq.w:27 uniq.x:28 uniq.y:29 └── filters - ├── x:22 = uniq.x:28 - ├── y:23 = uniq.y:29 - └── k:24 != uniq.k:25 + ├── x:34 = uniq.x:28 + ├── y:35 = uniq.y:29 + └── k:31 != uniq.k:25 exec-ddl CREATE TABLE uniq_overlaps_pk ( @@ -494,48 +527,51 @@ insert uniq_overlaps_pk └── unique-checks ├── unique-checks-item: uniq_overlaps_pk(b,c) │ └── semi-join (hash) - │ ├── columns: b:10!null c:11!null a:12!null + │ ├── columns: a:15!null b:16!null c:17!null d:18!null │ ├── with-scan &1 - │ │ ├── columns: b:10!null c:11!null a:12!null + │ │ ├── columns: a:15!null b:16!null c:17!null d:18!null │ │ └── mapping: - │ │ ├── column2:7 => b:10 - │ │ ├── column3:8 => c:11 - │ │ └── column1:6 => a:12 + │ │ ├── column1:6 => a:15 + │ │ ├── column2:7 => b:16 + │ │ ├── column3:8 => c:17 + │ │ └── column4:9 => d:18 │ ├── scan uniq_overlaps_pk - │ │ └── columns: uniq_overlaps_pk.a:13!null uniq_overlaps_pk.b:14!null uniq_overlaps_pk.c:15 + │ │ └── columns: uniq_overlaps_pk.a:10!null uniq_overlaps_pk.b:11!null uniq_overlaps_pk.c:12 uniq_overlaps_pk.d:13 │ └── filters - │ ├── b:10 = uniq_overlaps_pk.b:14 - │ ├── c:11 = uniq_overlaps_pk.c:15 - │ └── a:12 != uniq_overlaps_pk.a:13 + │ ├── b:16 = uniq_overlaps_pk.b:11 + │ ├── c:17 = uniq_overlaps_pk.c:12 + │ └── a:15 != uniq_overlaps_pk.a:10 ├── unique-checks-item: uniq_overlaps_pk(a) │ └── semi-join (hash) - │ ├── columns: a:18!null b:19!null + │ ├── columns: a:24!null b:25!null c:26!null d:27!null │ ├── with-scan &1 - │ │ ├── columns: a:18!null b:19!null + │ │ ├── columns: a:24!null b:25!null c:26!null d:27!null │ │ └── mapping: - │ │ ├── column1:6 => a:18 - │ │ └── column2:7 => b:19 + │ │ ├── column1:6 => a:24 + │ │ ├── column2:7 => b:25 + │ │ ├── column3:8 => c:26 + │ │ └── column4:9 => d:27 │ ├── scan uniq_overlaps_pk - │ │ └── columns: uniq_overlaps_pk.a:20!null uniq_overlaps_pk.b:21!null + │ │ └── columns: uniq_overlaps_pk.a:19!null uniq_overlaps_pk.b:20!null uniq_overlaps_pk.c:21 uniq_overlaps_pk.d:22 │ └── filters - │ ├── a:18 = uniq_overlaps_pk.a:20 - │ └── b:19 != uniq_overlaps_pk.b:21 + │ ├── a:24 = uniq_overlaps_pk.a:19 + │ └── b:25 != uniq_overlaps_pk.b:20 └── unique-checks-item: uniq_overlaps_pk(c,d) └── semi-join (hash) - ├── columns: c:25!null d:26!null a:27!null b:28!null + ├── columns: a:33!null b:34!null c:35!null d:36!null ├── with-scan &1 - │ ├── columns: c:25!null d:26!null a:27!null b:28!null + │ ├── columns: a:33!null b:34!null c:35!null d:36!null │ └── mapping: - │ ├── column3:8 => c:25 - │ ├── column4:9 => d:26 - │ ├── column1:6 => a:27 - │ └── column2:7 => b:28 + │ ├── column1:6 => a:33 + │ ├── column2:7 => b:34 + │ ├── column3:8 => c:35 + │ └── column4:9 => d:36 ├── scan uniq_overlaps_pk - │ └── columns: uniq_overlaps_pk.a:29!null uniq_overlaps_pk.b:30!null uniq_overlaps_pk.c:31 uniq_overlaps_pk.d:32 + │ └── columns: uniq_overlaps_pk.a:28!null uniq_overlaps_pk.b:29!null uniq_overlaps_pk.c:30 uniq_overlaps_pk.d:31 └── filters - ├── c:25 = uniq_overlaps_pk.c:31 - ├── d:26 = uniq_overlaps_pk.d:32 - └── (a:27 != uniq_overlaps_pk.a:29) OR (b:28 != uniq_overlaps_pk.b:30) + ├── c:35 = uniq_overlaps_pk.c:30 + ├── d:36 = uniq_overlaps_pk.d:31 + └── (a:33 != uniq_overlaps_pk.a:28) OR (b:34 != uniq_overlaps_pk.b:29) # Insert with non-constant input. # Add inequality filters for the primary key columns that are not part of each @@ -558,48 +594,51 @@ insert uniq_overlaps_pk └── unique-checks ├── unique-checks-item: uniq_overlaps_pk(b,c) │ └── semi-join (hash) - │ ├── columns: b:13 c:14 a:15 + │ ├── columns: a:18 b:19 c:20 d:21 │ ├── with-scan &1 - │ │ ├── columns: b:13 c:14 a:15 + │ │ ├── columns: a:18 b:19 c:20 d:21 │ │ └── mapping: - │ │ ├── v:7 => b:13 - │ │ ├── x:9 => c:14 - │ │ └── k:6 => a:15 + │ │ ├── k:6 => a:18 + │ │ ├── v:7 => b:19 + │ │ ├── x:9 => c:20 + │ │ └── y:10 => d:21 │ ├── scan uniq_overlaps_pk - │ │ └── columns: uniq_overlaps_pk.a:16!null uniq_overlaps_pk.b:17!null uniq_overlaps_pk.c:18 + │ │ └── columns: uniq_overlaps_pk.a:13!null uniq_overlaps_pk.b:14!null uniq_overlaps_pk.c:15 uniq_overlaps_pk.d:16 │ └── filters - │ ├── b:13 = uniq_overlaps_pk.b:17 - │ ├── c:14 = uniq_overlaps_pk.c:18 - │ └── a:15 != uniq_overlaps_pk.a:16 + │ ├── b:19 = uniq_overlaps_pk.b:14 + │ ├── c:20 = uniq_overlaps_pk.c:15 + │ └── a:18 != uniq_overlaps_pk.a:13 ├── unique-checks-item: uniq_overlaps_pk(a) │ └── semi-join (hash) - │ ├── columns: a:21 b:22 + │ ├── columns: a:27 b:28 c:29 d:30 │ ├── with-scan &1 - │ │ ├── columns: a:21 b:22 + │ │ ├── columns: a:27 b:28 c:29 d:30 │ │ └── mapping: - │ │ ├── k:6 => a:21 - │ │ └── v:7 => b:22 + │ │ ├── k:6 => a:27 + │ │ ├── v:7 => b:28 + │ │ ├── x:9 => c:29 + │ │ └── y:10 => d:30 │ ├── scan uniq_overlaps_pk - │ │ └── columns: uniq_overlaps_pk.a:23!null uniq_overlaps_pk.b:24!null + │ │ └── columns: uniq_overlaps_pk.a:22!null uniq_overlaps_pk.b:23!null uniq_overlaps_pk.c:24 uniq_overlaps_pk.d:25 │ └── filters - │ ├── a:21 = uniq_overlaps_pk.a:23 - │ └── b:22 != uniq_overlaps_pk.b:24 + │ ├── a:27 = uniq_overlaps_pk.a:22 + │ └── b:28 != uniq_overlaps_pk.b:23 └── unique-checks-item: uniq_overlaps_pk(c,d) └── semi-join (hash) - ├── columns: c:28 d:29 a:30 b:31 + ├── columns: a:36 b:37 c:38 d:39 ├── with-scan &1 - │ ├── columns: c:28 d:29 a:30 b:31 + │ ├── columns: a:36 b:37 c:38 d:39 │ └── mapping: - │ ├── x:9 => c:28 - │ ├── y:10 => d:29 - │ ├── k:6 => a:30 - │ └── v:7 => b:31 + │ ├── k:6 => a:36 + │ ├── v:7 => b:37 + │ ├── x:9 => c:38 + │ └── y:10 => d:39 ├── scan uniq_overlaps_pk - │ └── columns: uniq_overlaps_pk.a:32!null uniq_overlaps_pk.b:33!null uniq_overlaps_pk.c:34 uniq_overlaps_pk.d:35 + │ └── columns: uniq_overlaps_pk.a:31!null uniq_overlaps_pk.b:32!null uniq_overlaps_pk.c:33 uniq_overlaps_pk.d:34 └── filters - ├── c:28 = uniq_overlaps_pk.c:34 - ├── d:29 = uniq_overlaps_pk.d:35 - └── (a:30 != uniq_overlaps_pk.a:32) OR (b:31 != uniq_overlaps_pk.b:33) + ├── c:38 = uniq_overlaps_pk.c:33 + ├── d:39 = uniq_overlaps_pk.d:34 + └── (a:36 != uniq_overlaps_pk.a:31) OR (b:37 != uniq_overlaps_pk.b:32) exec-ddl CREATE TABLE uniq_hidden_pk ( @@ -638,49 +677,55 @@ insert uniq_hidden_pk └── unique-checks ├── unique-checks-item: uniq_hidden_pk(b,c) │ └── semi-join (hash) - │ ├── columns: b:12!null c:13!null rowid:14 + │ ├── columns: a:18!null b:19!null c:20!null d:21!null rowid:22 │ ├── with-scan &1 - │ │ ├── columns: b:12!null c:13!null rowid:14 + │ │ ├── columns: a:18!null b:19!null c:20!null d:21!null rowid:22 │ │ └── mapping: - │ │ ├── column2:8 => b:12 - │ │ ├── column3:9 => c:13 - │ │ └── column11:11 => rowid:14 + │ │ ├── column1:7 => a:18 + │ │ ├── column2:8 => b:19 + │ │ ├── column3:9 => c:20 + │ │ ├── column4:10 => d:21 + │ │ └── column11:11 => rowid:22 │ ├── scan uniq_hidden_pk - │ │ └── columns: uniq_hidden_pk.b:16 uniq_hidden_pk.c:17 uniq_hidden_pk.rowid:19!null + │ │ └── columns: uniq_hidden_pk.a:12 uniq_hidden_pk.b:13 uniq_hidden_pk.c:14 uniq_hidden_pk.d:15 uniq_hidden_pk.rowid:16!null │ └── filters - │ ├── b:12 = uniq_hidden_pk.b:16 - │ ├── c:13 = uniq_hidden_pk.c:17 - │ └── rowid:14 != uniq_hidden_pk.rowid:19 + │ ├── b:19 = uniq_hidden_pk.b:13 + │ ├── c:20 = uniq_hidden_pk.c:14 + │ └── rowid:22 != uniq_hidden_pk.rowid:16 ├── unique-checks-item: uniq_hidden_pk(a,b,d) │ └── semi-join (hash) - │ ├── columns: a:21!null b:22!null d:23!null rowid:24 + │ ├── columns: a:29!null b:30!null c:31!null d:32!null rowid:33 │ ├── with-scan &1 - │ │ ├── columns: a:21!null b:22!null d:23!null rowid:24 + │ │ ├── columns: a:29!null b:30!null c:31!null d:32!null rowid:33 │ │ └── mapping: - │ │ ├── column1:7 => a:21 - │ │ ├── column2:8 => b:22 - │ │ ├── column4:10 => d:23 - │ │ └── column11:11 => rowid:24 + │ │ ├── column1:7 => a:29 + │ │ ├── column2:8 => b:30 + │ │ ├── column3:9 => c:31 + │ │ ├── column4:10 => d:32 + │ │ └── column11:11 => rowid:33 │ ├── scan uniq_hidden_pk - │ │ └── columns: uniq_hidden_pk.a:25 uniq_hidden_pk.b:26 uniq_hidden_pk.d:28 uniq_hidden_pk.rowid:29!null + │ │ └── columns: uniq_hidden_pk.a:23 uniq_hidden_pk.b:24 uniq_hidden_pk.c:25 uniq_hidden_pk.d:26 uniq_hidden_pk.rowid:27!null │ └── filters - │ ├── a:21 = uniq_hidden_pk.a:25 - │ ├── b:22 = uniq_hidden_pk.b:26 - │ ├── d:23 = uniq_hidden_pk.d:28 - │ └── rowid:24 != uniq_hidden_pk.rowid:29 + │ ├── a:29 = uniq_hidden_pk.a:23 + │ ├── b:30 = uniq_hidden_pk.b:24 + │ ├── d:32 = uniq_hidden_pk.d:26 + │ └── rowid:33 != uniq_hidden_pk.rowid:27 └── unique-checks-item: uniq_hidden_pk(a) └── semi-join (hash) - ├── columns: a:31!null rowid:32 + ├── columns: a:40!null b:41!null c:42!null d:43!null rowid:44 ├── with-scan &1 - │ ├── columns: a:31!null rowid:32 + │ ├── columns: a:40!null b:41!null c:42!null d:43!null rowid:44 │ └── mapping: - │ ├── column1:7 => a:31 - │ └── column11:11 => rowid:32 + │ ├── column1:7 => a:40 + │ ├── column2:8 => b:41 + │ ├── column3:9 => c:42 + │ ├── column4:10 => d:43 + │ └── column11:11 => rowid:44 ├── scan uniq_hidden_pk - │ └── columns: uniq_hidden_pk.a:33 uniq_hidden_pk.rowid:37!null + │ └── columns: uniq_hidden_pk.a:34 uniq_hidden_pk.b:35 uniq_hidden_pk.c:36 uniq_hidden_pk.d:37 uniq_hidden_pk.rowid:38!null └── filters - ├── a:31 = uniq_hidden_pk.a:33 - └── rowid:32 != uniq_hidden_pk.rowid:37 + ├── a:40 = uniq_hidden_pk.a:34 + └── rowid:44 != uniq_hidden_pk.rowid:38 # Insert with non-constant input. # Add inequality filters for the hidden primary key column. @@ -707,46 +752,425 @@ insert uniq_hidden_pk └── unique-checks ├── unique-checks-item: uniq_hidden_pk(b,c) │ └── semi-join (hash) - │ ├── columns: b:15 c:16 rowid:17 + │ ├── columns: a:21 b:22 c:23 d:24 rowid:25 │ ├── with-scan &1 - │ │ ├── columns: b:15 c:16 rowid:17 + │ │ ├── columns: a:21 b:22 c:23 d:24 rowid:25 │ │ └── mapping: - │ │ ├── v:8 => b:15 - │ │ ├── x:10 => c:16 - │ │ └── column14:14 => rowid:17 + │ │ ├── k:7 => a:21 + │ │ ├── v:8 => b:22 + │ │ ├── x:10 => c:23 + │ │ ├── y:11 => d:24 + │ │ └── column14:14 => rowid:25 │ ├── scan uniq_hidden_pk - │ │ └── columns: uniq_hidden_pk.b:19 uniq_hidden_pk.c:20 uniq_hidden_pk.rowid:22!null + │ │ └── columns: uniq_hidden_pk.a:15 uniq_hidden_pk.b:16 uniq_hidden_pk.c:17 uniq_hidden_pk.d:18 uniq_hidden_pk.rowid:19!null │ └── filters - │ ├── b:15 = uniq_hidden_pk.b:19 - │ ├── c:16 = uniq_hidden_pk.c:20 - │ └── rowid:17 != uniq_hidden_pk.rowid:22 + │ ├── b:22 = uniq_hidden_pk.b:16 + │ ├── c:23 = uniq_hidden_pk.c:17 + │ └── rowid:25 != uniq_hidden_pk.rowid:19 ├── unique-checks-item: uniq_hidden_pk(a,b,d) │ └── semi-join (hash) - │ ├── columns: a:24 b:25 d:26 rowid:27 + │ ├── columns: a:32 b:33 c:34 d:35 rowid:36 │ ├── with-scan &1 - │ │ ├── columns: a:24 b:25 d:26 rowid:27 + │ │ ├── columns: a:32 b:33 c:34 d:35 rowid:36 │ │ └── mapping: - │ │ ├── k:7 => a:24 - │ │ ├── v:8 => b:25 - │ │ ├── y:11 => d:26 - │ │ └── column14:14 => rowid:27 + │ │ ├── k:7 => a:32 + │ │ ├── v:8 => b:33 + │ │ ├── x:10 => c:34 + │ │ ├── y:11 => d:35 + │ │ └── column14:14 => rowid:36 │ ├── scan uniq_hidden_pk - │ │ └── columns: uniq_hidden_pk.a:28 uniq_hidden_pk.b:29 uniq_hidden_pk.d:31 uniq_hidden_pk.rowid:32!null + │ │ └── columns: uniq_hidden_pk.a:26 uniq_hidden_pk.b:27 uniq_hidden_pk.c:28 uniq_hidden_pk.d:29 uniq_hidden_pk.rowid:30!null │ └── filters - │ ├── a:24 = uniq_hidden_pk.a:28 - │ ├── b:25 = uniq_hidden_pk.b:29 - │ ├── d:26 = uniq_hidden_pk.d:31 - │ └── rowid:27 != uniq_hidden_pk.rowid:32 + │ ├── a:32 = uniq_hidden_pk.a:26 + │ ├── b:33 = uniq_hidden_pk.b:27 + │ ├── d:35 = uniq_hidden_pk.d:29 + │ └── rowid:36 != uniq_hidden_pk.rowid:30 └── unique-checks-item: uniq_hidden_pk(a) └── semi-join (hash) - ├── columns: a:34 rowid:35 + ├── columns: a:43 b:44 c:45 d:46 rowid:47 ├── with-scan &1 - │ ├── columns: a:34 rowid:35 + │ ├── columns: a:43 b:44 c:45 d:46 rowid:47 │ └── mapping: - │ ├── k:7 => a:34 - │ └── column14:14 => rowid:35 + │ ├── k:7 => a:43 + │ ├── v:8 => b:44 + │ ├── x:10 => c:45 + │ ├── y:11 => d:46 + │ └── column14:14 => rowid:47 ├── scan uniq_hidden_pk - │ └── columns: uniq_hidden_pk.a:36 uniq_hidden_pk.rowid:40!null + │ └── columns: uniq_hidden_pk.a:37 uniq_hidden_pk.b:38 uniq_hidden_pk.c:39 uniq_hidden_pk.d:40 uniq_hidden_pk.rowid:41!null + └── filters + ├── a:43 = uniq_hidden_pk.a:37 + └── rowid:47 != uniq_hidden_pk.rowid:41 + +exec-ddl +CREATE TABLE uniq_partial ( + k INT PRIMARY KEY, + a INT, + b INT, + UNIQUE WITHOUT INDEX (a) WHERE b > 0 +) +---- + +# None of the inserted values have nulls. +build +INSERT INTO uniq_partial VALUES (1, 1, 1), (2, 2, 2) +---- +insert uniq_partial + ├── columns: + ├── insert-mapping: + │ ├── column1:5 => uniq_partial.k:1 + │ ├── column2:6 => uniq_partial.a:2 + │ └── column3:7 => uniq_partial.b:3 + ├── input binding: &1 + ├── values + │ ├── columns: column1:5!null column2:6!null column3:7!null + │ ├── (1, 1, 1) + │ └── (2, 2, 2) + └── unique-checks + └── unique-checks-item: uniq_partial(a) + └── semi-join (hash) + ├── columns: k:12!null a:13!null b:14!null + ├── with-scan &1 + │ ├── columns: k:12!null a:13!null b:14!null + │ └── mapping: + │ ├── column1:5 => k:12 + │ ├── column2:6 => a:13 + │ └── column3:7 => b:14 + ├── scan uniq_partial + │ └── columns: uniq_partial.k:8!null uniq_partial.a:9 uniq_partial.b:10 + └── filters + ├── a:13 = uniq_partial.a:9 + ├── b:14 > 0 + ├── uniq_partial.b:10 > 0 + └── k:12 != uniq_partial.k:8 + +# Some of the inserted values have nulls. +build +INSERT INTO uniq_partial VALUES (1, 1, 1), (2, 2, 2), (3, NULL, 3) +---- +insert uniq_partial + ├── columns: + ├── insert-mapping: + │ ├── column1:5 => uniq_partial.k:1 + │ ├── column2:6 => uniq_partial.a:2 + │ └── column3:7 => uniq_partial.b:3 + ├── input binding: &1 + ├── values + │ ├── columns: column1:5!null column2:6 column3:7!null + │ ├── (1, 1, 1) + │ ├── (2, 2, 2) + │ └── (3, NULL::INT8, 3) + └── unique-checks + └── unique-checks-item: uniq_partial(a) + └── semi-join (hash) + ├── columns: k:12!null a:13 b:14!null + ├── with-scan &1 + │ ├── columns: k:12!null a:13 b:14!null + │ └── mapping: + │ ├── column1:5 => k:12 + │ ├── column2:6 => a:13 + │ └── column3:7 => b:14 + ├── scan uniq_partial + │ └── columns: uniq_partial.k:8!null uniq_partial.a:9 uniq_partial.b:10 + └── filters + ├── a:13 = uniq_partial.a:9 + ├── b:14 > 0 + ├── uniq_partial.b:10 > 0 + └── k:12 != uniq_partial.k:8 + +# No need to plan checks for a since it's always null. +build +INSERT INTO uniq_partial VALUES (1, NULL, 1), (2, NULL, 2) +---- +insert uniq_partial + ├── columns: + ├── insert-mapping: + │ ├── column1:5 => k:1 + │ ├── column2:6 => a:2 + │ └── column3:7 => b:3 + └── values + ├── columns: column1:5!null column2:6 column3:7!null + ├── (1, NULL::INT8, 1) + └── (2, NULL::INT8, 2) + +# Insert with non-constant input. +build +INSERT INTO uniq_partial SELECT k, v, w FROM other +---- +insert uniq_partial + ├── columns: + ├── insert-mapping: + │ ├── other.k:5 => uniq_partial.k:1 + │ ├── v:6 => uniq_partial.a:2 + │ └── w:7 => uniq_partial.b:3 + ├── input binding: &1 + ├── project + │ ├── columns: other.k:5 v:6 w:7!null + │ └── scan other + │ └── columns: other.k:5 v:6 w:7!null x:8 y:9 rowid:10!null other.crdb_internal_mvcc_timestamp:11 + └── unique-checks + └── unique-checks-item: uniq_partial(a) + └── semi-join (hash) + ├── columns: k:16 a:17 b:18!null + ├── with-scan &1 + │ ├── columns: k:16 a:17 b:18!null + │ └── mapping: + │ ├── other.k:5 => k:16 + │ ├── v:6 => a:17 + │ └── w:7 => b:18 + ├── scan uniq_partial + │ └── columns: uniq_partial.k:12!null uniq_partial.a:13 uniq_partial.b:14 + └── filters + ├── a:17 = uniq_partial.a:13 + ├── b:18 > 0 + ├── uniq_partial.b:14 > 0 + └── k:16 != uniq_partial.k:12 + +exec-ddl +CREATE TABLE uniq_partial_overlaps_pk ( + a INT, + b INT, + c INT, + d INT, + PRIMARY KEY (a, b), + UNIQUE WITHOUT INDEX (c) WHERE d > 0, + UNIQUE WITHOUT INDEX (a) WHERE d > 0, + UNIQUE WITHOUT INDEX (a, b) WHERE d > 0, + UNIQUE WITHOUT INDEX (b, c) WHERE d > 0, + UNIQUE WITHOUT INDEX (a, b, c) WHERE d > 0 +) +---- + +# Insert with constant input. +# Do not build uniqueness checks when the primary key columns are a subset of +# the partial unique constraint columns. +build +INSERT INTO uniq_partial_overlaps_pk VALUES (1, 1, 1, 1), (2, 2, 2, 2) +---- +insert uniq_partial_overlaps_pk + ├── columns: + ├── insert-mapping: + │ ├── column1:6 => uniq_partial_overlaps_pk.a:1 + │ ├── column2:7 => uniq_partial_overlaps_pk.b:2 + │ ├── column3:8 => uniq_partial_overlaps_pk.c:3 + │ └── column4:9 => uniq_partial_overlaps_pk.d:4 + ├── input binding: &1 + ├── values + │ ├── columns: column1:6!null column2:7!null column3:8!null column4:9!null + │ ├── (1, 1, 1, 1) + │ └── (2, 2, 2, 2) + └── unique-checks + ├── unique-checks-item: uniq_partial_overlaps_pk(c) + │ └── semi-join (hash) + │ ├── columns: a:15!null b:16!null c:17!null d:18!null + │ ├── with-scan &1 + │ │ ├── columns: a:15!null b:16!null c:17!null d:18!null + │ │ └── mapping: + │ │ ├── column1:6 => a:15 + │ │ ├── column2:7 => b:16 + │ │ ├── column3:8 => c:17 + │ │ └── column4:9 => d:18 + │ ├── scan uniq_partial_overlaps_pk + │ │ └── columns: uniq_partial_overlaps_pk.a:10!null uniq_partial_overlaps_pk.b:11!null uniq_partial_overlaps_pk.c:12 uniq_partial_overlaps_pk.d:13 + │ └── filters + │ ├── c:17 = uniq_partial_overlaps_pk.c:12 + │ ├── d:18 > 0 + │ ├── uniq_partial_overlaps_pk.d:13 > 0 + │ └── (a:15 != uniq_partial_overlaps_pk.a:10) OR (b:16 != uniq_partial_overlaps_pk.b:11) + ├── unique-checks-item: uniq_partial_overlaps_pk(a) + │ └── semi-join (hash) + │ ├── columns: a:24!null b:25!null c:26!null d:27!null + │ ├── with-scan &1 + │ │ ├── columns: a:24!null b:25!null c:26!null d:27!null + │ │ └── mapping: + │ │ ├── column1:6 => a:24 + │ │ ├── column2:7 => b:25 + │ │ ├── column3:8 => c:26 + │ │ └── column4:9 => d:27 + │ ├── scan uniq_partial_overlaps_pk + │ │ └── columns: uniq_partial_overlaps_pk.a:19!null uniq_partial_overlaps_pk.b:20!null uniq_partial_overlaps_pk.c:21 uniq_partial_overlaps_pk.d:22 + │ └── filters + │ ├── a:24 = uniq_partial_overlaps_pk.a:19 + │ ├── d:27 > 0 + │ ├── uniq_partial_overlaps_pk.d:22 > 0 + │ └── b:25 != uniq_partial_overlaps_pk.b:20 + └── unique-checks-item: uniq_partial_overlaps_pk(b,c) + └── semi-join (hash) + ├── columns: a:33!null b:34!null c:35!null d:36!null + ├── with-scan &1 + │ ├── columns: a:33!null b:34!null c:35!null d:36!null + │ └── mapping: + │ ├── column1:6 => a:33 + │ ├── column2:7 => b:34 + │ ├── column3:8 => c:35 + │ └── column4:9 => d:36 + ├── scan uniq_partial_overlaps_pk + │ └── columns: uniq_partial_overlaps_pk.a:28!null uniq_partial_overlaps_pk.b:29!null uniq_partial_overlaps_pk.c:30 uniq_partial_overlaps_pk.d:31 + └── filters + ├── b:34 = uniq_partial_overlaps_pk.b:29 + ├── c:35 = uniq_partial_overlaps_pk.c:30 + ├── d:36 > 0 + ├── uniq_partial_overlaps_pk.d:31 > 0 + └── a:33 != uniq_partial_overlaps_pk.a:28 + +# Insert with non-constant input. +# Do not build uniqueness checks when the primary key columns are a subset of +# the partial unique constraint columns. +build +INSERT INTO uniq_partial_overlaps_pk SELECT k, v, x, y FROM other +---- +insert uniq_partial_overlaps_pk + ├── columns: + ├── insert-mapping: + │ ├── k:6 => uniq_partial_overlaps_pk.a:1 + │ ├── v:7 => uniq_partial_overlaps_pk.b:2 + │ ├── x:9 => uniq_partial_overlaps_pk.c:3 + │ └── y:10 => uniq_partial_overlaps_pk.d:4 + ├── input binding: &1 + ├── project + │ ├── columns: k:6 v:7 x:9 y:10 + │ └── scan other + │ └── columns: k:6 v:7 w:8!null x:9 y:10 rowid:11!null other.crdb_internal_mvcc_timestamp:12 + └── unique-checks + ├── unique-checks-item: uniq_partial_overlaps_pk(c) + │ └── semi-join (hash) + │ ├── columns: a:18 b:19 c:20 d:21 + │ ├── with-scan &1 + │ │ ├── columns: a:18 b:19 c:20 d:21 + │ │ └── mapping: + │ │ ├── k:6 => a:18 + │ │ ├── v:7 => b:19 + │ │ ├── x:9 => c:20 + │ │ └── y:10 => d:21 + │ ├── scan uniq_partial_overlaps_pk + │ │ └── columns: uniq_partial_overlaps_pk.a:13!null uniq_partial_overlaps_pk.b:14!null uniq_partial_overlaps_pk.c:15 uniq_partial_overlaps_pk.d:16 + │ └── filters + │ ├── c:20 = uniq_partial_overlaps_pk.c:15 + │ ├── d:21 > 0 + │ ├── uniq_partial_overlaps_pk.d:16 > 0 + │ └── (a:18 != uniq_partial_overlaps_pk.a:13) OR (b:19 != uniq_partial_overlaps_pk.b:14) + ├── unique-checks-item: uniq_partial_overlaps_pk(a) + │ └── semi-join (hash) + │ ├── columns: a:27 b:28 c:29 d:30 + │ ├── with-scan &1 + │ │ ├── columns: a:27 b:28 c:29 d:30 + │ │ └── mapping: + │ │ ├── k:6 => a:27 + │ │ ├── v:7 => b:28 + │ │ ├── x:9 => c:29 + │ │ └── y:10 => d:30 + │ ├── scan uniq_partial_overlaps_pk + │ │ └── columns: uniq_partial_overlaps_pk.a:22!null uniq_partial_overlaps_pk.b:23!null uniq_partial_overlaps_pk.c:24 uniq_partial_overlaps_pk.d:25 + │ └── filters + │ ├── a:27 = uniq_partial_overlaps_pk.a:22 + │ ├── d:30 > 0 + │ ├── uniq_partial_overlaps_pk.d:25 > 0 + │ └── b:28 != uniq_partial_overlaps_pk.b:23 + └── unique-checks-item: uniq_partial_overlaps_pk(b,c) + └── semi-join (hash) + ├── columns: a:36 b:37 c:38 d:39 + ├── with-scan &1 + │ ├── columns: a:36 b:37 c:38 d:39 + │ └── mapping: + │ ├── k:6 => a:36 + │ ├── v:7 => b:37 + │ ├── x:9 => c:38 + │ └── y:10 => d:39 + ├── scan uniq_partial_overlaps_pk + │ └── columns: uniq_partial_overlaps_pk.a:31!null uniq_partial_overlaps_pk.b:32!null uniq_partial_overlaps_pk.c:33 uniq_partial_overlaps_pk.d:34 + └── filters + ├── b:37 = uniq_partial_overlaps_pk.b:32 + ├── c:38 = uniq_partial_overlaps_pk.c:33 + ├── d:39 > 0 + ├── uniq_partial_overlaps_pk.d:34 > 0 + └── a:36 != uniq_partial_overlaps_pk.a:31 + +exec-ddl +CREATE TABLE uniq_partial_hidden_pk ( + a INT, + b INT, + c INT, + UNIQUE WITHOUT INDEX (b) WHERE c > 0 +) +---- + +# Insert with constant input. +# Add inequality filters for the hidden primary key column. +build +INSERT INTO uniq_partial_hidden_pk VALUES (1, 1), (2, 2) +---- +insert uniq_partial_hidden_pk + ├── columns: + ├── insert-mapping: + │ ├── column1:6 => uniq_partial_hidden_pk.a:1 + │ ├── column2:7 => uniq_partial_hidden_pk.b:2 + │ ├── column8:8 => uniq_partial_hidden_pk.c:3 + │ └── column9:9 => uniq_partial_hidden_pk.rowid:4 + ├── input binding: &1 + ├── project + │ ├── columns: column8:8 column9:9 column1:6!null column2:7!null + │ ├── values + │ │ ├── columns: column1:6!null column2:7!null + │ │ ├── (1, 1) + │ │ └── (2, 2) + │ └── projections + │ ├── NULL::INT8 [as=column8:8] + │ └── unique_rowid() [as=column9:9] + └── unique-checks + └── unique-checks-item: uniq_partial_hidden_pk(b) + └── semi-join (hash) + ├── columns: a:15!null b:16!null c:17 rowid:18 + ├── with-scan &1 + │ ├── columns: a:15!null b:16!null c:17 rowid:18 + │ └── mapping: + │ ├── column1:6 => a:15 + │ ├── column2:7 => b:16 + │ ├── column8:8 => c:17 + │ └── column9:9 => rowid:18 + ├── scan uniq_partial_hidden_pk + │ └── columns: uniq_partial_hidden_pk.a:10 uniq_partial_hidden_pk.b:11 uniq_partial_hidden_pk.c:12 uniq_partial_hidden_pk.rowid:13!null + └── filters + ├── b:16 = uniq_partial_hidden_pk.b:11 + ├── c:17 > 0 + ├── uniq_partial_hidden_pk.c:12 > 0 + └── rowid:18 != uniq_partial_hidden_pk.rowid:13 + +# Add inequality filters for the hidden primary key column. +build +INSERT INTO uniq_partial_hidden_pk SELECT k, v FROM other +---- +insert uniq_partial_hidden_pk + ├── columns: + ├── insert-mapping: + │ ├── k:6 => uniq_partial_hidden_pk.a:1 + │ ├── v:7 => uniq_partial_hidden_pk.b:2 + │ ├── column13:13 => uniq_partial_hidden_pk.c:3 + │ └── column14:14 => uniq_partial_hidden_pk.rowid:4 + ├── input binding: &1 + ├── project + │ ├── columns: column13:13 column14:14 k:6 v:7 + │ ├── project + │ │ ├── columns: k:6 v:7 + │ │ └── scan other + │ │ └── columns: k:6 v:7 w:8!null x:9 y:10 other.rowid:11!null other.crdb_internal_mvcc_timestamp:12 + │ └── projections + │ ├── NULL::INT8 [as=column13:13] + │ └── unique_rowid() [as=column14:14] + └── unique-checks + └── unique-checks-item: uniq_partial_hidden_pk(b) + └── semi-join (hash) + ├── columns: a:20 b:21 c:22 rowid:23 + ├── with-scan &1 + │ ├── columns: a:20 b:21 c:22 rowid:23 + │ └── mapping: + │ ├── k:6 => a:20 + │ ├── v:7 => b:21 + │ ├── column13:13 => c:22 + │ └── column14:14 => rowid:23 + ├── scan uniq_partial_hidden_pk + │ └── columns: uniq_partial_hidden_pk.a:15 uniq_partial_hidden_pk.b:16 uniq_partial_hidden_pk.c:17 uniq_partial_hidden_pk.rowid:18!null └── filters - ├── a:34 = uniq_hidden_pk.a:36 - └── rowid:35 != uniq_hidden_pk.rowid:40 + ├── b:21 = uniq_partial_hidden_pk.b:16 + ├── c:22 > 0 + ├── uniq_partial_hidden_pk.c:17 > 0 + └── rowid:23 != uniq_partial_hidden_pk.rowid:18 diff --git a/pkg/sql/opt/optbuilder/testdata/unique-checks-update b/pkg/sql/opt/optbuilder/testdata/unique-checks-update index b83fdf774dfe..77ced5f530b6 100644 --- a/pkg/sql/opt/optbuilder/testdata/unique-checks-update +++ b/pkg/sql/opt/optbuilder/testdata/unique-checks-update @@ -15,47 +15,52 @@ UPDATE uniq SET w = 1, x = 2 ---- update uniq ├── columns: - ├── fetch columns: uniq.k:7 v:8 uniq.w:9 uniq.x:10 uniq.y:11 + ├── fetch columns: uniq.k:7 uniq.v:8 uniq.w:9 uniq.x:10 uniq.y:11 ├── update-mapping: │ ├── w_new:13 => uniq.w:3 │ └── x_new:14 => uniq.x:4 ├── input binding: &1 ├── project - │ ├── columns: w_new:13!null x_new:14!null uniq.k:7!null v:8 uniq.w:9 uniq.x:10 uniq.y:11 crdb_internal_mvcc_timestamp:12 + │ ├── columns: w_new:13!null x_new:14!null uniq.k:7!null uniq.v:8 uniq.w:9 uniq.x:10 uniq.y:11 crdb_internal_mvcc_timestamp:12 │ ├── scan uniq - │ │ └── columns: uniq.k:7!null v:8 uniq.w:9 uniq.x:10 uniq.y:11 crdb_internal_mvcc_timestamp:12 + │ │ └── columns: uniq.k:7!null uniq.v:8 uniq.w:9 uniq.x:10 uniq.y:11 crdb_internal_mvcc_timestamp:12 │ └── projections │ ├── 1 [as=w_new:13] │ └── 2 [as=x_new:14] └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: w:15!null k:16!null + │ ├── columns: k:21!null v:22 w:23!null x:24!null y:25 │ ├── with-scan &1 - │ │ ├── columns: w:15!null k:16!null + │ │ ├── columns: k:21!null v:22 w:23!null x:24!null y:25 │ │ └── mapping: - │ │ ├── w_new:13 => w:15 - │ │ └── uniq.k:7 => k:16 + │ │ ├── uniq.k:7 => k:21 + │ │ ├── uniq.v:8 => v:22 + │ │ ├── w_new:13 => w:23 + │ │ ├── x_new:14 => x:24 + │ │ └── uniq.y:11 => y:25 │ ├── scan uniq - │ │ └── columns: uniq.k:17!null uniq.w:19 + │ │ └── columns: uniq.k:15!null uniq.v:16 uniq.w:17 uniq.x:18 uniq.y:19 │ └── filters - │ ├── w:15 = uniq.w:19 - │ └── k:16 != uniq.k:17 + │ ├── w:23 = uniq.w:17 + │ └── k:21 != uniq.k:15 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: x:23!null y:24 k:25!null + ├── columns: k:32!null v:33 w:34!null x:35!null y:36 ├── with-scan &1 - │ ├── columns: x:23!null y:24 k:25!null + │ ├── columns: k:32!null v:33 w:34!null x:35!null y:36 │ └── mapping: - │ ├── x_new:14 => x:23 - │ ├── uniq.y:11 => y:24 - │ └── uniq.k:7 => k:25 + │ ├── uniq.k:7 => k:32 + │ ├── uniq.v:8 => v:33 + │ ├── w_new:13 => w:34 + │ ├── x_new:14 => x:35 + │ └── uniq.y:11 => y:36 ├── scan uniq - │ └── columns: uniq.k:26!null uniq.x:29 uniq.y:30 + │ └── columns: uniq.k:26!null uniq.v:27 uniq.w:28 uniq.x:29 uniq.y:30 └── filters - ├── x:23 = uniq.x:29 - ├── y:24 = uniq.y:30 - └── k:25 != uniq.k:26 + ├── x:35 = uniq.x:29 + ├── y:36 = uniq.y:30 + └── k:32 != uniq.k:26 # No need to plan checks for w since it's aways null. build @@ -63,34 +68,36 @@ UPDATE uniq SET w = NULL, x = 1 ---- update uniq ├── columns: - ├── fetch columns: uniq.k:7 v:8 w:9 uniq.x:10 uniq.y:11 + ├── fetch columns: uniq.k:7 uniq.v:8 uniq.w:9 uniq.x:10 uniq.y:11 ├── update-mapping: - │ ├── w_new:13 => w:3 + │ ├── w_new:13 => uniq.w:3 │ └── x_new:14 => uniq.x:4 ├── input binding: &1 ├── project - │ ├── columns: w_new:13 x_new:14!null uniq.k:7!null v:8 w:9 uniq.x:10 uniq.y:11 crdb_internal_mvcc_timestamp:12 + │ ├── columns: w_new:13 x_new:14!null uniq.k:7!null uniq.v:8 uniq.w:9 uniq.x:10 uniq.y:11 crdb_internal_mvcc_timestamp:12 │ ├── scan uniq - │ │ └── columns: uniq.k:7!null v:8 w:9 uniq.x:10 uniq.y:11 crdb_internal_mvcc_timestamp:12 + │ │ └── columns: uniq.k:7!null uniq.v:8 uniq.w:9 uniq.x:10 uniq.y:11 crdb_internal_mvcc_timestamp:12 │ └── projections │ ├── NULL::INT8 [as=w_new:13] │ └── 1 [as=x_new:14] └── unique-checks └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: x:15!null y:16 k:17!null + ├── columns: k:21!null v:22 w:23 x:24!null y:25 ├── with-scan &1 - │ ├── columns: x:15!null y:16 k:17!null + │ ├── columns: k:21!null v:22 w:23 x:24!null y:25 │ └── mapping: - │ ├── x_new:14 => x:15 - │ ├── uniq.y:11 => y:16 - │ └── uniq.k:7 => k:17 + │ ├── uniq.k:7 => k:21 + │ ├── uniq.v:8 => v:22 + │ ├── w_new:13 => w:23 + │ ├── x_new:14 => x:24 + │ └── uniq.y:11 => y:25 ├── scan uniq - │ └── columns: uniq.k:18!null uniq.x:21 uniq.y:22 + │ └── columns: uniq.k:15!null uniq.v:16 uniq.w:17 uniq.x:18 uniq.y:19 └── filters - ├── x:15 = uniq.x:21 - ├── y:16 = uniq.y:22 - └── k:17 != uniq.k:18 + ├── x:24 = uniq.x:18 + ├── y:25 = uniq.y:19 + └── k:21 != uniq.k:15 # No need to plan checks for x,y since x is aways null. # Also update the primary key. @@ -99,16 +106,16 @@ UPDATE uniq SET k = 1, w = 2, x = NULL ---- update uniq ├── columns: - ├── fetch columns: uniq.k:7 v:8 uniq.w:9 x:10 y:11 + ├── fetch columns: uniq.k:7 uniq.v:8 uniq.w:9 uniq.x:10 uniq.y:11 ├── update-mapping: │ ├── k_new:13 => uniq.k:1 │ ├── w_new:14 => uniq.w:3 - │ └── x_new:15 => x:4 + │ └── x_new:15 => uniq.x:4 ├── input binding: &1 ├── project - │ ├── columns: k_new:13!null w_new:14!null x_new:15 uniq.k:7!null v:8 uniq.w:9 x:10 y:11 crdb_internal_mvcc_timestamp:12 + │ ├── columns: k_new:13!null w_new:14!null x_new:15 uniq.k:7!null uniq.v:8 uniq.w:9 uniq.x:10 uniq.y:11 crdb_internal_mvcc_timestamp:12 │ ├── scan uniq - │ │ └── columns: uniq.k:7!null v:8 uniq.w:9 x:10 y:11 crdb_internal_mvcc_timestamp:12 + │ │ └── columns: uniq.k:7!null uniq.v:8 uniq.w:9 uniq.x:10 uniq.y:11 crdb_internal_mvcc_timestamp:12 │ └── projections │ ├── 1 [as=k_new:13] │ ├── 2 [as=w_new:14] @@ -116,17 +123,20 @@ update uniq └── unique-checks └── unique-checks-item: uniq(w) └── semi-join (hash) - ├── columns: w:16!null k:17!null + ├── columns: k:22!null v:23 w:24!null x:25 y:26 ├── with-scan &1 - │ ├── columns: w:16!null k:17!null + │ ├── columns: k:22!null v:23 w:24!null x:25 y:26 │ └── mapping: - │ ├── w_new:14 => w:16 - │ └── k_new:13 => k:17 + │ ├── k_new:13 => k:22 + │ ├── uniq.v:8 => v:23 + │ ├── w_new:14 => w:24 + │ ├── x_new:15 => x:25 + │ └── uniq.y:11 => y:26 ├── scan uniq - │ └── columns: uniq.k:18!null uniq.w:20 + │ └── columns: uniq.k:16!null uniq.v:17 uniq.w:18 uniq.x:19 uniq.y:20 └── filters - ├── w:16 = uniq.w:20 - └── k:17 != uniq.k:18 + ├── w:24 = uniq.w:18 + └── k:22 != uniq.k:16 # No need to plan checks for x,y since y is aways null. build @@ -134,17 +144,17 @@ UPDATE uniq SET w = 1, y = NULL WHERE k = 1 ---- update uniq ├── columns: - ├── fetch columns: uniq.k:7 v:8 uniq.w:9 x:10 y:11 + ├── fetch columns: uniq.k:7 uniq.v:8 uniq.w:9 uniq.x:10 uniq.y:11 ├── update-mapping: │ ├── w_new:13 => uniq.w:3 - │ └── y_new:14 => y:5 + │ └── y_new:14 => uniq.y:5 ├── input binding: &1 ├── project - │ ├── columns: w_new:13!null y_new:14 uniq.k:7!null v:8 uniq.w:9 x:10 y:11 crdb_internal_mvcc_timestamp:12 + │ ├── columns: w_new:13!null y_new:14 uniq.k:7!null uniq.v:8 uniq.w:9 uniq.x:10 uniq.y:11 crdb_internal_mvcc_timestamp:12 │ ├── select - │ │ ├── columns: uniq.k:7!null v:8 uniq.w:9 x:10 y:11 crdb_internal_mvcc_timestamp:12 + │ │ ├── columns: uniq.k:7!null uniq.v:8 uniq.w:9 uniq.x:10 uniq.y:11 crdb_internal_mvcc_timestamp:12 │ │ ├── scan uniq - │ │ │ └── columns: uniq.k:7!null v:8 uniq.w:9 x:10 y:11 crdb_internal_mvcc_timestamp:12 + │ │ │ └── columns: uniq.k:7!null uniq.v:8 uniq.w:9 uniq.x:10 uniq.y:11 crdb_internal_mvcc_timestamp:12 │ │ └── filters │ │ └── uniq.k:7 = 1 │ └── projections @@ -153,17 +163,20 @@ update uniq └── unique-checks └── unique-checks-item: uniq(w) └── semi-join (hash) - ├── columns: w:15!null k:16!null + ├── columns: k:21!null v:22 w:23!null x:24 y:25 ├── with-scan &1 - │ ├── columns: w:15!null k:16!null + │ ├── columns: k:21!null v:22 w:23!null x:24 y:25 │ └── mapping: - │ ├── w_new:13 => w:15 - │ └── uniq.k:7 => k:16 + │ ├── uniq.k:7 => k:21 + │ ├── uniq.v:8 => v:22 + │ ├── w_new:13 => w:23 + │ ├── uniq.x:10 => x:24 + │ └── y_new:14 => y:25 ├── scan uniq - │ └── columns: uniq.k:17!null uniq.w:19 + │ └── columns: uniq.k:15!null uniq.v:16 uniq.w:17 uniq.x:18 uniq.y:19 └── filters - ├── w:15 = uniq.w:19 - └── k:16 != uniq.k:17 + ├── w:23 = uniq.w:17 + └── k:21 != uniq.k:15 # No need to plan checks since none of the columns requiring checks are updated. build @@ -236,32 +249,37 @@ update uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: w:20!null k:21!null + │ ├── columns: k:26!null v:27 w:28!null x:29 y:30 │ ├── with-scan &1 - │ │ ├── columns: w:20!null k:21!null + │ │ ├── columns: k:26!null v:27 w:28!null x:29 y:30 │ │ └── mapping: - │ │ ├── other.w:15 => w:20 - │ │ └── uniq.k:7 => k:21 + │ │ ├── uniq.k:7 => k:26 + │ │ ├── uniq.v:8 => v:27 + │ │ ├── other.w:15 => w:28 + │ │ ├── other.x:16 => x:29 + │ │ └── uniq.y:11 => y:30 │ ├── scan uniq - │ │ └── columns: uniq.k:22!null uniq.w:24 + │ │ └── columns: uniq.k:20!null uniq.v:21 uniq.w:22 uniq.x:23 uniq.y:24 │ └── filters - │ ├── w:20 = uniq.w:24 - │ └── k:21 != uniq.k:22 + │ ├── w:28 = uniq.w:22 + │ └── k:26 != uniq.k:20 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: x:28 y:29 k:30!null + ├── columns: k:37!null v:38 w:39!null x:40 y:41 ├── with-scan &1 - │ ├── columns: x:28 y:29 k:30!null + │ ├── columns: k:37!null v:38 w:39!null x:40 y:41 │ └── mapping: - │ ├── other.x:16 => x:28 - │ ├── uniq.y:11 => y:29 - │ └── uniq.k:7 => k:30 + │ ├── uniq.k:7 => k:37 + │ ├── uniq.v:8 => v:38 + │ ├── other.w:15 => w:39 + │ ├── other.x:16 => x:40 + │ └── uniq.y:11 => y:41 ├── scan uniq - │ └── columns: uniq.k:31!null uniq.x:34 uniq.y:35 + │ └── columns: uniq.k:31!null uniq.v:32 uniq.w:33 uniq.x:34 uniq.y:35 └── filters - ├── x:28 = uniq.x:34 - ├── y:29 = uniq.y:35 - └── k:30 != uniq.k:31 + ├── x:40 = uniq.x:34 + ├── y:41 = uniq.y:35 + └── k:37 != uniq.k:31 exec-ddl CREATE TABLE uniq_overlaps_pk ( @@ -308,48 +326,51 @@ update uniq_overlaps_pk └── unique-checks ├── unique-checks-item: uniq_overlaps_pk(b,c) │ └── semi-join (hash) - │ ├── columns: b:15!null c:16!null a:17!null + │ ├── columns: a:20!null b:21!null c:22!null d:23!null │ ├── with-scan &1 - │ │ ├── columns: b:15!null c:16!null a:17!null + │ │ ├── columns: a:20!null b:21!null c:22!null d:23!null │ │ └── mapping: - │ │ ├── b_new:12 => b:15 - │ │ ├── c_new:13 => c:16 - │ │ └── a_new:11 => a:17 + │ │ ├── a_new:11 => a:20 + │ │ ├── b_new:12 => b:21 + │ │ ├── c_new:13 => c:22 + │ │ └── d_new:14 => d:23 │ ├── scan uniq_overlaps_pk - │ │ └── columns: uniq_overlaps_pk.a:18!null uniq_overlaps_pk.b:19!null uniq_overlaps_pk.c:20 + │ │ └── columns: uniq_overlaps_pk.a:15!null uniq_overlaps_pk.b:16!null uniq_overlaps_pk.c:17 uniq_overlaps_pk.d:18 │ └── filters - │ ├── b:15 = uniq_overlaps_pk.b:19 - │ ├── c:16 = uniq_overlaps_pk.c:20 - │ └── a:17 != uniq_overlaps_pk.a:18 + │ ├── b:21 = uniq_overlaps_pk.b:16 + │ ├── c:22 = uniq_overlaps_pk.c:17 + │ └── a:20 != uniq_overlaps_pk.a:15 ├── unique-checks-item: uniq_overlaps_pk(a) │ └── semi-join (hash) - │ ├── columns: a:23!null b:24!null + │ ├── columns: a:29!null b:30!null c:31!null d:32!null │ ├── with-scan &1 - │ │ ├── columns: a:23!null b:24!null + │ │ ├── columns: a:29!null b:30!null c:31!null d:32!null │ │ └── mapping: - │ │ ├── a_new:11 => a:23 - │ │ └── b_new:12 => b:24 + │ │ ├── a_new:11 => a:29 + │ │ ├── b_new:12 => b:30 + │ │ ├── c_new:13 => c:31 + │ │ └── d_new:14 => d:32 │ ├── scan uniq_overlaps_pk - │ │ └── columns: uniq_overlaps_pk.a:25!null uniq_overlaps_pk.b:26!null + │ │ └── columns: uniq_overlaps_pk.a:24!null uniq_overlaps_pk.b:25!null uniq_overlaps_pk.c:26 uniq_overlaps_pk.d:27 │ └── filters - │ ├── a:23 = uniq_overlaps_pk.a:25 - │ └── b:24 != uniq_overlaps_pk.b:26 + │ ├── a:29 = uniq_overlaps_pk.a:24 + │ └── b:30 != uniq_overlaps_pk.b:25 └── unique-checks-item: uniq_overlaps_pk(c,d) └── semi-join (hash) - ├── columns: c:30!null d:31!null a:32!null b:33!null + ├── columns: a:38!null b:39!null c:40!null d:41!null ├── with-scan &1 - │ ├── columns: c:30!null d:31!null a:32!null b:33!null + │ ├── columns: a:38!null b:39!null c:40!null d:41!null │ └── mapping: - │ ├── c_new:13 => c:30 - │ ├── d_new:14 => d:31 - │ ├── a_new:11 => a:32 - │ └── b_new:12 => b:33 + │ ├── a_new:11 => a:38 + │ ├── b_new:12 => b:39 + │ ├── c_new:13 => c:40 + │ └── d_new:14 => d:41 ├── scan uniq_overlaps_pk - │ └── columns: uniq_overlaps_pk.a:34!null uniq_overlaps_pk.b:35!null uniq_overlaps_pk.c:36 uniq_overlaps_pk.d:37 + │ └── columns: uniq_overlaps_pk.a:33!null uniq_overlaps_pk.b:34!null uniq_overlaps_pk.c:35 uniq_overlaps_pk.d:36 └── filters - ├── c:30 = uniq_overlaps_pk.c:36 - ├── d:31 = uniq_overlaps_pk.d:37 - └── (a:32 != uniq_overlaps_pk.a:34) OR (b:33 != uniq_overlaps_pk.b:35) + ├── c:40 = uniq_overlaps_pk.c:35 + ├── d:41 = uniq_overlaps_pk.d:36 + └── (a:38 != uniq_overlaps_pk.a:33) OR (b:39 != uniq_overlaps_pk.b:34) # Update with non-constant input. # No need to add a check for b,c since those columns weren't updated. @@ -399,33 +420,35 @@ update uniq_overlaps_pk └── unique-checks ├── unique-checks-item: uniq_overlaps_pk(a) │ └── semi-join (hash) - │ ├── columns: a:18 b:19!null + │ ├── columns: a:23 b:24!null c:25 d:26 │ ├── with-scan &1 - │ │ ├── columns: a:18 b:19!null + │ │ ├── columns: a:23 b:24!null c:25 d:26 │ │ └── mapping: - │ │ ├── k:11 => a:18 - │ │ └── uniq_overlaps_pk.b:7 => b:19 + │ │ ├── k:11 => a:23 + │ │ ├── uniq_overlaps_pk.b:7 => b:24 + │ │ ├── uniq_overlaps_pk.c:8 => c:25 + │ │ └── v:12 => d:26 │ ├── scan uniq_overlaps_pk - │ │ └── columns: uniq_overlaps_pk.a:20!null uniq_overlaps_pk.b:21!null + │ │ └── columns: uniq_overlaps_pk.a:18!null uniq_overlaps_pk.b:19!null uniq_overlaps_pk.c:20 uniq_overlaps_pk.d:21 │ └── filters - │ ├── a:18 = uniq_overlaps_pk.a:20 - │ └── b:19 != uniq_overlaps_pk.b:21 + │ ├── a:23 = uniq_overlaps_pk.a:18 + │ └── b:24 != uniq_overlaps_pk.b:19 └── unique-checks-item: uniq_overlaps_pk(c,d) └── semi-join (hash) - ├── columns: c:25 d:26 a:27 b:28!null + ├── columns: a:32 b:33!null c:34 d:35 ├── with-scan &1 - │ ├── columns: c:25 d:26 a:27 b:28!null + │ ├── columns: a:32 b:33!null c:34 d:35 │ └── mapping: - │ ├── uniq_overlaps_pk.c:8 => c:25 - │ ├── v:12 => d:26 - │ ├── k:11 => a:27 - │ └── uniq_overlaps_pk.b:7 => b:28 + │ ├── k:11 => a:32 + │ ├── uniq_overlaps_pk.b:7 => b:33 + │ ├── uniq_overlaps_pk.c:8 => c:34 + │ └── v:12 => d:35 ├── scan uniq_overlaps_pk - │ └── columns: uniq_overlaps_pk.a:29!null uniq_overlaps_pk.b:30!null uniq_overlaps_pk.c:31 uniq_overlaps_pk.d:32 + │ └── columns: uniq_overlaps_pk.a:27!null uniq_overlaps_pk.b:28!null uniq_overlaps_pk.c:29 uniq_overlaps_pk.d:30 └── filters - ├── c:25 = uniq_overlaps_pk.c:31 - ├── d:26 = uniq_overlaps_pk.d:32 - └── (a:27 != uniq_overlaps_pk.a:29) OR (b:28 != uniq_overlaps_pk.b:30) + ├── c:34 = uniq_overlaps_pk.c:29 + ├── d:35 = uniq_overlaps_pk.d:30 + └── (a:32 != uniq_overlaps_pk.a:27) OR (b:33 != uniq_overlaps_pk.b:28) exec-ddl CREATE TABLE uniq_hidden_pk ( @@ -447,47 +470,51 @@ UPDATE uniq_hidden_pk SET a = 1 ---- update uniq_hidden_pk ├── columns: - ├── fetch columns: uniq_hidden_pk.a:7 uniq_hidden_pk.b:8 c:9 uniq_hidden_pk.d:10 uniq_hidden_pk.rowid:11 + ├── fetch columns: uniq_hidden_pk.a:7 uniq_hidden_pk.b:8 uniq_hidden_pk.c:9 uniq_hidden_pk.d:10 uniq_hidden_pk.rowid:11 ├── update-mapping: │ └── a_new:13 => uniq_hidden_pk.a:1 ├── input binding: &1 ├── project - │ ├── columns: a_new:13!null uniq_hidden_pk.a:7 uniq_hidden_pk.b:8 c:9 uniq_hidden_pk.d:10 uniq_hidden_pk.rowid:11!null crdb_internal_mvcc_timestamp:12 + │ ├── columns: a_new:13!null uniq_hidden_pk.a:7 uniq_hidden_pk.b:8 uniq_hidden_pk.c:9 uniq_hidden_pk.d:10 uniq_hidden_pk.rowid:11!null crdb_internal_mvcc_timestamp:12 │ ├── scan uniq_hidden_pk - │ │ └── columns: uniq_hidden_pk.a:7 uniq_hidden_pk.b:8 c:9 uniq_hidden_pk.d:10 uniq_hidden_pk.rowid:11!null crdb_internal_mvcc_timestamp:12 + │ │ └── columns: uniq_hidden_pk.a:7 uniq_hidden_pk.b:8 uniq_hidden_pk.c:9 uniq_hidden_pk.d:10 uniq_hidden_pk.rowid:11!null crdb_internal_mvcc_timestamp:12 │ └── projections │ └── 1 [as=a_new:13] └── unique-checks ├── unique-checks-item: uniq_hidden_pk(a,b,d) │ └── semi-join (hash) - │ ├── columns: a:14!null b:15 d:16 rowid:17!null + │ ├── columns: a:20!null b:21 c:22 d:23 rowid:24!null │ ├── with-scan &1 - │ │ ├── columns: a:14!null b:15 d:16 rowid:17!null + │ │ ├── columns: a:20!null b:21 c:22 d:23 rowid:24!null │ │ └── mapping: - │ │ ├── a_new:13 => a:14 - │ │ ├── uniq_hidden_pk.b:8 => b:15 - │ │ ├── uniq_hidden_pk.d:10 => d:16 - │ │ └── uniq_hidden_pk.rowid:11 => rowid:17 + │ │ ├── a_new:13 => a:20 + │ │ ├── uniq_hidden_pk.b:8 => b:21 + │ │ ├── uniq_hidden_pk.c:9 => c:22 + │ │ ├── uniq_hidden_pk.d:10 => d:23 + │ │ └── uniq_hidden_pk.rowid:11 => rowid:24 │ ├── scan uniq_hidden_pk - │ │ └── columns: uniq_hidden_pk.a:18 uniq_hidden_pk.b:19 uniq_hidden_pk.d:21 uniq_hidden_pk.rowid:22!null + │ │ └── columns: uniq_hidden_pk.a:14 uniq_hidden_pk.b:15 uniq_hidden_pk.c:16 uniq_hidden_pk.d:17 uniq_hidden_pk.rowid:18!null │ └── filters - │ ├── a:14 = uniq_hidden_pk.a:18 - │ ├── b:15 = uniq_hidden_pk.b:19 - │ ├── d:16 = uniq_hidden_pk.d:21 - │ └── rowid:17 != uniq_hidden_pk.rowid:22 + │ ├── a:20 = uniq_hidden_pk.a:14 + │ ├── b:21 = uniq_hidden_pk.b:15 + │ ├── d:23 = uniq_hidden_pk.d:17 + │ └── rowid:24 != uniq_hidden_pk.rowid:18 └── unique-checks-item: uniq_hidden_pk(a) └── semi-join (hash) - ├── columns: a:24!null rowid:25!null + ├── columns: a:31!null b:32 c:33 d:34 rowid:35!null ├── with-scan &1 - │ ├── columns: a:24!null rowid:25!null + │ ├── columns: a:31!null b:32 c:33 d:34 rowid:35!null │ └── mapping: - │ ├── a_new:13 => a:24 - │ └── uniq_hidden_pk.rowid:11 => rowid:25 + │ ├── a_new:13 => a:31 + │ ├── uniq_hidden_pk.b:8 => b:32 + │ ├── uniq_hidden_pk.c:9 => c:33 + │ ├── uniq_hidden_pk.d:10 => d:34 + │ └── uniq_hidden_pk.rowid:11 => rowid:35 ├── scan uniq_hidden_pk - │ └── columns: uniq_hidden_pk.a:26 uniq_hidden_pk.rowid:30!null + │ └── columns: uniq_hidden_pk.a:25 uniq_hidden_pk.b:26 uniq_hidden_pk.c:27 uniq_hidden_pk.d:28 uniq_hidden_pk.rowid:29!null └── filters - ├── a:24 = uniq_hidden_pk.a:26 - └── rowid:25 != uniq_hidden_pk.rowid:30 + ├── a:31 = uniq_hidden_pk.a:25 + └── rowid:35 != uniq_hidden_pk.rowid:29 # Update with non-constant input. # No need to add a check for b,c since those columns weren't updated. @@ -497,45 +524,49 @@ UPDATE uniq_hidden_pk SET a = k FROM other ---- update uniq_hidden_pk ├── columns: - ├── fetch columns: uniq_hidden_pk.a:7 uniq_hidden_pk.b:8 c:9 uniq_hidden_pk.d:10 uniq_hidden_pk.rowid:11 + ├── fetch columns: uniq_hidden_pk.a:7 uniq_hidden_pk.b:8 uniq_hidden_pk.c:9 uniq_hidden_pk.d:10 uniq_hidden_pk.rowid:11 ├── update-mapping: │ └── k:13 => uniq_hidden_pk.a:1 ├── input binding: &1 ├── inner-join (cross) - │ ├── columns: uniq_hidden_pk.a:7 uniq_hidden_pk.b:8 c:9 uniq_hidden_pk.d:10 uniq_hidden_pk.rowid:11!null uniq_hidden_pk.crdb_internal_mvcc_timestamp:12 k:13 v:14 w:15!null x:16 y:17 other.rowid:18!null other.crdb_internal_mvcc_timestamp:19 + │ ├── columns: uniq_hidden_pk.a:7 uniq_hidden_pk.b:8 uniq_hidden_pk.c:9 uniq_hidden_pk.d:10 uniq_hidden_pk.rowid:11!null uniq_hidden_pk.crdb_internal_mvcc_timestamp:12 k:13 v:14 w:15!null x:16 y:17 other.rowid:18!null other.crdb_internal_mvcc_timestamp:19 │ ├── scan uniq_hidden_pk - │ │ └── columns: uniq_hidden_pk.a:7 uniq_hidden_pk.b:8 c:9 uniq_hidden_pk.d:10 uniq_hidden_pk.rowid:11!null uniq_hidden_pk.crdb_internal_mvcc_timestamp:12 + │ │ └── columns: uniq_hidden_pk.a:7 uniq_hidden_pk.b:8 uniq_hidden_pk.c:9 uniq_hidden_pk.d:10 uniq_hidden_pk.rowid:11!null uniq_hidden_pk.crdb_internal_mvcc_timestamp:12 │ ├── scan other │ │ └── columns: k:13 v:14 w:15!null x:16 y:17 other.rowid:18!null other.crdb_internal_mvcc_timestamp:19 │ └── filters (true) └── unique-checks ├── unique-checks-item: uniq_hidden_pk(a,b,d) │ └── semi-join (hash) - │ ├── columns: a:20 b:21 d:22 rowid:23!null + │ ├── columns: a:26 b:27 c:28 d:29 rowid:30!null │ ├── with-scan &1 - │ │ ├── columns: a:20 b:21 d:22 rowid:23!null + │ │ ├── columns: a:26 b:27 c:28 d:29 rowid:30!null │ │ └── mapping: - │ │ ├── k:13 => a:20 - │ │ ├── uniq_hidden_pk.b:8 => b:21 - │ │ ├── uniq_hidden_pk.d:10 => d:22 - │ │ └── uniq_hidden_pk.rowid:11 => rowid:23 + │ │ ├── k:13 => a:26 + │ │ ├── uniq_hidden_pk.b:8 => b:27 + │ │ ├── uniq_hidden_pk.c:9 => c:28 + │ │ ├── uniq_hidden_pk.d:10 => d:29 + │ │ └── uniq_hidden_pk.rowid:11 => rowid:30 │ ├── scan uniq_hidden_pk - │ │ └── columns: uniq_hidden_pk.a:24 uniq_hidden_pk.b:25 uniq_hidden_pk.d:27 uniq_hidden_pk.rowid:28!null + │ │ └── columns: uniq_hidden_pk.a:20 uniq_hidden_pk.b:21 uniq_hidden_pk.c:22 uniq_hidden_pk.d:23 uniq_hidden_pk.rowid:24!null │ └── filters - │ ├── a:20 = uniq_hidden_pk.a:24 - │ ├── b:21 = uniq_hidden_pk.b:25 - │ ├── d:22 = uniq_hidden_pk.d:27 - │ └── rowid:23 != uniq_hidden_pk.rowid:28 + │ ├── a:26 = uniq_hidden_pk.a:20 + │ ├── b:27 = uniq_hidden_pk.b:21 + │ ├── d:29 = uniq_hidden_pk.d:23 + │ └── rowid:30 != uniq_hidden_pk.rowid:24 └── unique-checks-item: uniq_hidden_pk(a) └── semi-join (hash) - ├── columns: a:30 rowid:31!null + ├── columns: a:37 b:38 c:39 d:40 rowid:41!null ├── with-scan &1 - │ ├── columns: a:30 rowid:31!null + │ ├── columns: a:37 b:38 c:39 d:40 rowid:41!null │ └── mapping: - │ ├── k:13 => a:30 - │ └── uniq_hidden_pk.rowid:11 => rowid:31 + │ ├── k:13 => a:37 + │ ├── uniq_hidden_pk.b:8 => b:38 + │ ├── uniq_hidden_pk.c:9 => c:39 + │ ├── uniq_hidden_pk.d:10 => d:40 + │ └── uniq_hidden_pk.rowid:11 => rowid:41 ├── scan uniq_hidden_pk - │ └── columns: uniq_hidden_pk.a:32 uniq_hidden_pk.rowid:36!null + │ └── columns: uniq_hidden_pk.a:31 uniq_hidden_pk.b:32 uniq_hidden_pk.c:33 uniq_hidden_pk.d:34 uniq_hidden_pk.rowid:35!null └── filters - ├── a:30 = uniq_hidden_pk.a:32 - └── rowid:31 != uniq_hidden_pk.rowid:36 + ├── a:37 = uniq_hidden_pk.a:31 + └── rowid:41 != uniq_hidden_pk.rowid:35 diff --git a/pkg/sql/opt/optbuilder/testdata/unique-checks-upsert b/pkg/sql/opt/optbuilder/testdata/unique-checks-upsert index bc202fc5242b..2d4e220a5389 100644 --- a/pkg/sql/opt/optbuilder/testdata/unique-checks-upsert +++ b/pkg/sql/opt/optbuilder/testdata/unique-checks-upsert @@ -21,23 +21,23 @@ upsert uniq ├── columns: ├── arbiter indexes: primary ├── canary column: uniq.k:12 - ├── fetch columns: uniq.k:12 v:13 uniq.w:14 uniq.x:15 uniq.y:16 + ├── fetch columns: uniq.k:12 uniq.v:13 uniq.w:14 uniq.x:15 uniq.y:16 ├── insert-mapping: │ ├── column1:7 => uniq.k:1 - │ ├── column2:8 => v:2 + │ ├── column2:8 => uniq.v:2 │ ├── column3:9 => uniq.w:3 │ ├── column4:10 => uniq.x:4 │ └── column5:11 => uniq.y:5 ├── update-mapping: - │ ├── column2:8 => v:2 + │ ├── column2:8 => uniq.v:2 │ ├── column3:9 => uniq.w:3 │ ├── column4:10 => uniq.x:4 │ └── column5:11 => uniq.y:5 ├── input binding: &1 ├── project - │ ├── columns: upsert_k:18 column1:7!null column2:8!null column3:9!null column4:10!null column5:11!null uniq.k:12 v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 + │ ├── columns: upsert_k:18 column1:7!null column2:8!null column3:9!null column4:10!null column5:11!null uniq.k:12 uniq.v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 │ ├── left-join (hash) - │ │ ├── columns: column1:7!null column2:8!null column3:9!null column4:10!null column5:11!null uniq.k:12 v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 + │ │ ├── columns: column1:7!null column2:8!null column3:9!null column4:10!null column5:11!null uniq.k:12 uniq.v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 │ │ ├── ensure-upsert-distinct-on │ │ │ ├── columns: column1:7!null column2:8!null column3:9!null column4:10!null column5:11!null │ │ │ ├── grouping columns: column1:7!null @@ -55,7 +55,7 @@ upsert uniq │ │ │ └── first-agg [as=column5:11] │ │ │ └── column5:11 │ │ ├── scan uniq - │ │ │ └── columns: uniq.k:12!null v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 + │ │ │ └── columns: uniq.k:12!null uniq.v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 │ │ └── filters │ │ └── column1:7 = uniq.k:12 │ └── projections @@ -63,32 +63,37 @@ upsert uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: w:19!null k:20 + │ ├── columns: k:25 v:26!null w:27!null x:28!null y:29!null │ ├── with-scan &1 - │ │ ├── columns: w:19!null k:20 + │ │ ├── columns: k:25 v:26!null w:27!null x:28!null y:29!null │ │ └── mapping: - │ │ ├── column3:9 => w:19 - │ │ └── upsert_k:18 => k:20 + │ │ ├── upsert_k:18 => k:25 + │ │ ├── column2:8 => v:26 + │ │ ├── column3:9 => w:27 + │ │ ├── column4:10 => x:28 + │ │ └── column5:11 => y:29 │ ├── scan uniq - │ │ └── columns: uniq.k:21!null uniq.w:23 + │ │ └── columns: uniq.k:19!null uniq.v:20 uniq.w:21 uniq.x:22 uniq.y:23 │ └── filters - │ ├── w:19 = uniq.w:23 - │ └── k:20 != uniq.k:21 + │ ├── w:27 = uniq.w:21 + │ └── k:25 != uniq.k:19 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: x:27!null y:28!null k:29 + ├── columns: k:36 v:37!null w:38!null x:39!null y:40!null ├── with-scan &1 - │ ├── columns: x:27!null y:28!null k:29 + │ ├── columns: k:36 v:37!null w:38!null x:39!null y:40!null │ └── mapping: - │ ├── column4:10 => x:27 - │ ├── column5:11 => y:28 - │ └── upsert_k:18 => k:29 + │ ├── upsert_k:18 => k:36 + │ ├── column2:8 => v:37 + │ ├── column3:9 => w:38 + │ ├── column4:10 => x:39 + │ └── column5:11 => y:40 ├── scan uniq - │ └── columns: uniq.k:30!null uniq.x:33 uniq.y:34 + │ └── columns: uniq.k:30!null uniq.v:31 uniq.w:32 uniq.x:33 uniq.y:34 └── filters - ├── x:27 = uniq.x:33 - ├── y:28 = uniq.y:34 - └── k:29 != uniq.k:30 + ├── x:39 = uniq.x:33 + ├── y:40 = uniq.y:34 + └── k:36 != uniq.k:30 # TODO(rytaft): The default value for x is NULL, and we're not updating either # x or y. Therefore, we could avoid planning checks for (x,y) (see #58300). @@ -99,21 +104,21 @@ upsert uniq ├── columns: ├── arbiter indexes: primary ├── canary column: uniq.k:12 - ├── fetch columns: uniq.k:12 v:13 uniq.w:14 uniq.x:15 uniq.y:16 + ├── fetch columns: uniq.k:12 uniq.v:13 uniq.w:14 uniq.x:15 uniq.y:16 ├── insert-mapping: │ ├── column1:7 => uniq.k:1 - │ ├── column2:8 => v:2 + │ ├── column2:8 => uniq.v:2 │ ├── column3:9 => uniq.w:3 │ ├── column10:10 => uniq.x:4 │ └── column11:11 => uniq.y:5 ├── update-mapping: - │ ├── column2:8 => v:2 + │ ├── column2:8 => uniq.v:2 │ └── column3:9 => uniq.w:3 ├── input binding: &1 ├── project - │ ├── columns: upsert_k:18 upsert_x:19 upsert_y:20 column1:7!null column2:8!null column3:9!null column10:10 column11:11!null uniq.k:12 v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 + │ ├── columns: upsert_k:18 upsert_x:19 upsert_y:20 column1:7!null column2:8!null column3:9!null column10:10 column11:11!null uniq.k:12 uniq.v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 │ ├── left-join (hash) - │ │ ├── columns: column1:7!null column2:8!null column3:9!null column10:10 column11:11!null uniq.k:12 v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 + │ │ ├── columns: column1:7!null column2:8!null column3:9!null column10:10 column11:11!null uniq.k:12 uniq.v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 │ │ ├── ensure-upsert-distinct-on │ │ │ ├── columns: column1:7!null column2:8!null column3:9!null column10:10 column11:11!null │ │ │ ├── grouping columns: column1:7!null @@ -136,7 +141,7 @@ upsert uniq │ │ │ └── first-agg [as=column11:11] │ │ │ └── column11:11 │ │ ├── scan uniq - │ │ │ └── columns: uniq.k:12!null v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 + │ │ │ └── columns: uniq.k:12!null uniq.v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 │ │ └── filters │ │ └── column1:7 = uniq.k:12 │ └── projections @@ -146,32 +151,37 @@ upsert uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: w:21!null k:22 + │ ├── columns: k:27 v:28!null w:29!null x:30 y:31 │ ├── with-scan &1 - │ │ ├── columns: w:21!null k:22 + │ │ ├── columns: k:27 v:28!null w:29!null x:30 y:31 │ │ └── mapping: - │ │ ├── column3:9 => w:21 - │ │ └── upsert_k:18 => k:22 + │ │ ├── upsert_k:18 => k:27 + │ │ ├── column2:8 => v:28 + │ │ ├── column3:9 => w:29 + │ │ ├── upsert_x:19 => x:30 + │ │ └── upsert_y:20 => y:31 │ ├── scan uniq - │ │ └── columns: uniq.k:23!null uniq.w:25 + │ │ └── columns: uniq.k:21!null uniq.v:22 uniq.w:23 uniq.x:24 uniq.y:25 │ └── filters - │ ├── w:21 = uniq.w:25 - │ └── k:22 != uniq.k:23 + │ ├── w:29 = uniq.w:23 + │ └── k:27 != uniq.k:21 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: x:29 y:30 k:31 + ├── columns: k:38 v:39!null w:40!null x:41 y:42 ├── with-scan &1 - │ ├── columns: x:29 y:30 k:31 + │ ├── columns: k:38 v:39!null w:40!null x:41 y:42 │ └── mapping: - │ ├── upsert_x:19 => x:29 - │ ├── upsert_y:20 => y:30 - │ └── upsert_k:18 => k:31 + │ ├── upsert_k:18 => k:38 + │ ├── column2:8 => v:39 + │ ├── column3:9 => w:40 + │ ├── upsert_x:19 => x:41 + │ └── upsert_y:20 => y:42 ├── scan uniq - │ └── columns: uniq.k:32!null uniq.x:35 uniq.y:36 + │ └── columns: uniq.k:32!null uniq.v:33 uniq.w:34 uniq.x:35 uniq.y:36 └── filters - ├── x:29 = uniq.x:35 - ├── y:30 = uniq.y:36 - └── k:31 != uniq.k:32 + ├── x:41 = uniq.x:35 + ├── y:42 = uniq.y:36 + └── k:38 != uniq.k:32 # TODO(rytaft): No need to plan checks for w since it's aways NULL. # We currently can't determine that w is always NULL since the function @@ -183,10 +193,10 @@ upsert uniq ├── columns: ├── arbiter indexes: primary ├── canary column: uniq.k:12 - ├── fetch columns: uniq.k:12 v:13 uniq.w:14 uniq.x:15 uniq.y:16 + ├── fetch columns: uniq.k:12 uniq.v:13 uniq.w:14 uniq.x:15 uniq.y:16 ├── insert-mapping: │ ├── column1:7 => uniq.k:1 - │ ├── column10:10 => v:2 + │ ├── column10:10 => uniq.v:2 │ ├── column2:8 => uniq.w:3 │ ├── column3:9 => uniq.x:4 │ └── column11:11 => uniq.y:5 @@ -195,9 +205,9 @@ upsert uniq │ └── column3:9 => uniq.x:4 ├── input binding: &1 ├── project - │ ├── columns: upsert_k:18 upsert_v:19 upsert_y:20 column1:7!null column2:8 column3:9 column10:10 column11:11!null uniq.k:12 v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 + │ ├── columns: upsert_k:18 upsert_v:19 upsert_y:20 column1:7!null column2:8 column3:9 column10:10 column11:11!null uniq.k:12 uniq.v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 │ ├── left-join (hash) - │ │ ├── columns: column1:7!null column2:8 column3:9 column10:10 column11:11!null uniq.k:12 v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 + │ │ ├── columns: column1:7!null column2:8 column3:9 column10:10 column11:11!null uniq.k:12 uniq.v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 │ │ ├── ensure-upsert-distinct-on │ │ │ ├── columns: column1:7!null column2:8 column3:9 column10:10 column11:11!null │ │ │ ├── grouping columns: column1:7!null @@ -220,42 +230,47 @@ upsert uniq │ │ │ └── first-agg [as=column11:11] │ │ │ └── column11:11 │ │ ├── scan uniq - │ │ │ └── columns: uniq.k:12!null v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 + │ │ │ └── columns: uniq.k:12!null uniq.v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 │ │ └── filters │ │ └── column1:7 = uniq.k:12 │ └── projections │ ├── CASE WHEN uniq.k:12 IS NULL THEN column1:7 ELSE uniq.k:12 END [as=upsert_k:18] - │ ├── CASE WHEN uniq.k:12 IS NULL THEN column10:10 ELSE v:13 END [as=upsert_v:19] + │ ├── CASE WHEN uniq.k:12 IS NULL THEN column10:10 ELSE uniq.v:13 END [as=upsert_v:19] │ └── CASE WHEN uniq.k:12 IS NULL THEN column11:11 ELSE uniq.y:16 END [as=upsert_y:20] └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: w:21 k:22 + │ ├── columns: k:27 v:28 w:29 x:30 y:31 │ ├── with-scan &1 - │ │ ├── columns: w:21 k:22 + │ │ ├── columns: k:27 v:28 w:29 x:30 y:31 │ │ └── mapping: - │ │ ├── column2:8 => w:21 - │ │ └── upsert_k:18 => k:22 + │ │ ├── upsert_k:18 => k:27 + │ │ ├── upsert_v:19 => v:28 + │ │ ├── column2:8 => w:29 + │ │ ├── column3:9 => x:30 + │ │ └── upsert_y:20 => y:31 │ ├── scan uniq - │ │ └── columns: uniq.k:23!null uniq.w:25 + │ │ └── columns: uniq.k:21!null uniq.v:22 uniq.w:23 uniq.x:24 uniq.y:25 │ └── filters - │ ├── w:21 = uniq.w:25 - │ └── k:22 != uniq.k:23 + │ ├── w:29 = uniq.w:23 + │ └── k:27 != uniq.k:21 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: x:29 y:30 k:31 + ├── columns: k:38 v:39 w:40 x:41 y:42 ├── with-scan &1 - │ ├── columns: x:29 y:30 k:31 + │ ├── columns: k:38 v:39 w:40 x:41 y:42 │ └── mapping: - │ ├── column3:9 => x:29 - │ ├── upsert_y:20 => y:30 - │ └── upsert_k:18 => k:31 + │ ├── upsert_k:18 => k:38 + │ ├── upsert_v:19 => v:39 + │ ├── column2:8 => w:40 + │ ├── column3:9 => x:41 + │ └── upsert_y:20 => y:42 ├── scan uniq - │ └── columns: uniq.k:32!null uniq.x:35 uniq.y:36 + │ └── columns: uniq.k:32!null uniq.v:33 uniq.w:34 uniq.x:35 uniq.y:36 └── filters - ├── x:29 = uniq.x:35 - ├── y:30 = uniq.y:36 - └── k:31 != uniq.k:32 + ├── x:41 = uniq.x:35 + ├── y:42 = uniq.y:36 + └── k:38 != uniq.k:32 # Upsert with non-constant input. # TODO(rytaft): The default value for x is NULL, and we're not updating either @@ -314,32 +329,37 @@ upsert uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: w:23!null k:24 + │ ├── columns: k:29 v:30 w:31!null x:32 y:33!null │ ├── with-scan &1 - │ │ ├── columns: w:23!null k:24 + │ │ ├── columns: k:29 v:30 w:31!null x:32 y:33!null │ │ └── mapping: - │ │ ├── other.w:9 => w:23 - │ │ └── upsert_k:22 => k:24 + │ │ ├── upsert_k:22 => k:29 + │ │ ├── other.v:8 => v:30 + │ │ ├── other.w:9 => w:31 + │ │ ├── column14:14 => x:32 + │ │ └── column15:15 => y:33 │ ├── scan uniq - │ │ └── columns: uniq.k:25!null uniq.w:27 + │ │ └── columns: uniq.k:23!null uniq.v:24 uniq.w:25 uniq.x:26 uniq.y:27 │ └── filters - │ ├── w:23 = uniq.w:27 - │ └── k:24 != uniq.k:25 + │ ├── w:31 = uniq.w:25 + │ └── k:29 != uniq.k:23 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: x:31 y:32!null k:33 + ├── columns: k:40 v:41 w:42!null x:43 y:44!null ├── with-scan &1 - │ ├── columns: x:31 y:32!null k:33 + │ ├── columns: k:40 v:41 w:42!null x:43 y:44!null │ └── mapping: - │ ├── column14:14 => x:31 - │ ├── column15:15 => y:32 - │ └── upsert_k:22 => k:33 + │ ├── upsert_k:22 => k:40 + │ ├── other.v:8 => v:41 + │ ├── other.w:9 => w:42 + │ ├── column14:14 => x:43 + │ └── column15:15 => y:44 ├── scan uniq - │ └── columns: uniq.k:34!null uniq.x:37 uniq.y:38 + │ └── columns: uniq.k:34!null uniq.v:35 uniq.w:36 uniq.x:37 uniq.y:38 └── filters - ├── x:31 = uniq.x:37 - ├── y:32 = uniq.y:38 - └── k:33 != uniq.k:34 + ├── x:43 = uniq.x:37 + ├── y:44 = uniq.y:38 + └── k:40 != uniq.k:34 # On conflict do update with constant input. # TODO(rytaft): The default value for x is NULL, and we're not updating either @@ -352,10 +372,10 @@ upsert uniq ├── columns: ├── arbiter indexes: primary ├── canary column: uniq.k:11 - ├── fetch columns: uniq.k:11 v:12 uniq.w:13 uniq.x:14 uniq.y:15 + ├── fetch columns: uniq.k:11 uniq.v:12 uniq.w:13 uniq.x:14 uniq.y:15 ├── insert-mapping: │ ├── column1:7 => uniq.k:1 - │ ├── column2:8 => v:2 + │ ├── column2:8 => uniq.v:2 │ ├── column9:9 => uniq.w:3 │ ├── column9:9 => uniq.x:4 │ └── column10:10 => uniq.y:5 @@ -363,11 +383,11 @@ upsert uniq │ └── upsert_w:20 => uniq.w:3 ├── input binding: &1 ├── project - │ ├── columns: upsert_k:18 upsert_v:19 upsert_w:20 upsert_x:21 upsert_y:22 column1:7!null column2:8!null column9:9 column10:10!null uniq.k:11 v:12 uniq.w:13 uniq.x:14 uniq.y:15 crdb_internal_mvcc_timestamp:16 w_new:17 + │ ├── columns: upsert_k:18 upsert_v:19 upsert_w:20 upsert_x:21 upsert_y:22 column1:7!null column2:8!null column9:9 column10:10!null uniq.k:11 uniq.v:12 uniq.w:13 uniq.x:14 uniq.y:15 crdb_internal_mvcc_timestamp:16 w_new:17 │ ├── project - │ │ ├── columns: w_new:17 column1:7!null column2:8!null column9:9 column10:10!null uniq.k:11 v:12 uniq.w:13 uniq.x:14 uniq.y:15 crdb_internal_mvcc_timestamp:16 + │ │ ├── columns: w_new:17 column1:7!null column2:8!null column9:9 column10:10!null uniq.k:11 uniq.v:12 uniq.w:13 uniq.x:14 uniq.y:15 crdb_internal_mvcc_timestamp:16 │ │ ├── left-join (hash) - │ │ │ ├── columns: column1:7!null column2:8!null column9:9 column10:10!null uniq.k:11 v:12 uniq.w:13 uniq.x:14 uniq.y:15 crdb_internal_mvcc_timestamp:16 + │ │ │ ├── columns: column1:7!null column2:8!null column9:9 column10:10!null uniq.k:11 uniq.v:12 uniq.w:13 uniq.x:14 uniq.y:15 crdb_internal_mvcc_timestamp:16 │ │ │ ├── ensure-upsert-distinct-on │ │ │ │ ├── columns: column1:7!null column2:8!null column9:9 column10:10!null │ │ │ │ ├── grouping columns: column1:7!null @@ -388,46 +408,51 @@ upsert uniq │ │ │ │ └── first-agg [as=column10:10] │ │ │ │ └── column10:10 │ │ │ ├── scan uniq - │ │ │ │ └── columns: uniq.k:11!null v:12 uniq.w:13 uniq.x:14 uniq.y:15 crdb_internal_mvcc_timestamp:16 + │ │ │ │ └── columns: uniq.k:11!null uniq.v:12 uniq.w:13 uniq.x:14 uniq.y:15 crdb_internal_mvcc_timestamp:16 │ │ │ └── filters │ │ │ └── column1:7 = uniq.k:11 │ │ └── projections │ │ └── column9:9 + 1 [as=w_new:17] │ └── projections │ ├── CASE WHEN uniq.k:11 IS NULL THEN column1:7 ELSE uniq.k:11 END [as=upsert_k:18] - │ ├── CASE WHEN uniq.k:11 IS NULL THEN column2:8 ELSE v:12 END [as=upsert_v:19] + │ ├── CASE WHEN uniq.k:11 IS NULL THEN column2:8 ELSE uniq.v:12 END [as=upsert_v:19] │ ├── CASE WHEN uniq.k:11 IS NULL THEN column9:9 ELSE w_new:17 END [as=upsert_w:20] │ ├── CASE WHEN uniq.k:11 IS NULL THEN column9:9 ELSE uniq.x:14 END [as=upsert_x:21] │ └── CASE WHEN uniq.k:11 IS NULL THEN column10:10 ELSE uniq.y:15 END [as=upsert_y:22] └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: w:23 k:24 + │ ├── columns: k:29 v:30 w:31 x:32 y:33 │ ├── with-scan &1 - │ │ ├── columns: w:23 k:24 + │ │ ├── columns: k:29 v:30 w:31 x:32 y:33 │ │ └── mapping: - │ │ ├── upsert_w:20 => w:23 - │ │ └── upsert_k:18 => k:24 + │ │ ├── upsert_k:18 => k:29 + │ │ ├── upsert_v:19 => v:30 + │ │ ├── upsert_w:20 => w:31 + │ │ ├── upsert_x:21 => x:32 + │ │ └── upsert_y:22 => y:33 │ ├── scan uniq - │ │ └── columns: uniq.k:25!null uniq.w:27 + │ │ └── columns: uniq.k:23!null uniq.v:24 uniq.w:25 uniq.x:26 uniq.y:27 │ └── filters - │ ├── w:23 = uniq.w:27 - │ └── k:24 != uniq.k:25 + │ ├── w:31 = uniq.w:25 + │ └── k:29 != uniq.k:23 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: x:31 y:32 k:33 + ├── columns: k:40 v:41 w:42 x:43 y:44 ├── with-scan &1 - │ ├── columns: x:31 y:32 k:33 + │ ├── columns: k:40 v:41 w:42 x:43 y:44 │ └── mapping: - │ ├── upsert_x:21 => x:31 - │ ├── upsert_y:22 => y:32 - │ └── upsert_k:18 => k:33 + │ ├── upsert_k:18 => k:40 + │ ├── upsert_v:19 => v:41 + │ ├── upsert_w:20 => w:42 + │ ├── upsert_x:21 => x:43 + │ └── upsert_y:22 => y:44 ├── scan uniq - │ └── columns: uniq.k:34!null uniq.x:37 uniq.y:38 + │ └── columns: uniq.k:34!null uniq.v:35 uniq.w:36 uniq.x:37 uniq.y:38 └── filters - ├── x:31 = uniq.x:37 - ├── y:32 = uniq.y:38 - └── k:33 != uniq.k:34 + ├── x:43 = uniq.x:37 + ├── y:44 = uniq.y:38 + └── k:40 != uniq.k:34 # On conflict do update with non-constant input. # TODO(rytaft): The default value for x is NULL, and we're not updating either @@ -489,32 +514,37 @@ upsert uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: w:28 k:29 + │ ├── columns: k:34 v:35 w:36 x:37 y:38 │ ├── with-scan &1 - │ │ ├── columns: w:28 k:29 + │ │ ├── columns: k:34 v:35 w:36 x:37 y:38 │ │ └── mapping: - │ │ ├── upsert_w:25 => w:28 - │ │ └── upsert_k:23 => k:29 + │ │ ├── upsert_k:23 => k:34 + │ │ ├── upsert_v:24 => v:35 + │ │ ├── upsert_w:25 => w:36 + │ │ ├── upsert_x:26 => x:37 + │ │ └── upsert_y:27 => y:38 │ ├── scan uniq - │ │ └── columns: uniq.k:30!null uniq.w:32 + │ │ └── columns: uniq.k:28!null uniq.v:29 uniq.w:30 uniq.x:31 uniq.y:32 │ └── filters - │ ├── w:28 = uniq.w:32 - │ └── k:29 != uniq.k:30 + │ ├── w:36 = uniq.w:30 + │ └── k:34 != uniq.k:28 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: x:36 y:37 k:38 + ├── columns: k:45 v:46 w:47 x:48 y:49 ├── with-scan &1 - │ ├── columns: x:36 y:37 k:38 + │ ├── columns: k:45 v:46 w:47 x:48 y:49 │ └── mapping: - │ ├── upsert_x:26 => x:36 - │ ├── upsert_y:27 => y:37 - │ └── upsert_k:23 => k:38 + │ ├── upsert_k:23 => k:45 + │ ├── upsert_v:24 => v:46 + │ ├── upsert_w:25 => w:47 + │ ├── upsert_x:26 => x:48 + │ └── upsert_y:27 => y:49 ├── scan uniq - │ └── columns: uniq.k:39!null uniq.x:42 uniq.y:43 + │ └── columns: uniq.k:39!null uniq.v:40 uniq.w:41 uniq.x:42 uniq.y:43 └── filters - ├── x:36 = uniq.x:42 - ├── y:37 = uniq.y:43 - └── k:38 != uniq.k:39 + ├── x:48 = uniq.x:42 + ├── y:49 = uniq.y:43 + └── k:45 != uniq.k:39 # On conflict do update with constant input, conflict on UNIQUE WITHOUT INDEX # column. @@ -525,10 +555,10 @@ upsert uniq ├── columns: ├── arbiter constraints: unique_w ├── canary column: uniq.k:12 - ├── fetch columns: uniq.k:12 v:13 uniq.w:14 uniq.x:15 uniq.y:16 + ├── fetch columns: uniq.k:12 uniq.v:13 uniq.w:14 uniq.x:15 uniq.y:16 ├── insert-mapping: │ ├── column1:7 => uniq.k:1 - │ ├── column2:8 => v:2 + │ ├── column2:8 => uniq.v:2 │ ├── column3:9 => uniq.w:3 │ ├── column10:10 => uniq.x:4 │ └── column11:11 => uniq.y:5 @@ -536,11 +566,11 @@ upsert uniq │ └── upsert_w:21 => uniq.w:3 ├── input binding: &1 ├── project - │ ├── columns: upsert_k:19 upsert_v:20 upsert_w:21!null upsert_x:22 upsert_y:23 column1:7!null column2:8!null column3:9!null column10:10 column11:11!null uniq.k:12 v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 w_new:18!null + │ ├── columns: upsert_k:19 upsert_v:20 upsert_w:21!null upsert_x:22 upsert_y:23 column1:7!null column2:8!null column3:9!null column10:10 column11:11!null uniq.k:12 uniq.v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 w_new:18!null │ ├── project - │ │ ├── columns: w_new:18!null column1:7!null column2:8!null column3:9!null column10:10 column11:11!null uniq.k:12 v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 + │ │ ├── columns: w_new:18!null column1:7!null column2:8!null column3:9!null column10:10 column11:11!null uniq.k:12 uniq.v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 │ │ ├── left-join (hash) - │ │ │ ├── columns: column1:7!null column2:8!null column3:9!null column10:10 column11:11!null uniq.k:12 v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 + │ │ │ ├── columns: column1:7!null column2:8!null column3:9!null column10:10 column11:11!null uniq.k:12 uniq.v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 │ │ │ ├── ensure-upsert-distinct-on │ │ │ │ ├── columns: column1:7!null column2:8!null column3:9!null column10:10 column11:11!null │ │ │ │ ├── grouping columns: column3:9!null @@ -563,46 +593,51 @@ upsert uniq │ │ │ │ └── first-agg [as=column11:11] │ │ │ │ └── column11:11 │ │ │ ├── scan uniq - │ │ │ │ └── columns: uniq.k:12!null v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 + │ │ │ │ └── columns: uniq.k:12!null uniq.v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 │ │ │ └── filters │ │ │ └── column3:9 = uniq.w:14 │ │ └── projections │ │ └── 10 [as=w_new:18] │ └── projections │ ├── CASE WHEN uniq.k:12 IS NULL THEN column1:7 ELSE uniq.k:12 END [as=upsert_k:19] - │ ├── CASE WHEN uniq.k:12 IS NULL THEN column2:8 ELSE v:13 END [as=upsert_v:20] + │ ├── CASE WHEN uniq.k:12 IS NULL THEN column2:8 ELSE uniq.v:13 END [as=upsert_v:20] │ ├── CASE WHEN uniq.k:12 IS NULL THEN column3:9 ELSE w_new:18 END [as=upsert_w:21] │ ├── CASE WHEN uniq.k:12 IS NULL THEN column10:10 ELSE uniq.x:15 END [as=upsert_x:22] │ └── CASE WHEN uniq.k:12 IS NULL THEN column11:11 ELSE uniq.y:16 END [as=upsert_y:23] └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: w:24!null k:25 + │ ├── columns: k:30 v:31 w:32!null x:33 y:34 │ ├── with-scan &1 - │ │ ├── columns: w:24!null k:25 + │ │ ├── columns: k:30 v:31 w:32!null x:33 y:34 │ │ └── mapping: - │ │ ├── upsert_w:21 => w:24 - │ │ └── upsert_k:19 => k:25 + │ │ ├── upsert_k:19 => k:30 + │ │ ├── upsert_v:20 => v:31 + │ │ ├── upsert_w:21 => w:32 + │ │ ├── upsert_x:22 => x:33 + │ │ └── upsert_y:23 => y:34 │ ├── scan uniq - │ │ └── columns: uniq.k:26!null uniq.w:28 + │ │ └── columns: uniq.k:24!null uniq.v:25 uniq.w:26 uniq.x:27 uniq.y:28 │ └── filters - │ ├── w:24 = uniq.w:28 - │ └── k:25 != uniq.k:26 + │ ├── w:32 = uniq.w:26 + │ └── k:30 != uniq.k:24 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: x:32 y:33 k:34 + ├── columns: k:41 v:42 w:43!null x:44 y:45 ├── with-scan &1 - │ ├── columns: x:32 y:33 k:34 + │ ├── columns: k:41 v:42 w:43!null x:44 y:45 │ └── mapping: - │ ├── upsert_x:22 => x:32 - │ ├── upsert_y:23 => y:33 - │ └── upsert_k:19 => k:34 + │ ├── upsert_k:19 => k:41 + │ ├── upsert_v:20 => v:42 + │ ├── upsert_w:21 => w:43 + │ ├── upsert_x:22 => x:44 + │ └── upsert_y:23 => y:45 ├── scan uniq - │ └── columns: uniq.k:35!null uniq.x:38 uniq.y:39 + │ └── columns: uniq.k:35!null uniq.v:36 uniq.w:37 uniq.x:38 uniq.y:39 └── filters - ├── x:32 = uniq.x:38 - ├── y:33 = uniq.y:39 - └── k:34 != uniq.k:35 + ├── x:44 = uniq.x:38 + ├── y:45 = uniq.y:39 + └── k:41 != uniq.k:35 # On conflict do update with constant input, conflict on UNIQUE WITHOUT INDEX # columns. @@ -613,22 +648,22 @@ upsert uniq ├── columns: ├── arbiter constraints: unique_x_y ├── canary column: uniq.k:12 - ├── fetch columns: uniq.k:12 v:13 uniq.w:14 uniq.x:15 uniq.y:16 + ├── fetch columns: uniq.k:12 uniq.v:13 uniq.w:14 uniq.x:15 uniq.y:16 ├── insert-mapping: │ ├── column1:7 => uniq.k:1 - │ ├── column2:8 => v:2 + │ ├── column2:8 => uniq.v:2 │ ├── column3:9 => uniq.w:3 │ ├── column4:10 => uniq.x:4 │ └── column5:11 => uniq.y:5 ├── update-mapping: - │ └── upsert_v:20 => v:2 + │ └── upsert_v:20 => uniq.v:2 ├── input binding: &1 ├── project - │ ├── columns: upsert_k:19 upsert_v:20!null upsert_w:21 upsert_x:22 upsert_y:23 column1:7!null column2:8!null column3:9!null column4:10!null column5:11!null uniq.k:12 v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 v_new:18!null + │ ├── columns: upsert_k:19 upsert_v:20!null upsert_w:21 upsert_x:22 upsert_y:23 column1:7!null column2:8!null column3:9!null column4:10!null column5:11!null uniq.k:12 uniq.v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 v_new:18!null │ ├── project - │ │ ├── columns: v_new:18!null column1:7!null column2:8!null column3:9!null column4:10!null column5:11!null uniq.k:12 v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 + │ │ ├── columns: v_new:18!null column1:7!null column2:8!null column3:9!null column4:10!null column5:11!null uniq.k:12 uniq.v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 │ │ ├── left-join (hash) - │ │ │ ├── columns: column1:7!null column2:8!null column3:9!null column4:10!null column5:11!null uniq.k:12 v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 + │ │ │ ├── columns: column1:7!null column2:8!null column3:9!null column4:10!null column5:11!null uniq.k:12 uniq.v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 │ │ │ ├── ensure-upsert-distinct-on │ │ │ │ ├── columns: column1:7!null column2:8!null column3:9!null column4:10!null column5:11!null │ │ │ │ ├── grouping columns: column4:10!null column5:11!null @@ -643,7 +678,7 @@ upsert uniq │ │ │ │ └── first-agg [as=column3:9] │ │ │ │ └── column3:9 │ │ │ ├── scan uniq - │ │ │ │ └── columns: uniq.k:12!null v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 + │ │ │ │ └── columns: uniq.k:12!null uniq.v:13 uniq.w:14 uniq.x:15 uniq.y:16 crdb_internal_mvcc_timestamp:17 │ │ │ └── filters │ │ │ ├── column4:10 = uniq.x:15 │ │ │ └── column5:11 = uniq.y:16 @@ -658,32 +693,37 @@ upsert uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: w:24 k:25 + │ ├── columns: k:30 v:31!null w:32 x:33 y:34 │ ├── with-scan &1 - │ │ ├── columns: w:24 k:25 + │ │ ├── columns: k:30 v:31!null w:32 x:33 y:34 │ │ └── mapping: - │ │ ├── upsert_w:21 => w:24 - │ │ └── upsert_k:19 => k:25 + │ │ ├── upsert_k:19 => k:30 + │ │ ├── upsert_v:20 => v:31 + │ │ ├── upsert_w:21 => w:32 + │ │ ├── upsert_x:22 => x:33 + │ │ └── upsert_y:23 => y:34 │ ├── scan uniq - │ │ └── columns: uniq.k:26!null uniq.w:28 + │ │ └── columns: uniq.k:24!null uniq.v:25 uniq.w:26 uniq.x:27 uniq.y:28 │ └── filters - │ ├── w:24 = uniq.w:28 - │ └── k:25 != uniq.k:26 + │ ├── w:32 = uniq.w:26 + │ └── k:30 != uniq.k:24 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: x:32 y:33 k:34 + ├── columns: k:41 v:42!null w:43 x:44 y:45 ├── with-scan &1 - │ ├── columns: x:32 y:33 k:34 + │ ├── columns: k:41 v:42!null w:43 x:44 y:45 │ └── mapping: - │ ├── upsert_x:22 => x:32 - │ ├── upsert_y:23 => y:33 - │ └── upsert_k:19 => k:34 + │ ├── upsert_k:19 => k:41 + │ ├── upsert_v:20 => v:42 + │ ├── upsert_w:21 => w:43 + │ ├── upsert_x:22 => x:44 + │ └── upsert_y:23 => y:45 ├── scan uniq - │ └── columns: uniq.k:35!null uniq.x:38 uniq.y:39 + │ └── columns: uniq.k:35!null uniq.v:36 uniq.w:37 uniq.x:38 uniq.y:39 └── filters - ├── x:32 = uniq.x:38 - ├── y:33 = uniq.y:39 - └── k:34 != uniq.k:35 + ├── x:44 = uniq.x:38 + ├── y:45 = uniq.y:39 + └── k:41 != uniq.k:35 # Cannot conflict on a subset of columns in a unique constraint. build @@ -732,48 +772,51 @@ upsert uniq_overlaps_pk └── unique-checks ├── unique-checks-item: uniq_overlaps_pk(b,c) │ └── semi-join (hash) - │ ├── columns: b:10!null c:11!null a:12!null + │ ├── columns: a:15!null b:16!null c:17!null d:18!null │ ├── with-scan &1 - │ │ ├── columns: b:10!null c:11!null a:12!null + │ │ ├── columns: a:15!null b:16!null c:17!null d:18!null │ │ └── mapping: - │ │ ├── column2:7 => b:10 - │ │ ├── column3:8 => c:11 - │ │ └── column1:6 => a:12 + │ │ ├── column1:6 => a:15 + │ │ ├── column2:7 => b:16 + │ │ ├── column3:8 => c:17 + │ │ └── column4:9 => d:18 │ ├── scan uniq_overlaps_pk - │ │ └── columns: uniq_overlaps_pk.a:13!null uniq_overlaps_pk.b:14!null uniq_overlaps_pk.c:15 + │ │ └── columns: uniq_overlaps_pk.a:10!null uniq_overlaps_pk.b:11!null uniq_overlaps_pk.c:12 uniq_overlaps_pk.d:13 │ └── filters - │ ├── b:10 = uniq_overlaps_pk.b:14 - │ ├── c:11 = uniq_overlaps_pk.c:15 - │ └── a:12 != uniq_overlaps_pk.a:13 + │ ├── b:16 = uniq_overlaps_pk.b:11 + │ ├── c:17 = uniq_overlaps_pk.c:12 + │ └── a:15 != uniq_overlaps_pk.a:10 ├── unique-checks-item: uniq_overlaps_pk(a) │ └── semi-join (hash) - │ ├── columns: a:18!null b:19!null + │ ├── columns: a:24!null b:25!null c:26!null d:27!null │ ├── with-scan &1 - │ │ ├── columns: a:18!null b:19!null + │ │ ├── columns: a:24!null b:25!null c:26!null d:27!null │ │ └── mapping: - │ │ ├── column1:6 => a:18 - │ │ └── column2:7 => b:19 + │ │ ├── column1:6 => a:24 + │ │ ├── column2:7 => b:25 + │ │ ├── column3:8 => c:26 + │ │ └── column4:9 => d:27 │ ├── scan uniq_overlaps_pk - │ │ └── columns: uniq_overlaps_pk.a:20!null uniq_overlaps_pk.b:21!null + │ │ └── columns: uniq_overlaps_pk.a:19!null uniq_overlaps_pk.b:20!null uniq_overlaps_pk.c:21 uniq_overlaps_pk.d:22 │ └── filters - │ ├── a:18 = uniq_overlaps_pk.a:20 - │ └── b:19 != uniq_overlaps_pk.b:21 + │ ├── a:24 = uniq_overlaps_pk.a:19 + │ └── b:25 != uniq_overlaps_pk.b:20 └── unique-checks-item: uniq_overlaps_pk(c,d) └── semi-join (hash) - ├── columns: c:25!null d:26!null a:27!null b:28!null + ├── columns: a:33!null b:34!null c:35!null d:36!null ├── with-scan &1 - │ ├── columns: c:25!null d:26!null a:27!null b:28!null + │ ├── columns: a:33!null b:34!null c:35!null d:36!null │ └── mapping: - │ ├── column3:8 => c:25 - │ ├── column4:9 => d:26 - │ ├── column1:6 => a:27 - │ └── column2:7 => b:28 + │ ├── column1:6 => a:33 + │ ├── column2:7 => b:34 + │ ├── column3:8 => c:35 + │ └── column4:9 => d:36 ├── scan uniq_overlaps_pk - │ └── columns: uniq_overlaps_pk.a:29!null uniq_overlaps_pk.b:30!null uniq_overlaps_pk.c:31 uniq_overlaps_pk.d:32 + │ └── columns: uniq_overlaps_pk.a:28!null uniq_overlaps_pk.b:29!null uniq_overlaps_pk.c:30 uniq_overlaps_pk.d:31 └── filters - ├── c:25 = uniq_overlaps_pk.c:31 - ├── d:26 = uniq_overlaps_pk.d:32 - └── (a:27 != uniq_overlaps_pk.a:29) OR (b:28 != uniq_overlaps_pk.b:30) + ├── c:35 = uniq_overlaps_pk.c:30 + ├── d:36 = uniq_overlaps_pk.d:31 + └── (a:33 != uniq_overlaps_pk.a:28) OR (b:34 != uniq_overlaps_pk.b:29) # Upsert with non-constant input. # Add inequality filters for the primary key columns that are not part of each @@ -788,7 +831,7 @@ upsert uniq_overlaps_pk │ ├── k:6 => uniq_overlaps_pk.a:1 │ ├── v:7 => uniq_overlaps_pk.b:2 │ ├── x:9 => uniq_overlaps_pk.c:3 - │ └── column13:13 => d:4 + │ └── column13:13 => uniq_overlaps_pk.d:4 ├── input binding: &1 ├── project │ ├── columns: column13:13 k:6 v:7 x:9 @@ -801,32 +844,35 @@ upsert uniq_overlaps_pk └── unique-checks ├── unique-checks-item: uniq_overlaps_pk(b,c) │ └── semi-join (hash) - │ ├── columns: b:14 c:15 a:16 + │ ├── columns: a:19 b:20 c:21 d:22 │ ├── with-scan &1 - │ │ ├── columns: b:14 c:15 a:16 + │ │ ├── columns: a:19 b:20 c:21 d:22 │ │ └── mapping: - │ │ ├── v:7 => b:14 - │ │ ├── x:9 => c:15 - │ │ └── k:6 => a:16 + │ │ ├── k:6 => a:19 + │ │ ├── v:7 => b:20 + │ │ ├── x:9 => c:21 + │ │ └── column13:13 => d:22 │ ├── scan uniq_overlaps_pk - │ │ └── columns: uniq_overlaps_pk.a:17!null uniq_overlaps_pk.b:18!null uniq_overlaps_pk.c:19 + │ │ └── columns: uniq_overlaps_pk.a:14!null uniq_overlaps_pk.b:15!null uniq_overlaps_pk.c:16 uniq_overlaps_pk.d:17 │ └── filters - │ ├── b:14 = uniq_overlaps_pk.b:18 - │ ├── c:15 = uniq_overlaps_pk.c:19 - │ └── a:16 != uniq_overlaps_pk.a:17 + │ ├── b:20 = uniq_overlaps_pk.b:15 + │ ├── c:21 = uniq_overlaps_pk.c:16 + │ └── a:19 != uniq_overlaps_pk.a:14 └── unique-checks-item: uniq_overlaps_pk(a) └── semi-join (hash) - ├── columns: a:22 b:23 + ├── columns: a:28 b:29 c:30 d:31 ├── with-scan &1 - │ ├── columns: a:22 b:23 + │ ├── columns: a:28 b:29 c:30 d:31 │ └── mapping: - │ ├── k:6 => a:22 - │ └── v:7 => b:23 + │ ├── k:6 => a:28 + │ ├── v:7 => b:29 + │ ├── x:9 => c:30 + │ └── column13:13 => d:31 ├── scan uniq_overlaps_pk - │ └── columns: uniq_overlaps_pk.a:24!null uniq_overlaps_pk.b:25!null + │ └── columns: uniq_overlaps_pk.a:23!null uniq_overlaps_pk.b:24!null uniq_overlaps_pk.c:25 uniq_overlaps_pk.d:26 └── filters - ├── a:22 = uniq_overlaps_pk.a:24 - └── b:23 != uniq_overlaps_pk.b:25 + ├── a:28 = uniq_overlaps_pk.a:23 + └── b:29 != uniq_overlaps_pk.b:24 # On conflict do update with constant input, conflict on UNIQUE WITHOUT INDEX # column. @@ -880,48 +926,51 @@ upsert uniq_overlaps_pk └── unique-checks ├── unique-checks-item: uniq_overlaps_pk(b,c) │ └── semi-join (hash) - │ ├── columns: b:20 c:21 a:22!null + │ ├── columns: a:25!null b:26 c:27 d:28 │ ├── with-scan &1 - │ │ ├── columns: b:20 c:21 a:22!null + │ │ ├── columns: a:25!null b:26 c:27 d:28 │ │ └── mapping: - │ │ ├── upsert_b:17 => b:20 - │ │ ├── upsert_c:18 => c:21 - │ │ └── upsert_a:16 => a:22 + │ │ ├── upsert_a:16 => a:25 + │ │ ├── upsert_b:17 => b:26 + │ │ ├── upsert_c:18 => c:27 + │ │ └── upsert_d:19 => d:28 │ ├── scan uniq_overlaps_pk - │ │ └── columns: uniq_overlaps_pk.a:23!null uniq_overlaps_pk.b:24!null uniq_overlaps_pk.c:25 + │ │ └── columns: uniq_overlaps_pk.a:20!null uniq_overlaps_pk.b:21!null uniq_overlaps_pk.c:22 uniq_overlaps_pk.d:23 │ └── filters - │ ├── b:20 = uniq_overlaps_pk.b:24 - │ ├── c:21 = uniq_overlaps_pk.c:25 - │ └── a:22 != uniq_overlaps_pk.a:23 + │ ├── b:26 = uniq_overlaps_pk.b:21 + │ ├── c:27 = uniq_overlaps_pk.c:22 + │ └── a:25 != uniq_overlaps_pk.a:20 ├── unique-checks-item: uniq_overlaps_pk(a) │ └── semi-join (hash) - │ ├── columns: a:28!null b:29 + │ ├── columns: a:34!null b:35 c:36 d:37 │ ├── with-scan &1 - │ │ ├── columns: a:28!null b:29 + │ │ ├── columns: a:34!null b:35 c:36 d:37 │ │ └── mapping: - │ │ ├── upsert_a:16 => a:28 - │ │ └── upsert_b:17 => b:29 + │ │ ├── upsert_a:16 => a:34 + │ │ ├── upsert_b:17 => b:35 + │ │ ├── upsert_c:18 => c:36 + │ │ └── upsert_d:19 => d:37 │ ├── scan uniq_overlaps_pk - │ │ └── columns: uniq_overlaps_pk.a:30!null uniq_overlaps_pk.b:31!null + │ │ └── columns: uniq_overlaps_pk.a:29!null uniq_overlaps_pk.b:30!null uniq_overlaps_pk.c:31 uniq_overlaps_pk.d:32 │ └── filters - │ ├── a:28 = uniq_overlaps_pk.a:30 - │ └── b:29 != uniq_overlaps_pk.b:31 + │ ├── a:34 = uniq_overlaps_pk.a:29 + │ └── b:35 != uniq_overlaps_pk.b:30 └── unique-checks-item: uniq_overlaps_pk(c,d) └── semi-join (hash) - ├── columns: c:35 d:36 a:37!null b:38 + ├── columns: a:43!null b:44 c:45 d:46 ├── with-scan &1 - │ ├── columns: c:35 d:36 a:37!null b:38 + │ ├── columns: a:43!null b:44 c:45 d:46 │ └── mapping: - │ ├── upsert_c:18 => c:35 - │ ├── upsert_d:19 => d:36 - │ ├── upsert_a:16 => a:37 - │ └── upsert_b:17 => b:38 + │ ├── upsert_a:16 => a:43 + │ ├── upsert_b:17 => b:44 + │ ├── upsert_c:18 => c:45 + │ └── upsert_d:19 => d:46 ├── scan uniq_overlaps_pk - │ └── columns: uniq_overlaps_pk.a:39!null uniq_overlaps_pk.b:40!null uniq_overlaps_pk.c:41 uniq_overlaps_pk.d:42 + │ └── columns: uniq_overlaps_pk.a:38!null uniq_overlaps_pk.b:39!null uniq_overlaps_pk.c:40 uniq_overlaps_pk.d:41 └── filters - ├── c:35 = uniq_overlaps_pk.c:41 - ├── d:36 = uniq_overlaps_pk.d:42 - └── (a:37 != uniq_overlaps_pk.a:39) OR (b:38 != uniq_overlaps_pk.b:40) + ├── c:45 = uniq_overlaps_pk.c:40 + ├── d:46 = uniq_overlaps_pk.d:41 + └── (a:43 != uniq_overlaps_pk.a:38) OR (b:44 != uniq_overlaps_pk.b:39) # On conflict do update with constant input, conflict on UNIQUE WITHOUT INDEX # columns. @@ -973,48 +1022,51 @@ upsert uniq_overlaps_pk └── unique-checks ├── unique-checks-item: uniq_overlaps_pk(b,c) │ └── semi-join (hash) - │ ├── columns: b:20!null c:21 a:22 + │ ├── columns: a:25 b:26!null c:27 d:28 │ ├── with-scan &1 - │ │ ├── columns: b:20!null c:21 a:22 + │ │ ├── columns: a:25 b:26!null c:27 d:28 │ │ └── mapping: - │ │ ├── upsert_b:17 => b:20 - │ │ ├── upsert_c:18 => c:21 - │ │ └── upsert_a:16 => a:22 + │ │ ├── upsert_a:16 => a:25 + │ │ ├── upsert_b:17 => b:26 + │ │ ├── upsert_c:18 => c:27 + │ │ └── upsert_d:19 => d:28 │ ├── scan uniq_overlaps_pk - │ │ └── columns: uniq_overlaps_pk.a:23!null uniq_overlaps_pk.b:24!null uniq_overlaps_pk.c:25 + │ │ └── columns: uniq_overlaps_pk.a:20!null uniq_overlaps_pk.b:21!null uniq_overlaps_pk.c:22 uniq_overlaps_pk.d:23 │ └── filters - │ ├── b:20 = uniq_overlaps_pk.b:24 - │ ├── c:21 = uniq_overlaps_pk.c:25 - │ └── a:22 != uniq_overlaps_pk.a:23 + │ ├── b:26 = uniq_overlaps_pk.b:21 + │ ├── c:27 = uniq_overlaps_pk.c:22 + │ └── a:25 != uniq_overlaps_pk.a:20 ├── unique-checks-item: uniq_overlaps_pk(a) │ └── semi-join (hash) - │ ├── columns: a:28 b:29!null + │ ├── columns: a:34 b:35!null c:36 d:37 │ ├── with-scan &1 - │ │ ├── columns: a:28 b:29!null + │ │ ├── columns: a:34 b:35!null c:36 d:37 │ │ └── mapping: - │ │ ├── upsert_a:16 => a:28 - │ │ └── upsert_b:17 => b:29 + │ │ ├── upsert_a:16 => a:34 + │ │ ├── upsert_b:17 => b:35 + │ │ ├── upsert_c:18 => c:36 + │ │ └── upsert_d:19 => d:37 │ ├── scan uniq_overlaps_pk - │ │ └── columns: uniq_overlaps_pk.a:30!null uniq_overlaps_pk.b:31!null + │ │ └── columns: uniq_overlaps_pk.a:29!null uniq_overlaps_pk.b:30!null uniq_overlaps_pk.c:31 uniq_overlaps_pk.d:32 │ └── filters - │ ├── a:28 = uniq_overlaps_pk.a:30 - │ └── b:29 != uniq_overlaps_pk.b:31 + │ ├── a:34 = uniq_overlaps_pk.a:29 + │ └── b:35 != uniq_overlaps_pk.b:30 └── unique-checks-item: uniq_overlaps_pk(c,d) └── semi-join (hash) - ├── columns: c:35 d:36 a:37 b:38!null + ├── columns: a:43 b:44!null c:45 d:46 ├── with-scan &1 - │ ├── columns: c:35 d:36 a:37 b:38!null + │ ├── columns: a:43 b:44!null c:45 d:46 │ └── mapping: - │ ├── upsert_c:18 => c:35 - │ ├── upsert_d:19 => d:36 - │ ├── upsert_a:16 => a:37 - │ └── upsert_b:17 => b:38 + │ ├── upsert_a:16 => a:43 + │ ├── upsert_b:17 => b:44 + │ ├── upsert_c:18 => c:45 + │ └── upsert_d:19 => d:46 ├── scan uniq_overlaps_pk - │ └── columns: uniq_overlaps_pk.a:39!null uniq_overlaps_pk.b:40!null uniq_overlaps_pk.c:41 uniq_overlaps_pk.d:42 + │ └── columns: uniq_overlaps_pk.a:38!null uniq_overlaps_pk.b:39!null uniq_overlaps_pk.c:40 uniq_overlaps_pk.d:41 └── filters - ├── c:35 = uniq_overlaps_pk.c:41 - ├── d:36 = uniq_overlaps_pk.d:42 - └── (a:37 != uniq_overlaps_pk.a:39) OR (b:38 != uniq_overlaps_pk.b:40) + ├── c:45 = uniq_overlaps_pk.c:40 + ├── d:46 = uniq_overlaps_pk.d:41 + └── (a:43 != uniq_overlaps_pk.a:38) OR (b:44 != uniq_overlaps_pk.b:39) exec-ddl CREATE TABLE uniq_hidden_pk ( @@ -1084,49 +1136,55 @@ upsert uniq_hidden_pk └── unique-checks ├── unique-checks-item: uniq_hidden_pk(b,c) │ └── semi-join (hash) - │ ├── columns: b:20!null c:21 rowid:22 + │ ├── columns: a:26!null b:27!null c:28 d:29!null rowid:30 │ ├── with-scan &1 - │ │ ├── columns: b:20!null c:21 rowid:22 + │ │ ├── columns: a:26!null b:27!null c:28 d:29!null rowid:30 │ │ └── mapping: - │ │ ├── column2:8 => b:20 - │ │ ├── upsert_c:18 => c:21 - │ │ └── upsert_rowid:19 => rowid:22 + │ │ ├── column1:7 => a:26 + │ │ ├── column2:8 => b:27 + │ │ ├── upsert_c:18 => c:28 + │ │ ├── column3:9 => d:29 + │ │ └── upsert_rowid:19 => rowid:30 │ ├── scan uniq_hidden_pk - │ │ └── columns: uniq_hidden_pk.b:24 uniq_hidden_pk.c:25 uniq_hidden_pk.rowid:27!null + │ │ └── columns: uniq_hidden_pk.a:20 uniq_hidden_pk.b:21 uniq_hidden_pk.c:22 uniq_hidden_pk.d:23 uniq_hidden_pk.rowid:24!null │ └── filters - │ ├── b:20 = uniq_hidden_pk.b:24 - │ ├── c:21 = uniq_hidden_pk.c:25 - │ └── rowid:22 != uniq_hidden_pk.rowid:27 + │ ├── b:27 = uniq_hidden_pk.b:21 + │ ├── c:28 = uniq_hidden_pk.c:22 + │ └── rowid:30 != uniq_hidden_pk.rowid:24 ├── unique-checks-item: uniq_hidden_pk(a,b,d) │ └── semi-join (hash) - │ ├── columns: a:29!null b:30!null d:31!null rowid:32 + │ ├── columns: a:37!null b:38!null c:39 d:40!null rowid:41 │ ├── with-scan &1 - │ │ ├── columns: a:29!null b:30!null d:31!null rowid:32 + │ │ ├── columns: a:37!null b:38!null c:39 d:40!null rowid:41 │ │ └── mapping: - │ │ ├── column1:7 => a:29 - │ │ ├── column2:8 => b:30 - │ │ ├── column3:9 => d:31 - │ │ └── upsert_rowid:19 => rowid:32 + │ │ ├── column1:7 => a:37 + │ │ ├── column2:8 => b:38 + │ │ ├── upsert_c:18 => c:39 + │ │ ├── column3:9 => d:40 + │ │ └── upsert_rowid:19 => rowid:41 │ ├── scan uniq_hidden_pk - │ │ └── columns: uniq_hidden_pk.a:33 uniq_hidden_pk.b:34 uniq_hidden_pk.d:36 uniq_hidden_pk.rowid:37!null + │ │ └── columns: uniq_hidden_pk.a:31 uniq_hidden_pk.b:32 uniq_hidden_pk.c:33 uniq_hidden_pk.d:34 uniq_hidden_pk.rowid:35!null │ └── filters - │ ├── a:29 = uniq_hidden_pk.a:33 - │ ├── b:30 = uniq_hidden_pk.b:34 - │ ├── d:31 = uniq_hidden_pk.d:36 - │ └── rowid:32 != uniq_hidden_pk.rowid:37 + │ ├── a:37 = uniq_hidden_pk.a:31 + │ ├── b:38 = uniq_hidden_pk.b:32 + │ ├── d:40 = uniq_hidden_pk.d:34 + │ └── rowid:41 != uniq_hidden_pk.rowid:35 └── unique-checks-item: uniq_hidden_pk(a) └── semi-join (hash) - ├── columns: a:39!null rowid:40 + ├── columns: a:48!null b:49!null c:50 d:51!null rowid:52 ├── with-scan &1 - │ ├── columns: a:39!null rowid:40 + │ ├── columns: a:48!null b:49!null c:50 d:51!null rowid:52 │ └── mapping: - │ ├── column1:7 => a:39 - │ └── upsert_rowid:19 => rowid:40 + │ ├── column1:7 => a:48 + │ ├── column2:8 => b:49 + │ ├── upsert_c:18 => c:50 + │ ├── column3:9 => d:51 + │ └── upsert_rowid:19 => rowid:52 ├── scan uniq_hidden_pk - │ └── columns: uniq_hidden_pk.a:41 uniq_hidden_pk.rowid:45!null + │ └── columns: uniq_hidden_pk.a:42 uniq_hidden_pk.b:43 uniq_hidden_pk.c:44 uniq_hidden_pk.d:45 uniq_hidden_pk.rowid:46!null └── filters - ├── a:39 = uniq_hidden_pk.a:41 - └── rowid:40 != uniq_hidden_pk.rowid:45 + ├── a:48 = uniq_hidden_pk.a:42 + └── rowid:52 != uniq_hidden_pk.rowid:46 # Upsert with non-constant input. # Add inequality filters for the hidden primary key column. @@ -1153,49 +1211,55 @@ upsert uniq_hidden_pk └── unique-checks ├── unique-checks-item: uniq_hidden_pk(b,c) │ └── semi-join (hash) - │ ├── columns: b:15 c:16 rowid:17 + │ ├── columns: a:21 b:22 c:23 d:24 rowid:25 │ ├── with-scan &1 - │ │ ├── columns: b:15 c:16 rowid:17 + │ │ ├── columns: a:21 b:22 c:23 d:24 rowid:25 │ │ └── mapping: - │ │ ├── v:8 => b:15 - │ │ ├── x:10 => c:16 - │ │ └── column14:14 => rowid:17 + │ │ ├── k:7 => a:21 + │ │ ├── v:8 => b:22 + │ │ ├── x:10 => c:23 + │ │ ├── y:11 => d:24 + │ │ └── column14:14 => rowid:25 │ ├── scan uniq_hidden_pk - │ │ └── columns: uniq_hidden_pk.b:19 uniq_hidden_pk.c:20 uniq_hidden_pk.rowid:22!null + │ │ └── columns: uniq_hidden_pk.a:15 uniq_hidden_pk.b:16 uniq_hidden_pk.c:17 uniq_hidden_pk.d:18 uniq_hidden_pk.rowid:19!null │ └── filters - │ ├── b:15 = uniq_hidden_pk.b:19 - │ ├── c:16 = uniq_hidden_pk.c:20 - │ └── rowid:17 != uniq_hidden_pk.rowid:22 + │ ├── b:22 = uniq_hidden_pk.b:16 + │ ├── c:23 = uniq_hidden_pk.c:17 + │ └── rowid:25 != uniq_hidden_pk.rowid:19 ├── unique-checks-item: uniq_hidden_pk(a,b,d) │ └── semi-join (hash) - │ ├── columns: a:24 b:25 d:26 rowid:27 + │ ├── columns: a:32 b:33 c:34 d:35 rowid:36 │ ├── with-scan &1 - │ │ ├── columns: a:24 b:25 d:26 rowid:27 + │ │ ├── columns: a:32 b:33 c:34 d:35 rowid:36 │ │ └── mapping: - │ │ ├── k:7 => a:24 - │ │ ├── v:8 => b:25 - │ │ ├── y:11 => d:26 - │ │ └── column14:14 => rowid:27 + │ │ ├── k:7 => a:32 + │ │ ├── v:8 => b:33 + │ │ ├── x:10 => c:34 + │ │ ├── y:11 => d:35 + │ │ └── column14:14 => rowid:36 │ ├── scan uniq_hidden_pk - │ │ └── columns: uniq_hidden_pk.a:28 uniq_hidden_pk.b:29 uniq_hidden_pk.d:31 uniq_hidden_pk.rowid:32!null + │ │ └── columns: uniq_hidden_pk.a:26 uniq_hidden_pk.b:27 uniq_hidden_pk.c:28 uniq_hidden_pk.d:29 uniq_hidden_pk.rowid:30!null │ └── filters - │ ├── a:24 = uniq_hidden_pk.a:28 - │ ├── b:25 = uniq_hidden_pk.b:29 - │ ├── d:26 = uniq_hidden_pk.d:31 - │ └── rowid:27 != uniq_hidden_pk.rowid:32 + │ ├── a:32 = uniq_hidden_pk.a:26 + │ ├── b:33 = uniq_hidden_pk.b:27 + │ ├── d:35 = uniq_hidden_pk.d:29 + │ └── rowid:36 != uniq_hidden_pk.rowid:30 └── unique-checks-item: uniq_hidden_pk(a) └── semi-join (hash) - ├── columns: a:34 rowid:35 + ├── columns: a:43 b:44 c:45 d:46 rowid:47 ├── with-scan &1 - │ ├── columns: a:34 rowid:35 + │ ├── columns: a:43 b:44 c:45 d:46 rowid:47 │ └── mapping: - │ ├── k:7 => a:34 - │ └── column14:14 => rowid:35 + │ ├── k:7 => a:43 + │ ├── v:8 => b:44 + │ ├── x:10 => c:45 + │ ├── y:11 => d:46 + │ └── column14:14 => rowid:47 ├── scan uniq_hidden_pk - │ └── columns: uniq_hidden_pk.a:36 uniq_hidden_pk.rowid:40!null + │ └── columns: uniq_hidden_pk.a:37 uniq_hidden_pk.b:38 uniq_hidden_pk.c:39 uniq_hidden_pk.d:40 uniq_hidden_pk.rowid:41!null └── filters - ├── a:34 = uniq_hidden_pk.a:36 - └── rowid:35 != uniq_hidden_pk.rowid:40 + ├── a:43 = uniq_hidden_pk.a:37 + └── rowid:47 != uniq_hidden_pk.rowid:41 # On conflict do update with constant input, conflict on UNIQUE WITHOUT INDEX # columns. @@ -1254,49 +1318,55 @@ upsert uniq_hidden_pk └── unique-checks ├── unique-checks-item: uniq_hidden_pk(b,c) │ └── semi-join (hash) - │ ├── columns: b:24 c:25 rowid:26 + │ ├── columns: a:30!null b:31 c:32 d:33 rowid:34 │ ├── with-scan &1 - │ │ ├── columns: b:24 c:25 rowid:26 + │ │ ├── columns: a:30!null b:31 c:32 d:33 rowid:34 │ │ └── mapping: - │ │ ├── upsert_b:20 => b:24 - │ │ ├── upsert_c:21 => c:25 - │ │ └── upsert_rowid:23 => rowid:26 + │ │ ├── upsert_a:19 => a:30 + │ │ ├── upsert_b:20 => b:31 + │ │ ├── upsert_c:21 => c:32 + │ │ ├── upsert_d:22 => d:33 + │ │ └── upsert_rowid:23 => rowid:34 │ ├── scan uniq_hidden_pk - │ │ └── columns: uniq_hidden_pk.b:28 uniq_hidden_pk.c:29 uniq_hidden_pk.rowid:31!null + │ │ └── columns: uniq_hidden_pk.a:24 uniq_hidden_pk.b:25 uniq_hidden_pk.c:26 uniq_hidden_pk.d:27 uniq_hidden_pk.rowid:28!null │ └── filters - │ ├── b:24 = uniq_hidden_pk.b:28 - │ ├── c:25 = uniq_hidden_pk.c:29 - │ └── rowid:26 != uniq_hidden_pk.rowid:31 + │ ├── b:31 = uniq_hidden_pk.b:25 + │ ├── c:32 = uniq_hidden_pk.c:26 + │ └── rowid:34 != uniq_hidden_pk.rowid:28 ├── unique-checks-item: uniq_hidden_pk(a,b,d) │ └── semi-join (hash) - │ ├── columns: a:33!null b:34 d:35 rowid:36 + │ ├── columns: a:41!null b:42 c:43 d:44 rowid:45 │ ├── with-scan &1 - │ │ ├── columns: a:33!null b:34 d:35 rowid:36 + │ │ ├── columns: a:41!null b:42 c:43 d:44 rowid:45 │ │ └── mapping: - │ │ ├── upsert_a:19 => a:33 - │ │ ├── upsert_b:20 => b:34 - │ │ ├── upsert_d:22 => d:35 - │ │ └── upsert_rowid:23 => rowid:36 + │ │ ├── upsert_a:19 => a:41 + │ │ ├── upsert_b:20 => b:42 + │ │ ├── upsert_c:21 => c:43 + │ │ ├── upsert_d:22 => d:44 + │ │ └── upsert_rowid:23 => rowid:45 │ ├── scan uniq_hidden_pk - │ │ └── columns: uniq_hidden_pk.a:37 uniq_hidden_pk.b:38 uniq_hidden_pk.d:40 uniq_hidden_pk.rowid:41!null + │ │ └── columns: uniq_hidden_pk.a:35 uniq_hidden_pk.b:36 uniq_hidden_pk.c:37 uniq_hidden_pk.d:38 uniq_hidden_pk.rowid:39!null │ └── filters - │ ├── a:33 = uniq_hidden_pk.a:37 - │ ├── b:34 = uniq_hidden_pk.b:38 - │ ├── d:35 = uniq_hidden_pk.d:40 - │ └── rowid:36 != uniq_hidden_pk.rowid:41 + │ ├── a:41 = uniq_hidden_pk.a:35 + │ ├── b:42 = uniq_hidden_pk.b:36 + │ ├── d:44 = uniq_hidden_pk.d:38 + │ └── rowid:45 != uniq_hidden_pk.rowid:39 └── unique-checks-item: uniq_hidden_pk(a) └── semi-join (hash) - ├── columns: a:43!null rowid:44 + ├── columns: a:52!null b:53 c:54 d:55 rowid:56 ├── with-scan &1 - │ ├── columns: a:43!null rowid:44 + │ ├── columns: a:52!null b:53 c:54 d:55 rowid:56 │ └── mapping: - │ ├── upsert_a:19 => a:43 - │ └── upsert_rowid:23 => rowid:44 + │ ├── upsert_a:19 => a:52 + │ ├── upsert_b:20 => b:53 + │ ├── upsert_c:21 => c:54 + │ ├── upsert_d:22 => d:55 + │ └── upsert_rowid:23 => rowid:56 ├── scan uniq_hidden_pk - │ └── columns: uniq_hidden_pk.a:45 uniq_hidden_pk.rowid:49!null + │ └── columns: uniq_hidden_pk.a:46 uniq_hidden_pk.b:47 uniq_hidden_pk.c:48 uniq_hidden_pk.d:49 uniq_hidden_pk.rowid:50!null └── filters - ├── a:43 = uniq_hidden_pk.a:45 - └── rowid:44 != uniq_hidden_pk.rowid:49 + ├── a:52 = uniq_hidden_pk.a:46 + └── rowid:56 != uniq_hidden_pk.rowid:50 exec-ddl CREATE TABLE uniq_fk_parent ( @@ -1358,17 +1428,17 @@ upsert uniq_fk_parent ├── unique-checks │ └── unique-checks-item: uniq_fk_parent(a) │ └── semi-join (hash) - │ ├── columns: a:10!null rowid:11 + │ ├── columns: a:13!null rowid:14 │ ├── with-scan &1 - │ │ ├── columns: a:10!null rowid:11 + │ │ ├── columns: a:13!null rowid:14 │ │ └── mapping: - │ │ ├── column1:4 => a:10 - │ │ └── upsert_rowid:9 => rowid:11 + │ │ ├── column1:4 => a:13 + │ │ └── upsert_rowid:9 => rowid:14 │ ├── scan uniq_fk_parent - │ │ └── columns: uniq_fk_parent.a:12 uniq_fk_parent.rowid:13!null + │ │ └── columns: uniq_fk_parent.a:10 uniq_fk_parent.rowid:11!null │ └── filters - │ ├── a:10 = uniq_fk_parent.a:12 - │ └── rowid:11 != uniq_fk_parent.rowid:13 + │ ├── a:13 = uniq_fk_parent.a:10 + │ └── rowid:14 != uniq_fk_parent.rowid:11 └── f-k-checks └── f-k-checks-item: uniq_fk_child(a) -> uniq_fk_parent(a) └── semi-join (hash) @@ -1481,17 +1551,17 @@ upsert t └── unique-checks └── unique-checks-item: t(i) └── semi-join (hash) - ├── columns: i:14!null rowid:15 + ├── columns: i:17!null rowid:18 ├── with-scan &1 - │ ├── columns: i:14!null rowid:15 + │ ├── columns: i:17!null rowid:18 │ └── mapping: - │ ├── upsert_i:10 => i:14 - │ └── upsert_rowid:11 => rowid:15 + │ ├── upsert_i:10 => i:17 + │ └── upsert_rowid:11 => rowid:18 ├── scan t - │ ├── columns: t.i:16 t.rowid:17!null + │ ├── columns: t.i:14 t.rowid:15!null │ └── partial index predicates │ └── i1: filters - │ └── t.i:16 > 0 + │ └── t.i:14 > 0 └── filters - ├── i:14 = t.i:16 - └── rowid:15 != t.rowid:17 + ├── i:17 = t.i:14 + └── rowid:18 != t.rowid:15