diff --git a/pkg/sql/catalog/descpb/locking.proto b/pkg/sql/catalog/descpb/locking.proto index d89f731d7f62..1522b18f6e95 100644 --- a/pkg/sql/catalog/descpb/locking.proto +++ b/pkg/sql/catalog/descpb/locking.proto @@ -124,9 +124,6 @@ enum ScanLockingWaitPolicy { BLOCK = 0; // SKIP_LOCKED represents SKIP LOCKED - skip rows that can't be locked. - // - // NOTE: SKIP_LOCKED is not currently implemented and does not make it out of - // the SQL optimizer without throwing an error. SKIP_LOCKED = 1; // ERROR represents NOWAIT - raise an error if a row cannot be locked. diff --git a/pkg/sql/logictest/testdata/logic_test/select_for_update b/pkg/sql/logictest/testdata/logic_test/select_for_update index 90cca24d5cbe..7ee81b57dbec 100644 --- a/pkg/sql/logictest/testdata/logic_test/select_for_update +++ b/pkg/sql/logictest/testdata/logic_test/select_for_update @@ -53,31 +53,38 @@ SELECT 1 FOR UPDATE OF public.a query error pgcode 42601 FOR UPDATE must specify unqualified relation names SELECT 1 FOR UPDATE OF db.public.a -# We don't currently support SKIP LOCKED, since it returns an inconsistent view -# and generally has strange semantics with respect to serializable isolation. -# Support may be added in the future. - -query error unimplemented: SKIP LOCKED lock wait policy is not supported +query I SELECT 1 FOR UPDATE SKIP LOCKED +---- +1 -query error unimplemented: SKIP LOCKED lock wait policy is not supported +query I SELECT 1 FOR NO KEY UPDATE SKIP LOCKED +---- +1 -query error unimplemented: SKIP LOCKED lock wait policy is not supported +query I SELECT 1 FOR SHARE SKIP LOCKED +---- +1 -query error unimplemented: SKIP LOCKED lock wait policy is not supported +query I SELECT 1 FOR KEY SHARE SKIP LOCKED +---- +1 -query error unimplemented: SKIP LOCKED lock wait policy is not supported +query error pgcode 42P01 relation "a" in FOR UPDATE clause not found in FROM clause SELECT 1 FOR UPDATE OF a SKIP LOCKED -query error unimplemented: SKIP LOCKED lock wait policy is not supported +query error pgcode 42P01 relation "a" in FOR UPDATE clause not found in FROM clause SELECT 1 FOR UPDATE OF a SKIP LOCKED FOR NO KEY UPDATE OF b SKIP LOCKED -query error unimplemented: SKIP LOCKED lock wait policy is not supported +query error pgcode 42P01 relation "a" in FOR UPDATE clause not found in FROM clause SELECT 1 FOR UPDATE OF a SKIP LOCKED FOR NO KEY UPDATE OF b NOWAIT +query error pgcode 42P01 relation "a" in FOR UPDATE clause not found in FROM clause +SELECT 1 FOR UPDATE OF a SKIP LOCKED FOR SHARE OF b, c SKIP LOCKED FOR NO KEY UPDATE OF d SKIP LOCKED FOR KEY SHARE OF e, f SKIP LOCKED + query I SELECT 1 FOR UPDATE NOWAIT ---- @@ -105,18 +112,24 @@ query error pgcode 42P01 relation "a" in FOR UPDATE clause not found in FROM cla SELECT 1 FOR UPDATE OF a NOWAIT FOR NO KEY UPDATE OF b NOWAIT query error pgcode 42P01 relation "a" in FOR UPDATE clause not found in FROM clause -SELECT 1 FOR UPDATE OF a NOWAIT FOR SHARE OF b, c NOWAIT FOR NO KEY UPDATE OF d NOWAIT FOR KEY SHARE OF e, f NOWAIT +SELECT 1 FOR UPDATE OF a NOWAIT FOR SHARE OF b, c NOWAIT FOR NO KEY UPDATE OF d NOWAIT FOR KEY SHARE OF e, f NOWAIT # Locking clauses both inside and outside of parenthesis are handled correctly. -query error unimplemented: SKIP LOCKED lock wait policy is not supported +query I ((SELECT 1)) FOR UPDATE SKIP LOCKED +---- +1 -query error unimplemented: SKIP LOCKED lock wait policy is not supported +query I ((SELECT 1) FOR UPDATE SKIP LOCKED) +---- +1 -query error unimplemented: SKIP LOCKED lock wait policy is not supported +query I ((SELECT 1 FOR UPDATE SKIP LOCKED)) +---- +1 # FOR READ ONLY is ignored, like in Postgres. query I @@ -295,7 +308,7 @@ SELECT * FROM t FOR KEY SHARE statement ok ROLLBACK -# The NOWAIT wait policy returns error when conflicting lock is encountered. +# The NOWAIT wait policy returns error when a conflicting lock is encountered. statement ok INSERT INTO t VALUES (1, 1) @@ -372,3 +385,50 @@ user root statement ok ROLLBACK + +# The SKIP LOCKED wait policy skip rows when a conflicting lock is encountered. + +statement ok +INSERT INTO t VALUES (2, 2), (3, 3), (4, 4) + +statement ok +BEGIN; UPDATE t SET v = 3 WHERE k = 2 + +user testuser + +statement ok +BEGIN + +query II +SELECT * FROM t FOR UPDATE SKIP LOCKED +---- +1 1 +3 3 +4 4 + +statement ok +UPDATE t SET v = 4 WHERE k = 3 + +query II +SELECT * FROM t FOR UPDATE SKIP LOCKED +---- +1 1 +3 4 +4 4 + +user root + +query II +SELECT * FROM t FOR UPDATE SKIP LOCKED +---- +2 3 + +statement ok +ROLLBACK + +user testuser + +statement ok +ROLLBACK + +user root diff --git a/pkg/sql/opt/exec/execbuilder/testdata/select_for_update b/pkg/sql/opt/exec/execbuilder/testdata/select_for_update index 77f6cf64d115..5328a88202a8 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/select_for_update +++ b/pkg/sql/opt/exec/execbuilder/testdata/select_for_update @@ -2228,3 +2228,275 @@ vectorized: true spans: /1/0 locking strength: for update locking wait policy: nowait + +# ------------------------------------------------------------------------------ +# Tests with the SKIP LOCKED lock wait policy. +# ------------------------------------------------------------------------------ + +query T +EXPLAIN (VERBOSE) SELECT * FROM t FOR UPDATE SKIP LOCKED +---- +distribution: local +vectorized: true +· +• scan + columns: (a, b) + estimated row count: 1,000 (missing stats) + table: t@t_pkey + spans: FULL SCAN + locking strength: for update + locking wait policy: skip locked + +query T +EXPLAIN (VERBOSE) SELECT * FROM t FOR NO KEY UPDATE SKIP LOCKED +---- +distribution: local +vectorized: true +· +• scan + columns: (a, b) + estimated row count: 1,000 (missing stats) + table: t@t_pkey + spans: FULL SCAN + locking strength: for no key update + locking wait policy: skip locked + +query T +EXPLAIN (VERBOSE) SELECT * FROM t FOR SHARE SKIP LOCKED +---- +distribution: local +vectorized: true +· +• scan + columns: (a, b) + estimated row count: 1,000 (missing stats) + table: t@t_pkey + spans: FULL SCAN + locking strength: for share + locking wait policy: skip locked + +query T +EXPLAIN (VERBOSE) SELECT * FROM t FOR KEY SHARE SKIP LOCKED +---- +distribution: local +vectorized: true +· +• scan + columns: (a, b) + estimated row count: 1,000 (missing stats) + table: t@t_pkey + spans: FULL SCAN + locking strength: for key share + locking wait policy: skip locked + +query T +EXPLAIN (VERBOSE) SELECT * FROM t FOR KEY SHARE FOR SHARE SKIP LOCKED +---- +distribution: local +vectorized: true +· +• scan + columns: (a, b) + estimated row count: 1,000 (missing stats) + table: t@t_pkey + spans: FULL SCAN + locking strength: for share + locking wait policy: skip locked + +query T +EXPLAIN (VERBOSE) SELECT * FROM t FOR KEY SHARE FOR SHARE SKIP LOCKED FOR NO KEY UPDATE +---- +distribution: local +vectorized: true +· +• scan + columns: (a, b) + estimated row count: 1,000 (missing stats) + table: t@t_pkey + spans: FULL SCAN + locking strength: for no key update + locking wait policy: skip locked + +query T +EXPLAIN (VERBOSE) SELECT * FROM t FOR KEY SHARE FOR SHARE SKIP LOCKED FOR NO KEY UPDATE FOR UPDATE SKIP LOCKED +---- +distribution: local +vectorized: true +· +• scan + columns: (a, b) + estimated row count: 1,000 (missing stats) + table: t@t_pkey + spans: FULL SCAN + locking strength: for update + locking wait policy: skip locked + +query T +EXPLAIN (VERBOSE) SELECT * FROM t FOR UPDATE OF t SKIP LOCKED +---- +distribution: local +vectorized: true +· +• scan + columns: (a, b) + estimated row count: 1,000 (missing stats) + table: t@t_pkey + spans: FULL SCAN + locking strength: for update + locking wait policy: skip locked + +query error pgcode 42P01 relation "t2" in FOR UPDATE clause not found in FROM clause +EXPLAIN (VERBOSE) SELECT * FROM t FOR UPDATE OF t2 SKIP LOCKED + +query T +EXPLAIN (VERBOSE) SELECT 1 FROM t FOR UPDATE OF t SKIP LOCKED +---- +distribution: local +vectorized: true +· +• render +│ columns: ("?column?") +│ estimated row count: 1,000 (missing stats) +│ render ?column?: 1 +│ +└── • scan + columns: () + estimated row count: 1,000 (missing stats) + table: t@t_pkey + spans: FULL SCAN + locking strength: for update + locking wait policy: skip locked + +query T +EXPLAIN (VERBOSE) SELECT * FROM t WHERE a = 1 FOR UPDATE SKIP LOCKED +---- +distribution: local +vectorized: true +· +• scan + columns: (a, b) + estimated row count: 1 (missing stats) + table: t@t_pkey + spans: /1/0 + locking strength: for update + locking wait policy: skip locked + +query T +EXPLAIN (VERBOSE) SELECT * FROM t WHERE a = 1 FOR NO KEY UPDATE SKIP LOCKED +---- +distribution: local +vectorized: true +· +• scan + columns: (a, b) + estimated row count: 1 (missing stats) + table: t@t_pkey + spans: /1/0 + locking strength: for no key update + locking wait policy: skip locked + +query T +EXPLAIN (VERBOSE) SELECT * FROM t WHERE a = 1 FOR SHARE SKIP LOCKED +---- +distribution: local +vectorized: true +· +• scan + columns: (a, b) + estimated row count: 1 (missing stats) + table: t@t_pkey + spans: /1/0 + locking strength: for share + locking wait policy: skip locked + +query T +EXPLAIN (VERBOSE) SELECT * FROM t WHERE a = 1 FOR KEY SHARE SKIP LOCKED +---- +distribution: local +vectorized: true +· +• scan + columns: (a, b) + estimated row count: 1 (missing stats) + table: t@t_pkey + spans: /1/0 + locking strength: for key share + locking wait policy: skip locked + +query T +EXPLAIN (VERBOSE) SELECT * FROM t WHERE a = 1 FOR KEY SHARE FOR SHARE SKIP LOCKED +---- +distribution: local +vectorized: true +· +• scan + columns: (a, b) + estimated row count: 1 (missing stats) + table: t@t_pkey + spans: /1/0 + locking strength: for share + locking wait policy: skip locked + +query T +EXPLAIN (VERBOSE) SELECT * FROM t WHERE a = 1 FOR KEY SHARE FOR SHARE FOR NO KEY UPDATE SKIP LOCKED +---- +distribution: local +vectorized: true +· +• scan + columns: (a, b) + estimated row count: 1 (missing stats) + table: t@t_pkey + spans: /1/0 + locking strength: for no key update + locking wait policy: skip locked + +query T +EXPLAIN (VERBOSE) SELECT * FROM t WHERE a = 1 FOR KEY SHARE FOR SHARE FOR NO KEY UPDATE FOR UPDATE SKIP LOCKED +---- +distribution: local +vectorized: true +· +• scan + columns: (a, b) + estimated row count: 1 (missing stats) + table: t@t_pkey + spans: /1/0 + locking strength: for update + locking wait policy: skip locked + +query T +EXPLAIN (VERBOSE) SELECT * FROM t WHERE a = 1 FOR UPDATE OF t SKIP LOCKED +---- +distribution: local +vectorized: true +· +• scan + columns: (a, b) + estimated row count: 1 (missing stats) + table: t@t_pkey + spans: /1/0 + locking strength: for update + locking wait policy: skip locked + +query error pgcode 42P01 relation "t2" in FOR UPDATE clause not found in FROM clause +EXPLAIN (VERBOSE) SELECT * FROM t WHERE a = 1 FOR UPDATE OF t2 SKIP LOCKED + +query T +EXPLAIN (VERBOSE) SELECT 1 FROM t WHERE a = 1 FOR UPDATE OF t SKIP LOCKED +---- +distribution: local +vectorized: true +· +• render +│ columns: ("?column?") +│ estimated row count: 1 (missing stats) +│ render ?column?: 1 +│ +└── • scan + columns: (a) + estimated row count: 1 (missing stats) + table: t@t_pkey + spans: /1/0 + locking strength: for update + locking wait policy: skip locked diff --git a/pkg/sql/opt/optbuilder/select.go b/pkg/sql/opt/optbuilder/select.go index 9ce7d79aa6a6..2ce9aa9fa7dc 100644 --- a/pkg/sql/opt/optbuilder/select.go +++ b/pkg/sql/opt/optbuilder/select.go @@ -1317,8 +1317,7 @@ func (b *Builder) validateLockingInFrom( case tree.LockWaitBlock: // Default. Block on conflicting locks. case tree.LockWaitSkipLocked: - panic(unimplementedWithIssueDetailf(40476, "", - "SKIP LOCKED lock wait policy is not supported")) + // Skip rows that can't be locked. case tree.LockWaitError: // Raise an error on conflicting locks. default: diff --git a/pkg/sql/opt/optbuilder/testdata/select_for_update b/pkg/sql/opt/optbuilder/testdata/select_for_update index b4aa58ee6b05..820b260cd25f 100644 --- a/pkg/sql/opt/optbuilder/testdata/select_for_update +++ b/pkg/sql/opt/optbuilder/testdata/select_for_update @@ -1305,3 +1305,95 @@ project │ └── locking: for-update,nowait └── projections └── 1 [as="?column?":5] + +# ------------------------------------------------------------------------------ +# Tests with the SKIP LOCKED lock wait policy. +# ------------------------------------------------------------------------------ + +build +SELECT * FROM t FOR UPDATE SKIP LOCKED +---- +project + ├── columns: a:1!null b:2 + └── scan t + ├── columns: a:1!null b:2 crdb_internal_mvcc_timestamp:3 tableoid:4 + └── locking: for-update,skip-locked + +build +SELECT * FROM t FOR NO KEY UPDATE SKIP LOCKED +---- +project + ├── columns: a:1!null b:2 + └── scan t + ├── columns: a:1!null b:2 crdb_internal_mvcc_timestamp:3 tableoid:4 + └── locking: for-no-key-update,skip-locked + +build +SELECT * FROM t FOR SHARE SKIP LOCKED +---- +project + ├── columns: a:1!null b:2 + └── scan t + ├── columns: a:1!null b:2 crdb_internal_mvcc_timestamp:3 tableoid:4 + └── locking: for-share,skip-locked + +build +SELECT * FROM t FOR KEY SHARE SKIP LOCKED +---- +project + ├── columns: a:1!null b:2 + └── scan t + ├── columns: a:1!null b:2 crdb_internal_mvcc_timestamp:3 tableoid:4 + └── locking: for-key-share,skip-locked + +build +SELECT * FROM t FOR KEY SHARE FOR SHARE SKIP LOCKED +---- +project + ├── columns: a:1!null b:2 + └── scan t + ├── columns: a:1!null b:2 crdb_internal_mvcc_timestamp:3 tableoid:4 + └── locking: for-share,skip-locked + +build +SELECT * FROM t FOR KEY SHARE FOR SHARE SKIP LOCKED FOR NO KEY UPDATE +---- +project + ├── columns: a:1!null b:2 + └── scan t + ├── columns: a:1!null b:2 crdb_internal_mvcc_timestamp:3 tableoid:4 + └── locking: for-no-key-update,skip-locked + +build +SELECT * FROM t FOR KEY SHARE FOR SHARE SKIP LOCKED FOR NO KEY UPDATE FOR UPDATE SKIP LOCKED +---- +project + ├── columns: a:1!null b:2 + └── scan t + ├── columns: a:1!null b:2 crdb_internal_mvcc_timestamp:3 tableoid:4 + └── locking: for-update,skip-locked + +build +SELECT * FROM t FOR UPDATE OF t SKIP LOCKED +---- +project + ├── columns: a:1!null b:2 + └── scan t + ├── columns: a:1!null b:2 crdb_internal_mvcc_timestamp:3 tableoid:4 + └── locking: for-update,skip-locked + +build +SELECT * FROM t FOR UPDATE OF t2 SKIP LOCKED +---- +error (42P01): relation "t2" in FOR UPDATE clause not found in FROM clause + +build +SELECT 1 FROM t FOR UPDATE OF t SKIP LOCKED +---- +project + ├── columns: "?column?":5!null + ├── scan t + │ ├── columns: a:1!null b:2 crdb_internal_mvcc_timestamp:3 tableoid:4 + │ └── locking: for-update,skip-locked + └── projections + └── 1 [as="?column?":5] diff --git a/pkg/sql/row/locking.go b/pkg/sql/row/locking.go index 3a98d75dd1cb..657343048be9 100644 --- a/pkg/sql/row/locking.go +++ b/pkg/sql/row/locking.go @@ -52,8 +52,7 @@ func getWaitPolicy(lockWaitPolicy descpb.ScanLockingWaitPolicy) lock.WaitPolicy return lock.WaitPolicy_Block case descpb.ScanLockingWaitPolicy_SKIP_LOCKED: - // Should not get here. Query should be rejected during planning. - panic(errors.AssertionFailedf("unsupported wait policy %s", lockWaitPolicy)) + return lock.WaitPolicy_SkipLocked case descpb.ScanLockingWaitPolicy_ERROR: return lock.WaitPolicy_Error