Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
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
craig[bot] and michae2 committed Sep 16, 2023
2 parents 36a773f + 0aa36db commit 6cbd07e
Show file tree
Hide file tree
Showing 32 changed files with 754 additions and 47 deletions.
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'
7 changes: 7 additions & 0 deletions pkg/ccl/logictestccl/tests/3node-tenant/generated_test.go

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
Expand Up @@ -15,7 +15,7 @@ go_test(
exec_properties = {
"Pool": "large",
},
shard_count = 19,
shard_count = 20,
tags = [
"ccl_test",
"cpu:4",
Expand Down

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
Expand Up @@ -15,7 +15,7 @@ go_test(
exec_properties = {
"Pool": "large",
},
shard_count = 15,
shard_count = 16,
tags = [
"ccl_test",
"cpu:4",
Expand Down

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
Expand Up @@ -15,7 +15,7 @@ go_test(
exec_properties = {
"Pool": "large",
},
shard_count = 8,
shard_count = 9,
tags = [
"ccl_test",
"cpu:4",
Expand Down

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
Expand Up @@ -15,7 +15,7 @@ go_test(
exec_properties = {
"Pool": "large",
},
shard_count = 26,
shard_count = 27,
tags = [
"ccl_test",
"cpu:4",
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

108 changes: 108 additions & 0 deletions pkg/sql/logictest/testdata/logic_test/unique_read_committed
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'])
7 changes: 7 additions & 0 deletions pkg/sql/logictest/tests/fakedist-disk/generated_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions pkg/sql/logictest/tests/fakedist-vec-off/generated_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions pkg/sql/logictest/tests/fakedist/generated_test.go

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.

Loading

0 comments on commit 6cbd07e

Please sign in to comment.