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..fd923686f516 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/unique +++ b/pkg/sql/opt/exec/execbuilder/testdata/unique @@ -71,6 +71,40 @@ 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, + 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 +126,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 +195,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 +310,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 +322,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 +333,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 +345,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 +701,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 +736,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 +816,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 +932,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 +967,265 @@ 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 + +# 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 +· +• insert fast path + into: uniq_partial(k, a, b) + auto commit + size: 3 columns, 2 rows + +# 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 + +# 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 +1285,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 +1691,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 +1714,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 +1726,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 +1749,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 +1879,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 +2502,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 +2537,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 +2654,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 +2689,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 49d6f4990edd..4266c4a4dad4 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 w:10 x:11 uniq.y:12 + │ ├── columns: w_new:15!null x_new:16!null uniq.k:8!null uniq.v:9 w:10 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 w:10 x:11 uniq.y:12 + │ │ ├── columns: uniq.k:8!null uniq.v:9 w:10 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 w:10 x:11 uniq.y:12 + │ │ │ ├── columns: uniq.k:8!null uniq.v:9 w:10 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_new:17!null k:18!null + │ ├── columns: k:24!null v:25 w_new:26!null x_new:27!null y:28 z:29 │ ├── cardinality: [0 - 1] │ ├── key: () - │ ├── fd: ()-->(17,18) + │ ├── fd: ()-->(24-29) │ ├── with-scan &1 - │ │ ├── columns: w_new:17!null k:18!null + │ │ ├── columns: k:24!null v:25 w_new:26!null x_new:27!null y:28 z:29 │ │ ├── mapping: - │ │ │ ├── w_new:15 => w_new:17 - │ │ │ └── uniq.k:8 => k:18 + │ │ │ ├── uniq.k:8 => k:24 + │ │ │ ├── uniq.v:9 => v:25 + │ │ │ ├── w_new:15 => w_new:26 + │ │ │ ├── x_new:16 => x_new: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 w:21 - │ │ ├── key: (19) - │ │ └── fd: (19)-->(21) + │ │ ├── columns: uniq.k:17!null w:19 + │ │ ├── key: (17) + │ │ └── fd: (17)-->(19) │ └── filters - │ ├── w_new:17 = 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_new:26 = 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_new:26!null y:27 k:28!null + ├── columns: k:37!null v:38 w_new:39!null x_new:40!null y:41 z:42 ├── cardinality: [0 - 1] ├── key: () - ├── fd: ()-->(26-28) + ├── fd: ()-->(37-42) ├── with-scan &1 - │ ├── columns: x_new:26!null y:27 k:28!null + │ ├── columns: k:37!null v:38 w_new:39!null x_new:40!null y:41 z:42 │ ├── mapping: - │ │ ├── x_new:16 => x_new: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_new:39 + │ │ ├── x_new:16 => x_new: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 x:32 uniq.y:33 - │ ├── key: (29) - │ └── fd: (29)-->(32,33) + │ ├── columns: uniq.k:30!null x:33 uniq.y:34 + │ ├── key: (30) + │ └── fd: (30)-->(33,34) └── filters - ├── x_new:26 = 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_new:40 = 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 @@ -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 k:10 b:12 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 k:10 b:12 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 k:10 a:11 b:12 c:13 + │ │ ├── columns: column1:7!null column2:8!null column9:9 k:10 a:11 b:12 c:13 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: k:10!null a:11 b:12 c:13 + │ │ │ ├── columns: k:10!null a:11 b:12 c:13 d:14 │ │ │ ├── cardinality: [0 - 1] │ │ │ ├── key: () - │ │ │ ├── fd: ()-->(10-13) + │ │ │ ├── fd: ()-->(10-14) │ │ │ ├── scan uniq_fk_parent - │ │ │ │ ├── columns: k:10!null a:11 b:12 c:13 + │ │ │ │ ├── columns: k:10!null a:11 b:12 c:13 d:14 │ │ │ │ ├── key: (10) - │ │ │ │ └── fd: (10)-->(11-13) + │ │ │ │ └── fd: (10)-->(11-14) │ │ │ └── filters │ │ │ └── k:10 = 2 [outer=(10), constraints=(/10: [/2 - /2]; tight), fd=()-->(10)] │ │ └── filters (true) @@ -3710,52 +3717,58 @@ upsert uniq_fk_parent │ ├── CASE WHEN k:10 IS NULL THEN column1:7 ELSE k:10 END [as=upsert_k:17, outer=(7,10)] │ ├── CASE WHEN k:10 IS NULL THEN column2:8 ELSE a:11 END [as=upsert_a:18, outer=(8,10,11)] │ ├── CASE WHEN k:10 IS NULL THEN column9:9 ELSE b:12 END [as=upsert_b:19, outer=(9,10,12)] - │ └── CASE WHEN k:10 IS NULL THEN column9:9 ELSE 1 END [as=upsert_c:20, outer=(9,10)] + │ ├── CASE WHEN k:10 IS NULL THEN column9:9 ELSE 1 END [as=upsert_c:20, outer=(9,10)] + │ └── CASE WHEN k:10 IS NULL THEN column9:9 ELSE d:14 END [as=upsert_d:21, outer=(9,10,14)] └── unique-checks ├── unique-checks-item: uniq_fk_parent(a) │ └── semi-join (hash) - │ ├── columns: upsert_a:22 upsert_k:23 + │ ├── columns: upsert_k:28 upsert_a:29 upsert_b:30 upsert_c:31 upsert_d:32 │ ├── cardinality: [0 - 1] │ ├── key: () - │ ├── fd: ()-->(22,23) + │ ├── fd: ()-->(28-32) │ ├── with-scan &1 - │ │ ├── columns: upsert_a:22 upsert_k:23 + │ │ ├── columns: upsert_k:28 upsert_a:29 upsert_b:30 upsert_c:31 upsert_d:32 │ │ ├── mapping: - │ │ │ ├── upsert_a:18 => upsert_a:22 - │ │ │ └── upsert_k:17 => upsert_k:23 + │ │ │ ├── upsert_k:17 => upsert_k:28 + │ │ │ ├── upsert_a:18 => upsert_a:29 + │ │ │ ├── upsert_b:19 => upsert_b:30 + │ │ │ ├── upsert_c:20 => upsert_c:31 + │ │ │ └── upsert_d:21 => upsert_d:32 │ │ ├── cardinality: [1 - 1] │ │ ├── key: () - │ │ └── fd: ()-->(22,23) + │ │ └── fd: ()-->(28-32) │ ├── scan uniq_fk_parent - │ │ ├── columns: k:24!null a:25 - │ │ ├── key: (24) - │ │ └── fd: (24)-->(25) + │ │ ├── columns: k:22!null a:23 + │ │ ├── key: (22) + │ │ └── fd: (22)-->(23) │ └── filters - │ ├── upsert_a:22 = a:25 [outer=(22,25), constraints=(/22: (/NULL - ]; /25: (/NULL - ]), fd=(22)==(25), (25)==(22)] - │ └── upsert_k:23 != k:24 [outer=(23,24), constraints=(/23: (/NULL - ]; /24: (/NULL - ])] + │ ├── upsert_a:29 = a:23 [outer=(23,29), constraints=(/23: (/NULL - ]; /29: (/NULL - ]), fd=(23)==(29), (29)==(23)] + │ └── upsert_k:28 != k:22 [outer=(22,28), constraints=(/22: (/NULL - ]; /28: (/NULL - ])] └── unique-checks-item: uniq_fk_parent(b,c) └── semi-join (hash) - ├── columns: upsert_b:30 upsert_c:31 upsert_k:32 + ├── columns: upsert_k:39 upsert_a:40 upsert_b:41 upsert_c:42 upsert_d:43 ├── cardinality: [0 - 1] ├── key: () - ├── fd: ()-->(30-32) + ├── fd: ()-->(39-43) ├── with-scan &1 - │ ├── columns: upsert_b:30 upsert_c:31 upsert_k:32 + │ ├── columns: upsert_k:39 upsert_a:40 upsert_b:41 upsert_c:42 upsert_d:43 │ ├── mapping: - │ │ ├── upsert_b:19 => upsert_b:30 - │ │ ├── upsert_c:20 => upsert_c:31 - │ │ └── upsert_k:17 => upsert_k:32 + │ │ ├── upsert_k:17 => upsert_k:39 + │ │ ├── upsert_a:18 => upsert_a:40 + │ │ ├── upsert_b:19 => upsert_b:41 + │ │ ├── upsert_c:20 => upsert_c:42 + │ │ └── upsert_d:21 => upsert_d:43 │ ├── cardinality: [1 - 1] │ ├── key: () - │ └── fd: ()-->(30-32) + │ └── fd: ()-->(39-43) ├── scan uniq_fk_parent │ ├── columns: k:33!null b:35 c:36 │ ├── key: (33) │ └── fd: (33)-->(35,36) └── filters - ├── upsert_b:30 = b:35 [outer=(30,35), constraints=(/30: (/NULL - ]; /35: (/NULL - ]), fd=(30)==(35), (35)==(30)] - ├── upsert_c:31 = c:36 [outer=(31,36), constraints=(/31: (/NULL - ]; /36: (/NULL - ]), fd=(31)==(36), (36)==(31)] - └── upsert_k:32 != k:33 [outer=(32,33), constraints=(/32: (/NULL - ]; /33: (/NULL - ])] + ├── upsert_b:41 = b:35 [outer=(35,41), constraints=(/35: (/NULL - ]; /41: (/NULL - ]), fd=(35)==(41), (41)==(35)] + ├── upsert_c:42 = c:36 [outer=(36,42), constraints=(/36: (/NULL - ]; /42: (/NULL - ]), fd=(36)==(42), (42)==(36)] + └── upsert_k:39 != k:33 [outer=(33,39), constraints=(/33: (/NULL - ]; /39: (/NULL - ])] # Prune inbound foreign key columns when they are not updated. norm expect=PruneMutationInputCols @@ -3815,48 +3828,53 @@ upsert uniq_fk_parent └── unique-checks ├── unique-checks-item: uniq_fk_parent(a) │ └── semi-join (hash) - │ ├── columns: upsert_a:21 upsert_k:22 + │ ├── columns: upsert_k:27 upsert_a:28 upsert_b:29 upsert_c:30 upsert_d:31 │ ├── cardinality: [0 - 1] │ ├── key: () - │ ├── fd: ()-->(21,22) + │ ├── fd: ()-->(27-31) │ ├── with-scan &1 - │ │ ├── columns: upsert_a:21 upsert_k:22 + │ │ ├── columns: upsert_k:27 upsert_a:28 upsert_b:29 upsert_c:30 upsert_d:31 │ │ ├── mapping: - │ │ │ ├── upsert_a:17 => upsert_a:21 - │ │ │ └── upsert_k:16 => upsert_k:22 + │ │ │ ├── upsert_k:16 => upsert_k:27 + │ │ │ ├── upsert_a:17 => upsert_a:28 + │ │ │ ├── upsert_b:18 => upsert_b:29 + │ │ │ ├── upsert_c:19 => upsert_c:30 + │ │ │ └── upsert_d:20 => upsert_d:31 │ │ ├── cardinality: [1 - 1] │ │ ├── key: () - │ │ └── fd: ()-->(21,22) + │ │ └── fd: ()-->(27-31) │ ├── scan uniq_fk_parent - │ │ ├── columns: k:23!null a:24 - │ │ ├── key: (23) - │ │ └── fd: (23)-->(24) + │ │ ├── columns: k:21!null a:22 + │ │ ├── key: (21) + │ │ └── fd: (21)-->(22) │ └── filters - │ ├── upsert_a:21 = a:24 [outer=(21,24), constraints=(/21: (/NULL - ]; /24: (/NULL - ]), fd=(21)==(24), (24)==(21)] - │ └── upsert_k:22 != k:23 [outer=(22,23), constraints=(/22: (/NULL - ]; /23: (/NULL - ])] + │ ├── upsert_a:28 = a:22 [outer=(22,28), constraints=(/22: (/NULL - ]; /28: (/NULL - ]), fd=(22)==(28), (28)==(22)] + │ └── upsert_k:27 != k:21 [outer=(21,27), constraints=(/21: (/NULL - ]; /27: (/NULL - ])] └── unique-checks-item: uniq_fk_parent(b,c) └── semi-join (hash) - ├── columns: upsert_b:29 upsert_c:30 upsert_k:31 + ├── columns: upsert_k:38 upsert_a:39 upsert_b:40 upsert_c:41 upsert_d:42 ├── cardinality: [0 - 1] ├── key: () - ├── fd: ()-->(29-31) + ├── fd: ()-->(38-42) ├── with-scan &1 - │ ├── columns: upsert_b:29 upsert_c:30 upsert_k:31 + │ ├── columns: upsert_k:38 upsert_a:39 upsert_b:40 upsert_c:41 upsert_d:42 │ ├── mapping: - │ │ ├── upsert_b:18 => upsert_b:29 - │ │ ├── upsert_c:19 => upsert_c:30 - │ │ └── upsert_k:16 => upsert_k:31 + │ │ ├── upsert_k:16 => upsert_k:38 + │ │ ├── upsert_a:17 => upsert_a:39 + │ │ ├── upsert_b:18 => upsert_b:40 + │ │ ├── upsert_c:19 => upsert_c:41 + │ │ └── upsert_d:20 => upsert_d:42 │ ├── cardinality: [1 - 1] │ ├── key: () - │ └── fd: ()-->(29-31) + │ └── fd: ()-->(38-42) ├── scan uniq_fk_parent │ ├── columns: k:32!null b:34 c:35 │ ├── key: (32) │ └── fd: (32)-->(34,35) └── filters - ├── upsert_b:29 = b:34 [outer=(29,34), constraints=(/29: (/NULL - ]; /34: (/NULL - ]), fd=(29)==(34), (34)==(29)] - ├── upsert_c:30 = c:35 [outer=(30,35), constraints=(/30: (/NULL - ]; /35: (/NULL - ]), fd=(30)==(35), (35)==(30)] - └── upsert_k:31 != k:32 [outer=(31,32), constraints=(/31: (/NULL - ]; /32: (/NULL - ])] + ├── upsert_b:40 = b:34 [outer=(34,40), constraints=(/34: (/NULL - ]; /40: (/NULL - ]), fd=(34)==(40), (40)==(34)] + ├── upsert_c:41 = c:35 [outer=(35,41), constraints=(/35: (/NULL - ]; /41: (/NULL - ]), fd=(35)==(41), (41)==(35)] + └── upsert_k:38 != 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 bd49503facb8..8aa408c3b77f 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 constraints 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. diff --git a/pkg/sql/opt/optbuilder/mutation_builder_unique.go b/pkg/sql/opt/optbuilder/mutation_builder_unique.go index cc30ee2bdc42..4971c5c5942c 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): Should mutation columns be included here? Does uniqueness + // need to hold true for write-only columns? + 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 876623dcd9ba..c392d7c51874 100644 --- a/pkg/sql/opt/optbuilder/testdata/unique-checks-insert +++ b/pkg/sql/opt/optbuilder/testdata/unique-checks-insert @@ -29,32 +29,37 @@ insert uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: column3:12!null column1:13!null + │ ├── columns: column1:18!null column2:19!null column3:20!null column4:21!null column5:22!null │ ├── with-scan &1 - │ │ ├── columns: column3:12!null column1:13!null + │ │ ├── columns: column1:18!null column2:19!null column3:20!null column4:21!null column5:22!null │ │ └── mapping: - │ │ ├── column3:9 => column3:12 - │ │ └── column1:7 => column1:13 + │ │ ├── column1:7 => column1:18 + │ │ ├── column2:8 => column2:19 + │ │ ├── column3:9 => column3:20 + │ │ ├── column4:10 => column4:21 + │ │ └── column5:11 => column5:22 │ ├── scan uniq - │ │ └── columns: k:14!null w:16 + │ │ └── columns: k:12!null v:13 w:14 x:15 y:16 │ └── filters - │ ├── column3:12 = w:16 - │ └── column1:13 != k:14 + │ ├── column3:20 = w:14 + │ └── column1:18 != k:12 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: column4:20!null column5:21!null column1:22!null + ├── columns: column1:29!null column2:30!null column3:31!null column4:32!null column5:33!null ├── with-scan &1 - │ ├── columns: column4:20!null column5:21!null column1:22!null + │ ├── columns: column1:29!null column2:30!null column3:31!null column4:32!null column5:33!null │ └── mapping: - │ ├── column4:10 => column4:20 - │ ├── column5:11 => column5:21 - │ └── column1:7 => column1:22 + │ ├── column1:7 => column1:29 + │ ├── column2:8 => column2:30 + │ ├── column3:9 => column3:31 + │ ├── column4:10 => column4:32 + │ └── column5:11 => column5:33 ├── scan uniq - │ └── columns: k:23!null x:26 y:27 + │ └── columns: k:23!null v:24 w:25 x:26 y:27 └── filters - ├── column4:20 = x:26 - ├── column5:21 = y:27 - └── column1:22 != k:23 + ├── column4:32 = x:26 + ├── column5:33 = y:27 + └── column1:29 != k:23 # Some of the inserted values have nulls. build @@ -77,34 +82,39 @@ insert uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: column3:12 column1:13!null + │ ├── columns: column1:18!null column2:19 column3:20 column4:21 column5:22!null │ ├── with-scan &1 - │ │ ├── columns: column3:12 column1:13!null + │ │ ├── columns: column1:18!null column2:19 column3:20 column4:21 column5:22!null │ │ └── mapping: - │ │ ├── column3:9 => column3:12 - │ │ └── column1:7 => column1:13 + │ │ ├── column1:7 => column1:18 + │ │ ├── column2:8 => column2:19 + │ │ ├── column3:9 => column3:20 + │ │ ├── column4:10 => column4:21 + │ │ └── column5:11 => column5:22 │ ├── scan uniq - │ │ └── columns: k:14!null w:16 + │ │ └── columns: k:12!null v:13 w:14 x:15 y:16 │ └── filters - │ ├── column3:12 = w:16 - │ └── column1:13 != k:14 + │ ├── column3:20 = w:14 + │ └── column1:18 != k:12 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: column4:20 column5:21!null column1:22!null + ├── columns: column1:29!null column2:30 column3:31 column4:32 column5:33!null ├── with-scan &1 - │ ├── columns: column4:20 column5:21!null column1:22!null + │ ├── columns: column1:29!null column2:30 column3:31 column4:32 column5:33!null │ └── mapping: - │ ├── column4:10 => column4:20 - │ ├── column5:11 => column5:21 - │ └── column1:7 => column1:22 + │ ├── column1:7 => column1:29 + │ ├── column2:8 => column2:30 + │ ├── column3:9 => column3:31 + │ ├── column4:10 => column4:32 + │ └── column5:11 => column5:33 ├── scan uniq - │ └── columns: k:23!null x:26 y:27 + │ └── columns: k:23!null v:24 w:25 x:26 y:27 └── filters - ├── column4:20 = x:26 - ├── column5:21 = y:27 - └── column1:22 != k:23 + ├── column4:32 = x:26 + ├── column5:33 = y:27 + └── column1:29 != 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) ---- @@ -124,21 +134,23 @@ insert uniq └── unique-checks └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: column4:12!null column5:13!null column1:14!null + ├── columns: column1:18!null column2:19!null column3:20 column4:21!null column5:22!null ├── with-scan &1 - │ ├── columns: column4:12!null column5:13!null column1:14!null + │ ├── columns: column1:18!null column2:19!null column3:20 column4:21!null column5:22!null │ └── mapping: - │ ├── column4:10 => column4:12 - │ ├── column5:11 => column5:13 - │ └── column1:7 => column1:14 + │ ├── column1:7 => column1:18 + │ ├── column2:8 => column2:19 + │ ├── column3:9 => column3:20 + │ ├── column4:10 => column4:21 + │ └── column5:11 => column5:22 ├── scan uniq - │ └── columns: k:15!null x:18 y:19 + │ └── columns: k:12!null v:13 w:14 x:15 y:16 └── filters - ├── column4:12 = x:18 - ├── column5:13 = y:19 - └── column1:14 != k:15 + ├── column4:21 = x:15 + ├── column5:22 = y:16 + └── column1:18 != 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) ---- @@ -158,19 +170,22 @@ insert uniq └── unique-checks └── unique-checks-item: uniq(w) └── semi-join (hash) - ├── columns: column3:12 column1:13!null + ├── columns: column1:18!null column2:19!null column3:20 column4:21 column5:22!null ├── with-scan &1 - │ ├── columns: column3:12 column1:13!null + │ ├── columns: column1:18!null column2:19!null column3:20 column4:21 column5:22!null │ └── mapping: - │ ├── column3:9 => column3:12 - │ └── column1:7 => column1:13 + │ ├── column1:7 => column1:18 + │ ├── column2:8 => column2:19 + │ ├── column3:9 => column3:20 + │ ├── column4:10 => column4:21 + │ └── column5:11 => column5:22 ├── scan uniq - │ └── columns: k:14!null w:16 + │ └── columns: k:12!null v:13 w:14 x:15 y:16 └── filters - ├── column3:12 = w:16 - └── column1:13 != k:14 + ├── column3:20 = w:14 + └── column1:18 != 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) ---- @@ -190,19 +205,22 @@ insert uniq └── unique-checks └── unique-checks-item: uniq(w) └── semi-join (hash) - ├── columns: column3:12!null column1:13!null + ├── columns: column1:18!null column2:19!null column3:20!null column4:21!null column5:22 ├── with-scan &1 - │ ├── columns: column3:12!null column1:13!null + │ ├── columns: column1:18!null column2:19!null column3:20!null column4:21!null column5:22 │ └── mapping: - │ ├── column3:9 => column3:12 - │ └── column1:7 => column1:13 + │ ├── column1:7 => column1:18 + │ ├── column2:8 => column2:19 + │ ├── column3:9 => column3:20 + │ ├── column4:10 => column4:21 + │ └── column5:11 => column5:22 ├── scan uniq - │ └── columns: k:14!null w:16 + │ └── columns: k:12!null v:13 w:14 x:15 y:16 └── filters - ├── column3:12 = w:16 - └── column1:13 != k:14 + ├── column3:20 = w:14 + └── column1:18 != 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) ---- @@ -314,32 +332,37 @@ insert uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: column3:36!null column1:37!null + │ ├── columns: column1:42!null column2:43!null column3:44!null column4:45!null column5:46!null │ ├── with-scan &1 - │ │ ├── columns: column3:36!null column1:37!null + │ │ ├── columns: column1:42!null column2:43!null column3:44!null column4:45!null column5:46!null │ │ └── mapping: - │ │ ├── column3:9 => column3:36 - │ │ └── column1:7 => column1:37 + │ │ ├── column1:7 => column1:42 + │ │ ├── column2:8 => column2:43 + │ │ ├── column3:9 => column3:44 + │ │ ├── column4:10 => column4:45 + │ │ └── column5:11 => column5:46 │ ├── scan uniq - │ │ └── columns: k:38!null w:40 + │ │ └── columns: k:36!null v:37 w:38 x:39 y:40 │ └── filters - │ ├── column3:36 = w:40 - │ └── column1:37 != k:38 + │ ├── column3:44 = w:38 + │ └── column1:42 != k:36 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: column4:44!null column5:45!null column1:46!null + ├── columns: column1:53!null column2:54!null column3:55!null column4:56!null column5:57!null ├── with-scan &1 - │ ├── columns: column4:44!null column5:45!null column1:46!null + │ ├── columns: column1:53!null column2:54!null column3:55!null column4:56!null column5:57!null │ └── mapping: - │ ├── column4:10 => column4:44 - │ ├── column5:11 => column5:45 - │ └── column1:7 => column1:46 + │ ├── column1:7 => column1:53 + │ ├── column2:8 => column2:54 + │ ├── column3:9 => column3:55 + │ ├── column4:10 => column4:56 + │ └── column5:11 => column5:57 ├── scan uniq - │ └── columns: k:47!null x:50 y:51 + │ └── columns: k:47!null v:48 w:49 x:50 y:51 └── filters - ├── column4:44 = x:50 - ├── column5:45 = y:51 - └── column1:46 != k:47 + ├── column4:56 = x:50 + ├── column5:57 = y:51 + └── column1:53 != 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 @@ -381,32 +404,37 @@ insert uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: column3:18!null column1:19!null + │ ├── columns: column1:24!null column2:25!null column3:26!null column4:27!null column5:28!null │ ├── with-scan &1 - │ │ ├── columns: column3:18!null column1:19!null + │ │ ├── columns: column1:24!null column2:25!null column3:26!null column4:27!null column5:28!null │ │ └── mapping: - │ │ ├── column3:9 => column3:18 - │ │ └── column1:7 => column1:19 + │ │ ├── column1:7 => column1:24 + │ │ ├── column2:8 => column2:25 + │ │ ├── column3:9 => column3:26 + │ │ ├── column4:10 => column4:27 + │ │ └── column5:11 => column5:28 │ ├── scan uniq - │ │ └── columns: k:20!null w:22 + │ │ └── columns: k:18!null v:19 w:20 x:21 y:22 │ └── filters - │ ├── column3:18 = w:22 - │ └── column1:19 != k:20 + │ ├── column3:26 = w:20 + │ └── column1:24 != k:18 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: column4:26!null column5:27!null column1:28!null + ├── columns: column1:35!null column2:36!null column3:37!null column4:38!null column5:39!null ├── with-scan &1 - │ ├── columns: column4:26!null column5:27!null column1:28!null + │ ├── columns: column1:35!null column2:36!null column3:37!null column4:38!null column5:39!null │ └── mapping: - │ ├── column4:10 => column4:26 - │ ├── column5:11 => column5:27 - │ └── column1:7 => column1:28 + │ ├── column1:7 => column1:35 + │ ├── column2:8 => column2:36 + │ ├── column3:9 => column3:37 + │ ├── column4:10 => column4:38 + │ └── column5:11 => column5:39 ├── scan uniq - │ └── columns: k:29!null x:32 y:33 + │ └── columns: k:29!null v:30 w:31 x:32 y:33 └── filters - ├── column4:26 = x:32 - ├── column5:27 = y:33 - └── column1:28 != k:29 + ├── column4:38 = x:32 + ├── column5:39 = y:33 + └── column1:35 != 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: column2:10!null column3:11!null column1:12!null + │ ├── columns: column1:15!null column2:16!null column3:17!null column4:18!null │ ├── with-scan &1 - │ │ ├── columns: column2:10!null column3:11!null column1:12!null + │ │ ├── columns: column1:15!null column2:16!null column3:17!null column4:18!null │ │ └── mapping: - │ │ ├── column2:7 => column2:10 - │ │ ├── column3:8 => column3:11 - │ │ └── column1:6 => column1:12 + │ │ ├── column1:6 => column1:15 + │ │ ├── column2:7 => column2:16 + │ │ ├── column3:8 => column3:17 + │ │ └── column4:9 => column4:18 │ ├── scan uniq_overlaps_pk - │ │ └── columns: a:13!null b:14!null c:15 + │ │ └── columns: a:10!null b:11!null c:12 d:13 │ └── filters - │ ├── column2:10 = b:14 - │ ├── column3:11 = c:15 - │ └── column1:12 != a:13 + │ ├── column2:16 = b:11 + │ ├── column3:17 = c:12 + │ └── column1:15 != a:10 ├── unique-checks-item: uniq_overlaps_pk(a) │ └── semi-join (hash) - │ ├── columns: column1:18!null column2:19!null + │ ├── columns: column1:24!null column2:25!null column3:26!null column4:27!null │ ├── with-scan &1 - │ │ ├── columns: column1:18!null column2:19!null + │ │ ├── columns: column1:24!null column2:25!null column3:26!null column4:27!null │ │ └── mapping: - │ │ ├── column1:6 => column1:18 - │ │ └── column2:7 => column2:19 + │ │ ├── column1:6 => column1:24 + │ │ ├── column2:7 => column2:25 + │ │ ├── column3:8 => column3:26 + │ │ └── column4:9 => column4:27 │ ├── scan uniq_overlaps_pk - │ │ └── columns: a:20!null b:21!null + │ │ └── columns: a:19!null b:20!null c:21 d:22 │ └── filters - │ ├── column1:18 = a:20 - │ └── column2:19 != b:21 + │ ├── column1:24 = a:19 + │ └── column2:25 != b:20 └── unique-checks-item: uniq_overlaps_pk(c,d) └── semi-join (hash) - ├── columns: column3:25!null column4:26!null column1:27!null column2:28!null + ├── columns: column1:33!null column2:34!null column3:35!null column4:36!null ├── with-scan &1 - │ ├── columns: column3:25!null column4:26!null column1:27!null column2:28!null + │ ├── columns: column1:33!null column2:34!null column3:35!null column4:36!null │ └── mapping: - │ ├── column3:8 => column3:25 - │ ├── column4:9 => column4:26 - │ ├── column1:6 => column1:27 - │ └── column2:7 => column2:28 + │ ├── column1:6 => column1:33 + │ ├── column2:7 => column2:34 + │ ├── column3:8 => column3:35 + │ └── column4:9 => column4:36 ├── scan uniq_overlaps_pk - │ └── columns: a:29!null b:30!null c:31 d:32 + │ └── columns: a:28!null b:29!null c:30 d:31 └── filters - ├── column3:25 = c:31 - ├── column4:26 = d:32 - └── (column1:27 != a:29) OR (column2:28 != b:30) + ├── column3:35 = c:30 + ├── column4:36 = d:31 + └── (column1:33 != a:28) OR (column2:34 != 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: v:13 x:14 k:15 + │ ├── columns: k:18 v:19 x:20 y:21 │ ├── with-scan &1 - │ │ ├── columns: v:13 x:14 k:15 + │ │ ├── columns: k:18 v:19 x:20 y:21 │ │ └── mapping: - │ │ ├── other.v:7 => v:13 - │ │ ├── other.x:9 => x:14 - │ │ └── other.k:6 => k:15 + │ │ ├── other.k:6 => k:18 + │ │ ├── other.v:7 => v:19 + │ │ ├── other.x:9 => x:20 + │ │ └── other.y:10 => y:21 │ ├── scan uniq_overlaps_pk - │ │ └── columns: a:16!null b:17!null c:18 + │ │ └── columns: a:13!null b:14!null c:15 d:16 │ └── filters - │ ├── v:13 = b:17 - │ ├── x:14 = c:18 - │ └── k:15 != a:16 + │ ├── v:19 = b:14 + │ ├── x:20 = c:15 + │ └── k:18 != a:13 ├── unique-checks-item: uniq_overlaps_pk(a) │ └── semi-join (hash) - │ ├── columns: k:21 v:22 + │ ├── columns: k:27 v:28 x:29 y:30 │ ├── with-scan &1 - │ │ ├── columns: k:21 v:22 + │ │ ├── columns: k:27 v:28 x:29 y:30 │ │ └── mapping: - │ │ ├── other.k:6 => k:21 - │ │ └── other.v:7 => v:22 + │ │ ├── other.k:6 => k:27 + │ │ ├── other.v:7 => v:28 + │ │ ├── other.x:9 => x:29 + │ │ └── other.y:10 => y:30 │ ├── scan uniq_overlaps_pk - │ │ └── columns: a:23!null b:24!null + │ │ └── columns: a:22!null b:23!null c:24 d:25 │ └── filters - │ ├── k:21 = a:23 - │ └── v:22 != b:24 + │ ├── k:27 = a:22 + │ └── v:28 != b:23 └── unique-checks-item: uniq_overlaps_pk(c,d) └── semi-join (hash) - ├── columns: x:28 y:29 k:30 v:31 + ├── columns: k:36 v:37 x:38 y:39 ├── with-scan &1 - │ ├── columns: x:28 y:29 k:30 v:31 + │ ├── columns: k:36 v:37 x:38 y:39 │ └── mapping: - │ ├── other.x:9 => x:28 - │ ├── other.y:10 => y:29 - │ ├── other.k:6 => k:30 - │ └── other.v:7 => v:31 + │ ├── other.k:6 => k:36 + │ ├── other.v:7 => v:37 + │ ├── other.x:9 => x:38 + │ └── other.y:10 => y:39 ├── scan uniq_overlaps_pk - │ └── columns: a:32!null b:33!null c:34 d:35 + │ └── columns: a:31!null b:32!null c:33 d:34 └── filters - ├── x:28 = c:34 - ├── y:29 = d:35 - └── (k:30 != a:32) OR (v:31 != b:33) + ├── x:38 = c:33 + ├── y:39 = d:34 + └── (k:36 != a:31) OR (v:37 != 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: column2:12!null column3:13!null column11:14 + │ ├── columns: column1:18!null column2:19!null column3:20!null column4:21!null column11:22 │ ├── with-scan &1 - │ │ ├── columns: column2:12!null column3:13!null column11:14 + │ │ ├── columns: column1:18!null column2:19!null column3:20!null column4:21!null column11:22 │ │ └── mapping: - │ │ ├── column2:8 => column2:12 - │ │ ├── column3:9 => column3:13 - │ │ └── column11:11 => column11:14 + │ │ ├── column1:7 => column1:18 + │ │ ├── column2:8 => column2:19 + │ │ ├── column3:9 => column3:20 + │ │ ├── column4:10 => column4:21 + │ │ └── column11:11 => column11:22 │ ├── scan uniq_hidden_pk - │ │ └── columns: b:16 c:17 rowid:19!null + │ │ └── columns: a:12 b:13 c:14 d:15 rowid:16!null │ └── filters - │ ├── column2:12 = b:16 - │ ├── column3:13 = c:17 - │ └── column11:14 != rowid:19 + │ ├── column2:19 = b:13 + │ ├── column3:20 = c:14 + │ └── column11:22 != rowid:16 ├── unique-checks-item: uniq_hidden_pk(a,b,d) │ └── semi-join (hash) - │ ├── columns: column1:21!null column2:22!null column4:23!null column11:24 + │ ├── columns: column1:29!null column2:30!null column3:31!null column4:32!null column11:33 │ ├── with-scan &1 - │ │ ├── columns: column1:21!null column2:22!null column4:23!null column11:24 + │ │ ├── columns: column1:29!null column2:30!null column3:31!null column4:32!null column11:33 │ │ └── mapping: - │ │ ├── column1:7 => column1:21 - │ │ ├── column2:8 => column2:22 - │ │ ├── column4:10 => column4:23 - │ │ └── column11:11 => column11:24 + │ │ ├── column1:7 => column1:29 + │ │ ├── column2:8 => column2:30 + │ │ ├── column3:9 => column3:31 + │ │ ├── column4:10 => column4:32 + │ │ └── column11:11 => column11:33 │ ├── scan uniq_hidden_pk - │ │ └── columns: a:25 b:26 d:28 rowid:29!null + │ │ └── columns: a:23 b:24 c:25 d:26 rowid:27!null │ └── filters - │ ├── column1:21 = a:25 - │ ├── column2:22 = b:26 - │ ├── column4:23 = d:28 - │ └── column11:24 != rowid:29 + │ ├── column1:29 = a:23 + │ ├── column2:30 = b:24 + │ ├── column4:32 = d:26 + │ └── column11:33 != rowid:27 └── unique-checks-item: uniq_hidden_pk(a) └── semi-join (hash) - ├── columns: column1:31!null column11:32 + ├── columns: column1:40!null column2:41!null column3:42!null column4:43!null column11:44 ├── with-scan &1 - │ ├── columns: column1:31!null column11:32 + │ ├── columns: column1:40!null column2:41!null column3:42!null column4:43!null column11:44 │ └── mapping: - │ ├── column1:7 => column1:31 - │ └── column11:11 => column11:32 + │ ├── column1:7 => column1:40 + │ ├── column2:8 => column2:41 + │ ├── column3:9 => column3:42 + │ ├── column4:10 => column4:43 + │ └── column11:11 => column11:44 ├── scan uniq_hidden_pk - │ └── columns: a:33 rowid:37!null + │ └── columns: a:34 b:35 c:36 d:37 rowid:38!null └── filters - ├── column1:31 = a:33 - └── column11:32 != rowid:37 + ├── column1:40 = a:34 + └── column11:44 != rowid:38 # Insert with non-constant input. # Add inequality filters for the hidden primary key column. @@ -707,46 +752,310 @@ insert uniq_hidden_pk └── unique-checks ├── unique-checks-item: uniq_hidden_pk(b,c) │ └── semi-join (hash) - │ ├── columns: v:15 x:16 column14:17 + │ ├── columns: k:21 v:22 x:23 y:24 column14:25 │ ├── with-scan &1 - │ │ ├── columns: v:15 x:16 column14:17 + │ │ ├── columns: k:21 v:22 x:23 y:24 column14:25 │ │ └── mapping: - │ │ ├── other.v:8 => v:15 - │ │ ├── other.x:10 => x:16 - │ │ └── column14:14 => column14:17 + │ │ ├── other.k:7 => k:21 + │ │ ├── other.v:8 => v:22 + │ │ ├── other.x:10 => x:23 + │ │ ├── other.y:11 => y:24 + │ │ └── column14:14 => column14:25 │ ├── scan uniq_hidden_pk - │ │ └── columns: b:19 c:20 uniq_hidden_pk.rowid:22!null + │ │ └── columns: a:15 b:16 c:17 d:18 uniq_hidden_pk.rowid:19!null │ └── filters - │ ├── v:15 = b:19 - │ ├── x:16 = c:20 - │ └── column14:17 != uniq_hidden_pk.rowid:22 + │ ├── v:22 = b:16 + │ ├── x:23 = c:17 + │ └── column14:25 != uniq_hidden_pk.rowid:19 ├── unique-checks-item: uniq_hidden_pk(a,b,d) │ └── semi-join (hash) - │ ├── columns: k:24 v:25 y:26 column14:27 + │ ├── columns: k:32 v:33 x:34 y:35 column14:36 │ ├── with-scan &1 - │ │ ├── columns: k:24 v:25 y:26 column14:27 + │ │ ├── columns: k:32 v:33 x:34 y:35 column14:36 │ │ └── mapping: - │ │ ├── other.k:7 => k:24 - │ │ ├── other.v:8 => v:25 - │ │ ├── other.y:11 => y:26 - │ │ └── column14:14 => column14:27 + │ │ ├── other.k:7 => k:32 + │ │ ├── other.v:8 => v:33 + │ │ ├── other.x:10 => x:34 + │ │ ├── other.y:11 => y:35 + │ │ └── column14:14 => column14:36 │ ├── scan uniq_hidden_pk - │ │ └── columns: a:28 b:29 d:31 uniq_hidden_pk.rowid:32!null + │ │ └── columns: a:26 b:27 c:28 d:29 uniq_hidden_pk.rowid:30!null │ └── filters - │ ├── k:24 = a:28 - │ ├── v:25 = b:29 - │ ├── y:26 = d:31 - │ └── column14:27 != uniq_hidden_pk.rowid:32 + │ ├── k:32 = a:26 + │ ├── v:33 = b:27 + │ ├── y:35 = d:29 + │ └── column14:36 != uniq_hidden_pk.rowid:30 └── unique-checks-item: uniq_hidden_pk(a) └── semi-join (hash) - ├── columns: k:34 column14:35 + ├── columns: k:43 v:44 x:45 y:46 column14:47 ├── with-scan &1 - │ ├── columns: k:34 column14:35 + │ ├── columns: k:43 v:44 x:45 y:46 column14:47 │ └── mapping: - │ ├── other.k:7 => k:34 - │ └── column14:14 => column14:35 + │ ├── other.k:7 => k:43 + │ ├── other.v:8 => v:44 + │ ├── other.x:10 => x:45 + │ ├── other.y:11 => y:46 + │ └── column14:14 => column14:47 ├── scan uniq_hidden_pk - │ └── columns: a:36 uniq_hidden_pk.rowid:40!null + │ └── columns: a:37 b:38 c:39 d:40 uniq_hidden_pk.rowid:41!null └── filters - ├── k:34 = a:36 - └── column14:35 != uniq_hidden_pk.rowid:40 + ├── k:43 = a:37 + └── column14: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 => k:1 + │ ├── column2:6 => a:2 + │ └── column3:7 => 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: column1:12!null column2:13!null column3:14!null + ├── with-scan &1 + │ ├── columns: column1:12!null column2:13!null column3:14!null + │ └── mapping: + │ ├── column1:5 => column1:12 + │ ├── column2:6 => column2:13 + │ └── column3:7 => column3:14 + ├── scan uniq_partial + │ └── columns: k:8!null a:9 b:10 + └── filters + ├── column2:13 = a:9 + ├── column3:14 > 0 + ├── b:10 > 0 + └── column1:12 != 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 => k:1 + │ ├── column2:6 => a:2 + │ └── column3:7 => 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: column1:12!null column2:13 column3:14!null + ├── with-scan &1 + │ ├── columns: column1:12!null column2:13 column3:14!null + │ └── mapping: + │ ├── column1:5 => column1:12 + │ ├── column2:6 => column2:13 + │ └── column3:7 => column3:14 + ├── scan uniq_partial + │ └── columns: k:8!null a:9 b:10 + └── filters + ├── column2:13 = a:9 + ├── column3:14 > 0 + ├── b:10 > 0 + └── column1:12 != 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 + │ ├── other.v:6 => a:2 + │ └── other.w:7 => b:3 + ├── input binding: &1 + ├── project + │ ├── columns: other.k:5 other.v:6 other.w:7!null + │ └── scan other + │ └── columns: other.k:5 other.v:6 other.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 v:17 w:18!null + ├── with-scan &1 + │ ├── columns: k:16 v:17 w:18!null + │ └── mapping: + │ ├── other.k:5 => k:16 + │ ├── other.v:6 => v:17 + │ └── other.w:7 => w:18 + ├── scan uniq_partial + │ └── columns: uniq_partial.k:12!null a:13 b:14 + └── filters + ├── v:17 = a:13 + ├── w:18 > 0 + ├── b:14 > 0 + └── k:16 != uniq_partial.k:12 + +exec-ddl +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 +) +---- + +# 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), (2, 2, 2) +---- +insert uniq_partial_overlaps_pk + ├── columns: + ├── insert-mapping: + │ ├── column1:5 => k:1 + │ ├── column2:6 => a:2 + │ └── column3:7 => b:3 + └── values + ├── columns: column1:5!null column2:6!null column3:7!null + ├── (1, 1, 1) + └── (2, 2, 2) + +# 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 FROM other +---- +insert uniq_partial_overlaps_pk + ├── columns: + ├── insert-mapping: + │ ├── other.k:5 => uniq_partial_overlaps_pk.k:1 + │ ├── v:6 => a:2 + │ └── x:8 => b:3 + └── project + ├── columns: other.k:5 v:6 x:8 + └── scan other + └── columns: other.k:5 v:6 w:7!null x:8 y:9 rowid:10!null other.crdb_internal_mvcc_timestamp:11 + +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 => a:1 + │ ├── column2:7 => b:2 + │ ├── column8:8 => c:3 + │ └── column9:9 => 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: column1:15!null column2:16!null column8:17 column9:18 + ├── with-scan &1 + │ ├── columns: column1:15!null column2:16!null column8:17 column9:18 + │ └── mapping: + │ ├── column1:6 => column1:15 + │ ├── column2:7 => column2:16 + │ ├── column8:8 => column8:17 + │ └── column9:9 => column9:18 + ├── scan uniq_partial_hidden_pk + │ └── columns: a:10 b:11 c:12 rowid:13!null + └── filters + ├── column2:16 = b:11 + ├── column8:17 > 0 + ├── c:12 > 0 + └── column9:18 != 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: + │ ├── other.k:6 => a:1 + │ ├── other.v:7 => b:2 + │ ├── column13:13 => c:3 + │ └── column14:14 => uniq_partial_hidden_pk.rowid:4 + ├── input binding: &1 + ├── project + │ ├── columns: column13:13 column14:14 other.k:6 other.v:7 + │ ├── project + │ │ ├── columns: other.k:6 other.v:7 + │ │ └── scan other + │ │ └── columns: other.k:6 other.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: k:20 v:21 column13:22 column14:23 + ├── with-scan &1 + │ ├── columns: k:20 v:21 column13:22 column14:23 + │ └── mapping: + │ ├── other.k:6 => k:20 + │ ├── other.v:7 => v:21 + │ ├── column13:13 => column13:22 + │ └── column14:14 => column14:23 + ├── scan uniq_partial_hidden_pk + │ └── columns: a:15 b:16 c:17 uniq_partial_hidden_pk.rowid:18!null + └── filters + ├── v:21 = b:16 + ├── column13:22 > 0 + ├── c:17 > 0 + └── column14: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 007675fc8f92..569b50dde45b 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 w:9 x:10 uniq.y:11 + ├── fetch columns: uniq.k:7 uniq.v:8 w:9 x:10 uniq.y:11 ├── update-mapping: │ ├── w_new:13 => w:3 │ └── x_new:14 => x:4 ├── input binding: &1 ├── project - │ ├── columns: w_new:13!null x_new:14!null uniq.k:7!null v:8 w:9 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 w:9 x:10 uniq.y:11 crdb_internal_mvcc_timestamp:12 │ ├── scan uniq - │ │ └── columns: uniq.k:7!null v:8 w:9 x:10 uniq.y:11 crdb_internal_mvcc_timestamp:12 + │ │ └── columns: uniq.k:7!null uniq.v:8 w:9 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_new:15!null k:16!null + │ ├── columns: k:21!null v:22 w_new:23!null x_new:24!null y:25 │ ├── with-scan &1 - │ │ ├── columns: w_new:15!null k:16!null + │ │ ├── columns: k:21!null v:22 w_new:23!null x_new:24!null y:25 │ │ └── mapping: - │ │ ├── w_new:13 => w_new:15 - │ │ └── uniq.k:7 => k:16 + │ │ ├── uniq.k:7 => k:21 + │ │ ├── uniq.v:8 => v:22 + │ │ ├── w_new:13 => w_new:23 + │ │ ├── x_new:14 => x_new:24 + │ │ └── uniq.y:11 => y:25 │ ├── scan uniq - │ │ └── columns: uniq.k:17!null w:19 + │ │ └── columns: uniq.k:15!null uniq.v:16 w:17 x:18 uniq.y:19 │ └── filters - │ ├── w_new:15 = w:19 - │ └── k:16 != uniq.k:17 + │ ├── w_new:23 = w:17 + │ └── k:21 != uniq.k:15 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: x_new:23!null y:24 k:25!null + ├── columns: k:32!null v:33 w_new:34!null x_new:35!null y:36 ├── with-scan &1 - │ ├── columns: x_new:23!null y:24 k:25!null + │ ├── columns: k:32!null v:33 w_new:34!null x_new:35!null y:36 │ └── mapping: - │ ├── x_new:14 => x_new: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_new:34 + │ ├── x_new:14 => x_new:35 + │ └── uniq.y:11 => y:36 ├── scan uniq - │ └── columns: uniq.k:26!null x:29 uniq.y:30 + │ └── columns: uniq.k:26!null uniq.v:27 w:28 x:29 uniq.y:30 └── filters - ├── x_new:23 = x:29 - ├── y:24 = uniq.y:30 - └── k:25 != uniq.k:26 + ├── x_new:35 = 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 x:10 uniq.y:11 + ├── fetch columns: uniq.k:7 uniq.v:8 w:9 x:10 uniq.y:11 ├── update-mapping: │ ├── w_new:13 => w:3 │ └── x_new:14 => x:4 ├── input binding: &1 ├── project - │ ├── columns: w_new:13 x_new:14!null uniq.k:7!null v:8 w:9 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 w:9 x:10 uniq.y:11 crdb_internal_mvcc_timestamp:12 │ ├── scan uniq - │ │ └── columns: uniq.k:7!null v:8 w:9 x:10 uniq.y:11 crdb_internal_mvcc_timestamp:12 + │ │ └── columns: uniq.k:7!null uniq.v:8 w:9 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_new:15!null y:16 k:17!null + ├── columns: k:21!null v:22 w_new:23 x_new:24!null y:25 ├── with-scan &1 - │ ├── columns: x_new:15!null y:16 k:17!null + │ ├── columns: k:21!null v:22 w_new:23 x_new:24!null y:25 │ └── mapping: - │ ├── x_new:14 => x_new: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_new:23 + │ ├── x_new:14 => x_new:24 + │ └── uniq.y:11 => y:25 ├── scan uniq - │ └── columns: uniq.k:18!null x:21 uniq.y:22 + │ └── columns: uniq.k:15!null uniq.v:16 w:17 x:18 uniq.y:19 └── filters - ├── x_new:15 = x:21 - ├── y:16 = uniq.y:22 - └── k:17 != uniq.k:18 + ├── x_new:24 = 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: k:7 v:8 w:9 x:10 y:11 + ├── fetch columns: k:7 uniq.v:8 w:9 x:10 uniq.y:11 ├── update-mapping: │ ├── k_new:13 => k:1 │ ├── w_new:14 => w:3 │ └── x_new:15 => x:4 ├── input binding: &1 ├── project - │ ├── columns: k_new:13!null w_new:14!null x_new:15 k:7!null v:8 w:9 x:10 y:11 crdb_internal_mvcc_timestamp:12 + │ ├── columns: k_new:13!null w_new:14!null x_new:15 k:7!null uniq.v:8 w:9 x:10 uniq.y:11 crdb_internal_mvcc_timestamp:12 │ ├── scan uniq - │ │ └── columns: k:7!null v:8 w:9 x:10 y:11 crdb_internal_mvcc_timestamp:12 + │ │ └── columns: k:7!null uniq.v:8 w:9 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_new:16!null k_new:17!null + ├── columns: k_new:22!null v:23 w_new:24!null x_new:25 y:26 ├── with-scan &1 - │ ├── columns: w_new:16!null k_new:17!null + │ ├── columns: k_new:22!null v:23 w_new:24!null x_new:25 y:26 │ └── mapping: - │ ├── w_new:14 => w_new:16 - │ └── k_new:13 => k_new:17 + │ ├── k_new:13 => k_new:22 + │ ├── uniq.v:8 => v:23 + │ ├── w_new:14 => w_new:24 + │ ├── x_new:15 => x_new:25 + │ └── uniq.y:11 => y:26 ├── scan uniq - │ └── columns: k:18!null w:20 + │ └── columns: k:16!null uniq.v:17 w:18 x:19 uniq.y:20 └── filters - ├── w_new:16 = w:20 - └── k_new:17 != k:18 + ├── w_new:24 = w:18 + └── k_new:22 != 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 w:9 x:10 y:11 + ├── fetch columns: uniq.k:7 uniq.v:8 w:9 uniq.x:10 y:11 ├── update-mapping: │ ├── w_new:13 => w:3 │ └── y_new:14 => y:5 ├── input binding: &1 ├── project - │ ├── columns: w_new:13!null y_new:14 uniq.k:7!null v:8 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 w:9 uniq.x:10 y:11 crdb_internal_mvcc_timestamp:12 │ ├── select - │ │ ├── columns: uniq.k:7!null v:8 w:9 x:10 y:11 crdb_internal_mvcc_timestamp:12 + │ │ ├── columns: uniq.k:7!null uniq.v:8 w:9 uniq.x:10 y:11 crdb_internal_mvcc_timestamp:12 │ │ ├── scan uniq - │ │ │ └── columns: uniq.k:7!null v:8 w:9 x:10 y:11 crdb_internal_mvcc_timestamp:12 + │ │ │ └── columns: uniq.k:7!null uniq.v:8 w:9 uniq.x:10 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_new:15!null k:16!null + ├── columns: k:21!null v:22 w_new:23!null x:24 y_new:25 ├── with-scan &1 - │ ├── columns: w_new:15!null k:16!null + │ ├── columns: k:21!null v:22 w_new:23!null x:24 y_new:25 │ └── mapping: - │ ├── w_new:13 => w_new:15 - │ └── uniq.k:7 => k:16 + │ ├── uniq.k:7 => k:21 + │ ├── uniq.v:8 => v:22 + │ ├── w_new:13 => w_new:23 + │ ├── uniq.x:10 => x:24 + │ └── y_new:14 => y_new:25 ├── scan uniq - │ └── columns: uniq.k:17!null w:19 + │ └── columns: uniq.k:15!null uniq.v:16 w:17 uniq.x:18 y:19 └── filters - ├── w_new:15 = w:19 - └── k:16 != uniq.k:17 + ├── w_new:23 = 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_new:15!null c_new:16!null a_new:17!null + │ ├── columns: a_new:20!null b_new:21!null c_new:22!null d_new:23!null │ ├── with-scan &1 - │ │ ├── columns: b_new:15!null c_new:16!null a_new:17!null + │ │ ├── columns: a_new:20!null b_new:21!null c_new:22!null d_new:23!null │ │ └── mapping: - │ │ ├── b_new:12 => b_new:15 - │ │ ├── c_new:13 => c_new:16 - │ │ └── a_new:11 => a_new:17 + │ │ ├── a_new:11 => a_new:20 + │ │ ├── b_new:12 => b_new:21 + │ │ ├── c_new:13 => c_new:22 + │ │ └── d_new:14 => d_new:23 │ ├── scan uniq_overlaps_pk - │ │ └── columns: a:18!null b:19!null c:20 + │ │ └── columns: a:15!null b:16!null c:17 d:18 │ └── filters - │ ├── b_new:15 = b:19 - │ ├── c_new:16 = c:20 - │ └── a_new:17 != a:18 + │ ├── b_new:21 = b:16 + │ ├── c_new:22 = c:17 + │ └── a_new:20 != a:15 ├── unique-checks-item: uniq_overlaps_pk(a) │ └── semi-join (hash) - │ ├── columns: a_new:23!null b_new:24!null + │ ├── columns: a_new:29!null b_new:30!null c_new:31!null d_new:32!null │ ├── with-scan &1 - │ │ ├── columns: a_new:23!null b_new:24!null + │ │ ├── columns: a_new:29!null b_new:30!null c_new:31!null d_new:32!null │ │ └── mapping: - │ │ ├── a_new:11 => a_new:23 - │ │ └── b_new:12 => b_new:24 + │ │ ├── a_new:11 => a_new:29 + │ │ ├── b_new:12 => b_new:30 + │ │ ├── c_new:13 => c_new:31 + │ │ └── d_new:14 => d_new:32 │ ├── scan uniq_overlaps_pk - │ │ └── columns: a:25!null b:26!null + │ │ └── columns: a:24!null b:25!null c:26 d:27 │ └── filters - │ ├── a_new:23 = a:25 - │ └── b_new:24 != b:26 + │ ├── a_new:29 = a:24 + │ └── b_new:30 != b:25 └── unique-checks-item: uniq_overlaps_pk(c,d) └── semi-join (hash) - ├── columns: c_new:30!null d_new:31!null a_new:32!null b_new:33!null + ├── columns: a_new:38!null b_new:39!null c_new:40!null d_new:41!null ├── with-scan &1 - │ ├── columns: c_new:30!null d_new:31!null a_new:32!null b_new:33!null + │ ├── columns: a_new:38!null b_new:39!null c_new:40!null d_new:41!null │ └── mapping: - │ ├── c_new:13 => c_new:30 - │ ├── d_new:14 => d_new:31 - │ ├── a_new:11 => a_new:32 - │ └── b_new:12 => b_new:33 + │ ├── a_new:11 => a_new:38 + │ ├── b_new:12 => b_new:39 + │ ├── c_new:13 => c_new:40 + │ └── d_new:14 => d_new:41 ├── scan uniq_overlaps_pk - │ └── columns: a:34!null b:35!null c:36 d:37 + │ └── columns: a:33!null b:34!null c:35 d:36 └── filters - ├── c_new:30 = c:36 - ├── d_new:31 = d:37 - └── (a_new:32 != a:34) OR (b_new:33 != b:35) + ├── c_new:40 = c:35 + ├── d_new:41 = d:36 + └── (a_new:38 != a:33) OR (b_new:39 != 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: k:18 b:19!null + │ ├── columns: k:23 b:24!null c:25 v:26 │ ├── with-scan &1 - │ │ ├── columns: k:18 b:19!null + │ │ ├── columns: k:23 b:24!null c:25 v:26 │ │ └── mapping: - │ │ ├── other.k:11 => k:18 - │ │ └── uniq_overlaps_pk.b:7 => b:19 + │ │ ├── other.k:11 => k:23 + │ │ ├── uniq_overlaps_pk.b:7 => b:24 + │ │ ├── uniq_overlaps_pk.c:8 => c:25 + │ │ └── other.v:12 => v:26 │ ├── scan uniq_overlaps_pk - │ │ └── columns: a:20!null uniq_overlaps_pk.b:21!null + │ │ └── columns: a:18!null uniq_overlaps_pk.b:19!null uniq_overlaps_pk.c:20 d:21 │ └── filters - │ ├── k:18 = a:20 - │ └── b:19 != uniq_overlaps_pk.b:21 + │ ├── k:23 = a:18 + │ └── b:24 != uniq_overlaps_pk.b:19 └── unique-checks-item: uniq_overlaps_pk(c,d) └── semi-join (hash) - ├── columns: c:25 v:26 k:27 b:28!null + ├── columns: k:32 b:33!null c:34 v:35 ├── with-scan &1 - │ ├── columns: c:25 v:26 k:27 b:28!null + │ ├── columns: k:32 b:33!null c:34 v:35 │ └── mapping: - │ ├── uniq_overlaps_pk.c:8 => c:25 - │ ├── other.v:12 => v:26 - │ ├── other.k:11 => k:27 - │ └── uniq_overlaps_pk.b:7 => b:28 + │ ├── other.k:11 => k:32 + │ ├── uniq_overlaps_pk.b:7 => b:33 + │ ├── uniq_overlaps_pk.c:8 => c:34 + │ └── other.v:12 => v:35 ├── scan uniq_overlaps_pk - │ └── columns: a:29!null uniq_overlaps_pk.b:30!null uniq_overlaps_pk.c:31 d:32 + │ └── columns: a:27!null uniq_overlaps_pk.b:28!null uniq_overlaps_pk.c:29 d:30 └── filters - ├── c:25 = uniq_overlaps_pk.c:31 - ├── v:26 = d:32 - └── (k:27 != a:29) OR (b:28 != uniq_overlaps_pk.b:30) + ├── c:34 = uniq_overlaps_pk.c:29 + ├── v:35 = d:30 + └── (k:32 != 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: a:7 uniq_hidden_pk.b:8 c:9 uniq_hidden_pk.d:10 uniq_hidden_pk.rowid:11 + ├── fetch columns: 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 => a:1 ├── input binding: &1 ├── project - │ ├── columns: a_new:13!null 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 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: 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: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_new:14!null b:15 d:16 rowid:17!null + │ ├── columns: a_new:20!null b:21 c:22 d:23 rowid:24!null │ ├── with-scan &1 - │ │ ├── columns: a_new:14!null b:15 d:16 rowid:17!null + │ │ ├── columns: a_new:20!null b:21 c:22 d:23 rowid:24!null │ │ └── mapping: - │ │ ├── a_new:13 => a_new: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_new: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: a:18 uniq_hidden_pk.b:19 uniq_hidden_pk.d:21 uniq_hidden_pk.rowid:22!null + │ │ └── columns: 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_new:14 = a:18 - │ ├── b:15 = uniq_hidden_pk.b:19 - │ ├── d:16 = uniq_hidden_pk.d:21 - │ └── rowid:17 != uniq_hidden_pk.rowid:22 + │ ├── a_new:20 = 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_new:24!null rowid:25!null + ├── columns: a_new:31!null b:32 c:33 d:34 rowid:35!null ├── with-scan &1 - │ ├── columns: a_new:24!null rowid:25!null + │ ├── columns: a_new:31!null b:32 c:33 d:34 rowid:35!null │ └── mapping: - │ ├── a_new:13 => a_new:24 - │ └── uniq_hidden_pk.rowid:11 => rowid:25 + │ ├── a_new:13 => a_new: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: a:26 uniq_hidden_pk.rowid:30!null + │ └── columns: 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_new:24 = a:26 - └── rowid:25 != uniq_hidden_pk.rowid:30 + ├── a_new:31 = 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: a:7 uniq_hidden_pk.b:8 c:9 uniq_hidden_pk.d:10 uniq_hidden_pk.rowid:11 + ├── fetch columns: a:7 uniq_hidden_pk.b:8 uniq_hidden_pk.c:9 uniq_hidden_pk.d:10 uniq_hidden_pk.rowid:11 ├── update-mapping: │ └── other.k:13 => a:1 ├── input binding: &1 ├── inner-join (cross) - │ ├── columns: 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 other.k:13 v:14 w:15!null x:16 y:17 other.rowid:18!null other.crdb_internal_mvcc_timestamp:19 + │ ├── columns: 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 other.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: 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: 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: other.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: k:20 b:21 d:22 rowid:23!null + │ ├── columns: k:26 b:27 c:28 d:29 rowid:30!null │ ├── with-scan &1 - │ │ ├── columns: k:20 b:21 d:22 rowid:23!null + │ │ ├── columns: k:26 b:27 c:28 d:29 rowid:30!null │ │ └── mapping: - │ │ ├── other.k:13 => k:20 - │ │ ├── uniq_hidden_pk.b:8 => b:21 - │ │ ├── uniq_hidden_pk.d:10 => d:22 - │ │ └── uniq_hidden_pk.rowid:11 => rowid:23 + │ │ ├── other.k:13 => k: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: a:24 uniq_hidden_pk.b:25 uniq_hidden_pk.d:27 uniq_hidden_pk.rowid:28!null + │ │ └── columns: a:20 uniq_hidden_pk.b:21 uniq_hidden_pk.c:22 uniq_hidden_pk.d:23 uniq_hidden_pk.rowid:24!null │ └── filters - │ ├── k:20 = a:24 - │ ├── b:21 = uniq_hidden_pk.b:25 - │ ├── d:22 = uniq_hidden_pk.d:27 - │ └── rowid:23 != uniq_hidden_pk.rowid:28 + │ ├── k:26 = 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: k:30 rowid:31!null + ├── columns: k:37 b:38 c:39 d:40 rowid:41!null ├── with-scan &1 - │ ├── columns: k:30 rowid:31!null + │ ├── columns: k:37 b:38 c:39 d:40 rowid:41!null │ └── mapping: - │ ├── other.k:13 => k:30 - │ └── uniq_hidden_pk.rowid:11 => rowid:31 + │ ├── other.k:13 => k: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: a:32 uniq_hidden_pk.rowid:36!null + │ └── columns: a:31 uniq_hidden_pk.b:32 uniq_hidden_pk.c:33 uniq_hidden_pk.d:34 uniq_hidden_pk.rowid:35!null └── filters - ├── k:30 = a:32 - └── rowid:31 != uniq_hidden_pk.rowid:36 + ├── k:37 = 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 d463dd203dcb..e528887c03e9 100644 --- a/pkg/sql/opt/optbuilder/testdata/unique-checks-upsert +++ b/pkg/sql/opt/optbuilder/testdata/unique-checks-upsert @@ -63,32 +63,37 @@ upsert uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: column3:19!null upsert_k:20 + │ ├── columns: upsert_k:25 column2:26!null column3:27!null column4:28!null column5:29!null │ ├── with-scan &1 - │ │ ├── columns: column3:19!null upsert_k:20 + │ │ ├── columns: upsert_k:25 column2:26!null column3:27!null column4:28!null column5:29!null │ │ └── mapping: - │ │ ├── column3:9 => column3:19 - │ │ └── upsert_k:18 => upsert_k:20 + │ │ ├── upsert_k:18 => upsert_k:25 + │ │ ├── column2:8 => column2:26 + │ │ ├── column3:9 => column3:27 + │ │ ├── column4:10 => column4:28 + │ │ └── column5:11 => column5:29 │ ├── scan uniq - │ │ └── columns: k:21!null w:23 + │ │ └── columns: k:19!null v:20 w:21 x:22 y:23 │ └── filters - │ ├── column3:19 = w:23 - │ └── upsert_k:20 != k:21 + │ ├── column3:27 = w:21 + │ └── upsert_k:25 != k:19 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: column4:27!null column5:28!null upsert_k:29 + ├── columns: upsert_k:36 column2:37!null column3:38!null column4:39!null column5:40!null ├── with-scan &1 - │ ├── columns: column4:27!null column5:28!null upsert_k:29 + │ ├── columns: upsert_k:36 column2:37!null column3:38!null column4:39!null column5:40!null │ └── mapping: - │ ├── column4:10 => column4:27 - │ ├── column5:11 => column5:28 - │ └── upsert_k:18 => upsert_k:29 + │ ├── upsert_k:18 => upsert_k:36 + │ ├── column2:8 => column2:37 + │ ├── column3:9 => column3:38 + │ ├── column4:10 => column4:39 + │ └── column5:11 => column5:40 ├── scan uniq - │ └── columns: k:30!null x:33 y:34 + │ └── columns: k:30!null v:31 w:32 x:33 y:34 └── filters - ├── column4:27 = x:33 - ├── column5:28 = y:34 - └── upsert_k:29 != k:30 + ├── column4:39 = x:33 + ├── column5:40 = y:34 + └── upsert_k:36 != 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). @@ -146,32 +151,37 @@ upsert uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: column3:21!null upsert_k:22 + │ ├── columns: upsert_k:27 column2:28!null column3:29!null upsert_x:30 upsert_y:31 │ ├── with-scan &1 - │ │ ├── columns: column3:21!null upsert_k:22 + │ │ ├── columns: upsert_k:27 column2:28!null column3:29!null upsert_x:30 upsert_y:31 │ │ └── mapping: - │ │ ├── column3:9 => column3:21 - │ │ └── upsert_k:18 => upsert_k:22 + │ │ ├── upsert_k:18 => upsert_k:27 + │ │ ├── column2:8 => column2:28 + │ │ ├── column3:9 => column3:29 + │ │ ├── upsert_x:19 => upsert_x:30 + │ │ └── upsert_y:20 => upsert_y:31 │ ├── scan uniq - │ │ └── columns: k:23!null w:25 + │ │ └── columns: k:21!null v:22 w:23 x:24 y:25 │ └── filters - │ ├── column3:21 = w:25 - │ └── upsert_k:22 != k:23 + │ ├── column3:29 = w:23 + │ └── upsert_k:27 != k:21 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: upsert_x:29 upsert_y:30 upsert_k:31 + ├── columns: upsert_k:38 column2:39!null column3:40!null upsert_x:41 upsert_y:42 ├── with-scan &1 - │ ├── columns: upsert_x:29 upsert_y:30 upsert_k:31 + │ ├── columns: upsert_k:38 column2:39!null column3:40!null upsert_x:41 upsert_y:42 │ └── mapping: - │ ├── upsert_x:19 => upsert_x:29 - │ ├── upsert_y:20 => upsert_y:30 - │ └── upsert_k:18 => upsert_k:31 + │ ├── upsert_k:18 => upsert_k:38 + │ ├── column2:8 => column2:39 + │ ├── column3:9 => column3:40 + │ ├── upsert_x:19 => upsert_x:41 + │ └── upsert_y:20 => upsert_y:42 ├── scan uniq - │ └── columns: k:32!null x:35 y:36 + │ └── columns: k:32!null v:33 w:34 x:35 y:36 └── filters - ├── upsert_x:29 = x:35 - ├── upsert_y:30 = y:36 - └── upsert_k:31 != k:32 + ├── upsert_x:41 = x:35 + ├── upsert_y:42 = y:36 + └── upsert_k:38 != 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 @@ -230,32 +240,37 @@ upsert uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: column2:21 upsert_k:22 + │ ├── columns: upsert_k:27 upsert_v:28 column2:29 column3:30 upsert_y:31 │ ├── with-scan &1 - │ │ ├── columns: column2:21 upsert_k:22 + │ │ ├── columns: upsert_k:27 upsert_v:28 column2:29 column3:30 upsert_y:31 │ │ └── mapping: - │ │ ├── column2:8 => column2:21 - │ │ └── upsert_k:18 => upsert_k:22 + │ │ ├── upsert_k:18 => upsert_k:27 + │ │ ├── upsert_v:19 => upsert_v:28 + │ │ ├── column2:8 => column2:29 + │ │ ├── column3:9 => column3:30 + │ │ └── upsert_y:20 => upsert_y:31 │ ├── scan uniq - │ │ └── columns: k:23!null w:25 + │ │ └── columns: k:21!null v:22 w:23 x:24 y:25 │ └── filters - │ ├── column2:21 = w:25 - │ └── upsert_k:22 != k:23 + │ ├── column2:29 = w:23 + │ └── upsert_k:27 != k:21 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: column3:29 upsert_y:30 upsert_k:31 + ├── columns: upsert_k:38 upsert_v:39 column2:40 column3:41 upsert_y:42 ├── with-scan &1 - │ ├── columns: column3:29 upsert_y:30 upsert_k:31 + │ ├── columns: upsert_k:38 upsert_v:39 column2:40 column3:41 upsert_y:42 │ └── mapping: - │ ├── column3:9 => column3:29 - │ ├── upsert_y:20 => upsert_y:30 - │ └── upsert_k:18 => upsert_k:31 + │ ├── upsert_k:18 => upsert_k:38 + │ ├── upsert_v:19 => upsert_v:39 + │ ├── column2:8 => column2:40 + │ ├── column3:9 => column3:41 + │ └── upsert_y:20 => upsert_y:42 ├── scan uniq - │ └── columns: k:32!null x:35 y:36 + │ └── columns: k:32!null v:33 w:34 x:35 y:36 └── filters - ├── column3:29 = x:35 - ├── upsert_y:30 = y:36 - └── upsert_k:31 != k:32 + ├── column3:41 = x:35 + ├── upsert_y:42 = y:36 + └── upsert_k:38 != 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 upsert_k:24 + │ ├── columns: upsert_k:29 v:30 w:31!null column14:32 column15:33!null │ ├── with-scan &1 - │ │ ├── columns: w:23!null upsert_k:24 + │ │ ├── columns: upsert_k:29 v:30 w:31!null column14:32 column15:33!null │ │ └── mapping: - │ │ ├── other.w:9 => w:23 - │ │ └── upsert_k:22 => upsert_k:24 + │ │ ├── upsert_k:22 => upsert_k:29 + │ │ ├── other.v:8 => v:30 + │ │ ├── other.w:9 => w:31 + │ │ ├── column14:14 => column14:32 + │ │ └── column15:15 => column15: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 - │ └── upsert_k:24 != uniq.k:25 + │ ├── w:31 = uniq.w:25 + │ └── upsert_k:29 != uniq.k:23 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: column14:31 column15:32!null upsert_k:33 + ├── columns: upsert_k:40 v:41 w:42!null column14:43 column15:44!null ├── with-scan &1 - │ ├── columns: column14:31 column15:32!null upsert_k:33 + │ ├── columns: upsert_k:40 v:41 w:42!null column14:43 column15:44!null │ └── mapping: - │ ├── column14:14 => column14:31 - │ ├── column15:15 => column15:32 - │ └── upsert_k:22 => upsert_k:33 + │ ├── upsert_k:22 => upsert_k:40 + │ ├── other.v:8 => v:41 + │ ├── other.w:9 => w:42 + │ ├── column14:14 => column14:43 + │ └── column15:15 => column15: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 - ├── column14:31 = uniq.x:37 - ├── column15:32 = uniq.y:38 - └── upsert_k:33 != uniq.k:34 + ├── column14:43 = uniq.x:37 + ├── column15:44 = uniq.y:38 + └── upsert_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 @@ -402,32 +422,37 @@ upsert uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: upsert_w:23 upsert_k:24 + │ ├── columns: upsert_k:29 upsert_v:30 upsert_w:31 upsert_x:32 upsert_y:33 │ ├── with-scan &1 - │ │ ├── columns: upsert_w:23 upsert_k:24 + │ │ ├── columns: upsert_k:29 upsert_v:30 upsert_w:31 upsert_x:32 upsert_y:33 │ │ └── mapping: - │ │ ├── upsert_w:20 => upsert_w:23 - │ │ └── upsert_k:18 => upsert_k:24 + │ │ ├── upsert_k:18 => upsert_k:29 + │ │ ├── upsert_v:19 => upsert_v:30 + │ │ ├── upsert_w:20 => upsert_w:31 + │ │ ├── upsert_x:21 => upsert_x:32 + │ │ └── upsert_y:22 => upsert_y:33 │ ├── scan uniq - │ │ └── columns: k:25!null w:27 + │ │ └── columns: k:23!null v:24 w:25 x:26 y:27 │ └── filters - │ ├── upsert_w:23 = w:27 - │ └── upsert_k:24 != k:25 + │ ├── upsert_w:31 = w:25 + │ └── upsert_k:29 != k:23 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: upsert_x:31 upsert_y:32 upsert_k:33 + ├── columns: upsert_k:40 upsert_v:41 upsert_w:42 upsert_x:43 upsert_y:44 ├── with-scan &1 - │ ├── columns: upsert_x:31 upsert_y:32 upsert_k:33 + │ ├── columns: upsert_k:40 upsert_v:41 upsert_w:42 upsert_x:43 upsert_y:44 │ └── mapping: - │ ├── upsert_x:21 => upsert_x:31 - │ ├── upsert_y:22 => upsert_y:32 - │ └── upsert_k:18 => upsert_k:33 + │ ├── upsert_k:18 => upsert_k:40 + │ ├── upsert_v:19 => upsert_v:41 + │ ├── upsert_w:20 => upsert_w:42 + │ ├── upsert_x:21 => upsert_x:43 + │ └── upsert_y:22 => upsert_y:44 ├── scan uniq - │ └── columns: k:34!null x:37 y:38 + │ └── columns: k:34!null v:35 w:36 x:37 y:38 └── filters - ├── upsert_x:31 = x:37 - ├── upsert_y:32 = y:38 - └── upsert_k:33 != k:34 + ├── upsert_x:43 = x:37 + ├── upsert_y:44 = y:38 + └── upsert_k:40 != 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: upsert_w:28 upsert_k:29 + │ ├── columns: upsert_k:34 upsert_v:35 upsert_w:36 upsert_x:37 upsert_y:38 │ ├── with-scan &1 - │ │ ├── columns: upsert_w:28 upsert_k:29 + │ │ ├── columns: upsert_k:34 upsert_v:35 upsert_w:36 upsert_x:37 upsert_y:38 │ │ └── mapping: - │ │ ├── upsert_w:25 => upsert_w:28 - │ │ └── upsert_k:23 => upsert_k:29 + │ │ ├── upsert_k:23 => upsert_k:34 + │ │ ├── upsert_v:24 => upsert_v:35 + │ │ ├── upsert_w:25 => upsert_w:36 + │ │ ├── upsert_x:26 => upsert_x:37 + │ │ └── upsert_y:27 => upsert_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 - │ ├── upsert_w:28 = uniq.w:32 - │ └── upsert_k:29 != uniq.k:30 + │ ├── upsert_w:36 = uniq.w:30 + │ └── upsert_k:34 != uniq.k:28 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: upsert_x:36 upsert_y:37 upsert_k:38 + ├── columns: upsert_k:45 upsert_v:46 upsert_w:47 upsert_x:48 upsert_y:49 ├── with-scan &1 - │ ├── columns: upsert_x:36 upsert_y:37 upsert_k:38 + │ ├── columns: upsert_k:45 upsert_v:46 upsert_w:47 upsert_x:48 upsert_y:49 │ └── mapping: - │ ├── upsert_x:26 => upsert_x:36 - │ ├── upsert_y:27 => upsert_y:37 - │ └── upsert_k:23 => upsert_k:38 + │ ├── upsert_k:23 => upsert_k:45 + │ ├── upsert_v:24 => upsert_v:46 + │ ├── upsert_w:25 => upsert_w:47 + │ ├── upsert_x:26 => upsert_x:48 + │ └── upsert_y:27 => upsert_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 - ├── upsert_x:36 = uniq.x:42 - ├── upsert_y:37 = uniq.y:43 - └── upsert_k:38 != uniq.k:39 + ├── upsert_x:48 = uniq.x:42 + ├── upsert_y:49 = uniq.y:43 + └── upsert_k:45 != uniq.k:39 # On conflict do update with constant input, conflict on UNIQUE WITHOUT INDEX # column. @@ -577,32 +607,37 @@ upsert uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: upsert_w:24!null upsert_k:25 + │ ├── columns: upsert_k:30 upsert_v:31 upsert_w:32!null upsert_x:33 upsert_y:34 │ ├── with-scan &1 - │ │ ├── columns: upsert_w:24!null upsert_k:25 + │ │ ├── columns: upsert_k:30 upsert_v:31 upsert_w:32!null upsert_x:33 upsert_y:34 │ │ └── mapping: - │ │ ├── upsert_w:21 => upsert_w:24 - │ │ └── upsert_k:19 => upsert_k:25 + │ │ ├── upsert_k:19 => upsert_k:30 + │ │ ├── upsert_v:20 => upsert_v:31 + │ │ ├── upsert_w:21 => upsert_w:32 + │ │ ├── upsert_x:22 => upsert_x:33 + │ │ └── upsert_y:23 => upsert_y:34 │ ├── scan uniq - │ │ └── columns: k:26!null w:28 + │ │ └── columns: k:24!null v:25 w:26 x:27 y:28 │ └── filters - │ ├── upsert_w:24 = w:28 - │ └── upsert_k:25 != k:26 + │ ├── upsert_w:32 = w:26 + │ └── upsert_k:30 != k:24 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: upsert_x:32 upsert_y:33 upsert_k:34 + ├── columns: upsert_k:41 upsert_v:42 upsert_w:43!null upsert_x:44 upsert_y:45 ├── with-scan &1 - │ ├── columns: upsert_x:32 upsert_y:33 upsert_k:34 + │ ├── columns: upsert_k:41 upsert_v:42 upsert_w:43!null upsert_x:44 upsert_y:45 │ └── mapping: - │ ├── upsert_x:22 => upsert_x:32 - │ ├── upsert_y:23 => upsert_y:33 - │ └── upsert_k:19 => upsert_k:34 + │ ├── upsert_k:19 => upsert_k:41 + │ ├── upsert_v:20 => upsert_v:42 + │ ├── upsert_w:21 => upsert_w:43 + │ ├── upsert_x:22 => upsert_x:44 + │ └── upsert_y:23 => upsert_y:45 ├── scan uniq - │ └── columns: k:35!null x:38 y:39 + │ └── columns: k:35!null v:36 w:37 x:38 y:39 └── filters - ├── upsert_x:32 = x:38 - ├── upsert_y:33 = y:39 - └── upsert_k:34 != k:35 + ├── upsert_x:44 = x:38 + ├── upsert_y:45 = y:39 + └── upsert_k:41 != k:35 # On conflict do update with constant input, conflict on UNIQUE WITHOUT INDEX # columns. @@ -658,32 +693,37 @@ upsert uniq └── unique-checks ├── unique-checks-item: uniq(w) │ └── semi-join (hash) - │ ├── columns: upsert_w:24 upsert_k:25 + │ ├── columns: upsert_k:30 upsert_v:31!null upsert_w:32 upsert_x:33 upsert_y:34 │ ├── with-scan &1 - │ │ ├── columns: upsert_w:24 upsert_k:25 + │ │ ├── columns: upsert_k:30 upsert_v:31!null upsert_w:32 upsert_x:33 upsert_y:34 │ │ └── mapping: - │ │ ├── upsert_w:21 => upsert_w:24 - │ │ └── upsert_k:19 => upsert_k:25 + │ │ ├── upsert_k:19 => upsert_k:30 + │ │ ├── upsert_v:20 => upsert_v:31 + │ │ ├── upsert_w:21 => upsert_w:32 + │ │ ├── upsert_x:22 => upsert_x:33 + │ │ └── upsert_y:23 => upsert_y:34 │ ├── scan uniq - │ │ └── columns: k:26!null w:28 + │ │ └── columns: k:24!null v:25 w:26 x:27 y:28 │ └── filters - │ ├── upsert_w:24 = w:28 - │ └── upsert_k:25 != k:26 + │ ├── upsert_w:32 = w:26 + │ └── upsert_k:30 != k:24 └── unique-checks-item: uniq(x,y) └── semi-join (hash) - ├── columns: upsert_x:32 upsert_y:33 upsert_k:34 + ├── columns: upsert_k:41 upsert_v:42!null upsert_w:43 upsert_x:44 upsert_y:45 ├── with-scan &1 - │ ├── columns: upsert_x:32 upsert_y:33 upsert_k:34 + │ ├── columns: upsert_k:41 upsert_v:42!null upsert_w:43 upsert_x:44 upsert_y:45 │ └── mapping: - │ ├── upsert_x:22 => upsert_x:32 - │ ├── upsert_y:23 => upsert_y:33 - │ └── upsert_k:19 => upsert_k:34 + │ ├── upsert_k:19 => upsert_k:41 + │ ├── upsert_v:20 => upsert_v:42 + │ ├── upsert_w:21 => upsert_w:43 + │ ├── upsert_x:22 => upsert_x:44 + │ └── upsert_y:23 => upsert_y:45 ├── scan uniq - │ └── columns: k:35!null x:38 y:39 + │ └── columns: k:35!null v:36 w:37 x:38 y:39 └── filters - ├── upsert_x:32 = x:38 - ├── upsert_y:33 = y:39 - └── upsert_k:34 != k:35 + ├── upsert_x:44 = x:38 + ├── upsert_y:45 = y:39 + └── upsert_k:41 != 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: column2:10!null column3:11!null column1:12!null + │ ├── columns: column1:15!null column2:16!null column3:17!null column4:18!null │ ├── with-scan &1 - │ │ ├── columns: column2:10!null column3:11!null column1:12!null + │ │ ├── columns: column1:15!null column2:16!null column3:17!null column4:18!null │ │ └── mapping: - │ │ ├── column2:7 => column2:10 - │ │ ├── column3:8 => column3:11 - │ │ └── column1:6 => column1:12 + │ │ ├── column1:6 => column1:15 + │ │ ├── column2:7 => column2:16 + │ │ ├── column3:8 => column3:17 + │ │ └── column4:9 => column4:18 │ ├── scan uniq_overlaps_pk - │ │ └── columns: a:13!null b:14!null c:15 + │ │ └── columns: a:10!null b:11!null c:12 d:13 │ └── filters - │ ├── column2:10 = b:14 - │ ├── column3:11 = c:15 - │ └── column1:12 != a:13 + │ ├── column2:16 = b:11 + │ ├── column3:17 = c:12 + │ └── column1:15 != a:10 ├── unique-checks-item: uniq_overlaps_pk(a) │ └── semi-join (hash) - │ ├── columns: column1:18!null column2:19!null + │ ├── columns: column1:24!null column2:25!null column3:26!null column4:27!null │ ├── with-scan &1 - │ │ ├── columns: column1:18!null column2:19!null + │ │ ├── columns: column1:24!null column2:25!null column3:26!null column4:27!null │ │ └── mapping: - │ │ ├── column1:6 => column1:18 - │ │ └── column2:7 => column2:19 + │ │ ├── column1:6 => column1:24 + │ │ ├── column2:7 => column2:25 + │ │ ├── column3:8 => column3:26 + │ │ └── column4:9 => column4:27 │ ├── scan uniq_overlaps_pk - │ │ └── columns: a:20!null b:21!null + │ │ └── columns: a:19!null b:20!null c:21 d:22 │ └── filters - │ ├── column1:18 = a:20 - │ └── column2:19 != b:21 + │ ├── column1:24 = a:19 + │ └── column2:25 != b:20 └── unique-checks-item: uniq_overlaps_pk(c,d) └── semi-join (hash) - ├── columns: column3:25!null column4:26!null column1:27!null column2:28!null + ├── columns: column1:33!null column2:34!null column3:35!null column4:36!null ├── with-scan &1 - │ ├── columns: column3:25!null column4:26!null column1:27!null column2:28!null + │ ├── columns: column1:33!null column2:34!null column3:35!null column4:36!null │ └── mapping: - │ ├── column3:8 => column3:25 - │ ├── column4:9 => column4:26 - │ ├── column1:6 => column1:27 - │ └── column2:7 => column2:28 + │ ├── column1:6 => column1:33 + │ ├── column2:7 => column2:34 + │ ├── column3:8 => column3:35 + │ └── column4:9 => column4:36 ├── scan uniq_overlaps_pk - │ └── columns: a:29!null b:30!null c:31 d:32 + │ └── columns: a:28!null b:29!null c:30 d:31 └── filters - ├── column3:25 = c:31 - ├── column4:26 = d:32 - └── (column1:27 != a:29) OR (column2:28 != b:30) + ├── column3:35 = c:30 + ├── column4:36 = d:31 + └── (column1:33 != a:28) OR (column2:34 != b:29) # Upsert with non-constant input. # Add inequality filters for the primary key columns that are not part of each @@ -801,32 +844,35 @@ upsert uniq_overlaps_pk └── unique-checks ├── unique-checks-item: uniq_overlaps_pk(b,c) │ └── semi-join (hash) - │ ├── columns: v:14 x:15 k:16 + │ ├── columns: k:19 v:20 x:21 column13:22 │ ├── with-scan &1 - │ │ ├── columns: v:14 x:15 k:16 + │ │ ├── columns: k:19 v:20 x:21 column13:22 │ │ └── mapping: - │ │ ├── other.v:7 => v:14 - │ │ ├── other.x:9 => x:15 - │ │ └── other.k:6 => k:16 + │ │ ├── other.k:6 => k:19 + │ │ ├── other.v:7 => v:20 + │ │ ├── other.x:9 => x:21 + │ │ └── column13:13 => column13:22 │ ├── scan uniq_overlaps_pk - │ │ └── columns: a:17!null b:18!null c:19 + │ │ └── columns: a:14!null b:15!null c:16 d:17 │ └── filters - │ ├── v:14 = b:18 - │ ├── x:15 = c:19 - │ └── k:16 != a:17 + │ ├── v:20 = b:15 + │ ├── x:21 = c:16 + │ └── k:19 != a:14 └── unique-checks-item: uniq_overlaps_pk(a) └── semi-join (hash) - ├── columns: k:22 v:23 + ├── columns: k:28 v:29 x:30 column13:31 ├── with-scan &1 - │ ├── columns: k:22 v:23 + │ ├── columns: k:28 v:29 x:30 column13:31 │ └── mapping: - │ ├── other.k:6 => k:22 - │ └── other.v:7 => v:23 + │ ├── other.k:6 => k:28 + │ ├── other.v:7 => v:29 + │ ├── other.x:9 => x:30 + │ └── column13:13 => column13:31 ├── scan uniq_overlaps_pk - │ └── columns: a:24!null b:25!null + │ └── columns: a:23!null b:24!null c:25 d:26 └── filters - ├── k:22 = a:24 - └── v:23 != b:25 + ├── k:28 = a:23 + └── v:29 != 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: upsert_b:20 upsert_c:21 upsert_a:22!null + │ ├── columns: upsert_a:25!null upsert_b:26 upsert_c:27 upsert_d:28 │ ├── with-scan &1 - │ │ ├── columns: upsert_b:20 upsert_c:21 upsert_a:22!null + │ │ ├── columns: upsert_a:25!null upsert_b:26 upsert_c:27 upsert_d:28 │ │ └── mapping: - │ │ ├── upsert_b:17 => upsert_b:20 - │ │ ├── upsert_c:18 => upsert_c:21 - │ │ └── upsert_a:16 => upsert_a:22 + │ │ ├── upsert_a:16 => upsert_a:25 + │ │ ├── upsert_b:17 => upsert_b:26 + │ │ ├── upsert_c:18 => upsert_c:27 + │ │ └── upsert_d:19 => upsert_d:28 │ ├── scan uniq_overlaps_pk - │ │ └── columns: a:23!null b:24!null c:25 + │ │ └── columns: a:20!null b:21!null c:22 d:23 │ └── filters - │ ├── upsert_b:20 = b:24 - │ ├── upsert_c:21 = c:25 - │ └── upsert_a:22 != a:23 + │ ├── upsert_b:26 = b:21 + │ ├── upsert_c:27 = c:22 + │ └── upsert_a:25 != a:20 ├── unique-checks-item: uniq_overlaps_pk(a) │ └── semi-join (hash) - │ ├── columns: upsert_a:28!null upsert_b:29 + │ ├── columns: upsert_a:34!null upsert_b:35 upsert_c:36 upsert_d:37 │ ├── with-scan &1 - │ │ ├── columns: upsert_a:28!null upsert_b:29 + │ │ ├── columns: upsert_a:34!null upsert_b:35 upsert_c:36 upsert_d:37 │ │ └── mapping: - │ │ ├── upsert_a:16 => upsert_a:28 - │ │ └── upsert_b:17 => upsert_b:29 + │ │ ├── upsert_a:16 => upsert_a:34 + │ │ ├── upsert_b:17 => upsert_b:35 + │ │ ├── upsert_c:18 => upsert_c:36 + │ │ └── upsert_d:19 => upsert_d:37 │ ├── scan uniq_overlaps_pk - │ │ └── columns: a:30!null b:31!null + │ │ └── columns: a:29!null b:30!null c:31 d:32 │ └── filters - │ ├── upsert_a:28 = a:30 - │ └── upsert_b:29 != b:31 + │ ├── upsert_a:34 = a:29 + │ └── upsert_b:35 != b:30 └── unique-checks-item: uniq_overlaps_pk(c,d) └── semi-join (hash) - ├── columns: upsert_c:35 upsert_d:36 upsert_a:37!null upsert_b:38 + ├── columns: upsert_a:43!null upsert_b:44 upsert_c:45 upsert_d:46 ├── with-scan &1 - │ ├── columns: upsert_c:35 upsert_d:36 upsert_a:37!null upsert_b:38 + │ ├── columns: upsert_a:43!null upsert_b:44 upsert_c:45 upsert_d:46 │ └── mapping: - │ ├── upsert_c:18 => upsert_c:35 - │ ├── upsert_d:19 => upsert_d:36 - │ ├── upsert_a:16 => upsert_a:37 - │ └── upsert_b:17 => upsert_b:38 + │ ├── upsert_a:16 => upsert_a:43 + │ ├── upsert_b:17 => upsert_b:44 + │ ├── upsert_c:18 => upsert_c:45 + │ └── upsert_d:19 => upsert_d:46 ├── scan uniq_overlaps_pk - │ └── columns: a:39!null b:40!null c:41 d:42 + │ └── columns: a:38!null b:39!null c:40 d:41 └── filters - ├── upsert_c:35 = c:41 - ├── upsert_d:36 = d:42 - └── (upsert_a:37 != a:39) OR (upsert_b:38 != b:40) + ├── upsert_c:45 = c:40 + ├── upsert_d:46 = d:41 + └── (upsert_a:43 != a:38) OR (upsert_b:44 != 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: upsert_b:20!null upsert_c:21 upsert_a:22 + │ ├── columns: upsert_a:25 upsert_b:26!null upsert_c:27 upsert_d:28 │ ├── with-scan &1 - │ │ ├── columns: upsert_b:20!null upsert_c:21 upsert_a:22 + │ │ ├── columns: upsert_a:25 upsert_b:26!null upsert_c:27 upsert_d:28 │ │ └── mapping: - │ │ ├── upsert_b:17 => upsert_b:20 - │ │ ├── upsert_c:18 => upsert_c:21 - │ │ └── upsert_a:16 => upsert_a:22 + │ │ ├── upsert_a:16 => upsert_a:25 + │ │ ├── upsert_b:17 => upsert_b:26 + │ │ ├── upsert_c:18 => upsert_c:27 + │ │ └── upsert_d:19 => upsert_d:28 │ ├── scan uniq_overlaps_pk - │ │ └── columns: a:23!null b:24!null c:25 + │ │ └── columns: a:20!null b:21!null c:22 d:23 │ └── filters - │ ├── upsert_b:20 = b:24 - │ ├── upsert_c:21 = c:25 - │ └── upsert_a:22 != a:23 + │ ├── upsert_b:26 = b:21 + │ ├── upsert_c:27 = c:22 + │ └── upsert_a:25 != a:20 ├── unique-checks-item: uniq_overlaps_pk(a) │ └── semi-join (hash) - │ ├── columns: upsert_a:28 upsert_b:29!null + │ ├── columns: upsert_a:34 upsert_b:35!null upsert_c:36 upsert_d:37 │ ├── with-scan &1 - │ │ ├── columns: upsert_a:28 upsert_b:29!null + │ │ ├── columns: upsert_a:34 upsert_b:35!null upsert_c:36 upsert_d:37 │ │ └── mapping: - │ │ ├── upsert_a:16 => upsert_a:28 - │ │ └── upsert_b:17 => upsert_b:29 + │ │ ├── upsert_a:16 => upsert_a:34 + │ │ ├── upsert_b:17 => upsert_b:35 + │ │ ├── upsert_c:18 => upsert_c:36 + │ │ └── upsert_d:19 => upsert_d:37 │ ├── scan uniq_overlaps_pk - │ │ └── columns: a:30!null b:31!null + │ │ └── columns: a:29!null b:30!null c:31 d:32 │ └── filters - │ ├── upsert_a:28 = a:30 - │ └── upsert_b:29 != b:31 + │ ├── upsert_a:34 = a:29 + │ └── upsert_b:35 != b:30 └── unique-checks-item: uniq_overlaps_pk(c,d) └── semi-join (hash) - ├── columns: upsert_c:35 upsert_d:36 upsert_a:37 upsert_b:38!null + ├── columns: upsert_a:43 upsert_b:44!null upsert_c:45 upsert_d:46 ├── with-scan &1 - │ ├── columns: upsert_c:35 upsert_d:36 upsert_a:37 upsert_b:38!null + │ ├── columns: upsert_a:43 upsert_b:44!null upsert_c:45 upsert_d:46 │ └── mapping: - │ ├── upsert_c:18 => upsert_c:35 - │ ├── upsert_d:19 => upsert_d:36 - │ ├── upsert_a:16 => upsert_a:37 - │ └── upsert_b:17 => upsert_b:38 + │ ├── upsert_a:16 => upsert_a:43 + │ ├── upsert_b:17 => upsert_b:44 + │ ├── upsert_c:18 => upsert_c:45 + │ └── upsert_d:19 => upsert_d:46 ├── scan uniq_overlaps_pk - │ └── columns: a:39!null b:40!null c:41 d:42 + │ └── columns: a:38!null b:39!null c:40 d:41 └── filters - ├── upsert_c:35 = c:41 - ├── upsert_d:36 = d:42 - └── (upsert_a:37 != a:39) OR (upsert_b:38 != b:40) + ├── upsert_c:45 = c:40 + ├── upsert_d:46 = d:41 + └── (upsert_a:43 != a:38) OR (upsert_b:44 != 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: column2:20!null upsert_c:21 upsert_rowid:22 + │ ├── columns: column1:26!null column2:27!null upsert_c:28 column3:29!null upsert_rowid:30 │ ├── with-scan &1 - │ │ ├── columns: column2:20!null upsert_c:21 upsert_rowid:22 + │ │ ├── columns: column1:26!null column2:27!null upsert_c:28 column3:29!null upsert_rowid:30 │ │ └── mapping: - │ │ ├── column2:8 => column2:20 - │ │ ├── upsert_c:18 => upsert_c:21 - │ │ └── upsert_rowid:19 => upsert_rowid:22 + │ │ ├── column1:7 => column1:26 + │ │ ├── column2:8 => column2:27 + │ │ ├── upsert_c:18 => upsert_c:28 + │ │ ├── column3:9 => column3:29 + │ │ └── upsert_rowid:19 => upsert_rowid:30 │ ├── scan uniq_hidden_pk - │ │ └── columns: b:24 c:25 rowid:27!null + │ │ └── columns: a:20 b:21 c:22 d:23 rowid:24!null │ └── filters - │ ├── column2:20 = b:24 - │ ├── upsert_c:21 = c:25 - │ └── upsert_rowid:22 != rowid:27 + │ ├── column2:27 = b:21 + │ ├── upsert_c:28 = c:22 + │ └── upsert_rowid:30 != rowid:24 ├── unique-checks-item: uniq_hidden_pk(a,b,d) │ └── semi-join (hash) - │ ├── columns: column1:29!null column2:30!null column3:31!null upsert_rowid:32 + │ ├── columns: column1:37!null column2:38!null upsert_c:39 column3:40!null upsert_rowid:41 │ ├── with-scan &1 - │ │ ├── columns: column1:29!null column2:30!null column3:31!null upsert_rowid:32 + │ │ ├── columns: column1:37!null column2:38!null upsert_c:39 column3:40!null upsert_rowid:41 │ │ └── mapping: - │ │ ├── column1:7 => column1:29 - │ │ ├── column2:8 => column2:30 - │ │ ├── column3:9 => column3:31 - │ │ └── upsert_rowid:19 => upsert_rowid:32 + │ │ ├── column1:7 => column1:37 + │ │ ├── column2:8 => column2:38 + │ │ ├── upsert_c:18 => upsert_c:39 + │ │ ├── column3:9 => column3:40 + │ │ └── upsert_rowid:19 => upsert_rowid:41 │ ├── scan uniq_hidden_pk - │ │ └── columns: a:33 b:34 d:36 rowid:37!null + │ │ └── columns: a:31 b:32 c:33 d:34 rowid:35!null │ └── filters - │ ├── column1:29 = a:33 - │ ├── column2:30 = b:34 - │ ├── column3:31 = d:36 - │ └── upsert_rowid:32 != rowid:37 + │ ├── column1:37 = a:31 + │ ├── column2:38 = b:32 + │ ├── column3:40 = d:34 + │ └── upsert_rowid:41 != rowid:35 └── unique-checks-item: uniq_hidden_pk(a) └── semi-join (hash) - ├── columns: column1:39!null upsert_rowid:40 + ├── columns: column1:48!null column2:49!null upsert_c:50 column3:51!null upsert_rowid:52 ├── with-scan &1 - │ ├── columns: column1:39!null upsert_rowid:40 + │ ├── columns: column1:48!null column2:49!null upsert_c:50 column3:51!null upsert_rowid:52 │ └── mapping: - │ ├── column1:7 => column1:39 - │ └── upsert_rowid:19 => upsert_rowid:40 + │ ├── column1:7 => column1:48 + │ ├── column2:8 => column2:49 + │ ├── upsert_c:18 => upsert_c:50 + │ ├── column3:9 => column3:51 + │ └── upsert_rowid:19 => upsert_rowid:52 ├── scan uniq_hidden_pk - │ └── columns: a:41 rowid:45!null + │ └── columns: a:42 b:43 c:44 d:45 rowid:46!null └── filters - ├── column1:39 = a:41 - └── upsert_rowid:40 != rowid:45 + ├── column1:48 = a:42 + └── upsert_rowid:52 != 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: v:15 x:16 column14:17 + │ ├── columns: k:21 v:22 x:23 y:24 column14:25 │ ├── with-scan &1 - │ │ ├── columns: v:15 x:16 column14:17 + │ │ ├── columns: k:21 v:22 x:23 y:24 column14:25 │ │ └── mapping: - │ │ ├── other.v:8 => v:15 - │ │ ├── other.x:10 => x:16 - │ │ └── column14:14 => column14:17 + │ │ ├── other.k:7 => k:21 + │ │ ├── other.v:8 => v:22 + │ │ ├── other.x:10 => x:23 + │ │ ├── other.y:11 => y:24 + │ │ └── column14:14 => column14:25 │ ├── scan uniq_hidden_pk - │ │ └── columns: b:19 c:20 uniq_hidden_pk.rowid:22!null + │ │ └── columns: a:15 b:16 c:17 d:18 uniq_hidden_pk.rowid:19!null │ └── filters - │ ├── v:15 = b:19 - │ ├── x:16 = c:20 - │ └── column14:17 != uniq_hidden_pk.rowid:22 + │ ├── v:22 = b:16 + │ ├── x:23 = c:17 + │ └── column14:25 != uniq_hidden_pk.rowid:19 ├── unique-checks-item: uniq_hidden_pk(a,b,d) │ └── semi-join (hash) - │ ├── columns: k:24 v:25 y:26 column14:27 + │ ├── columns: k:32 v:33 x:34 y:35 column14:36 │ ├── with-scan &1 - │ │ ├── columns: k:24 v:25 y:26 column14:27 + │ │ ├── columns: k:32 v:33 x:34 y:35 column14:36 │ │ └── mapping: - │ │ ├── other.k:7 => k:24 - │ │ ├── other.v:8 => v:25 - │ │ ├── other.y:11 => y:26 - │ │ └── column14:14 => column14:27 + │ │ ├── other.k:7 => k:32 + │ │ ├── other.v:8 => v:33 + │ │ ├── other.x:10 => x:34 + │ │ ├── other.y:11 => y:35 + │ │ └── column14:14 => column14:36 │ ├── scan uniq_hidden_pk - │ │ └── columns: a:28 b:29 d:31 uniq_hidden_pk.rowid:32!null + │ │ └── columns: a:26 b:27 c:28 d:29 uniq_hidden_pk.rowid:30!null │ └── filters - │ ├── k:24 = a:28 - │ ├── v:25 = b:29 - │ ├── y:26 = d:31 - │ └── column14:27 != uniq_hidden_pk.rowid:32 + │ ├── k:32 = a:26 + │ ├── v:33 = b:27 + │ ├── y:35 = d:29 + │ └── column14:36 != uniq_hidden_pk.rowid:30 └── unique-checks-item: uniq_hidden_pk(a) └── semi-join (hash) - ├── columns: k:34 column14:35 + ├── columns: k:43 v:44 x:45 y:46 column14:47 ├── with-scan &1 - │ ├── columns: k:34 column14:35 + │ ├── columns: k:43 v:44 x:45 y:46 column14:47 │ └── mapping: - │ ├── other.k:7 => k:34 - │ └── column14:14 => column14:35 + │ ├── other.k:7 => k:43 + │ ├── other.v:8 => v:44 + │ ├── other.x:10 => x:45 + │ ├── other.y:11 => y:46 + │ └── column14:14 => column14:47 ├── scan uniq_hidden_pk - │ └── columns: a:36 uniq_hidden_pk.rowid:40!null + │ └── columns: a:37 b:38 c:39 d:40 uniq_hidden_pk.rowid:41!null └── filters - ├── k:34 = a:36 - └── column14:35 != uniq_hidden_pk.rowid:40 + ├── k:43 = a:37 + └── column14: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: upsert_b:24 upsert_c:25 upsert_rowid:26 + │ ├── columns: upsert_a:30!null upsert_b:31 upsert_c:32 upsert_d:33 upsert_rowid:34 │ ├── with-scan &1 - │ │ ├── columns: upsert_b:24 upsert_c:25 upsert_rowid:26 + │ │ ├── columns: upsert_a:30!null upsert_b:31 upsert_c:32 upsert_d:33 upsert_rowid:34 │ │ └── mapping: - │ │ ├── upsert_b:20 => upsert_b:24 - │ │ ├── upsert_c:21 => upsert_c:25 - │ │ └── upsert_rowid:23 => upsert_rowid:26 + │ │ ├── upsert_a:19 => upsert_a:30 + │ │ ├── upsert_b:20 => upsert_b:31 + │ │ ├── upsert_c:21 => upsert_c:32 + │ │ ├── upsert_d:22 => upsert_d:33 + │ │ └── upsert_rowid:23 => upsert_rowid:34 │ ├── scan uniq_hidden_pk - │ │ └── columns: b:28 c:29 rowid:31!null + │ │ └── columns: a:24 b:25 c:26 d:27 rowid:28!null │ └── filters - │ ├── upsert_b:24 = b:28 - │ ├── upsert_c:25 = c:29 - │ └── upsert_rowid:26 != rowid:31 + │ ├── upsert_b:31 = b:25 + │ ├── upsert_c:32 = c:26 + │ └── upsert_rowid:34 != rowid:28 ├── unique-checks-item: uniq_hidden_pk(a,b,d) │ └── semi-join (hash) - │ ├── columns: upsert_a:33!null upsert_b:34 upsert_d:35 upsert_rowid:36 + │ ├── columns: upsert_a:41!null upsert_b:42 upsert_c:43 upsert_d:44 upsert_rowid:45 │ ├── with-scan &1 - │ │ ├── columns: upsert_a:33!null upsert_b:34 upsert_d:35 upsert_rowid:36 + │ │ ├── columns: upsert_a:41!null upsert_b:42 upsert_c:43 upsert_d:44 upsert_rowid:45 │ │ └── mapping: - │ │ ├── upsert_a:19 => upsert_a:33 - │ │ ├── upsert_b:20 => upsert_b:34 - │ │ ├── upsert_d:22 => upsert_d:35 - │ │ └── upsert_rowid:23 => upsert_rowid:36 + │ │ ├── upsert_a:19 => upsert_a:41 + │ │ ├── upsert_b:20 => upsert_b:42 + │ │ ├── upsert_c:21 => upsert_c:43 + │ │ ├── upsert_d:22 => upsert_d:44 + │ │ └── upsert_rowid:23 => upsert_rowid:45 │ ├── scan uniq_hidden_pk - │ │ └── columns: a:37 b:38 d:40 rowid:41!null + │ │ └── columns: a:35 b:36 c:37 d:38 rowid:39!null │ └── filters - │ ├── upsert_a:33 = a:37 - │ ├── upsert_b:34 = b:38 - │ ├── upsert_d:35 = d:40 - │ └── upsert_rowid:36 != rowid:41 + │ ├── upsert_a:41 = a:35 + │ ├── upsert_b:42 = b:36 + │ ├── upsert_d:44 = d:38 + │ └── upsert_rowid:45 != rowid:39 └── unique-checks-item: uniq_hidden_pk(a) └── semi-join (hash) - ├── columns: upsert_a:43!null upsert_rowid:44 + ├── columns: upsert_a:52!null upsert_b:53 upsert_c:54 upsert_d:55 upsert_rowid:56 ├── with-scan &1 - │ ├── columns: upsert_a:43!null upsert_rowid:44 + │ ├── columns: upsert_a:52!null upsert_b:53 upsert_c:54 upsert_d:55 upsert_rowid:56 │ └── mapping: - │ ├── upsert_a:19 => upsert_a:43 - │ └── upsert_rowid:23 => upsert_rowid:44 + │ ├── upsert_a:19 => upsert_a:52 + │ ├── upsert_b:20 => upsert_b:53 + │ ├── upsert_c:21 => upsert_c:54 + │ ├── upsert_d:22 => upsert_d:55 + │ └── upsert_rowid:23 => upsert_rowid:56 ├── scan uniq_hidden_pk - │ └── columns: a:45 rowid:49!null + │ └── columns: a:46 b:47 c:48 d:49 rowid:50!null └── filters - ├── upsert_a:43 = a:45 - └── upsert_rowid:44 != rowid:49 + ├── upsert_a:52 = a:46 + └── upsert_rowid:56 != 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: column1:10!null upsert_rowid:11 + │ ├── columns: column1:13!null upsert_rowid:14 │ ├── with-scan &1 - │ │ ├── columns: column1:10!null upsert_rowid:11 + │ │ ├── columns: column1:13!null upsert_rowid:14 │ │ └── mapping: - │ │ ├── column1:4 => column1:10 - │ │ └── upsert_rowid:9 => upsert_rowid:11 + │ │ ├── column1:4 => column1:13 + │ │ └── upsert_rowid:9 => upsert_rowid:14 │ ├── scan uniq_fk_parent - │ │ └── columns: uniq_fk_parent.a:12 rowid:13!null + │ │ └── columns: uniq_fk_parent.a:10 rowid:11!null │ └── filters - │ ├── column1:10 = uniq_fk_parent.a:12 - │ └── upsert_rowid:11 != rowid:13 + │ ├── column1:13 = uniq_fk_parent.a:10 + │ └── upsert_rowid:14 != 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: upsert_i:14!null upsert_rowid:15 + ├── columns: upsert_i:17!null upsert_rowid:18 ├── with-scan &1 - │ ├── columns: upsert_i:14!null upsert_rowid:15 + │ ├── columns: upsert_i:17!null upsert_rowid:18 │ └── mapping: - │ ├── upsert_i:10 => upsert_i:14 - │ └── upsert_rowid:11 => upsert_rowid:15 + │ ├── upsert_i:10 => upsert_i:17 + │ └── upsert_rowid:11 => upsert_rowid:18 ├── scan t - │ ├── columns: i:16 rowid:17!null + │ ├── columns: i:14 rowid:15!null │ └── partial index predicates │ └── i1: filters - │ └── i:16 > 0 + │ └── i:14 > 0 └── filters - ├── upsert_i:14 = i:16 - └── upsert_rowid:15 != rowid:17 + ├── upsert_i:17 = i:14 + └── upsert_rowid:18 != rowid:15