Skip to content

Commit 1eb618c

Browse files
craig[bot]nvanbenschoten
craig[bot]
andcommitted
Merge #44015
44015: sql/opt/optbuilder: propagate FOR UPDATE locking in scope to ScanExprs r=nvanbenschoten a=nvanbenschoten Relates to #40205. This change updates optbuilder to propagate row-level locking modes through the `*scope` object to achieve the same locking clause scoping that Postgres contains. The change propagates the locking mode all the way down to the `ScanExpr` level, where a single locking mode becomes an optional property of a Scan. With proper scoping rules, the change also improves upon the locking validation introduced in #43887. This completes the SELECT FOR [KEY] SHARE/UPDATE optbuilder interactions. The next step will be to hook the ScanExpr locking property into the execbuilder. Co-authored-by: Nathan VanBenschoten <[email protected]>
2 parents 8cf3adf + 16ac7bd commit 1eb618c

File tree

19 files changed

+1528
-152
lines changed

19 files changed

+1528
-152
lines changed

pkg/sql/logictest/testdata/logic_test/select_for_update

Lines changed: 91 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,25 +25,36 @@ SELECT 1 FOR UPDATE FOR SHARE FOR NO KEY UPDATE FOR KEY SHARE
2525
----
2626
1
2727

28-
# Postgres gives an error if you specify a table that isn't available in the
29-
# FROM list for the OF ... clause, but since we're ignoring this list anyway,
30-
# we didn't bother to add that behavior.
31-
32-
query I
28+
query error pgcode 42P01 relation "a" in FOR UPDATE clause not found in FROM clause
3329
SELECT 1 FOR UPDATE OF a
34-
----
35-
1
3630

37-
query I
31+
query error pgcode 42P01 relation "a" in FOR SHARE clause not found in FROM clause
3832
SELECT 1 FOR SHARE OF a, b
39-
----
40-
1
4133

42-
query I
34+
query error pgcode 42P01 relation "a" in FOR UPDATE clause not found in FROM clause
4335
SELECT 1 FOR UPDATE OF a FOR SHARE OF b, c FOR NO KEY UPDATE OF d FOR KEY SHARE OF e, f
36+
37+
query I
38+
SELECT 1 FROM
39+
(SELECT 1) a,
40+
(SELECT 1) b,
41+
(SELECT 1) c,
42+
(SELECT 1) d,
43+
(SELECT 1) e,
44+
(SELECT 1) f
45+
FOR UPDATE OF a FOR SHARE OF b, c FOR NO KEY UPDATE OF d FOR KEY SHARE OF e, f
4446
----
4547
1
4648

49+
# However, we do mirror Postgres in that we require FOR UPDATE targets to be
50+
# unqualified names and reject anything else.
51+
52+
query error pgcode 42601 FOR UPDATE must specify unqualified relation names
53+
SELECT 1 FOR UPDATE OF public.a
54+
55+
query error pgcode 42601 FOR UPDATE must specify unqualified relation names
56+
SELECT 1 FOR UPDATE OF db.public.a
57+
4758
# We can't support SKIP LOCKED or NOWAIT, since they would actually behave
4859
# differently - NOWAIT returns an error to the client instead of blocking,
4960
# and SKIP LOCKED returns an inconsistent view.
@@ -110,30 +121,99 @@ SELECT 1 FOR READ ONLY
110121
statement error pgcode 0A000 FOR UPDATE is not allowed with UNION/INTERSECT/EXCEPT
111122
SELECT 1 UNION SELECT 1 FOR UPDATE
112123

124+
statement error pgcode 0A000 FOR UPDATE is not allowed with UNION/INTERSECT/EXCEPT
125+
SELECT * FROM (SELECT 1 UNION SELECT 1) a FOR UPDATE
126+
113127
statement error pgcode 0A000 FOR UPDATE is not allowed with VALUES
114128
VALUES (1) FOR UPDATE
115129

130+
statement error pgcode 0A000 FOR UPDATE is not allowed with VALUES
131+
SELECT * FROM (VALUES (1)) a FOR UPDATE
132+
116133
statement error pgcode 0A000 FOR UPDATE is not allowed with DISTINCT clause
117134
SELECT DISTINCT 1 FOR UPDATE
118135

136+
statement error pgcode 0A000 FOR UPDATE is not allowed with DISTINCT clause
137+
SELECT * FROM (SELECT DISTINCT 1) a FOR UPDATE
138+
119139
statement error pgcode 0A000 FOR UPDATE is not allowed with GROUP BY clause
120140
SELECT 1 GROUP BY 1 FOR UPDATE
121141

142+
statement error pgcode 0A000 FOR UPDATE is not allowed with GROUP BY clause
143+
SELECT * FROM (SELECT 1 GROUP BY 1) a FOR UPDATE
144+
122145
statement error pgcode 0A000 FOR UPDATE is not allowed with HAVING clause
123146
SELECT 1 HAVING TRUE FOR UPDATE
124147

148+
statement error pgcode 0A000 FOR UPDATE is not allowed with HAVING clause
149+
SELECT * FROM (SELECT 1 HAVING TRUE) a FOR UPDATE
150+
125151
statement error pgcode 0A000 FOR UPDATE is not allowed with aggregate functions
126152
SELECT count(1) FOR UPDATE
127153

154+
statement error pgcode 0A000 FOR UPDATE is not allowed with aggregate functions
155+
SELECT * FROM (SELECT count(1)) a FOR UPDATE
156+
128157
statement error pgcode 0A000 FOR UPDATE is not allowed with window functions
129158
SELECT count(1) OVER () FOR UPDATE
130159

160+
statement error pgcode 0A000 FOR UPDATE is not allowed with window functions
161+
SELECT * FROM (SELECT count(1) OVER ()) a FOR UPDATE
162+
131163
statement error pgcode 0A000 FOR UPDATE is not allowed with set-returning functions in the target list
132164
SELECT generate_series(1, 2) FOR UPDATE
133165

166+
statement error pgcode 0A000 FOR UPDATE is not allowed with set-returning functions in the target list
167+
SELECT * FROM (SELECT generate_series(1, 2)) a FOR UPDATE
168+
134169
# Set-returning functions in the from list are allowed.
135170
query I
136171
SELECT * FROM generate_series(1, 2) FOR UPDATE
137172
----
138173
1
139174
2
175+
176+
query I
177+
SELECT * FROM (SELECT * FROM generate_series(1, 2)) a FOR UPDATE
178+
----
179+
1
180+
2
181+
182+
# Use of SELECT FOR UPDATE/SHARE requires UPDATE privileges, not just SELECT privileges.
183+
184+
statement ok
185+
CREATE TABLE t (k INT PRIMARY KEY, v int)
186+
187+
statement ok
188+
GRANT GRANT on t to testuser
189+
190+
user testuser
191+
192+
statement error pgcode 42501 user testuser does not have SELECT privilege on relation t
193+
SELECT * FROM t
194+
195+
statement ok
196+
GRANT SELECT ON t TO testuser
197+
198+
statement ok
199+
SELECT * FROM t
200+
201+
statement error pgcode 42501 user testuser does not have UPDATE privilege on relation t
202+
SELECT * FROM t FOR UPDATE
203+
204+
statement error pgcode 42501 user testuser does not have UPDATE privilege on relation t
205+
SELECT * FROM t FOR SHARE
206+
207+
statement ok
208+
GRANT UPDATE ON t TO testuser
209+
210+
statement ok
211+
SELECT * FROM t FOR UPDATE
212+
213+
statement ok
214+
SELECT * FROM t FOR SHARE
215+
216+
user root
217+
218+
statement ok
219+
DROP TABLE t

pkg/sql/opt/memo/expr_format.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,29 @@ func (f *ExprFmtCtx) formatRelational(e RelExpr, tp treeprinter.Node) {
360360
tp.Childf("flags: force-index=%s%s", idx.Name(), dir)
361361
}
362362
}
363+
if t.Locking != nil {
364+
strength := ""
365+
switch t.Locking.Strength {
366+
case tree.ForNone:
367+
case tree.ForKeyShare:
368+
strength = "for-key-share"
369+
case tree.ForShare:
370+
strength = "for-share"
371+
case tree.ForNoKeyUpdate:
372+
strength = "for-no-key-update"
373+
case tree.ForUpdate:
374+
strength = "for-update"
375+
}
376+
wait := ""
377+
switch t.Locking.WaitPolicy {
378+
case tree.LockWaitBlock:
379+
case tree.LockWaitSkip:
380+
wait = ",skip-locked"
381+
case tree.LockWaitError:
382+
wait = ",nowait"
383+
}
384+
tp.Childf("locking: %s%s", strength, wait)
385+
}
363386

364387
case *LookupJoinExpr:
365388
if !t.Flags.Empty() {

pkg/sql/opt/memo/interner.go

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -311,17 +311,24 @@ func (h *hasher) HashFloat64(val float64) {
311311
h.hash *= prime64
312312
}
313313

314+
func (h *hasher) HashRune(val rune) {
315+
h.hash ^= internHash(val)
316+
h.hash *= prime64
317+
}
318+
314319
func (h *hasher) HashString(val string) {
315320
for _, c := range val {
316-
h.hash ^= internHash(c)
317-
h.hash *= prime64
321+
h.HashRune(c)
318322
}
319323
}
320324

325+
func (h *hasher) HashByte(val byte) {
326+
h.HashRune(rune(val))
327+
}
328+
321329
func (h *hasher) HashBytes(val []byte) {
322330
for _, c := range val {
323-
h.hash ^= internHash(c)
324-
h.hash *= prime64
331+
h.HashByte(c)
325332
}
326333
}
327334

@@ -540,6 +547,13 @@ func (h *hasher) HashPhysProps(val *physical.Required) {
540547
h.HashFloat64(val.LimitHint)
541548
}
542549

550+
func (h *hasher) HashLockingItem(val *tree.LockingItem) {
551+
if val != nil {
552+
h.HashByte(byte(val.Strength))
553+
h.HashByte(byte(val.WaitPolicy))
554+
}
555+
}
556+
543557
func (h *hasher) HashRelExpr(val RelExpr) {
544558
h.HashUint64(uint64(reflect.ValueOf(val).Pointer()))
545559
}
@@ -646,10 +660,18 @@ func (h *hasher) IsFloat64Equal(l, r float64) bool {
646660
return math.Float64bits(l) == math.Float64bits(r)
647661
}
648662

663+
func (h *hasher) IsRuneEqual(l, r rune) bool {
664+
return l == r
665+
}
666+
649667
func (h *hasher) IsStringEqual(l, r string) bool {
650668
return l == r
651669
}
652670

671+
func (h *hasher) IsByteEqual(l, r byte) bool {
672+
return l == r
673+
}
674+
653675
func (h *hasher) IsBytesEqual(l, r []byte) bool {
654676
return bytes.Equal(l, r)
655677
}
@@ -854,6 +876,13 @@ func (h *hasher) IsPhysPropsEqual(l, r *physical.Required) bool {
854876
return l.Equals(r)
855877
}
856878

879+
func (h *hasher) IsLockingItemEqual(l, r *tree.LockingItem) bool {
880+
if l == nil || r == nil {
881+
return l == r
882+
}
883+
return l.Strength == r.Strength && l.WaitPolicy == r.WaitPolicy
884+
}
885+
857886
func (h *hasher) IsPointerEqual(l, r unsafe.Pointer) bool {
858887
return l == r
859888
}

pkg/sql/opt/memo/interner_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,13 +177,27 @@ func TestInterner(t *testing.T) {
177177
{val1: float64(0), val2: math.Copysign(0, -1), equal: false},
178178
}},
179179

180+
{hashFn: in.hasher.HashRune, eqFn: in.hasher.IsRuneEqual, variations: []testVariation{
181+
{val1: rune(0), val2: rune(0), equal: true},
182+
{val1: rune('a'), val2: rune('b'), equal: false},
183+
{val1: rune('a'), val2: rune('A'), equal: false},
184+
{val1: rune('🐛'), val2: rune('🐛'), equal: true},
185+
}},
186+
180187
{hashFn: in.hasher.HashString, eqFn: in.hasher.IsStringEqual, variations: []testVariation{
181188
{val1: "", val2: "", equal: true},
182189
{val1: "abc", val2: "abcd", equal: false},
183190
{val1: "", val2: " ", equal: false},
184191
{val1: "the quick brown fox", val2: "the quick brown fox", equal: true},
185192
}},
186193

194+
{hashFn: in.hasher.HashByte, eqFn: in.hasher.IsByteEqual, variations: []testVariation{
195+
{val1: byte(0), val2: byte(0), equal: true},
196+
{val1: byte('a'), val2: byte('b'), equal: false},
197+
{val1: byte('a'), val2: byte('A'), equal: false},
198+
{val1: byte('z'), val2: byte('z'), equal: true},
199+
}},
200+
187201
{hashFn: in.hasher.HashBytes, eqFn: in.hasher.IsBytesEqual, variations: []testVariation{
188202
{val1: []byte{}, val2: []byte{}, equal: true},
189203
{val1: []byte{}, val2: []byte{0}, equal: false},
@@ -412,6 +426,30 @@ func TestInterner(t *testing.T) {
412426

413427
// PhysProps hash/isEqual methods are tested in TestInternerPhysProps.
414428

429+
{hashFn: in.hasher.HashLockingItem, eqFn: in.hasher.IsLockingItemEqual, variations: []testVariation{
430+
{val1: (*tree.LockingItem)(nil), val2: (*tree.LockingItem)(nil), equal: true},
431+
{
432+
val1: (*tree.LockingItem)(nil),
433+
val2: &tree.LockingItem{Strength: tree.ForUpdate},
434+
equal: false,
435+
},
436+
{
437+
val1: &tree.LockingItem{Strength: tree.ForShare},
438+
val2: &tree.LockingItem{Strength: tree.ForUpdate},
439+
equal: false,
440+
},
441+
{
442+
val1: &tree.LockingItem{WaitPolicy: tree.LockWaitSkip},
443+
val2: &tree.LockingItem{WaitPolicy: tree.LockWaitError},
444+
equal: false,
445+
},
446+
{
447+
val1: &tree.LockingItem{Strength: tree.ForUpdate, WaitPolicy: tree.LockWaitError},
448+
val2: &tree.LockingItem{Strength: tree.ForUpdate, WaitPolicy: tree.LockWaitError},
449+
equal: true,
450+
},
451+
}},
452+
415453
{hashFn: in.hasher.HashRelExpr, eqFn: in.hasher.IsRelExprEqual, variations: []testVariation{
416454
{val1: (*ScanExpr)(nil), val2: (*ScanExpr)(nil), equal: true},
417455
{val1: scanNode, val2: scanNode, equal: true},

pkg/sql/opt/ops/relational.opt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@ define ScanPrivate {
6565
# Flags modify how the table is scanned, such as which index is used to scan.
6666
Flags ScanFlags
6767

68+
# Locking represents the row-level locking mode of the Scan. Most scans
69+
# leave this unset (Strength = ForNone), which indicates that no row-level
70+
# locking will be performed while scanning the table. Stronger locking modes
71+
# are used by SELECT .. FOR [KEY] UPDATE/SHARE statements and by the initial
72+
# row retrieval of DELETE and UPDATE statements. The locking item's Targets
73+
# list will always be empty when part of a ScanPrivate.
74+
Locking LockingItem
75+
6876
# PartitionConstrainedScan records whether or not we were able to use partitions
6977
# to constrain the lookup spans further. This flag is used to record telemetry
7078
# about how often this optimization is getting applied.

pkg/sql/opt/optbuilder/builder.go

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,13 @@ func (b *Builder) buildStmt(
248248
}
249249
}
250250

251-
// NB: The case statements are sorted lexicographically.
252251
switch stmt := stmt.(type) {
252+
case *tree.Select:
253+
return b.buildSelect(stmt, noRowLocking, desiredTypes, inScope)
254+
255+
case *tree.ParenSelect:
256+
return b.buildSelect(stmt.Select, noRowLocking, desiredTypes, inScope)
257+
253258
case *tree.Delete:
254259
return b.processWiths(stmt.With, inScope, func(inScope *scope) *scope {
255260
return b.buildDelete(stmt, inScope)
@@ -260,33 +265,6 @@ func (b *Builder) buildStmt(
260265
return b.buildInsert(stmt, inScope)
261266
})
262267

263-
case *tree.ParenSelect:
264-
return b.processWiths(stmt.Select.With, inScope, func(inScope *scope) *scope {
265-
return b.buildSelect(stmt.Select, desiredTypes, inScope)
266-
})
267-
268-
case *tree.Select:
269-
rStmt := stmt
270-
with := stmt.With
271-
wrapped := stmt.Select
272-
for s, ok := wrapped.(*tree.ParenSelect); ok; s, ok = wrapped.(*tree.ParenSelect) {
273-
stmt = s.Select
274-
if stmt.With != nil {
275-
if with != nil {
276-
// (WITH ... (WITH ...))
277-
// Currently we are unable to nest the scopes inside ParenSelect so we
278-
// must refuse the syntax so that the query does not get invalid results.
279-
panic(unimplemented.NewWithIssue(24303, "multiple WITH clauses in parentheses"))
280-
}
281-
with = s.Select.With
282-
}
283-
wrapped = stmt.Select
284-
}
285-
286-
return b.processWiths(with, inScope, func(inScope *scope) *scope {
287-
return b.buildSelect(rStmt, desiredTypes, inScope)
288-
})
289-
290268
case *tree.Update:
291269
return b.processWiths(stmt.With, inScope, func(inScope *scope) *scope {
292270
return b.buildUpdate(stmt, inScope)

pkg/sql/opt/optbuilder/insert.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,7 @@ func (mb *mutationBuilder) buildInputForDoNothing(inScope *scope, onConflict *tr
664664
mb.b.addTable(mb.tab, &mb.alias),
665665
nil, /* ordinals */
666666
nil, /* indexFlags */
667+
noRowLocking,
667668
excludeMutations,
668669
inScope,
669670
)
@@ -746,6 +747,7 @@ func (mb *mutationBuilder) buildInputForUpsert(
746747
mb.b.addTable(mb.tab, &mb.alias),
747748
nil, /* ordinals */
748749
nil, /* indexFlags */
750+
noRowLocking,
749751
includeMutations,
750752
inScope,
751753
)

0 commit comments

Comments
 (0)