Skip to content

Commit 16ac7bd

Browse files
sql/opt/optbuilder: validate FOR UPDATE locking targets
This commit addresses a TODO from the previous commit. It adds logic into the optbuilder to validate that all locking targets are real relations that are present in the FROM clause of the SELECT statement.
1 parent c494608 commit 16ac7bd

File tree

3 files changed

+66
-163
lines changed

3 files changed

+66
-163
lines changed

pkg/sql/logictest/testdata/logic_test/select_for_update

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,24 @@ 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

pkg/sql/opt/optbuilder/select.go

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1214,7 +1214,8 @@ func (b *Builder) validateLockingInFrom(
12141214
panic(errors.AssertionFailedf("unknown locking wait policy: %s", li.WaitPolicy))
12151215
}
12161216

1217-
// Validate locking targets.
1217+
// Validate locking targets by checking that all targets are well-formed
1218+
// and all point to real relations present in the FROM clause.
12181219
for _, target := range li.Targets {
12191220
// Insist on unqualified alias names here. We could probably do
12201221
// something smarter, but it's better to just mirror Postgres
@@ -1223,12 +1224,29 @@ func (b *Builder) validateLockingInFrom(
12231224
panic(pgerror.Newf(pgcode.Syntax,
12241225
"%s must specify unqualified relation names", li.Strength))
12251226
}
1227+
1228+
// Search for the target in fromScope. If a target is missing from
1229+
// the scope then raise an error. This will end up looping over all
1230+
// columns in scope for each of the locking targets. We could use a
1231+
// more efficient data structure (e.g. a hash map of relation names)
1232+
// to improve the complexity here, but we don't expect the number of
1233+
// columns to be small enough that doing so is likely not worth it.
1234+
found := false
1235+
for _, col := range fromScope.cols {
1236+
if target.TableName == col.table.TableName {
1237+
found = true
1238+
break
1239+
}
1240+
}
1241+
if !found {
1242+
panic(pgerror.Newf(
1243+
pgcode.UndefinedTable,
1244+
"relation %q in %s clause not found in FROM clause",
1245+
target.TableName, li.Strength,
1246+
))
1247+
}
12261248
}
12271249
}
1228-
1229-
// TODO(nvanbenschoten): Postgres verifies that all locking targets
1230-
// point to real relations in the FROM clause. We should verify the
1231-
// same thing here.
12321250
}
12331251

12341252
// rejectIfLocking raises a locking error if a locking clause was specified.

pkg/sql/opt/optbuilder/testdata/select_for_update

Lines changed: 30 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,18 @@ scan t
7373
build
7474
SELECT * FROM t FOR UPDATE OF t2
7575
----
76-
scan t
77-
└── columns: a:1(int!null) b:2(int)
76+
error (42P01): relation "t2" in FOR UPDATE clause not found in FROM clause
77+
78+
build
79+
SELECT 1 FROM t FOR UPDATE OF t
80+
----
81+
project
82+
├── columns: "?column?":3(int!null)
83+
├── scan t
84+
│ ├── columns: a:1(int!null) b:2(int)
85+
│ └── locking: for-update
86+
└── projections
87+
└── const: 1 [type=int]
7888

7989
# ------------------------------------------------------------------------------
8090
# Tests with table aliases.
@@ -90,8 +100,7 @@ scan t2
90100
build
91101
SELECT * FROM t AS t2 FOR UPDATE OF t
92102
----
93-
scan t2
94-
└── columns: a:1(int!null) b:2(int)
103+
error (42P01): relation "t" in FOR UPDATE clause not found in FROM clause
95104

96105
build
97106
SELECT * FROM t AS t2 FOR UPDATE OF t2
@@ -122,8 +131,7 @@ scan t
122131
build
123132
SELECT * FROM [53 AS t] FOR UPDATE OF t2
124133
----
125-
scan t
126-
└── columns: a:1(int!null) b:2(int)
134+
error (42P01): relation "t2" in FOR UPDATE clause not found in FROM clause
127135

128136
# ------------------------------------------------------------------------------
129137
# Tests with views.
@@ -150,26 +158,17 @@ project
150158
build
151159
SELECT * FROM v FOR UPDATE OF v2
152160
----
153-
project
154-
├── columns: a:1(int!null)
155-
└── scan t2
156-
└── columns: a:1(int!null) b:2(int)
161+
error (42P01): relation "v2" in FOR UPDATE clause not found in FROM clause
157162

158163
build
159164
SELECT * FROM v FOR UPDATE OF t
160165
----
161-
project
162-
├── columns: a:1(int!null)
163-
└── scan t2
164-
└── columns: a:1(int!null) b:2(int)
166+
error (42P01): relation "t" in FOR UPDATE clause not found in FROM clause
165167

166168
build
167169
SELECT * FROM v FOR UPDATE OF t2
168170
----
169-
project
170-
├── columns: a:1(int!null)
171-
└── scan t2
172-
└── columns: a:1(int!null) b:2(int)
171+
error (42P01): relation "t2" in FOR UPDATE clause not found in FROM clause
173172

174173
# ------------------------------------------------------------------------------
175174
# Tests with aliased views.
@@ -187,10 +186,7 @@ project
187186
build
188187
SELECT * FROM v AS v2 FOR UPDATE OF v
189188
----
190-
project
191-
├── columns: a:1(int!null)
192-
└── scan t2
193-
└── columns: a:1(int!null) b:2(int)
189+
error (42P01): relation "v" in FOR UPDATE clause not found in FROM clause
194190

195191
build
196192
SELECT * FROM v AS v2 FOR UPDATE OF v2
@@ -245,15 +241,10 @@ project
245241
├── columns: a:1(int!null) b:2(int)
246242
└── locking: for-no-key-update
247243

248-
# TODO(nvanbenschoten): To match Postgres perfectly, this would throw an error.
249-
# It's not clear that it's worth going out of our way to mirror that behavior.
250244
build
251245
SELECT * FROM (SELECT a FROM t) FOR UPDATE OF t
252246
----
253-
project
254-
├── columns: a:1(int!null)
255-
└── scan t
256-
└── columns: a:1(int!null) b:2(int)
247+
error (42P01): relation "t" in FOR UPDATE clause not found in FROM clause
257248

258249
build
259250
SELECT * FROM (SELECT a FROM t FOR UPDATE OF t)
@@ -285,10 +276,7 @@ project
285276
build
286277
SELECT * FROM (SELECT a FROM t) AS r FOR UPDATE OF t
287278
----
288-
project
289-
├── columns: a:1(int!null)
290-
└── scan t
291-
└── columns: a:1(int!null) b:2(int)
279+
error (42P01): relation "t" in FOR UPDATE clause not found in FROM clause
292280

293281
build
294282
SELECT * FROM (SELECT a FROM t FOR UPDATE OF t) AS r
@@ -335,18 +323,7 @@ project
335323
build
336324
SELECT (SELECT a FROM t) FOR UPDATE OF t
337325
----
338-
project
339-
├── columns: a:3(int)
340-
├── values
341-
│ └── tuple [type=tuple]
342-
└── projections
343-
└── subquery [type=int]
344-
└── max1-row
345-
├── columns: t.a:1(int!null)
346-
└── project
347-
├── columns: t.a:1(int!null)
348-
└── scan t
349-
└── columns: t.a:1(int!null) b:2(int)
326+
error (42P01): relation "t" in FOR UPDATE clause not found in FROM clause
350327

351328
build
352329
SELECT (SELECT a FROM t FOR UPDATE OF t)
@@ -401,18 +378,7 @@ project
401378
build
402379
SELECT (SELECT a FROM t) AS r FOR UPDATE OF t
403380
----
404-
project
405-
├── columns: r:3(int)
406-
├── values
407-
│ └── tuple [type=tuple]
408-
└── projections
409-
└── subquery [type=int]
410-
└── max1-row
411-
├── columns: a:1(int!null)
412-
└── project
413-
├── columns: a:1(int!null)
414-
└── scan t
415-
└── columns: a:1(int!null) b:2(int)
381+
error (42P01): relation "t" in FOR UPDATE clause not found in FROM clause
416382

417383
build
418384
SELECT (SELECT a FROM t FOR UPDATE OF t) AS r
@@ -656,18 +622,7 @@ project
656622
build
657623
SELECT * FROM t JOIN u USING (a) FOR UPDATE OF t2 FOR SHARE OF u2
658624
----
659-
project
660-
├── columns: a:1(int!null) b:2(int) c:4(int)
661-
└── inner-join (hash)
662-
├── columns: t.a:1(int!null) b:2(int) u.a:3(int!null) c:4(int)
663-
├── scan t
664-
│ └── columns: t.a:1(int!null) b:2(int)
665-
├── scan u
666-
│ └── columns: u.a:3(int!null) c:4(int)
667-
└── filters
668-
└── eq [type=bool]
669-
├── variable: t.a [type=int]
670-
└── variable: u.a [type=int]
625+
error (42P01): relation "t2" in FOR UPDATE clause not found in FROM clause
671626

672627
build
673628
SELECT * FROM t AS t2 JOIN u AS u2 USING (a) FOR UPDATE OF t2 FOR SHARE OF u2
@@ -766,50 +721,17 @@ project
766721
build
767722
SELECT * FROM t AS t2 JOIN u AS u2 USING (a) FOR UPDATE OF t
768723
----
769-
project
770-
├── columns: a:1(int!null) b:2(int) c:4(int)
771-
└── inner-join (hash)
772-
├── columns: t2.a:1(int!null) b:2(int) u2.a:3(int!null) c:4(int)
773-
├── scan t2
774-
│ └── columns: t2.a:1(int!null) b:2(int)
775-
├── scan u2
776-
│ └── columns: u2.a:3(int!null) c:4(int)
777-
└── filters
778-
└── eq [type=bool]
779-
├── variable: t2.a [type=int]
780-
└── variable: u2.a [type=int]
724+
error (42P01): relation "t" in FOR UPDATE clause not found in FROM clause
781725

782726
build
783727
SELECT * FROM t AS t2 JOIN u AS u2 USING (a) FOR UPDATE OF u
784728
----
785-
project
786-
├── columns: a:1(int!null) b:2(int) c:4(int)
787-
└── inner-join (hash)
788-
├── columns: t2.a:1(int!null) b:2(int) u2.a:3(int!null) c:4(int)
789-
├── scan t2
790-
│ └── columns: t2.a:1(int!null) b:2(int)
791-
├── scan u2
792-
│ └── columns: u2.a:3(int!null) c:4(int)
793-
└── filters
794-
└── eq [type=bool]
795-
├── variable: t2.a [type=int]
796-
└── variable: u2.a [type=int]
729+
error (42P01): relation "u" in FOR UPDATE clause not found in FROM clause
797730

798731
build
799732
SELECT * FROM t AS t2 JOIN u AS u2 USING (a) FOR UPDATE OF t, u
800733
----
801-
project
802-
├── columns: a:1(int!null) b:2(int) c:4(int)
803-
└── inner-join (hash)
804-
├── columns: t2.a:1(int!null) b:2(int) u2.a:3(int!null) c:4(int)
805-
├── scan t2
806-
│ └── columns: t2.a:1(int!null) b:2(int)
807-
├── scan u2
808-
│ └── columns: u2.a:3(int!null) c:4(int)
809-
└── filters
810-
└── eq [type=bool]
811-
├── variable: t2.a [type=int]
812-
└── variable: u2.a [type=int]
734+
error (42P01): relation "t" in FOR UPDATE clause not found in FROM clause
813735

814736
build
815737
SELECT * FROM t AS t2 JOIN u AS u2 USING (a) FOR UPDATE OF t2
@@ -889,50 +811,17 @@ project
889811
build
890812
SELECT * FROM (t JOIN u AS u2 USING (a)) j FOR UPDATE OF t
891813
----
892-
project
893-
├── columns: a:1(int!null) b:2(int) c:4(int)
894-
└── inner-join (hash)
895-
├── columns: t.a:1(int!null) b:2(int) u2.a:3(int!null) c:4(int)
896-
├── scan t
897-
│ └── columns: t.a:1(int!null) b:2(int)
898-
├── scan u2
899-
│ └── columns: u2.a:3(int!null) c:4(int)
900-
└── filters
901-
└── eq [type=bool]
902-
├── variable: t.a [type=int]
903-
└── variable: u2.a [type=int]
814+
error (42P01): relation "t" in FOR UPDATE clause not found in FROM clause
904815

905816
build
906817
SELECT * FROM (t JOIN u AS u2 USING (a)) j FOR UPDATE OF u
907818
----
908-
project
909-
├── columns: a:1(int!null) b:2(int) c:4(int)
910-
└── inner-join (hash)
911-
├── columns: t.a:1(int!null) b:2(int) u2.a:3(int!null) c:4(int)
912-
├── scan t
913-
│ └── columns: t.a:1(int!null) b:2(int)
914-
├── scan u2
915-
│ └── columns: u2.a:3(int!null) c:4(int)
916-
└── filters
917-
└── eq [type=bool]
918-
├── variable: t.a [type=int]
919-
└── variable: u2.a [type=int]
819+
error (42P01): relation "u" in FOR UPDATE clause not found in FROM clause
920820

921821
build
922822
SELECT * FROM (t JOIN u AS u2 USING (a)) j FOR UPDATE OF u2
923823
----
924-
project
925-
├── columns: a:1(int!null) b:2(int) c:4(int)
926-
└── inner-join (hash)
927-
├── columns: t.a:1(int!null) b:2(int) u2.a:3(int!null) c:4(int)
928-
├── scan t
929-
│ └── columns: t.a:1(int!null) b:2(int)
930-
├── scan u2
931-
│ └── columns: u2.a:3(int!null) c:4(int)
932-
└── filters
933-
└── eq [type=bool]
934-
├── variable: t.a [type=int]
935-
└── variable: u2.a [type=int]
824+
error (42P01): relation "u2" in FOR UPDATE clause not found in FROM clause
936825

937826
build
938827
SELECT * FROM (t JOIN u AS u2 USING (a)) j FOR UPDATE OF j
@@ -1010,13 +899,7 @@ inner-join-apply
1010899
build
1011900
SELECT * FROM t, LATERAL (SELECT * FROM u) sub FOR UPDATE OF u
1012901
----
1013-
inner-join-apply
1014-
├── columns: a:1(int!null) b:2(int) a:3(int!null) c:4(int)
1015-
├── scan t
1016-
│ └── columns: t.a:1(int!null) b:2(int)
1017-
├── scan sub
1018-
│ └── columns: sub.a:3(int!null) c:4(int)
1019-
└── filters (true)
902+
error (42P01): relation "u" in FOR UPDATE clause not found in FROM clause
1020903

1021904
build
1022905
SELECT * FROM t, LATERAL (SELECT * FROM u) sub FOR UPDATE OF sub

0 commit comments

Comments
 (0)