From 73c9d6156bfa454af3a0deb3c612624b6ae8f58a Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Thu, 18 Jul 2024 21:05:46 +0200 Subject: [PATCH] sql: clarify UPDATE test cases with intermediate constraint violations --- src/sql/engine/local.rs | 4 +-- src/sql/testscripts/writes/delete_reference | 6 ++-- src/sql/testscripts/writes/update_expression | 20 +++++-------- src/sql/testscripts/writes/update_primary_key | 29 ++++--------------- src/sql/testscripts/writes/update_reference | 24 +++++++++++++-- src/sql/testscripts/writes/update_unique | 12 ++++++++ 6 files changed, 52 insertions(+), 43 deletions(-) diff --git a/src/sql/engine/local.rs b/src/sql/engine/local.rs index 561bcce09..6c500b295 100644 --- a/src/sql/engine/local.rs +++ b/src/sql/engine/local.rs @@ -190,9 +190,7 @@ impl super::Transaction for Transaction { } if let Some(source_id) = source_ids.into_iter().next() { return errinput!( - "row referenced by {}.{} for {}.{}={source_id}", - source.name, - column.name, + "primary key referenced by {}.{}={source_id}", source.name, source.columns[source.primary_key].name ); diff --git a/src/sql/testscripts/writes/delete_reference b/src/sql/testscripts/writes/delete_reference index 9cf85849c..7d54404c5 100644 --- a/src/sql/testscripts/writes/delete_reference +++ b/src/sql/testscripts/writes/delete_reference @@ -24,8 +24,8 @@ ok !> DELETE FROM ref !> DELETE FROM ref WHERE id = 1 --- -Error: invalid input: row referenced by name.ref_id for name.id=1 -Error: invalid input: row referenced by name.ref_id for name.id=1 +Error: invalid input: primary key referenced by name.id=1 +Error: invalid input: primary key referenced by name.id=1 > SELECT * FROM ref --- @@ -97,7 +97,7 @@ ok # Deleting a referenced row errors. !> DELETE FROM self WHERE id = 2 --- -Error: invalid input: row referenced by self.self_id for self.id=3 +Error: invalid input: primary key referenced by self.id=3 # Deleting an unreferenced row works. > DELETE FROM self WHERE id = 4 diff --git a/src/sql/testscripts/writes/update_expression b/src/sql/testscripts/writes/update_expression index 46db23b4f..7ce99e582 100644 --- a/src/sql/testscripts/writes/update_expression +++ b/src/sql/testscripts/writes/update_expression @@ -30,23 +30,19 @@ ok 2, 3, 12 # This is also true with primary key updates. -# TODO: fix this -!> UPDATE test SET id = id + 1, value = id, quantity = value +> UPDATE test SET id = id - 1, value = id, quantity = value > SELECT * FROM test --- -Error: invalid input: primary key 1 already exists -0, 1, 10 -1, 2, 11 -2, 3, 12 +-1, 0, 1 +0, 1, 2 +1, 2, 3 # UPDATE expressions respect constraints. -> UPDATE test SET value = NULL WHERE id = 1 +> UPDATE test SET value = NULL WHERE id = 0 > SELECT * FROM test ---- -0, 1, 10 -1, NULL, 11 -2, 3, 12 - !> UPDATE test SET quantity = value --- +-1, 0, 1 +0, NULL, 2 +1, 2, 3 Error: invalid input: NULL value not allowed for column quantity diff --git a/src/sql/testscripts/writes/update_primary_key b/src/sql/testscripts/writes/update_primary_key index cc12f3fc9..98f78df05 100644 --- a/src/sql/testscripts/writes/update_primary_key +++ b/src/sql/testscripts/writes/update_primary_key @@ -136,9 +136,9 @@ Error: invalid input: primary key ABC already exists Error: invalid input: primary key Hi! 👋 already exists Error: invalid input: invalid primary key NULL -# UPDATE can modify multiple primary keys whose intermediate keys conflict, -# as long as the final set doesn't conflict. -# TODO: fix this +# Primary key updates error if intermediate row updates violate primary key +# uniqueness, even if the final state wouldn't violate the constraints. This is +# also true with Postgres. > SELECT * FROM "int" --- 0 @@ -148,26 +148,9 @@ Error: invalid input: invalid primary key NULL --- Error: invalid input: primary key 1 already exists -# UPDATE can modify multiple primary keys that have foreign key references -# as long as the final set of primary keys still satisfy the references. -# We test this by swapping primary keys 0 <-> 1 in a single UPDATE. -# TODO: this shouldn't error. -> CREATE TABLE ref (id INT PRIMARY KEY, int_id INTEGER REFERENCES "int") -> INSERT INTO ref VALUES (0, 0), (1, 1) ---- -ok - -!> UPDATE "int" SET id = 1 - id +# The updates happen in primary key order, so the reverse update does work. +> UPDATE "int" SET id = id - 1 > SELECT * FROM "int" --- -Error: invalid input: row referenced by ref.int_id for ref.id=0 -0 -1 - -# If the UPDATE leaves a foreign key dangling, it should error. -!> UPDATE "int" SET id = id - 1 -> SELECT * FROM "int" ---- -Error: invalid input: row referenced by ref.int_id for ref.id=0 +-1 0 -1 diff --git a/src/sql/testscripts/writes/update_reference b/src/sql/testscripts/writes/update_reference index 7388c2aed..71f979a5a 100644 --- a/src/sql/testscripts/writes/update_reference +++ b/src/sql/testscripts/writes/update_reference @@ -158,11 +158,31 @@ storage delete mvcc:TxnActive(26) ["\x01\x00\x00\x00\x00\x00\x00\x00\x1a"] !> UPDATE self SET id = 4 WHERE id = 1 !> UPDATE self SET id = 4 WHERE id = 2 --- -Error: invalid input: row referenced by self.self_id for self.id=2 -Error: invalid input: row referenced by self.self_id for self.id=3 +Error: invalid input: primary key referenced by self.id=2 +Error: invalid input: primary key referenced by self.id=3 # Not even when only this row points to itself. > UPDATE self SET self_id = NULL WHERE id > 1 !> UPDATE self SET id = 4 WHERE id = 1 --- Error: invalid input: reference 1 not in table self + +# Updates can't violate foreign key references in intermediate states even if +# the final state retains foreign key integrity. Postgres can't either. +> SELECT * FROM "int" +--- +-1 +0 +1 + +> INSERT INTO name (id, "int") VALUES (2, -1), (3, 0), (4, 1) +> SELECT * FROM name +--- +1, NULL, NULL, NULL, NULL +2, NULL, -1, NULL, NULL +3, NULL, 0, NULL, NULL +4, NULL, 1, NULL, NULL + +!> UPDATE "int" SET id = -id +--- +Error: invalid input: primary key referenced by name.id=2 diff --git a/src/sql/testscripts/writes/update_unique b/src/sql/testscripts/writes/update_unique index fb40d1da8..80131cdec 100644 --- a/src/sql/testscripts/writes/update_unique +++ b/src/sql/testscripts/writes/update_unique @@ -260,3 +260,15 @@ storage delete mvcc:TxnActive(23) ["\x01\x00\x00\x00\x00\x00\x00\x00\x17"] --- 1, NULL, NULL, inf, case 2, NULL, NULL, -inf, CaSe + +# An UPDATE errors if intermediate states violate the uniqueness constraints, +# even if the final state wouldn't. This is the same in Postgres. +> UPDATE "unique" SET "bool" = id = 2 +> SELECT * FROM "unique" +--- +1, FALSE, NULL, inf, case +2, TRUE, NULL, -inf, CaSe + +!> UPDATE "unique" SET "bool" = NOT "bool" +--- +Error: invalid input: value TRUE already in unique column bool