-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
107961: sql: add implicit SELECT FOR SHARE locking to unique checks r=nvanbenschoten,mgartner a=michae2 Unique constraints not directly enforced by a unique index are maintained using unique checks. These unique checks verify the non-existence of conflicting rows during mutation statements. We use unique checks for `UNIQUE WITHOUT INDEX` constraints and `UNIQUE` constraints on `PARTITION ALL BY` and `REGIONAL BY ROW` tables. For example, the following insert must use unique checks to verify that there is no conflicting username in any region. ``` CREATE TABLE users ( username STRING NOT NULL UNIQUE, ... ) LOCALITY REGIONAL BY ROW; INSERT INTO users VALUES (...); ``` Under snapshot and read committed isolation, we must lock during all system-maintained constraint checks to prevent concurrent transactions from violating the constraints. This commit adds locking to unique checks, both when those checks verify the constraint and when those checks are used as arbiters in `UPSERT` and `INSERT ON CONFLICT` statements. The locking is: - guaranteed-durable, because it is required for correctness - for-share locking, because we only need to prevent modification of the checked rows, not prevent reading - predicate locking, because we must also prevent insertion of new rows matching the scanned span This last requirement leads us to create a new locking property, locking form, which distinguishes predicate locking from record locking. This will eventually be passed down to the KV layer but is currently only an optimizer property. Fixes: cockroachdb#100156 Epic: CRDB-25322 Release note: None Co-authored-by: Michael Erickson <[email protected]>
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
# tenant-cluster-setting-override-opt: sql.zone_configs.allow_for_secondary_tenant.enabled=true sql.multi_region.allow_abstractions_for_secondary_tenants.enabled=true | ||
# LogicTest: multiregion-9node-3region-3azs multiregion-9node-3region-3azs-vec-off multiregion-9node-3region-3azs-tenant multiregion-9node-3region-3azs-no-los | ||
|
||
statement ok | ||
SET CLUSTER SETTING sql.txn.read_committed_syntax.enabled = true | ||
|
||
statement ok | ||
SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ COMMITTED | ||
|
||
statement ok | ||
CREATE DATABASE multi_region_test_db PRIMARY REGION "ca-central-1" REGIONS "ap-southeast-2", "us-east-1" SURVIVE REGION FAILURE | ||
|
||
statement ok | ||
USE multi_region_test_db | ||
|
||
# Create a table with a computed region column. | ||
|
||
statement ok | ||
CREATE TABLE university ( | ||
name STRING NOT NULL, | ||
mascot STRING NOT NULL, | ||
postal_code STRING NOT NULL, | ||
region crdb_internal_region NOT NULL AS ( | ||
CASE | ||
WHEN left(postal_code, 2) = '97' THEN 'ca-central-1' -- Oregon | ||
WHEN left(postal_code, 2) = '98' THEN 'ap-southeast-2' -- Washington | ||
ELSE 'us-east-1' -- British Columbia | ||
END | ||
) STORED, | ||
PRIMARY KEY (name), | ||
UNIQUE INDEX (mascot), | ||
FAMILY (name, mascot, postal_code, region) | ||
) | ||
LOCALITY REGIONAL BY ROW AS region | ||
|
||
# Create a table with a non-computed region column. | ||
|
||
statement ok | ||
CREATE TABLE volcano ( | ||
name STRING NOT NULL, | ||
origin STRING NOT NULL, | ||
location GEOGRAPHY NOT NULL, | ||
region crdb_internal_region NOT NULL, | ||
PRIMARY KEY (name), | ||
UNIQUE INDEX (origin), | ||
INVERTED INDEX (location), | ||
FAMILY (name, origin, location, region) | ||
) | ||
LOCALITY REGIONAL BY ROW AS region | ||
|
||
# TODO(michae2): statement ok | ||
statement error pgcode 0A000 guaranteed-durable locking not yet implemented | ||
INSERT INTO university (name, mascot, postal_code) VALUES ('Western Oregon', 'wolves', '97361') | ||
|
||
# TODO(michae2): statement ok | ||
statement error pgcode 0A000 guaranteed-durable locking not yet implemented | ||
INSERT INTO volcano | ||
VALUES ('Mount Hood', 'Fought over Loowit and was transformed by Saghalie.', 'POINT(-121.695833 45.373611)', 'ca-central-1') | ||
|
||
# TODO(michae2): statement error pgcode 23505 pq: duplicate key value violates unique constraint "university_mascot_key"\nDETAIL: Key \(mascot\)=\('wolves'\) already exists. | ||
statement error pgcode 0A000 guaranteed-durable locking not yet implemented | ||
INSERT INTO university (name, mascot, postal_code) VALUES ('Thompson Rivers', 'wolves', 'V2C 0C8') | ||
|
||
# TODO(michae2): statement error pgcode 23505 pq: duplicate key value violates unique constraint "volcano_origin_key"\nDETAIL: Key \(origin\)=\('Fought over Loowit and was transformed by Saghalie.'\) already exists. | ||
statement error pgcode 0A000 guaranteed-durable locking not yet implemented | ||
INSERT INTO volcano VALUES | ||
('Mount Adams', 'Fought over Loowit and was transformed by Saghalie.', 'POINT(-121.490895 46.202412)', 'ap-southeast-2') | ||
|
||
# TODO(michae2): statement ok | ||
statement error pgcode 0A000 guaranteed-durable locking not yet implemented | ||
INSERT INTO university (name, mascot, postal_code) | ||
VALUES ('Thompson Rivers', 'wolves', 'V2C 0C8'), ('Evergreen State', 'geoducks', '98505') | ||
ON CONFLICT (mascot) DO NOTHING | ||
|
||
# TODO(michae2): statement ok | ||
statement error pgcode 0A000 guaranteed-durable locking not yet implemented | ||
INSERT INTO volcano VALUES | ||
('Mount Adams', 'Fought over Loowit and was transformed by Saghalie.', 'POINT(-121.490895 46.202412)', 'ap-southeast-2'), | ||
('Mount St. Helens', 'Fair maiden Loowit could not choose between Wyeast and Pahto and was transformed by Saghalie.', 'POINT(-122.1944 46.1912)', 'ap-southeast-2') | ||
ON CONFLICT (origin) DO NOTHING | ||
|
||
query TTT | ||
SELECT name, mascot, postal_code FROM university ORDER BY name | ||
---- | ||
|
||
query TTT | ||
SELECT name, origin, location FROM volcano ORDER BY name | ||
---- | ||
|
||
# TODO(michae2): statement error pgcode 23505 pq: duplicate key value violates unique constraint "university_mascot_key"\nDETAIL: Key \(mascot\)=\('wolves'\) already exists. | ||
statement error pgcode 0A000 guaranteed-durable locking not yet implemented | ||
UPSERT INTO university (name, mascot, postal_code) VALUES ('Thompson Rivers', 'wolves', 'V2C 0C8') | ||
|
||
# TODO(michae2): statement error pgcode 23505 pq: duplicate key value violates unique constraint "volcano_origin_key"\nDETAIL: Key \(origin\)=\('Fought over Loowit and was transformed by Saghalie.'\) already exists. | ||
statement error pgcode 0A000 guaranteed-durable locking not yet implemented | ||
UPSERT INTO volcano VALUES | ||
('Mount Adams', 'Fought over Loowit and was transformed by Saghalie.', 'POINT(-121.490895 46.202412)', 'ap-southeast-2') | ||
|
||
# TODO(michae2): statement error pgcode 23505 pq: duplicate key value violates unique constraint "university_mascot_key"\nDETAIL: Key \(mascot\)=\('wolves'\) already exists. | ||
statement error pgcode 0A000 guaranteed-durable locking not yet implemented | ||
UPDATE university SET mascot = 'wolves' WHERE name = 'Evergreen State' | ||
|
||
# TODO(michae2): statement error pgcode 23505 pq: duplicate key value violates unique constraint "volcano_origin_key"\nDETAIL: Key \(origin\)=\('Fought over Loowit and was transformed by Saghalie.'\) already exists. | ||
statement error pgcode 0A000 guaranteed-durable locking not yet implemented | ||
UPDATE volcano SET origin = 'Fought over Loowit and was transformed by Saghalie.' WHERE name = 'Mount St. Helens' | ||
|
||
# TODO(michae2): statement error pgcode 23505 pq: duplicate key value violates unique constraint "university_pkey"\nDETAIL: Key \(name\)=\('Evergreen State'\) already exists. | ||
statement error pgcode 0A000 guaranteed-durable locking not yet implemented | ||
INSERT INTO university (name, mascot, postal_code) | ||
VALUES ('Thompson Rivers', 'wolves', 'V2C 0C8'), ('Oregon Tech', 'owls', '97601') | ||
ON CONFLICT (mascot) DO UPDATE SET name = 'Evergreen State', mascot = 'banana slugs' | ||
|
||
# TODO(michae2): statement error pgcode 23505 pq: duplicate key value violates unique constraint "volcano_pkey"\nDETAIL: Key \(name\)=\('Mount St. Helens'\) already exists. | ||
statement error pgcode 0A000 guaranteed-durable locking not yet implemented | ||
INSERT INTO volcano VALUES | ||
('Mount Adams', 'Fought over Loowit and was transformed by Saghalie.', 'POINT(-121.490895 46.202412)', 'ap-southeast-2'), | ||
('Mount Garibaldi', 'Lightning from thunderbird eyes struck the ground.', 'POINT(-123.004722 49.850278)', 'us-east-1') | ||
ON CONFLICT (origin) DO UPDATE SET name = 'Mount St. Helens', origin = 'Discovered by the Vancouver expedition in 1792.', region = 'us-east-1' |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
# LogicTest: !local-mixed-22.2-23.1 | ||
|
||
statement ok | ||
SET CLUSTER SETTING sql.txn.read_committed_syntax.enabled = true | ||
|
||
statement ok | ||
SET experimental_enable_unique_without_index_constraints = true | ||
|
||
# Test UNIQUE WITHOUT INDEX with an enum PK. Under read committed isolation this | ||
# should work, using single-key predicate locks. | ||
|
||
statement ok | ||
CREATE TYPE region AS ENUM ('adriatic', 'aegean', 'black', 'caspian', 'mediterranean', 'persian', 'red') | ||
|
||
statement ok | ||
CREATE TABLE voyage ( | ||
sea region NOT NULL DEFAULT 'aegean', | ||
hero STRING NOT NULL, | ||
crew STRING NULL, | ||
quest STRING NOT NULL, | ||
PRIMARY KEY (sea, hero), | ||
UNIQUE INDEX (sea, quest, crew), | ||
UNIQUE WITHOUT INDEX (hero), | ||
UNIQUE WITHOUT INDEX (quest, crew), | ||
FAMILY (sea, hero, crew, quest) | ||
) | ||
|
||
statement ok | ||
SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ COMMITTED | ||
|
||
# TODO(michae2): statement ok | ||
statement error pgcode 0A000 guaranteed-durable locking not yet implemented | ||
INSERT INTO voyage VALUES ('caspian', 'hercules', 'argonauts', 'golden fleece') | ||
|
||
# The Argonauts searching for the golden fleece should fail the (quest, crew) | ||
# uniqueness check, even with a different sea. | ||
# TODO(michae2): statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_quest_crew"\nDETAIL: Key \(crew, quest\)=\('argonauts', 'golden fleece'\) already exists. | ||
statement error pgcode 0A000 guaranteed-durable locking not yet implemented | ||
INSERT INTO voyage | ||
VALUES (DEFAULT, 'odysseus', 'nobody', 'penelope'), ('black', 'jason', 'argonauts', 'golden fleece') | ||
|
||
# Only Odysseus should be inserted. | ||
# TODO(michae2): statement ok | ||
statement error pgcode 0A000 guaranteed-durable locking not yet implemented | ||
INSERT INTO voyage | ||
VALUES ('mediterranean', 'odysseus', 'nobody', 'penelope'), ('black', 'jason', 'argonauts', 'golden fleece') | ||
ON CONFLICT (quest, crew) DO NOTHING | ||
|
||
query TTTT | ||
SELECT * FROM voyage ORDER BY hero, crew, quest | ||
---- | ||
|
||
# Hercules should fail the (hero) uniqueness check, even with a different sea. | ||
# TODO(michae2): statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_hero"\nDETAIL: Key \(hero\)=\('hercules'\) already exists. | ||
statement error pgcode 0A000 guaranteed-durable locking not yet implemented | ||
INSERT INTO voyage (hero, quest) VALUES ('perseus', 'medusa'), ('hercules', 'geryon') | ||
|
||
# Only Perseus should be inserted. | ||
# TODO(michae2): statement ok | ||
statement error pgcode 0A000 guaranteed-durable locking not yet implemented | ||
INSERT INTO voyage (hero, quest) VALUES ('perseus', 'medusa'), ('hercules', 'geryon') | ||
ON CONFLICT (hero) DO NOTHING | ||
|
||
query TTTT | ||
SELECT * FROM voyage ORDER BY hero, crew, quest | ||
---- | ||
|
||
# TODO(michae2): statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_quest_crew"\nDETAIL: Key \(crew, quest\)=\('argonauts', 'golden fleece'\) already exists. | ||
statement error pgcode 0A000 guaranteed-durable locking not yet implemented | ||
UPSERT INTO voyage VALUES ('black', 'jason', 'argonauts', 'golden fleece') | ||
|
||
# TODO(michae2): statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_hero"\nDETAIL: Key \(hero\)=\('hercules'\) already exists. | ||
statement error pgcode 0A000 guaranteed-durable locking not yet implemented | ||
UPSERT INTO voyage (hero, quest) VALUES ('hercules', 'geryon') | ||
|
||
# TODO(michae2): statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_quest_crew"\nDETAIL: Key \(crew, quest\)=\('argonauts', 'golden fleece'\) already exists. | ||
statement error pgcode 0A000 guaranteed-durable locking not yet implemented | ||
UPDATE voyage SET crew = 'argonauts', quest = 'golden fleece' WHERE hero = 'perseus' | ||
|
||
# TODO(michae2): statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_hero"\nDETAIL: Key \(hero\)=\('hercules'\) already exists. | ||
statement error pgcode 0A000 guaranteed-durable locking not yet implemented | ||
UPDATE voyage SET hero = 'hercules' WHERE hero = 'odysseus' | ||
|
||
# TODO(michae2): statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_quest_crew"\nDETAIL: Key \(crew, quest\)=\('nobody', 'penelope'\) already exists. | ||
statement error pgcode 0A000 guaranteed-durable locking not yet implemented | ||
INSERT INTO voyage VALUES ('black', 'jason', 'argonauts', 'golden fleece') | ||
ON CONFLICT (quest, crew) DO UPDATE SET quest = 'penelope', crew = 'nobody' | ||
|
||
# TODO(michae2): statement error pgcode 23505 pq: duplicate key value violates unique constraint "unique_hero"\nDETAIL: Key \(hero\)=\('perseus'\) already exists. | ||
statement error pgcode 0A000 guaranteed-durable locking not yet implemented | ||
INSERT INTO voyage (hero, quest) VALUES ('hercules', 'geryon') | ||
ON CONFLICT (hero) DO UPDATE SET hero = 'perseus' | ||
|
||
# Test UNIQUE WITHOUT INDEX with a non-enum PK. Under read committed isolation | ||
# this will not work until predicate locks are supported on multi-key scans. | ||
|
||
statement ok | ||
CREATE TABLE titan ( | ||
name STRING NOT NULL, | ||
domain STRING NOT NULL, | ||
children STRING[], | ||
PRIMARY KEY (name), | ||
UNIQUE WITHOUT INDEX (domain), | ||
FAMILY (name, domain, children) | ||
) | ||
|
||
statement error pgcode 0A000 guaranteed-durable locking not yet implemented | ||
INSERT INTO titan VALUES ('cronus', 'time', ARRAY['zeus', 'hera', 'hades', 'poseidon', 'demeter', 'hestia']) |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.