Skip to content

Commit ff72e25

Browse files
craig[bot]DrewKimball
andcommitted
Merge #144347
144347: plpgsql: fix ordinal variable references and FOR loops r=yuzefovich,rytaft a=DrewKimball #### plpgsql: avoid redundant tuple when fetching into tuple var Previously, a FETCH statement redundantly called `projectRecordVar` when assigning a cursor result into a single composite-typed variable. The resulting nested tuple was then unwrapped and assigned into the variable. This commit removes the extra step, and just directly assigns the FETCH tuple into the composite-typed variable. This also fixes a bug with no known visible effects, where we were incorrectly using the type of the variable for the column projecting the nested tuple (it should have been a tuple with a single element of the variable's type). Epic: None Release note: None #### plpgsql: fix parameter ordinal references Previously, variable assignment in PL/pgSQL routines did not update the `paramOrd` field. As a result, referencing a variable via the `$1` ordinal syntax did not always reflect updates to the variable. In addition, it was possible to reference variables declared in blocks within the routine body, rather than just the original routine parameters. This commit fixes both issues by always setting `paramOrd` and setting a `maxParamOrd` while building SQL expressions and statements, which prevents the user from referencing block variables via ordinal syntax. We use a `maxParamOrd` instead of simply not setting `paramOrd` for block variables to allow the builder to internally reference any variable with ordinal syntax. Fixes #143887 Release note (bug fix): Fixed a bug existing since v24.1 that prevented variable references using ordinal syntax (like `$1`) from reflecting updates to the variable. Referencing variables declared in PL/pgSQL blocks (instead of parameters) via ordinal syntax is now disallowed. #### plpgsql: reference hidden variables only by ordinal Hidden variables cannot be referenced by the user, and are used internally to implement FOR LOOP bounds and counters. Previously, hidden variables were referenced via metadata name. This was fragile, and in fact had a bug because different loops used the same hidden variables names, leading to incorrect results for nested loops. This commit fixes the bug by tracking hidden variables by their ordinal position among all variables in the current scope. The name of a hidden variable is now only used for display. This ensures that a hidden variable can always be uniquely identified, even within nested PL/pgSQL constructs. Fixes #144321 Release note (bug fix): Fixed a bug existing since v24.3 that could cause PL/pgSQL FOR loops to terminate early or show incorrect values for the counter variable. The bug occurred when two or more FOR loops were nested within one another. Co-authored-by: Drew Kimball <[email protected]>
2 parents 790f93c + f9efd1f commit ff72e25

File tree

9 files changed

+692
-131
lines changed

9 files changed

+692
-131
lines changed

pkg/ccl/logictestccl/testdata/logic_test/plpgsql_assign

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,62 @@ CREATE FUNCTION f(val xy) RETURNS xy AS $$
122122
$$ LANGUAGE PLpgSQL;
123123

124124
subtest end
125+
126+
# Ordinal parameter references should reflect updates made by variable
127+
# assignment.
128+
subtest regression_143887
129+
130+
statement ok
131+
CREATE FUNCTION f(x INT) RETURNS INT AS $$
132+
BEGIN
133+
RAISE NOTICE '% = %', x, $1;
134+
x := $1 + 1;
135+
RAISE NOTICE '% = %', x, $1;
136+
IF x IS NOT NULL THEN
137+
x := $1 + 100;
138+
RAISE NOTICE '% = %', x, $1;
139+
END IF;
140+
RAISE NOTICE '% = %', x, $1;
141+
RETURN x + $1;
142+
END
143+
$$ LANGUAGE PLpgSQL;
144+
145+
query T noticetrace
146+
SELECT f(0);
147+
----
148+
NOTICE: 0 = 0
149+
NOTICE: 1 = 1
150+
NOTICE: 101 = 101
151+
NOTICE: 101 = 101
152+
153+
query I
154+
SELECT f(0);
155+
----
156+
202
157+
158+
statement ok
159+
DROP FUNCTION f
160+
161+
statement ok
162+
CREATE FUNCTION f(foo xy) RETURNS INT AS $$
163+
BEGIN
164+
foo := ROW(1, 2);
165+
RAISE NOTICE '% = %', foo, $1;
166+
RETURN (foo).x + ($1).x;
167+
END
168+
$$ LANGUAGE PLpgSQL;
169+
170+
query T noticetrace
171+
SELECT f((100, 200));
172+
----
173+
NOTICE: (1,2) = (1,2)
174+
175+
query I
176+
SELECT f((100, 200));
177+
----
178+
2
179+
180+
statement ok
181+
DROP FUNCTION f(xy);
182+
183+
subtest end

pkg/ccl/logictestccl/testdata/logic_test/plpgsql_call

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
statement ok
2+
CREATE TABLE xy (x INT, y INT);
3+
14
statement ok
25
CREATE PROCEDURE p_nested() AS $$
36
BEGIN
@@ -456,9 +459,6 @@ subtest end
456459
# parameters.
457460
subtest regression_143171
458461

459-
statement ok
460-
CREATE TABLE xy (x INT, y INT);
461-
462462
statement ok
463463
CREATE PROCEDURE p_143171(OUT foo INT) LANGUAGE PLpgSQL AS $$
464464
BEGIN
@@ -489,7 +489,83 @@ DROP PROCEDURE p2_143171;
489489
statement ok
490490
DROP PROCEDURE p_143171;
491491

492+
subtest end
493+
494+
# Ordinal parameter references should reflect updates made by
495+
# CALL statements.
496+
subtest regression_143887
497+
498+
statement ok
499+
CREATE PROCEDURE p143887(INOUT x INT) LANGUAGE PLpgSQL AS $$
500+
BEGIN
501+
IF x = 0 THEN
502+
x := 1;
503+
ELSE
504+
x := x + 100;
505+
END IF;
506+
END
507+
$$;
508+
509+
statement ok
510+
CREATE FUNCTION f(x INT) RETURNS INT AS $$
511+
BEGIN
512+
RAISE NOTICE '% = %', x, $1;
513+
CALL p143887(x);
514+
RAISE NOTICE '% = %', x, $1;
515+
IF x IS NOT NULL THEN
516+
CALL p143887(x);
517+
RAISE NOTICE '% = %', x, $1;
518+
END IF;
519+
RAISE NOTICE '% = %', x, $1;
520+
RETURN x + $1;
521+
END
522+
$$ LANGUAGE PLpgSQL;
523+
524+
query T noticetrace
525+
SELECT f(0);
526+
----
527+
NOTICE: 0 = 0
528+
NOTICE: 1 = 1
529+
NOTICE: 101 = 101
530+
NOTICE: 101 = 101
531+
532+
query I
533+
SELECT f(0);
534+
----
535+
202
536+
537+
statement ok
538+
DROP FUNCTION f(INT);
539+
DROP PROCEDURE p143887;
540+
541+
statement ok
542+
CREATE PROCEDURE p143887(INOUT foo xy) LANGUAGE PLpgSQL AS $$
543+
BEGIN
544+
foo := ROW(1, 2);
545+
END
546+
$$;
547+
548+
statement ok
549+
CREATE FUNCTION f(foo xy) RETURNS INT AS $$
550+
BEGIN
551+
CALL p143887(foo);
552+
RAISE NOTICE '% = %', foo, $1;
553+
RETURN (foo).x + ($1).x;
554+
END
555+
$$ LANGUAGE PLpgSQL;
556+
557+
query T noticetrace
558+
SELECT f((100, 200));
559+
----
560+
NOTICE: (1,2) = (1,2)
561+
562+
query I
563+
SELECT f((100, 200));
564+
----
565+
2
566+
492567
statement ok
493-
DROP TABLE xy;
568+
DROP FUNCTION f(xy);
569+
DROP PROCEDURE p143887;
494570

495571
subtest end

pkg/ccl/logictestccl/testdata/logic_test/plpgsql_cursor

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2104,3 +2104,68 @@ subtest end
21042104

21052105
statement ok
21062106
RESET autocommit_before_ddl
2107+
2108+
# Ordinal parameter references should reflect updates made by
2109+
# FETCH statements.
2110+
subtest regression_143887
2111+
2112+
statement ok
2113+
DROP FUNCTION f(INT);
2114+
CREATE FUNCTION f(x INT) RETURNS INT AS $$
2115+
DECLARE foo REFCURSOR;
2116+
BEGIN
2117+
OPEN foo FOR SELECT bar FROM (VALUES (1), (100)) AS v(bar);
2118+
RAISE NOTICE '% = %', x, $1;
2119+
FETCH foo INTO x;
2120+
RAISE NOTICE '% = %', x, $1;
2121+
IF x IS NOT NULL THEN
2122+
FETCH foo INTO x;
2123+
RAISE NOTICE '% = %', x, $1;
2124+
END IF;
2125+
RAISE NOTICE '% = %', x, $1;
2126+
RETURN x + $1;
2127+
END
2128+
$$ LANGUAGE PLpgSQL;
2129+
2130+
query T noticetrace
2131+
SELECT f(0);
2132+
----
2133+
NOTICE: 0 = 0
2134+
NOTICE: 1 = 1
2135+
NOTICE: 100 = 100
2136+
NOTICE: 100 = 100
2137+
2138+
query I
2139+
SELECT f(0);
2140+
----
2141+
200
2142+
2143+
statement ok
2144+
DROP FUNCTION f(INT);
2145+
2146+
statement ok
2147+
CREATE FUNCTION f(foo xy) RETURNS INT AS $$
2148+
DECLARE
2149+
bar REFCURSOR;
2150+
BEGIN
2151+
OPEN bar FOR SELECT 1, 2;
2152+
FETCH bar INTO foo;
2153+
RAISE NOTICE '% = %', foo, $1;
2154+
RETURN (foo).x + ($1).x;
2155+
END
2156+
$$ LANGUAGE PLpgSQL;
2157+
2158+
query T noticetrace
2159+
SELECT f((100, 200));
2160+
----
2161+
NOTICE: (1,2) = (1,2)
2162+
2163+
query I
2164+
SELECT f((100, 200));
2165+
----
2166+
2
2167+
2168+
statement ok
2169+
DROP FUNCTION f(xy);
2170+
2171+
subtest end

pkg/ccl/logictestccl/testdata/logic_test/plpgsql_into

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,3 +523,63 @@ statement ok
523523
RESET plpgsql_use_strict_into;
524524

525525
subtest end
526+
527+
# Ordinal parameter references should reflect updates made by
528+
# SELECT INTO statements.
529+
subtest regression_143887
530+
531+
statement ok
532+
DROP FUNCTION f(INT);
533+
CREATE FUNCTION f(x INT) RETURNS INT AS $$
534+
BEGIN
535+
RAISE NOTICE '% = %', x, $1;
536+
SELECT $1 + 1 INTO x;
537+
RAISE NOTICE '% = %', x, $1;
538+
IF x IS NOT NULL THEN
539+
SELECT $1 + 100 INTO x;
540+
RAISE NOTICE '% = %', x, $1;
541+
END IF;
542+
RAISE NOTICE '% = %', x, $1;
543+
RETURN x + $1;
544+
END
545+
$$ LANGUAGE PLpgSQL;
546+
547+
query T noticetrace
548+
SELECT f(0);
549+
----
550+
NOTICE: 0 = 0
551+
NOTICE: 1 = 1
552+
NOTICE: 101 = 101
553+
NOTICE: 101 = 101
554+
555+
query I
556+
SELECT f(0);
557+
----
558+
202
559+
560+
statement ok
561+
DROP FUNCTION f(INT);
562+
563+
statement ok
564+
CREATE FUNCTION f(foo xy) RETURNS INT AS $$
565+
BEGIN
566+
SELECT 1, 2 INTO foo;
567+
RAISE NOTICE '% = %', foo, $1;
568+
RETURN (foo).x + ($1).x;
569+
END
570+
$$ LANGUAGE PLpgSQL;
571+
572+
query T noticetrace
573+
SELECT f((100, 200));
574+
----
575+
NOTICE: (1,2) = (1,2)
576+
577+
query I
578+
SELECT f((100, 200));
579+
----
580+
2
581+
582+
statement ok
583+
DROP FUNCTION f(xy);
584+
585+
subtest end

0 commit comments

Comments
 (0)