diff --git a/pkg/sql/logictest/testdata/logic_test/alter_table b/pkg/sql/logictest/testdata/logic_test/alter_table index 3d9f0600de0a..75d3944f7e9d 100644 --- a/pkg/sql/logictest/testdata/logic_test/alter_table +++ b/pkg/sql/logictest/testdata/logic_test/alter_table @@ -226,7 +226,7 @@ ALTER TABLE t ADD d INT UNIQUE statement ok INSERT INTO t VALUES (4, 9, 1) -statement error duplicate key value \(d\)=\(1\) violates unique constraint \"t_d_key\" +statement error duplicate key value violates unique constraint \"t_d_key\"\nDETAIL: Key \(d\)=\(1\) already exists\. INSERT INTO t VALUES (5, 9, 1) # Add a column with no default value diff --git a/pkg/sql/logictest/testdata/logic_test/array b/pkg/sql/logictest/testdata/logic_test/array index 01c86c12884a..b9987d58757a 100644 --- a/pkg/sql/logictest/testdata/logic_test/array +++ b/pkg/sql/logictest/testdata/logic_test/array @@ -1362,7 +1362,7 @@ INSERT INTO t VALUES # Test that the unique index rejects bad inserts. # Disabled until #50659 is resolved. -#statement error pq: duplicate key value \(x\)=\(ARRAY\[1,NULL,10\]\) violates unique constraint "primary" +#statement error pq: duplicate key value violates unique constraint "primary"\nDETAIL: Key \(x\)=\(ARRAY\[1,NULL,10\]\) already exists\. #INSERT INTO t VALUES (ARRAY[1, NULL, 10]) query T diff --git a/pkg/sql/logictest/testdata/logic_test/cascade b/pkg/sql/logictest/testdata/logic_test/cascade index 6233daf881cd..a1bb3af7255c 100644 --- a/pkg/sql/logictest/testdata/logic_test/cascade +++ b/pkg/sql/logictest/testdata/logic_test/cascade @@ -331,7 +331,7 @@ SELECT * FROM b; 1 2 3 4 5 1006 7 8 9 10 # Also ensure that normal errors are still correctly wrapped even if cascading. -statement error pq: duplicate key value \(id\)=\(1\) violates unique constraint "primary" +statement error pq: duplicate key value violates unique constraint "primary"\nDETAIL: Key \(id\)=\(1\) already exists\. UPDATE a SET id = 1 WHERE id = 1006; # 7. ON DELETE SET NULL @@ -3293,7 +3293,7 @@ statement oK INSERT INTO a VALUES ('original'), ('default'); INSERT INTO b VALUES ('b1', 'original'), ('b2', 'default'); -statement error pq: duplicate key value \(a_id\)=\('default'\) violates unique constraint "b_a_id_key" +statement error pq: duplicate key value violates unique constraint "b_a_id_key"\nDETAIL: Key \(a_id\)=\('default'\) already exists\. DELETE FROM a WHERE id = 'original'; # Clean up after the test. @@ -3676,7 +3676,7 @@ statement oK INSERT INTO a VALUES ('original'), ('default'); INSERT INTO b VALUES ('b1', 'original'), ('b2', 'default'); -statement error pq: duplicate key value \(a_id\)=\('default'\) violates unique constraint "b_a_id_key" +statement error pq: duplicate key value violates unique constraint "b_a_id_key"\nDETAIL: Key \(a_id\)=\('default'\) already exists\. UPDATE a SET id = 'updated' WHERE id = 'original'; # Clean up after the test. diff --git a/pkg/sql/logictest/testdata/logic_test/collatedstring_constraint b/pkg/sql/logictest/testdata/logic_test/collatedstring_constraint index 6d7d2ad36a3d..b078c59a69af 100644 --- a/pkg/sql/logictest/testdata/logic_test/collatedstring_constraint +++ b/pkg/sql/logictest/testdata/logic_test/collatedstring_constraint @@ -8,7 +8,7 @@ CREATE TABLE p ( statement ok INSERT INTO p VALUES ('a' COLLATE en_u_ks_level1) -statement error duplicate key value \(a\)=\('a' COLLATE en_u_ks_level1\) violates unique constraint "primary" +statement error duplicate key value violates unique constraint "primary"\nDETAIL: Key \(a\)=\('a' COLLATE en_u_ks_level1\) already exists\. INSERT INTO p VALUES ('A' COLLATE en_u_ks_level1) statement ok diff --git a/pkg/sql/logictest/testdata/logic_test/datetime b/pkg/sql/logictest/testdata/logic_test/datetime index 9be122382f72..5764c2e07f68 100644 --- a/pkg/sql/logictest/testdata/logic_test/datetime +++ b/pkg/sql/logictest/testdata/logic_test/datetime @@ -30,7 +30,7 @@ SELECT * FROM t WHERE a = '2015-08-25 04:45:45.53453+01:00'::timestamp 2015-08-25 04:45:45.53453 +0000 +0000 2015-08-25 00:00:00 +0000 +0000 02:45:02.234 # insert duplicate value with different time zone offset -statement error duplicate key value \(a\)=\('2015-08-30 03:34:45\.34567'\) violates unique constraint "primary" +statement error duplicate key value violates unique constraint "primary"\nDETAIL: Key \(a\)=\('2015-08-30 03:34:45\.34567'\) already exists\. INSERT INTO t VALUES ('2015-08-30 03:34:45.34567-07:00', '2015-08-31', '35h2s') diff --git a/pkg/sql/logictest/testdata/logic_test/explain_analyze_plans b/pkg/sql/logictest/testdata/logic_test/explain_analyze_plans index 0264566a5b2f..ae1edbaec97a 100644 --- a/pkg/sql/logictest/testdata/logic_test/explain_analyze_plans +++ b/pkg/sql/logictest/testdata/logic_test/explain_analyze_plans @@ -285,7 +285,7 @@ vectorized: │ spans: LIMITED SCAN │ limit: 1 │ -└── • fk-check +└── • constraint-check │ └── • error if rows │ actual row count: 0 diff --git a/pkg/sql/logictest/testdata/logic_test/fk b/pkg/sql/logictest/testdata/logic_test/fk index 22a1bfb25335..0a526a3559e1 100644 --- a/pkg/sql/logictest/testdata/logic_test/fk +++ b/pkg/sql/logictest/testdata/logic_test/fk @@ -2824,7 +2824,7 @@ INSERT INTO child (c, p) VALUES (200, 2) # These two test cases are sort of undefined behavior, since their # success/failure depends on the order in which the updates are performed. -statement error duplicate key value \(p\)=\(3\) violates unique constraint "primary" +statement error duplicate key value violates unique constraint "primary"\nDETAIL: Key \(p\)=\(3\) already exists\. UPDATE parent SET p = p + 1 statement ok diff --git a/pkg/sql/logictest/testdata/logic_test/hash_sharded_index b/pkg/sql/logictest/testdata/logic_test/hash_sharded_index index f9bcbd8e308f..9b544f3f038a 100644 --- a/pkg/sql/logictest/testdata/logic_test/hash_sharded_index +++ b/pkg/sql/logictest/testdata/logic_test/hash_sharded_index @@ -49,7 +49,7 @@ sharded_primary CREATE TABLE public.sharded_primary ( statement ok INSERT INTO sharded_primary values (1), (2), (3) -query error pq: duplicate key value \(crdb_internal_a_shard_10,a\)=\(6,1\) violates unique constraint "primary" +query error pq: duplicate key value violates unique constraint "primary"\nDETAIL: Key \(crdb_internal_a_shard_10,a\)=\(6,1\) already exists\. INSERT INTO sharded_primary values (1) # Ensure that the shard column is assigned into the column family of the first column in diff --git a/pkg/sql/logictest/testdata/logic_test/insert b/pkg/sql/logictest/testdata/logic_test/insert index 8c5ee2d9348a..2119a79b469a 100644 --- a/pkg/sql/logictest/testdata/logic_test/insert +++ b/pkg/sql/logictest/testdata/logic_test/insert @@ -41,13 +41,13 @@ SELECT v || 'hello' FROM [INSERT INTO kv VALUES ('e', 'f'), ('g', '') RETURNING fhello hello -statement error pgcode 23505 duplicate key value \(v\)=\('f'\) violates unique constraint "a" +statement error pgcode 23505 duplicate key value violates unique constraint "a"\nDETAIL: Key \(v\)=\('f'\) already exists\. INSERT INTO kv VALUES ('h', 'f') statement ok INSERT INTO kv VALUES ('f', 'g') -statement error duplicate key value \(v\)=\('g'\) violates unique constraint "a" +statement error duplicate key value violates unique constraint "a"\nDETAIL: Key \(v\)=\('g'\) already exists\. INSERT INTO kv VALUES ('h', 'g') query TT @@ -330,7 +330,7 @@ INSERT INTO abc VALUES (1, 2, 10) # Verify we get the correct message, even though internally the ConditionalPut # for the index key will also fail. -statement error pgcode 23505 duplicate key value \(a,b\)=\(1,2\) violates unique constraint "primary" +statement error pgcode 23505 duplicate key value violates unique constraint "primary"\nDETAIL: Key \(a,b\)=\(1,2\) already exists\. INSERT INTO abc VALUES (1, 2, 20) statement ok @@ -352,7 +352,7 @@ CREATE TABLE blindcput ( # The optimization thresholds at 10 k/v operations, so we need at least that # many in one batch to trigger it. -statement error duplicate key value \(x\)=\(1\) violates unique constraint "primary" +statement error duplicate key value violates unique constraint "primary"\nDETAIL: Key \(x\)=\(1\) already exists\. INSERT INTO blindcput values (1, 1), (2, 2), (3, 3), (4, 4), (1, 5) statement ok diff --git a/pkg/sql/logictest/testdata/logic_test/interleaved b/pkg/sql/logictest/testdata/logic_test/interleaved index 6a03e75cb1d4..5ef1497f61b4 100644 --- a/pkg/sql/logictest/testdata/logic_test/interleaved +++ b/pkg/sql/logictest/testdata/logic_test/interleaved @@ -369,17 +369,17 @@ INSERT INTO p20067 VALUES (1, 'John Doe'); INSERT INTO c20067 VALUES (1, 1, 'John Doe Junior'); COMMIT; -statement error duplicate key value \(name\)=\('John Doe Junior'\) violates unique constraint "uq_name" +statement error duplicate key value violates unique constraint "uq_name"\nDETAIL: Key \(name\)=\('John Doe Junior'\) already exists\. INSERT INTO c20067 VALUES (2, 1, 'John Doe Junior') -statement error duplicate key value \(name\)=\('John Doe Junior'\) violates unique constraint "uq_name" +statement error duplicate key value violates unique constraint "uq_name"\nDETAIL: Key \(name\)=\('John Doe Junior'\) already exists\. BEGIN; INSERT INTO p20067 VALUES (2, 'John Doe'); INSERT INTO c20067 VALUES (2, 1, 'John Doe Junior'); END; # End the last transaction. statement ok END -statement error duplicate key value \(p_id,c_id\)=\(1,1\) violates unique constraint "primary" +statement error duplicate key value violates unique constraint "primary"\nDETAIL: Key \(p_id,c_id\)=\(1,1\) already exists\. INSERT INTO c20067 VALUES (1, 1, 'John Doe') # Regression test for #26756: ensure that interleaved table joins don't get diff --git a/pkg/sql/logictest/testdata/logic_test/materialized_view b/pkg/sql/logictest/testdata/logic_test/materialized_view index 48214ac41bf3..b7653df957b4 100644 --- a/pkg/sql/logictest/testdata/logic_test/materialized_view +++ b/pkg/sql/logictest/testdata/logic_test/materialized_view @@ -107,7 +107,7 @@ CREATE MATERIALIZED VIEW v_dup AS SELECT x FROM dup; CREATE UNIQUE INDEX i ON v_dup (x); INSERT INTO dup VALUES (1), (1); -statement error pq: duplicate key value \(x\)=\(1\) violates unique constraint "i" +statement error pq: duplicate key value violates unique constraint "i"\nDETAIL: Key \(x\)=\(1\) already exists\. REFRESH MATERIALIZED VIEW v_dup # We shouldn't be able to mix materialized and non materialized views in DDLs. diff --git a/pkg/sql/logictest/testdata/logic_test/multi_statement b/pkg/sql/logictest/testdata/logic_test/multi_statement index 8833a7edbdc4..40270962ca33 100644 --- a/pkg/sql/logictest/testdata/logic_test/multi_statement +++ b/pkg/sql/logictest/testdata/logic_test/multi_statement @@ -19,7 +19,7 @@ c d # error if either statement returns an error # first statement returns an error. Second stmt shouldn't execute. -statement error duplicate key value \(k\)=\('a'\) violates unique constraint "primary" +statement error duplicate key value violates unique constraint "primary"\nDETAIL: Key \(k\)=\('a'\) already exists\. INSERT INTO kv (k,v) VALUES ('a', 'b'); INSERT INTO kv (k,v) VALUES ('e', 'f') query TT rowsort @@ -29,7 +29,7 @@ a b c d # second statement returns an error -statement error duplicate key value \(k\)=\('a'\) violates unique constraint "primary" +statement error duplicate key value violates unique constraint "primary"\nDETAIL: Key \(k\)=\('a'\) already exists\. INSERT INTO kv (k,v) VALUES ('g', 'h'); INSERT INTO kv (k,v) VALUES ('a', 'b') query TT rowsort diff --git a/pkg/sql/logictest/testdata/logic_test/parallel_stmts_compat b/pkg/sql/logictest/testdata/logic_test/parallel_stmts_compat index ecadac108236..ec95a771de25 100644 --- a/pkg/sql/logictest/testdata/logic_test/parallel_stmts_compat +++ b/pkg/sql/logictest/testdata/logic_test/parallel_stmts_compat @@ -21,7 +21,7 @@ CREATE TABLE fk( statement ok INSERT INTO kv VALUES (1, 2) RETURNING NOTHING -statement error duplicate key value \(k\)=\(1\) violates unique constraint "primary" +statement error duplicate key value violates unique constraint "primary"\nDETAIL: Key \(k\)=\(1\) already exists\. INSERT INTO kv VALUES (1, 2) RETURNING NOTHING statement ok @@ -33,7 +33,7 @@ UPSERT INTO kv VALUES (2, 500) RETURNING NOTHING statement ok UPDATE kv SET v = k WHERE k = 3 RETURNING NOTHING -statement error duplicate key value \(k\)=\(1\) violates unique constraint "primary" +statement error duplicate key value violates unique constraint "primary"\nDETAIL: Key \(k\)=\(1\) already exists\. UPDATE kv SET k = 1 WHERE k = 2 RETURNING NOTHING statement ok @@ -84,7 +84,7 @@ BEGIN statement ok INSERT INTO kv VALUES (4, 5) RETURNING NOTHING -statement error duplicate key value \(k\)=\(2\) violates unique constraint "primary" +statement error duplicate key value violates unique constraint "primary"\nDETAIL: Key \(k\)=\(2\) already exists\. INSERT INTO kv VALUES (2, 3) RETURNING NOTHING statement error current transaction is aborted, commands ignored until end of transaction block @@ -190,7 +190,7 @@ BEGIN statement ok UPDATE kv SET k = 9 WHERE k = 1 RETURNING NOTHING -statement error duplicate key value \(k\)=\(3\) violates unique constraint "primary" +statement error duplicate key value violates unique constraint "primary"\nDETAIL: Key \(k\)=\(3\) already exists\. UPDATE kv SET k = 3 WHERE k = 2 RETURNING NOTHING statement error current transaction is aborted, commands ignored until end of transaction block @@ -316,7 +316,7 @@ SELECT k, v FROM kv ORDER BY k statement ok BEGIN -statement error duplicate key value \(k\)=\(1\) violates unique constraint "primary" +statement error duplicate key value violates unique constraint "primary"\nDETAIL: Key \(k\)=\(1\) already exists\. INSERT INTO kv VALUES (1, 2) RETURNING NOTHING statement error current transaction is aborted, commands ignored until end of transaction block @@ -361,7 +361,7 @@ BEGIN statement ok EXECUTE x(1, 2) -statement error duplicate key value \(k\)=\(1\) violates unique constraint "primary" +statement error duplicate key value violates unique constraint "primary"\nDETAIL: Key \(k\)=\(1\) already exists\. EXECUTE x(1, 2) statement ok diff --git a/pkg/sql/logictest/testdata/logic_test/partial_index b/pkg/sql/logictest/testdata/logic_test/partial_index index f5996dab02bc..3d13892617e5 100644 --- a/pkg/sql/logictest/testdata/logic_test/partial_index +++ b/pkg/sql/logictest/testdata/logic_test/partial_index @@ -660,7 +660,7 @@ CREATE TABLE u ( ) # Inserting multiple rows that conflicts fails. -statement error pgcode 23505 duplicate key value \(a\)=\(1\) violates unique constraint \"i\" +statement error pgcode 23505 duplicate key value violates unique constraint \"i\"\nDETAIL: Key \(a\)=\(1\) already exists\. INSERT INTO u VALUES (1, 1), (1, 2) # Inserting multiple rows that don't conflict succeeds. @@ -668,7 +668,7 @@ statement ok INSERT INTO u VALUES (1, 1), (2, 2), (1, -1) # Inserting a row that conflicts with an existing row fails. -statement error pgcode 23505 duplicate key value \(a\)=\(1\) violates unique constraint \"i\" +statement error pgcode 23505 duplicate key value violates unique constraint \"i\"\nDETAIL: Key \(a\)=\(1\) already exists\. INSERT INTO u VALUES (1, 3) query II rowsort @@ -684,12 +684,12 @@ DELETE FROM u WHERE a = 2; INSERT INTO u VALUES (2, 2); # Updating a row in the unique partial index to conflict with another row fails. -statement error pgcode 23505 duplicate key value \(a\)=\(2\) violates unique constraint \"i\" +statement error pgcode 23505 duplicate key value violates unique constraint \"i\"\nDETAIL: Key \(a\)=\(2\) already exists\. UPDATE u SET a = 2 WHERE b = 1 # Updating a row not in the unique partial index to conflict with a row in the # index fails. -statement error pgcode 23505 duplicate key value \(a\)=\(2\) violates unique constraint \"i\" +statement error pgcode 23505 duplicate key value violates unique constraint \"i\"\nDETAIL: Key \(a\)=\(2\) already exists\. UPDATE u SET a = 2, b = 1 WHERE b = -1 # Updating a row not in the unique index to remain out of the unique index @@ -885,7 +885,7 @@ statement ok CREATE UNIQUE INDEX i2 ON u (a) WHERE b < 0; INSERT INTO u VALUES (-1, -1); -statement error pgcode 23505 duplicate key value \(a\)=\(-1\) violates unique constraint \"i2\" +statement error pgcode 23505 duplicate key value violates unique constraint \"i2\"\nDETAIL: Key \(a\)=\(-1\) already exists\. INSERT INTO u VALUES (-1, -1) ON CONFLICT (a) WHERE b > 0 DO NOTHING # Two arbiters can be used to detect conflicts and avoid duplicate key errors. @@ -1005,7 +1005,7 @@ CREATE UNIQUE INDEX i2 ON u (a) WHERE b < 0; # There can be duplicate key errors from unique partial indexes that are not # arbiters. -statement error pgcode 23505 duplicate key value \(a\)=\(1\) violates unique constraint \"i2\" +statement error pgcode 23505 duplicate key value violates unique constraint \"i2\"\nDETAIL: Key \(a\)=\(1\) already exists\. INSERT INTO u VALUES (1, -1) ON CONFLICT (a) WHERE b > 0 DO UPDATE SET a = 100 statement ok diff --git a/pkg/sql/logictest/testdata/logic_test/txn b/pkg/sql/logictest/testdata/logic_test/txn index f3e577640848..b25d54efff40 100644 --- a/pkg/sql/logictest/testdata/logic_test/txn +++ b/pkg/sql/logictest/testdata/logic_test/txn @@ -112,7 +112,7 @@ a c statement ok BEGIN -statement error duplicate key value \(k\)=\('a'\) violates unique constraint "primary" +statement error duplicate key value violates unique constraint "primary"\nDETAIL: Key \(k\)=\('a'\) already exists\. INSERT INTO kv VALUES('unique_key', 'some value'); INSERT INTO kv VALUES('a', 'c'); INSERT INTO kv VALUES('unique_key2', 'some value'); diff --git a/pkg/sql/logictest/testdata/logic_test/unique b/pkg/sql/logictest/testdata/logic_test/unique new file mode 100644 index 000000000000..50c5de5b3896 --- /dev/null +++ b/pkg/sql/logictest/testdata/logic_test/unique @@ -0,0 +1,268 @@ +statement ok +SET experimental_enable_unique_without_index_constraints = true + +statement ok +CREATE TABLE uniq ( + k INT PRIMARY KEY, + v INT UNIQUE, + w INT UNIQUE WITHOUT INDEX, + x INT, + y INT, + UNIQUE WITHOUT INDEX (x, y) +) + +statement ok +CREATE TABLE uniq_overlaps_pk ( + a INT, + b INT, + c INT, + d INT, + PRIMARY KEY (a, b), + UNIQUE WITHOUT INDEX (b, c), + UNIQUE WITHOUT INDEX (a, b, d), + UNIQUE WITHOUT INDEX (a), + UNIQUE WITHOUT INDEX (c, d) +) + +statement ok +CREATE TABLE uniq_hidden_pk ( + a INT, + b INT, + c INT, + d INT, + UNIQUE WITHOUT INDEX (b, c), + UNIQUE WITHOUT INDEX (a, b, d), + UNIQUE WITHOUT INDEX (a) +) + +# TODO(rytaft): make this table use UNIQUE WITHOUT INDEX constraints once +# we allow foreign keys to reference them (see #57977). +statement ok +CREATE TABLE uniq_fk_parent ( + a INT UNIQUE, + b INT, + c INT, + UNIQUE (b, c) +) + +statement ok +CREATE TABLE uniq_fk_child ( + a INT REFERENCES uniq_fk_parent (a), + b INT, + c INT, + FOREIGN KEY (b, c) REFERENCES uniq_fk_parent (b, c) ON UPDATE CASCADE, + UNIQUE WITHOUT INDEX (c) +) + +statement ok +CREATE TABLE other (k INT, v INT, w INT NOT NULL, x INT, y INT) + +# Insert some data into the other table. +statement ok +INSERT INTO other VALUES (10, 10, 1, 1, 1) + + +# -- Tests with INSERT -- +subtest Insert + +# Insert some non-null data. +statement ok +INSERT INTO uniq VALUES (1, 1, 1, 1, 1), (2, 2, 2, 2, 2) + +# Regular primary key violation. +statement error pgcode 23505 pq: duplicate key value violates unique constraint "primary"\nDETAIL: Key \(k\)=\(1\) already exists\. +INSERT INTO uniq VALUES (1, 1, 1, 1, 1) + +# Regular unique index violation. +statement error pgcode 23505 pq: duplicate key value violates unique constraint "uniq_v_key"\nDETAIL: Key \(v\)=\(1\) already exists\. +INSERT INTO uniq VALUES (3, 1, 1, 1, 1) + +# Attempt to insert the same keys twice in the same statement. +statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_w"\nDETAIL: Key \(w\)=\(3\) already exists\. +INSERT INTO uniq VALUES (3, 3, 3, 3, 3), (4, 4, 3, 3, 3) + +statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_w"\nDETAIL: Key \(w\)=\(1\) already exists\. +INSERT INTO uniq VALUES (3, 3, 1, 1, 1) + +statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_x_y"\nDETAIL: Key \(x, y\)=\(1, 1\) already exists\. +INSERT INTO uniq VALUES (3, 3, 3, 1, 1) + +# Even though y=1 already exists, (x,y)=(3,1) is unique. +statement ok +INSERT INTO uniq VALUES (3, 3, 3, 3, 1) + +# Inserting these rows should succeed since at least one of the columns in each +# UNIQUE WITHOUT INDEX constraint is null. +statement ok +INSERT INTO uniq VALUES (4, 4, NULL, NULL, 1), (5, 5, NULL, 2, NULL), (6, 6, NULL, NULL, 1), (7, 7, NULL, 2, NULL) + +# Insert with non-constant input. +statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_w"\nDETAIL: Key \(w\)=\(1\) already exists\. +INSERT INTO uniq SELECT k, v, w, x, y FROM other + +query IIIII colnames,rowsort +SELECT * FROM uniq +---- +k v w x y +1 1 1 1 1 +2 2 2 2 2 +3 3 3 3 1 +4 4 NULL NULL 1 +5 5 NULL 2 NULL +6 6 NULL NULL 1 +7 7 NULL 2 NULL + + +# Insert into a table in which the primary key overlaps some of the unique +# constraints. +statement ok +INSERT INTO uniq_overlaps_pk VALUES (1, 1, 1, 1), (2, 2, 2, 2) + +statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_a"\nDETAIL: Key \(a\)=\(1\) already exists\. +INSERT INTO uniq_overlaps_pk VALUES (1, 2, 3, 4) + +statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_b_c"\nDETAIL: Key \(b, c\)=\(1, 1\) already exists\. +INSERT INTO uniq_overlaps_pk VALUES (3, 1, 1, 3) + +statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_c_d"\nDETAIL: Key \(c, d\)=\(1, 1\) already exists\. +INSERT INTO uniq_overlaps_pk VALUES (3, 3, 1, 1) + +statement ok +INSERT INTO uniq_overlaps_pk VALUES (3, 3, 1, 3) + +query IIII colnames,rowsort +SELECT * FROM uniq_overlaps_pk +---- +a b c d +1 1 1 1 +2 2 2 2 +3 3 1 3 + + +# Insert into a table with a hidden primary key. +statement ok +INSERT INTO uniq_hidden_pk VALUES (1, 1, 1, 1), (2, 2, 2, 2) + +# Insert with non-constant input. +statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_b_c"\nDETAIL: Key \(b, c\)=\(1, 1\) already exists\. +INSERT INTO uniq_hidden_pk SELECT k, w, x, y FROM other + +query IIII colnames,rowsort +SELECT * FROM uniq_hidden_pk +---- +a b c d +1 1 1 1 +2 2 2 2 + + +# Combine unique checks with foreign keys. +statement ok +INSERT INTO uniq_fk_parent VALUES (1, 1, 1), (2, 2, 2); +INSERT INTO uniq_fk_child VALUES (1, 1, 1), (2, 2, 2) + +# This passes the foreign key checks but fails the uniqueness checks. +statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_c"\nDETAIL: Key \(c\)=\(1\) already exists\. +INSERT INTO uniq_fk_child VALUES (1, 1, 1), (2, 2, 2) + +# This fails the foreign key checks but passes the uniqueness checks. +statement error pgcode 23503 pq: insert on table "uniq_fk_child" violates foreign key constraint "fk_b_ref_uniq_fk_parent"\nDETAIL: Key \(b, c\)=\(3, 3\) is not present in table "uniq_fk_parent"\. +INSERT INTO uniq_fk_child VALUES (3, 3, 3), (4, 4, 4) + +# This fails both types of checks. +statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_c"\nDETAIL: Key \(c\)=\(2\) already exists\. +INSERT INTO uniq_fk_child VALUES (1, 1, 2), (4, 2, 2) + +query III colnames,rowsort +SELECT * FROM uniq_fk_child +---- +a b c +1 1 1 +2 2 2 + + +# -- Tests with UPDATE -- +subtest Update + +# Set w to the same value it already has. +statement ok +UPDATE uniq SET w = 1, x = 2 WHERE k = 1 + +statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_w"\nDETAIL: Key \(w\)=\(1\) already exists\. +UPDATE uniq SET w = 1, x = 2 WHERE k = 2 + +# Fails because we are trying to update every row with the same values. +statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_w"\nDETAIL: Key \(w\)=\(100\) already exists\. +UPDATE uniq SET w = 100, x = 200 + +# This update targets the row (2, 2, 2, 2, 2). +statement ok +UPDATE uniq SET k = 10, v = 10, w = 10, x = NULL WHERE k = 2 + +# This insert should succeed now. +statement ok +INSERT INTO uniq VALUES (2, 2, 2, 2, 2) + +# No UNIQUE WITHOUT INDEX checks since none of the columns requiring checks are +# updated. +statement ok +UPDATE uniq SET k = 11, v = 11 WHERE k = 10 + +query IIIII colnames,rowsort +SELECT * FROM uniq +---- +k v w x y +1 1 1 2 1 +2 2 2 2 2 +3 3 3 3 1 +4 4 NULL NULL 1 +5 5 NULL 2 NULL +6 6 NULL NULL 1 +7 7 NULL 2 NULL +11 11 10 NULL 2 + + +# Update a table with multiple primary key columns. +# There are no rows with a=5. +statement ok +UPDATE uniq_overlaps_pk SET a = 1, b = 2, c = 3, d = 4 WHERE a = 5 + +statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_a"\nDETAIL: Key \(a\)=\(1\) already exists\. +UPDATE uniq_overlaps_pk SET a = 1, b = 2, c = 3, d = 4 WHERE a = 3 + +query IIII colnames,rowsort +SELECT * FROM uniq_overlaps_pk +---- +a b c d +1 1 1 1 +2 2 2 2 +3 3 1 3 + + +# Try to update a table with a hidden primary key with non-constant input. +statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_a"\nDETAIL: Key \(a\)=\(10\) already exists\. +UPDATE uniq_hidden_pk SET a = k FROM other + +query IIII colnames,rowsort +SELECT * FROM uniq_hidden_pk +---- +a b c d +1 1 1 1 +2 2 2 2 + + +# Combine unique checks with foreign keys. +# The cascade here should cause a uniqueness error for the child. +statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_c"\nDETAIL: Key \(c\)=\(1\) already exists\. +UPDATE uniq_fk_parent SET c = 1 + +# Combine unique checks with foreign keys. +# This passes the foreign key checks but fails the uniqueness check. +statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_c"\nDETAIL: Key \(c\)=\(2\) already exists\. +UPDATE uniq_fk_child SET b = 2, c = 2 + +query III colnames,rowsort +SELECT * FROM uniq_fk_child +---- +a b c +1 1 1 +2 2 2 diff --git a/pkg/sql/logictest/testdata/logic_test/update b/pkg/sql/logictest/testdata/logic_test/update index 7dfe307bc892..476807d84191 100644 --- a/pkg/sql/logictest/testdata/logic_test/update +++ b/pkg/sql/logictest/testdata/logic_test/update @@ -95,7 +95,7 @@ c d e f f g -statement error duplicate key value \(v\)=\('g'\) violates unique constraint "a" +statement error duplicate key value violates unique constraint "a"\nDETAIL: Key \(v\)=\('g'\) already exists\. UPDATE kv2 SET v = 'g' WHERE k IN ('a') statement count 1 @@ -255,10 +255,10 @@ SELECT * FROM abc statement count 1 INSERT INTO abc VALUES (4, 5, 6) -statement error duplicate key value \(a\)=\(4\) violates unique constraint "primary" +statement error duplicate key value violates unique constraint "primary"\nDETAIL: Key \(a\)=\(4\) already exists\. UPDATE abc SET a = 4, b = 3 -statement error duplicate key value \(c\)=\(6\) violates unique constraint "d" +statement error duplicate key value violates unique constraint "d"\nDETAIL: Key \(c\)=\(6\) already exists\. UPDATE abc SET a = 2, c = 6 query III @@ -347,7 +347,7 @@ CREATE TABLE pks ( statement count 2 INSERT INTO pks VALUES (1, 2, 3), (4, 5, 3) -statement error duplicate key value \(k2,v\)=\(5,3\) violates unique constraint "i" +statement error duplicate key value violates unique constraint "i"\nDETAIL: Key \(k2,v\)=\(5,3\) already exists\. UPDATE pks SET k2 = 5 where k1 = 1 # Test updating only one of the columns of a multi-column primary key. diff --git a/pkg/sql/logictest/testdata/logic_test/upsert b/pkg/sql/logictest/testdata/logic_test/upsert index 9f38630edb1f..285ec85bbd7e 100644 --- a/pkg/sql/logictest/testdata/logic_test/upsert +++ b/pkg/sql/logictest/testdata/logic_test/upsert @@ -120,7 +120,7 @@ INSERT INTO kv VALUES (4, 10) ON CONFLICT (k) DO UPDATE SET v = v + 1 statement count 1 INSERT INTO kv VALUES (4, 10) ON CONFLICT (k) DO UPDATE SET v = kv.v + 20 -statement error duplicate key value \(k\)=\(3\) violates unique constraint "primary" +statement error duplicate key value violates unique constraint "primary"\nDETAIL: Key \(k\)=\(3\) already exists\. INSERT INTO kv VALUES (2, 10) ON CONFLICT (k) DO UPDATE SET k = 3, v = 10 statement count 1 @@ -1030,7 +1030,7 @@ statement error pq: UPSERT or INSERT...ON CONFLICT command cannot affect row a s INSERT INTO tdup VALUES (1, 2, 1), (1, 3, 1) ON CONFLICT (x) DO UPDATE SET z=1 # Verify that duplicate insert into secondary fails with regular conflict error. -statement error pq: duplicate key value \(y\)=\(2\) violates unique constraint "tdup_y_key" +statement error pq: duplicate key value violates unique constraint "tdup_y_key"\nDETAIL: Key \(y\)=\(2\) already exists\. INSERT INTO tdup VALUES (2, 2, 2), (3, 2, 2) ON CONFLICT (x) DO UPDATE SET z=1 statement ok @@ -1073,7 +1073,7 @@ SELECT * FROM tdup@tdup_y_z_key 3 2 NULL # Verify that duplicate secondary key fails with regular conflict error. -statement error pq: duplicate key value \(y,z\)=\(1,2\) violates unique constraint "tdup_y_z_key" +statement error pq: duplicate key value violates unique constraint "tdup_y_z_key"\nDETAIL: Key \(y,z\)=\(1,2\) already exists\. INSERT INTO tdup VALUES (6, 1, 1), (7, 1, 2) ON CONFLICT (y, z) DO UPDATE SET z=2 # With constant grouping columns (no error). diff --git a/pkg/sql/mutation_test.go b/pkg/sql/mutation_test.go index 7d7fba2e1d5c..5b07b0d07602 100644 --- a/pkg/sql/mutation_test.go +++ b/pkg/sql/mutation_test.go @@ -54,7 +54,7 @@ INSERT INTO d.a(a) VALUES (1); for i, step := range []func() (*gosql.Rows, error){step1, step2} { rows, err := step() if err != nil { - if !testutils.IsError(err, `duplicate key value \(a\)=\(1\)`) { + if !testutils.IsError(err, `duplicate key value`) { t.Errorf("%d: %v", i, err) } } else { @@ -68,7 +68,7 @@ INSERT INTO d.a(a) VALUES (1); err := rows.Scan(&val) if err != nil { - if !testutils.IsError(err, `duplicate key value \(a\)=\(1\)`) { + if !testutils.IsError(err, `duplicate key value`) { t.Errorf("%d: %v", i, err) } } else { @@ -80,7 +80,7 @@ INSERT INTO d.a(a) VALUES (1); for rows.Next() { err := rows.Scan(&val) if err != nil { - if !testutils.IsError(err, `duplicate key value \(a\)=\(1\)`) { + if !testutils.IsError(err, `duplicate key value`) { t.Errorf("%d: %v", i, err) } } diff --git a/pkg/sql/opt/exec/execbuilder/mutation.go b/pkg/sql/opt/exec/execbuilder/mutation.go index 4aecfb00b1f7..7a035cbf79ba 100644 --- a/pkg/sql/opt/exec/execbuilder/mutation.go +++ b/pkg/sql/opt/exec/execbuilder/mutation.go @@ -115,7 +115,9 @@ func (b *Builder) buildInsert(ins *memo.InsertExpr) (execPlan, error) { ep.outputCols = mutationOutputColMap(ins) } - // TODO(rytaft): build unique checks. + if err := b.buildUniqueChecks(ins.UniqueChecks); err != nil { + return execPlan{}, err + } if err := b.buildFKChecks(ins.FKChecks); err != nil { return execPlan{}, err @@ -148,6 +150,12 @@ func (b *Builder) tryBuildFastPathInsert(ins *memo.InsertExpr) (_ execPlan, ok b return execPlan{}, false, nil } + // We cannot use the fast path if any uniqueness checks are needed. + // TODO(rytaft): try to relax this restriction (see #58047). + if len(ins.UniqueChecks) > 0 { + return execPlan{}, false, nil + } + md := b.mem.Metadata() tab := md.Table(ins.Table) @@ -337,7 +345,9 @@ func (b *Builder) buildUpdate(upd *memo.UpdateExpr) (execPlan, error) { return execPlan{}, err } - // TODO(rytaft): build unique checks. + if err := b.buildUniqueChecks(upd.UniqueChecks); err != nil { + return execPlan{}, err + } if err := b.buildFKChecks(upd.FKChecks); err != nil { return execPlan{}, err @@ -421,7 +431,9 @@ func (b *Builder) buildUpsert(ups *memo.UpsertExpr) (execPlan, error) { return execPlan{}, err } - // TODO(rytaft): build unique checks. + if err := b.buildUniqueChecks(ups.UniqueChecks); err != nil { + return execPlan{}, err + } if err := b.buildFKChecks(ups.FKChecks); err != nil { return execPlan{}, err @@ -769,6 +781,38 @@ func mutationOutputColMap(mutation memo.RelExpr) opt.ColMap { return colMap } +// buildUniqueChecks builds uniqueness check queries. These check queries are +// used to enforce UNIQUE WITHOUT INDEX constraints. +// +// The checks consist of queries that will only return rows if a constraint is +// violated. Those queries are each wrapped in an ErrorIfRows operator, which +// will throw an appropriate error in case the inner query returns any rows. +func (b *Builder) buildUniqueChecks(checks memo.UniqueChecksExpr) error { + md := b.mem.Metadata() + for i := range checks { + c := &checks[i] + // Construct the query that returns uniqueness violations. + query, err := b.buildRelational(c.Check) + if err != nil { + return err + } + // Wrap the query in an error node. + mkErr := func(row tree.Datums) error { + keyVals := make(tree.Datums, len(c.KeyCols)) + for i, col := range c.KeyCols { + keyVals[i] = row[query.getNodeColumnOrdinal(col)] + } + return mkUniqueCheckErr(md, c, keyVals) + } + node, err := b.factory.ConstructErrorIfRows(query.root, mkErr) + if err != nil { + return err + } + b.checks = append(b.checks, node) + } + return nil +} + func (b *Builder) buildFKChecks(checks memo.FKChecksExpr) error { md := b.mem.Metadata() for i := range checks { @@ -795,6 +839,48 @@ func (b *Builder) buildFKChecks(checks memo.FKChecksExpr) error { return nil } +// mkUniqueCheckErr generates a user-friendly error describing a uniqueness +// violation. The keyVals are the values that correspond to the +// cat.UniqueConstraint columns. +func mkUniqueCheckErr(md *opt.Metadata, c *memo.UniqueChecksItem, keyVals tree.Datums) error { + tabMeta := md.TableMeta(c.Table) + uc := tabMeta.Table.Unique(c.CheckOrdinal) + constraintName := uc.Name() + var msg, details bytes.Buffer + + // Generate an error of the form: + // ERROR: duplicate key value violates unique constraint "foo" + // DETAIL: Key (k)=(2) already exists. + msg.WriteString("duplicate key value violates unique constraint ") + lexbase.EncodeEscapedSQLIdent(&msg, constraintName) + + details.WriteString("Key (") + for i := 0; i < uc.ColumnCount(); i++ { + if i > 0 { + details.WriteString(", ") + } + col := tabMeta.Table.Column(uc.ColumnOrdinal(tabMeta.Table, i)) + details.WriteString(string(col.ColName())) + } + details.WriteString(")=(") + for i, d := range keyVals { + if i > 0 { + details.WriteString(", ") + } + details.WriteString(d.String()) + } + + details.WriteString(") already exists.") + + return errors.WithDetail( + pgerror.WithConstraintName( + pgerror.Newf(pgcode.UniqueViolation, "%s", msg.String()), + constraintName, + ), + details.String(), + ) +} + // mkFKCheckErr generates a user-friendly error describing a foreign key // violation. The keyVals are the values that correspond to the // cat.ForeignKeyConstraint columns. diff --git a/pkg/sql/opt/exec/execbuilder/testdata/delete b/pkg/sql/opt/exec/execbuilder/testdata/delete index 3e2c78a0d438..37fef69ef74f 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/delete +++ b/pkg/sql/opt/exec/execbuilder/testdata/delete @@ -416,7 +416,7 @@ vectorized: true │ fk: fk_pid_ref_parent │ input: buffer 1 │ -└── • fk-check +└── • constraint-check │ └── • error if rows │ @@ -476,7 +476,7 @@ vectorized: true │ fk: fk_pid_ref_parent │ input: buffer 1 │ -└── • fk-check +└── • constraint-check │ └── • error if rows │ diff --git a/pkg/sql/opt/exec/execbuilder/testdata/fk b/pkg/sql/opt/exec/execbuilder/testdata/fk index 1ad69517cf17..d02dca60d793 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/fk +++ b/pkg/sql/opt/exec/execbuilder/testdata/fk @@ -29,7 +29,7 @@ vectorized: true │ └── • values │ size: 2 columns, 2 rows │ -└── • fk-check +└── • constraint-check │ └── • error if rows │ @@ -64,7 +64,7 @@ vectorized: true │ table: xy@primary │ spans: FULL SCAN │ -└── • fk-check +└── • constraint-check │ └── • error if rows │ @@ -102,7 +102,7 @@ vectorized: true │ └── • values │ size: 2 columns, 2 rows │ -└── • fk-check +└── • constraint-check │ └── • error if rows │ @@ -146,7 +146,7 @@ vectorized: true │ └── • values │ size: 4 columns, 2 rows │ -└── • fk-check +└── • constraint-check │ └── • error if rows │ @@ -194,7 +194,7 @@ vectorized: true │ └── • values │ size: 4 columns, 2 rows │ -├── • fk-check +├── • constraint-check │ │ │ └── • error if rows │ │ @@ -209,7 +209,7 @@ vectorized: true │ └── • scan buffer │ label: buffer 1 │ -└── • fk-check +└── • constraint-check │ └── • error if rows │ @@ -259,7 +259,7 @@ vectorized: true │ table: parent@primary │ spans: [/3 - /3] │ -├── • fk-check +├── • constraint-check │ │ │ └── • error if rows │ │ @@ -270,7 +270,7 @@ vectorized: true │ └── • scan buffer │ label: buffer 1 │ -└── • fk-check +└── • constraint-check │ └── • error if rows │ @@ -303,7 +303,7 @@ vectorized: true │ table: parent@primary │ spans: [/3 - /3] │ -├── • fk-check +├── • constraint-check │ │ │ └── • error if rows │ │ @@ -314,7 +314,7 @@ vectorized: true │ └── • scan buffer │ label: buffer 1 │ -├── • fk-check +├── • constraint-check │ │ │ └── • error if rows │ │ @@ -325,7 +325,7 @@ vectorized: true │ └── • scan buffer │ label: buffer 1 │ -└── • fk-check +└── • constraint-check │ └── • error if rows │ @@ -367,7 +367,7 @@ vectorized: true │ table: doubleparent@primary │ spans: [/10 - /10] │ -└── • fk-check +└── • constraint-check │ └── • error if rows │ @@ -403,7 +403,7 @@ vectorized: true │ spans: FULL SCAN │ locking strength: for update │ -└── • fk-check +└── • constraint-check │ └── • error if rows │ @@ -442,7 +442,7 @@ vectorized: true │ spans: [/10 - /10] │ locking strength: for update │ -└── • fk-check +└── • constraint-check │ └── • error if rows │ @@ -477,7 +477,7 @@ vectorized: true │ spans: FULL SCAN │ locking strength: for update │ -├── • fk-check +├── • constraint-check │ │ │ └── • error if rows │ │ @@ -503,7 +503,7 @@ vectorized: true │ table: child@child_p_idx │ spans: FULL SCAN │ -└── • fk-check +└── • constraint-check │ └── • error if rows │ @@ -552,7 +552,7 @@ vectorized: true │ spans: [/10 - /10] │ locking strength: for update │ -├── • fk-check +├── • constraint-check │ │ │ └── • error if rows │ │ @@ -568,7 +568,7 @@ vectorized: true │ └── • scan buffer │ label: buffer 1 │ -└── • fk-check +└── • constraint-check │ └── • error if rows │ @@ -610,7 +610,7 @@ vectorized: true │ spans: FULL SCAN │ locking strength: for update │ -└── • fk-check +└── • constraint-check │ └── • error if rows │ @@ -659,7 +659,7 @@ vectorized: true │ spans: FULL SCAN │ locking strength: for update │ -└── • fk-check +└── • constraint-check │ └── • error if rows │ @@ -696,7 +696,7 @@ vectorized: true │ spans: FULL SCAN │ locking strength: for update │ -└── • fk-check +└── • constraint-check │ └── • error if rows │ @@ -735,7 +735,7 @@ vectorized: true │ spans: FULL SCAN │ locking strength: for update │ -├── • fk-check +├── • constraint-check │ │ │ └── • error if rows │ │ @@ -751,7 +751,7 @@ vectorized: true │ table: parent@primary │ spans: FULL SCAN │ -└── • fk-check +└── • constraint-check │ └── • error if rows │ @@ -803,7 +803,7 @@ vectorized: true │ spans: FULL SCAN │ locking strength: for update │ -└── • fk-check +└── • constraint-check │ └── • error if rows │ @@ -845,7 +845,7 @@ vectorized: true │ spans: FULL SCAN │ locking strength: for update │ -└── • fk-check +└── • constraint-check │ └── • error if rows │ @@ -884,7 +884,7 @@ vectorized: true │ spans: FULL SCAN │ locking strength: for update │ -└── • fk-check +└── • constraint-check │ └── • error if rows │ @@ -1205,7 +1205,7 @@ vectorized: true │ └── • values │ size: 2 columns, 3 rows │ -└── • fk-check +└── • constraint-check │ └── • error if rows │ @@ -1244,7 +1244,7 @@ vectorized: true │ spans: [/1 - ] │ locking strength: for update │ -└── • fk-check +└── • constraint-check │ └── • error if rows │ @@ -1286,7 +1286,7 @@ vectorized: true │ spans: [/1 - ] │ locking strength: for update │ -└── • fk-check +└── • constraint-check │ └── • error if rows │ @@ -1326,7 +1326,7 @@ vectorized: true │ table: p@primary │ spans: [/1 - ] │ -└── • fk-check +└── • constraint-check │ └── • error if rows │ @@ -1381,7 +1381,7 @@ vectorized: true │ spans: [/1 - ] │ locking strength: for update │ -└── • fk-check +└── • constraint-check │ └── • error if rows │ @@ -1419,7 +1419,7 @@ vectorized: true │ spans: [/1 - ] │ locking strength: for update │ -└── • fk-check +└── • constraint-check │ └── • error if rows │ @@ -1454,7 +1454,7 @@ vectorized: true │ table: p@primary │ spans: [/1 - ] │ -└── • fk-check +└── • constraint-check │ └── • error if rows │ @@ -1505,7 +1505,7 @@ vectorized: true │ │ │ └── • emptyrow │ -└── • fk-check +└── • constraint-check │ └── • error if rows │ diff --git a/pkg/sql/opt/exec/execbuilder/testdata/insert b/pkg/sql/opt/exec/execbuilder/testdata/insert index ca1a9ef154ec..6fe6d60130ea 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/insert +++ b/pkg/sql/opt/exec/execbuilder/testdata/insert @@ -81,7 +81,7 @@ output row: ['a' 'b'] output row: ['c' 'd'] output row: ['e' 'f'] -statement error pgcode 23505 duplicate key value \(v\)=\('f'\) violates unique constraint "a" +statement error pgcode 23505 duplicate key value violates unique constraint "a"\nDETAIL: Key \(v\)=\('f'\) already exists\. INSERT INTO kv VALUES ('h', 'f') statement ok @@ -208,7 +208,7 @@ output row: ['c' 'd'] output row: ['e' 'f'] output row: ['f' 'g'] -statement error duplicate key value \(v\)=\('g'\) violates unique constraint "a" +statement error duplicate key value violates unique constraint "a"\nDETAIL: Key \(v\)=\('g'\) already exists\. INSERT INTO kv VALUES ('h', 'g') statement ok diff --git a/pkg/sql/opt/exec/execbuilder/testdata/show_trace b/pkg/sql/opt/exec/execbuilder/testdata/show_trace index e2bf66a56de0..603ee06cdcd3 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/show_trace +++ b/pkg/sql/opt/exec/execbuilder/testdata/show_trace @@ -95,7 +95,7 @@ SELECT operation, message FROM [SHOW KV TRACE FOR SESSION] flow CPut /Table/54/1/1/0 -> /TUPLE/2:2:Int/2 flow InitPut /Table/54/2/2/0 -> /BYTES/0x89 kv.DistSender: sending partial batch r28: sending batch 1 CPut, 1 EndTxn to (n1,s1):1 -exec stmt execution failed after 0 rows: duplicate key value (k)=(1) violates unique constraint "primary" +exec stmt execution failed after 0 rows: duplicate key value violates unique constraint "primary"\nDETAIL: Key (k)=(1) already exists\. statement error duplicate key value SET tracing = on,kv,results; INSERT INTO t.kv(k, v) VALUES (2,2); SET tracing = off @@ -108,7 +108,7 @@ SELECT operation, message FROM [SHOW KV TRACE FOR SESSION] flow CPut /Table/54/1/2/0 -> /TUPLE/2:2:Int/2 flow InitPut /Table/54/2/2/0 -> /BYTES/0x8a kv.DistSender: sending partial batch r28: sending batch 1 CPut, 1 EndTxn to (n1,s1):1 -exec stmt execution failed after 0 rows: duplicate key value (v)=(2) violates unique constraint "woo" +exec stmt execution failed after 0 rows: duplicate key value violates unique constraint "woo"\nDETAIL: Key (v)=(2) already exists\. statement ok SET tracing = on,kv,results; CREATE TABLE t.kv2 AS TABLE t.kv; SET tracing = off diff --git a/pkg/sql/opt/exec/execbuilder/testdata/unique b/pkg/sql/opt/exec/execbuilder/testdata/unique new file mode 100644 index 000000000000..7f209d8d4d4f --- /dev/null +++ b/pkg/sql/opt/exec/execbuilder/testdata/unique @@ -0,0 +1,809 @@ +# LogicTest: local + +statement ok +SET experimental_enable_unique_without_index_constraints = true + +statement ok +CREATE TABLE uniq ( + k INT PRIMARY KEY, + v INT UNIQUE, + w INT UNIQUE WITHOUT INDEX, + x INT, + y INT, + UNIQUE WITHOUT INDEX (x, y) +) + +statement ok +CREATE TABLE uniq_overlaps_pk ( + a INT, + b INT, + c INT, + d INT, + PRIMARY KEY (a, b), + UNIQUE WITHOUT INDEX (b, c), + UNIQUE WITHOUT INDEX (a, b, d), + UNIQUE WITHOUT INDEX (a), + UNIQUE WITHOUT INDEX (c, d) +) + +statement ok +CREATE TABLE uniq_hidden_pk ( + a INT, + b INT, + c INT, + d INT, + UNIQUE WITHOUT INDEX (b, c), + UNIQUE WITHOUT INDEX (a, b, d), + UNIQUE WITHOUT INDEX (a) +) + +# TODO(rytaft): make this table use UNIQUE WITHOUT INDEX constraints once +# we allow foreign keys to reference them (see #57977). +statement ok +CREATE TABLE uniq_fk_parent ( + a INT UNIQUE, + b INT, + c INT, + UNIQUE (b, c), + FAMILY (rowid, a, b, c) +) + +statement ok +CREATE TABLE uniq_fk_child ( + a INT REFERENCES uniq_fk_parent (a), + b INT, + c INT, + FOREIGN KEY (b, c) REFERENCES uniq_fk_parent (b, c) ON UPDATE CASCADE, + UNIQUE WITHOUT INDEX (c) +) + +statement ok +CREATE TABLE other (k INT, v INT, w INT NOT NULL, x INT, y INT) + +# -- Tests with INSERT -- +subtest Insert + +# None of the inserted values have nulls. +query T +EXPLAIN INSERT INTO uniq VALUES (1, 1, 1, 1, 1), (2, 2, 2, 2, 2) +---- +distribution: local +vectorized: true +· +• root +│ +├── • insert +│ │ into: uniq(k, v, w, x, y) +│ │ +│ └── • buffer +│ │ label: buffer 1 +│ │ +│ └── • values +│ size: 5 columns, 2 rows +│ +├── • constraint-check +│ │ +│ └── • error if rows +│ │ +│ └── • hash join (right semi) +│ │ equality: (w) = (column3) +│ │ pred: column1 != k +│ │ +│ ├── • scan +│ │ missing stats +│ │ table: uniq@primary +│ │ spans: FULL SCAN +│ │ +│ └── • scan buffer +│ label: buffer 1 +│ +└── • constraint-check + │ + └── • error if rows + │ + └── • hash join (right semi) + │ equality: (x, y) = (column4, column5) + │ pred: column1 != k + │ + ├── • scan + │ missing stats + │ table: uniq@primary + │ spans: FULL SCAN + │ + └── • scan buffer + label: buffer 1 + +# No need to plan checks for w since it's aways 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) +---- +distribution: local +vectorized: true +· +• root +│ +├── • insert +│ │ into: uniq(k, v, w, x, y) +│ │ +│ └── • buffer +│ │ label: buffer 1 +│ │ +│ └── • values +│ size: 5 columns, 2 rows +│ +└── • constraint-check + │ + └── • error if rows + │ + └── • hash join (right semi) + │ equality: (x, y) = (column4, column5) + │ pred: column1 != k + │ + ├── • scan + │ missing stats + │ table: uniq@primary + │ spans: FULL SCAN + │ + └── • scan buffer + label: buffer 1 + +# Insert with non-constant input. +query T +EXPLAIN INSERT INTO uniq SELECT k, v, w, x, y FROM other +---- +distribution: local +vectorized: true +· +• root +│ +├── • insert +│ │ into: uniq(k, v, w, x, y) +│ │ +│ └── • buffer +│ │ label: buffer 1 +│ │ +│ └── • scan +│ missing stats +│ table: other@primary +│ spans: FULL SCAN +│ +├── • constraint-check +│ │ +│ └── • error if rows +│ │ +│ └── • hash join (semi) +│ │ equality: (w) = (w) +│ │ pred: k != k +│ │ +│ ├── • scan buffer +│ │ label: buffer 1 +│ │ +│ └── • scan +│ missing stats +│ table: uniq@primary +│ spans: FULL SCAN +│ +└── • constraint-check + │ + └── • error if rows + │ + └── • hash join (semi) + │ equality: (x, y) = (x, y) + │ pred: k != k + │ + ├── • scan buffer + │ label: buffer 1 + │ + └── • scan + missing stats + table: uniq@primary + spans: FULL SCAN + +# Add inequality filters for the primary key columns that are not part of each +# unique constraint to prevent rows from matching themselves in the semi join. +query T +EXPLAIN INSERT INTO uniq_overlaps_pk VALUES (1, 1, 1, 1), (2, 2, 2, 2) +---- +distribution: local +vectorized: true +· +• root +│ +├── • insert +│ │ into: uniq_overlaps_pk(a, b, c, d) +│ │ +│ └── • buffer +│ │ label: buffer 1 +│ │ +│ └── • values +│ size: 4 columns, 2 rows +│ +├── • constraint-check +│ │ +│ └── • error if rows +│ │ +│ └── • hash join (right semi) +│ │ equality: (b, c) = (column2, column3) +│ │ pred: column1 != a +│ │ +│ ├── • scan +│ │ missing stats +│ │ table: uniq_overlaps_pk@primary +│ │ spans: FULL SCAN +│ │ +│ └── • scan buffer +│ label: buffer 1 +│ +├── • constraint-check +│ │ +│ └── • error if rows +│ │ +│ └── • lookup join (semi) +│ │ table: uniq_overlaps_pk@primary +│ │ equality: (column1) = (a) +│ │ pred: column2 != b +│ │ +│ └── • scan buffer +│ label: buffer 1 +│ +└── • constraint-check + │ + └── • error if rows + │ + └── • hash join (right semi) + │ equality: (c, d) = (column3, column4) + │ pred: (column1 != a) OR (column2 != b) + │ + ├── • scan + │ missing stats + │ table: uniq_overlaps_pk@primary + │ spans: FULL SCAN + │ + └── • scan buffer + label: buffer 1 + +# Insert with non-constant input. +# Add inequality filters for the hidden primary key column. +query T +EXPLAIN INSERT INTO uniq_hidden_pk SELECT k, v, x, y FROM other +---- +distribution: local +vectorized: true +· +• root +│ +├── • insert +│ │ into: uniq_hidden_pk(a, b, c, d, 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, x) = (b, c) +│ │ pred: column16 != rowid +│ │ +│ ├── • scan buffer +│ │ label: buffer 1 +│ │ +│ └── • scan +│ missing stats +│ table: uniq_hidden_pk@primary +│ spans: FULL SCAN +│ +├── • constraint-check +│ │ +│ └── • error if rows +│ │ +│ └── • hash join (semi) +│ │ equality: (k, v, y) = (a, b, d) +│ │ pred: column16 != rowid +│ │ +│ ├── • scan buffer +│ │ label: buffer 1 +│ │ +│ └── • scan +│ missing stats +│ table: uniq_hidden_pk@primary +│ spans: FULL SCAN +│ +└── • constraint-check + │ + └── • error if rows + │ + └── • hash join (semi) + │ equality: (k) = (a) + │ pred: column16 != rowid + │ + ├── • scan buffer + │ label: buffer 1 + │ + └── • scan + missing stats + table: uniq_hidden_pk@primary + spans: FULL SCAN + +# Combine unique checks with foreign keys. +# TODO(rytaft): This currently isn't testing anything, since uniq_fk_parent +# doesn't have any UNIQUE WITHOUT INDEX constraints. See comment above where +# uniq_fk_parent is created. +query T +EXPLAIN INSERT INTO uniq_fk_parent VALUES (1, 1, 1), (2, 2, 2) +---- +distribution: local +vectorized: true +· +• insert +│ into: uniq_fk_parent(a, b, c, rowid) +│ auto commit +│ +└── • render + │ + └── • values + size: 3 columns, 2 rows + +# Combine unique checks with foreign keys. There should be two foreign key +# checks and one uniqueness check. +query T +EXPLAIN INSERT INTO uniq_fk_child VALUES (1, 1, 1), (2, 2, 2) +---- +distribution: local +vectorized: true +· +• root +│ +├── • insert +│ │ into: uniq_fk_child(a, b, c, rowid) +│ │ +│ └── • buffer +│ │ label: buffer 1 +│ │ +│ └── • render +│ │ +│ └── • values +│ size: 3 columns, 2 rows +│ +├── • constraint-check +│ │ +│ └── • error if rows +│ │ +│ └── • hash join (right semi) +│ │ equality: (c) = (column3) +│ │ pred: column10 != rowid +│ │ +│ ├── • scan +│ │ missing stats +│ │ table: uniq_fk_child@primary +│ │ spans: FULL SCAN +│ │ +│ └── • scan buffer +│ label: buffer 1 +│ +├── • constraint-check +│ │ +│ └── • error if rows +│ │ +│ └── • lookup join (anti) +│ │ table: uniq_fk_parent@uniq_fk_parent_b_c_key +│ │ equality: (column2, column3) = (b,c) +│ │ equality cols are key +│ │ +│ └── • scan buffer +│ label: buffer 1 +│ +└── • constraint-check + │ + └── • error if rows + │ + └── • lookup join (anti) + │ table: uniq_fk_parent@uniq_fk_parent_a_key + │ equality: (column1) = (a) + │ equality cols are key + │ + └── • scan buffer + label: buffer 1 + +# -- Tests with UPDATE -- +subtest Update + +# None of the updated values have nulls. +query T +EXPLAIN UPDATE uniq SET w = 1, x = 2 +---- +distribution: local +vectorized: true +· +• root +│ +├── • update +│ │ table: uniq +│ │ set: w, x +│ │ +│ └── • buffer +│ │ label: buffer 1 +│ │ +│ └── • render +│ │ +│ └── • scan +│ missing stats +│ table: uniq@primary +│ spans: FULL SCAN +│ locking strength: for update +│ +├── • constraint-check +│ │ +│ └── • error if rows +│ │ +│ └── • hash join (semi) +│ │ equality: (w_new) = (w) +│ │ pred: k != k +│ │ +│ ├── • scan buffer +│ │ label: buffer 1 +│ │ +│ └── • scan +│ missing stats +│ table: uniq@primary +│ spans: FULL SCAN +│ +└── • constraint-check + │ + └── • error if rows + │ + └── • hash join (semi) + │ equality: (x_new, y) = (x, y) + │ pred: k != k + │ + ├── • scan buffer + │ label: buffer 1 + │ + └── • scan + missing stats + table: uniq@primary + spans: FULL SCAN + +# No need to plan checks for x,y since x is aways null. +# Also update the primary key. +query T +EXPLAIN UPDATE uniq SET k = 1, w = 2, x = NULL +---- +distribution: local +vectorized: true +· +• root +│ +├── • update +│ │ table: uniq +│ │ set: k, w, x +│ │ +│ └── • buffer +│ │ label: buffer 1 +│ │ +│ └── • render +│ │ +│ └── • scan +│ missing stats +│ table: uniq@primary +│ spans: FULL SCAN +│ locking strength: for update +│ +└── • constraint-check + │ + └── • error if rows + │ + └── • hash join (semi) + │ equality: (w_new) = (w) + │ pred: k_new != k + │ + ├── • scan buffer + │ label: buffer 1 + │ + └── • scan + missing stats + table: uniq@primary + spans: FULL SCAN + +# No need to plan checks since none of the columns requiring checks are updated. +query T +EXPLAIN UPDATE uniq SET k = 1, v = 2 +---- +distribution: local +vectorized: true +· +• update +│ table: uniq +│ set: k, v +│ auto commit +│ +└── • render + │ + └── • scan + missing stats + table: uniq@primary + spans: FULL SCAN + locking strength: for update + +# Add inequality filters for the primary key columns that are not part of each +# unique constraint to prevent rows from matching themselves in the semi join. +query T +EXPLAIN UPDATE uniq_overlaps_pk SET a = 1, b = 2, c = 3, d = 4 WHERE a = 5 +---- +distribution: local +vectorized: true +· +• root +│ +├── • update +│ │ table: uniq_overlaps_pk +│ │ set: a, b, c, d +│ │ +│ └── • buffer +│ │ label: buffer 1 +│ │ +│ └── • render +│ │ +│ └── • scan +│ missing stats +│ table: uniq_overlaps_pk@primary +│ spans: [/5 - /5] +│ locking strength: for update +│ +├── • constraint-check +│ │ +│ └── • error if rows +│ │ +│ └── • hash join (right semi) +│ │ equality: (b, c) = (b_new, c_new) +│ │ pred: a_new != a +│ │ +│ ├── • scan +│ │ missing stats +│ │ table: uniq_overlaps_pk@primary +│ │ spans: FULL SCAN +│ │ +│ └── • scan buffer +│ label: buffer 1 +│ +├── • constraint-check +│ │ +│ └── • error if rows +│ │ +│ └── • lookup join (semi) +│ │ table: uniq_overlaps_pk@primary +│ │ equality: (a_new) = (a) +│ │ pred: b_new != b +│ │ +│ └── • scan buffer +│ label: buffer 1 +│ +└── • constraint-check + │ + └── • error if rows + │ + └── • hash join (right semi) + │ equality: (c, d) = (c_new, d_new) + │ pred: (a_new != a) OR (b_new != b) + │ + ├── • scan + │ missing stats + │ table: uniq_overlaps_pk@primary + │ spans: FULL SCAN + │ + └── • scan buffer + label: buffer 1 + +# Update with non-constant input. +# No need to add a check for b,c since those columns weren't updated. +# Add inequality filters for the hidden primary key column. +query T +EXPLAIN UPDATE uniq_hidden_pk SET a = k FROM other +---- +distribution: local +vectorized: true +· +• root +│ +├── • update +│ │ table: uniq_hidden_pk +│ │ set: a +│ │ +│ └── • buffer +│ │ label: buffer 1 +│ │ +│ └── • cross join +│ │ +│ ├── • scan +│ │ missing stats +│ │ table: uniq_hidden_pk@primary +│ │ spans: FULL SCAN +│ │ +│ └── • scan +│ missing stats +│ table: other@primary +│ spans: FULL SCAN +│ +├── • constraint-check +│ │ +│ └── • error if rows +│ │ +│ └── • hash join (semi) +│ │ equality: (k, b, d) = (a, b, d) +│ │ pred: rowid != rowid +│ │ +│ ├── • scan buffer +│ │ label: buffer 1 +│ │ +│ └── • scan +│ missing stats +│ table: uniq_hidden_pk@primary +│ spans: FULL SCAN +│ +└── • constraint-check + │ + └── • error if rows + │ + └── • hash join (semi) + │ equality: (k) = (a) + │ pred: rowid != rowid + │ + ├── • scan buffer + │ label: buffer 1 + │ + └── • scan + missing stats + table: uniq_hidden_pk@primary + spans: FULL SCAN + +# Combine unique checks with foreign keys. +# The cascade here affects the unique column in uniq_fk_child. +query T +EXPLAIN UPDATE uniq_fk_parent SET c = 1 +---- +distribution: local +vectorized: true +· +• root +│ +├── • update +│ │ table: uniq_fk_parent +│ │ set: c +│ │ +│ └── • buffer +│ │ label: buffer 1 +│ │ +│ └── • render +│ │ +│ └── • scan +│ missing stats +│ table: uniq_fk_parent@primary +│ spans: FULL SCAN +│ locking strength: for update +│ +└── • fk-cascade + fk: fk_b_ref_uniq_fk_parent + input: buffer 1 + +# Combine unique checks with foreign keys. +# There is no uniquness check since column c is not updated. +query T +EXPLAIN UPDATE uniq_fk_child SET a = 1, b = 2 +---- +distribution: local +vectorized: true +· +• root +│ +├── • update +│ │ table: uniq_fk_child +│ │ set: a, b +│ │ +│ └── • buffer +│ │ label: buffer 1 +│ │ +│ └── • render +│ │ +│ └── • scan +│ missing stats +│ table: uniq_fk_child@primary +│ spans: FULL SCAN +│ locking strength: for update +│ +├── • constraint-check +│ │ +│ └── • error if rows +│ │ +│ └── • hash join (right anti) +│ │ equality: (b, c) = (b_new, c) +│ │ +│ ├── • scan +│ │ missing stats +│ │ table: uniq_fk_parent@uniq_fk_parent_b_c_key +│ │ spans: FULL SCAN +│ │ +│ └── • filter +│ │ filter: c IS NOT NULL +│ │ +│ └── • scan buffer +│ label: buffer 1 +│ +└── • constraint-check + │ + └── • error if rows + │ + └── • hash join (anti) + │ equality: (a_new) = (a) + │ + ├── • scan buffer + │ label: buffer 1 + │ + └── • scan + missing stats + table: uniq_fk_parent@uniq_fk_parent_a_key + spans: FULL SCAN + +# Combine unique checks with foreign keys. +# There should be one fk check and one uniqueness check. +query T +EXPLAIN UPDATE uniq_fk_child SET b = 1, c = 2 +---- +distribution: local +vectorized: true +· +• root +│ +├── • update +│ │ table: uniq_fk_child +│ │ set: b, c +│ │ +│ └── • buffer +│ │ label: buffer 1 +│ │ +│ └── • render +│ │ +│ └── • scan +│ missing stats +│ table: uniq_fk_child@primary +│ spans: FULL SCAN +│ locking strength: for update +│ +├── • constraint-check +│ │ +│ └── • error if rows +│ │ +│ └── • hash join (semi) +│ │ equality: (c_new) = (c) +│ │ pred: rowid != rowid +│ │ +│ ├── • scan buffer +│ │ label: buffer 1 +│ │ +│ └── • scan +│ missing stats +│ table: uniq_fk_child@primary +│ spans: FULL SCAN +│ +└── • constraint-check + │ + └── • error if rows + │ + └── • hash join (anti) + │ equality: (b_new, c_new) = (b, c) + │ + ├── • scan buffer + │ label: buffer 1 + │ + └── • scan + missing stats + table: uniq_fk_parent@uniq_fk_parent_b_c_key + spans: FULL SCAN diff --git a/pkg/sql/opt/exec/execbuilder/testdata/update b/pkg/sql/opt/exec/execbuilder/testdata/update index 4a819543a803..09c650eb7e4a 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/update +++ b/pkg/sql/opt/exec/execbuilder/testdata/update @@ -50,7 +50,7 @@ output row: ['c' 'd'] output row: ['e' 'f'] output row: ['f' 'g'] -statement error duplicate key value \(v\)=\('g'\) violates unique constraint "a" +statement error duplicate key value violates unique constraint "a"\nDETAIL: Key \(v\)=\('g'\) already exists\. UPDATE kv2 SET v = 'g' WHERE k IN ('a') statement ok @@ -212,7 +212,7 @@ CREATE TABLE pks ( statement count 2 INSERT INTO pks VALUES (1, 2, 3), (4, 5, 3) -statement error duplicate key value \(k2,v\)=\(5,3\) violates unique constraint "i" +statement error duplicate key value violates unique constraint "i"\nDETAIL: Key \(k2,v\)=\(5,3\) already exists\. UPDATE pks SET k2 = 5 where k1 = 1 # Test updating only one of the columns of a multi-column primary key. diff --git a/pkg/sql/opt/exec/execbuilder/testdata/upsert b/pkg/sql/opt/exec/execbuilder/testdata/upsert index b59036943c0c..616b7e908f75 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/upsert +++ b/pkg/sql/opt/exec/execbuilder/testdata/upsert @@ -372,7 +372,7 @@ flow Put /Table/57/1/2/0 -> /TUPLE/2:2:Int/2 flow Del /Table/57/2/3/0 flow CPut /Table/57/2/2/0 -> /BYTES/0x8a (expecting does not exist) kv.DistSender: sending partial batch r35: sending batch 1 Put, 1 EndTxn to (n1,s1):1 -exec stmt execution failed after 0 rows: duplicate key value (v)=(2) violates unique constraint "woo" +exec stmt execution failed after 0 rows: duplicate key value violates unique constraint "woo"\nDETAIL: Key (v)=(2) already exists\. subtest regression_32473 diff --git a/pkg/sql/opt/exec/explain/emit.go b/pkg/sql/opt/exec/explain/emit.go index 11c02eee7cae..3f0e835e7f41 100644 --- a/pkg/sql/opt/exec/explain/emit.go +++ b/pkg/sql/opt/exec/explain/emit.go @@ -106,7 +106,7 @@ func Emit(plan *Plan, ob *OutputBuilder, spanFormatFn SpanFormatFn) error { ob.LeaveNode() } for _, n := range plan.Checks { - ob.EnterMetaNode("fk-check") + ob.EnterMetaNode("constraint-check") if err := walk(n); err != nil { return err } diff --git a/pkg/sql/opt/optbuilder/mutation_builder_unique.go b/pkg/sql/opt/optbuilder/mutation_builder_unique.go index af3fbb0536c6..65f97269c4be 100644 --- a/pkg/sql/opt/optbuilder/mutation_builder_unique.go +++ b/pkg/sql/opt/optbuilder/mutation_builder_unique.go @@ -212,8 +212,11 @@ func (h *uniqueCheckHelper) buildInsertionCheck() memo.UniqueChecksItem { return f.ConstructUniqueChecksItem(semiJoin, &memo.UniqueChecksItemPrivate{ Table: h.mb.tabID, CheckOrdinal: h.uniqueOrdinal, - KeyCols: withScanCols, - OpName: h.mb.opName, + // uniqueOrdinals is always a prefix of uniqueAndPrimaryKeyOrdinals, which + // maps 1-to-1 to the columns in withScanCols. The remaining columns are + // primary key columns and should not be included in the KeyCols. + KeyCols: withScanCols[:len(h.uniqueOrdinals)], + OpName: h.mb.opName, }) } diff --git a/pkg/sql/opt_catalog.go b/pkg/sql/opt_catalog.go index 351efaa72d66..0e24fdab3c5c 100644 --- a/pkg/sql/opt_catalog.go +++ b/pkg/sql/opt_catalog.go @@ -1068,14 +1068,12 @@ func (ot *optTable) InboundForeignKey(i int) cat.ForeignKeyConstraint { // UniqueCount is part of the cat.Table interface. func (ot *optTable) UniqueCount() int { - // TODO(rytaft): return the number of unique constraints (both with and - // without indexes). - return 0 + return len(ot.uniqueConstraints) } // Unique is part of the cat.Table interface. func (ot *optTable) Unique(i int) cat.UniqueConstraint { - panic(errors.AssertionFailedf("unique constraint [%d] does not exist", i)) + return &ot.uniqueConstraints[i] } // lookupColumnOrdinal returns the ordinal of the column with the given ID. A diff --git a/pkg/sql/row/errors.go b/pkg/sql/row/errors.go index cae0f3e8a8d9..4303f1caebc3 100644 --- a/pkg/sql/row/errors.go +++ b/pkg/sql/row/errors.go @@ -12,6 +12,7 @@ package row import ( "context" + "fmt" "strings" "github.com/cockroachdb/cockroach/pkg/keys" @@ -95,12 +96,14 @@ func NewUniquenessConstraintViolationError( "duplicate key value: decoding err=%s", err) } - return pgerror.WithConstraintName(pgerror.Newf(pgcode.UniqueViolation, - "duplicate key value (%s)=(%s) violates unique constraint %q", - strings.Join(names, ","), - strings.Join(values, ","), - index.Name, - ), index.Name) + return errors.WithDetail( + pgerror.WithConstraintName(pgerror.Newf(pgcode.UniqueViolation, + "duplicate key value violates unique constraint %q", index.Name, + ), index.Name), + fmt.Sprintf( + "Key (%s)=(%s) already exists.", strings.Join(names, ","), strings.Join(values, ","), + ), + ) } // NewLockNotAvailableError creates an error that represents an inability to diff --git a/pkg/sql/testdata/savepoints b/pkg/sql/testdata/savepoints index 6d08298bf4e1..7c1f4a16c9f2 100644 --- a/pkg/sql/testdata/savepoints +++ b/pkg/sql/testdata/savepoints @@ -172,7 +172,7 @@ COMMIT -- NoTxn -> Open ###..... (none) 4: SAVEPOINT foo -- 0 rows -- Open -> Open ####.... foo -5: INSERT INTO t(x) VALUES (1) -- pq: duplicate key value (x)=(1) violates unique constraint "t_x_key" +5: INSERT INTO t(x) VALUES (1) -- pq: duplicate key value violates unique constraint "t_x_key" -- Open -> Aborted XXXXXXXX foo 6: ROLLBACK TO SAVEPOINT foo -- 0 rows -- Aborted -> Open ####.... foo