Skip to content

Commit e57e974

Browse files
craig[bot]DrewKimball
andcommitted
107682: plpgsql: add support for CONSTANT variable declarations r=DrewKimball a=DrewKimball #### plpgsql: validate PLpgSQL functions during creation This patch ensures that PLpgSQL functions are built during function creation, not just during invocation. This ensures that compile-time errors are thrown when the function is created. This commit also tests and corrects the codes for some of the errors that can be thrown while building a PLpgSQL function. Note that some of the existing tests had to be removed since they called UDFs within other UDFs. We didn't catch this before because the restriction is only checked during function creation when the body statements are first built. Fixes cockroachdb#107681 Release note: None #### plpgsql: add support for CONSTANT variable declarations It is possible to declare variables as CONSTANT in PLpgSQL. This will cause any attempt to assign to the variable to result in a compile-time error. This patch implements CONSTANT variables by adding a check whenever an assignment is built that the assigned variable is non-constant. Fixes cockroachdb#105241 Release note (sql change): Added support for CONSTANT variable declarations in PLpgSQL routines. Any assignment to a variable declared with the CONSTANT keyword will raise a compile-time error. Co-authored-by: Drew Kimball <[email protected]>
2 parents f295bd8 + a181794 commit e57e974

File tree

6 files changed

+224
-198
lines changed

6 files changed

+224
-198
lines changed

pkg/sql/logictest/testdata/logic_test/udf_plpgsql

Lines changed: 151 additions & 174 deletions
Original file line numberDiff line numberDiff line change
@@ -325,172 +325,16 @@ SELECT f(1, 5), f(-5, 5), f(0, 1)
325325
----
326326
10 10 0
327327

328-
# Dijkstra's Algorithm
329-
#
330-
# ┌─┬────8──┬─┬──7────┬─┐
331-
# ┌─────┤1│ │2│ │3├─────┐
332-
# │ └┬┘ └┬┴───┐ └┬┘ │
333-
# │ │ │ │ │ │
334-
# 4 │ 2 │ │ 9
335-
# │ │ │ │ │ │
336-
# ┌┴┐ │ ┌┴┐ │ │ ┌┴┐
337-
# │0│ 11 ┌───┤8│ │ 14 │4│
338-
# └┬┘ │ │ └┬┘ │ │ └┬┘
339-
# │ │ 7 │ │ │ │
340-
# 8 │ │ 6 │ │ 10
341-
# │ │ │ │ │ │ │
342-
# │ ┌┴┬───┘ ┌┴┐ └───┬┴┐ │
343-
# └─────┤7│ │6│ │5├─────┘
344-
# └─┴────1──┴─┴──4────┴─┘
345-
#
346-
# Encode the graph as a series of undirected edges, where "a" and "b" are the
347-
# "to" and "from" nodes and "weight" is the weight of the edge.
348-
statement ok
349-
CREATE TABLE edges (a INT, b INT, weight INT);
350-
INSERT INTO edges VALUES
351-
(0, 1, 4),
352-
(0, 7, 8),
353-
(1, 7, 11),
354-
(1, 2, 8),
355-
(2, 8, 2),
356-
(7, 8, 7),
357-
(7, 6, 1),
358-
(6, 8, 6),
359-
(2, 5, 4),
360-
(5, 6, 2),
361-
(2, 3, 7),
362-
(3, 5, 14),
363-
(3, 4, 9),
364-
(4, 5, 10);
365-
366-
# Get the number of vertexes in the graph.
367-
statement ok
368-
CREATE FUNCTION vertexes() RETURNS INT AS $$ SELECT max(greatest(a, b)) + 1 FROM edges $$ LANGUAGE SQL;
369-
370-
# Get the maximum int32 value.
371-
statement ok
372-
CREATE FUNCTION max_int() RETURNS INT AS $$ SELECT 2147483647 $$ LANGUAGE SQL;
373-
374-
# Get the weight of the edge between the two given nodes, if any.
375-
statement ok
376-
CREATE FUNCTION graph(x INT, y INT) RETURNS INT AS $$
377-
SELECT coalesce((SELECT weight FROM edges WHERE (a = x AND b = y) OR (a = y AND b = x) LIMIT 1), 0);
378-
$$ LANGUAGE SQL;
379-
380-
# Replace the element at the given index of the array with the given value.
381-
statement ok
382-
CREATE FUNCTION replace(arr INT[], idx INT, val INT) RETURNS INT[] AS $$
383-
DECLARE
384-
i INT;
385-
n INT := array_length(arr, 1);
386-
res INT[] := ARRAY[]::INT[];
387-
BEGIN
388-
i := 0;
389-
LOOP
390-
IF i = idx THEN
391-
res := res || val;
392-
ELSE
393-
res := res || arr[i+1];
394-
END IF;
395-
i := i + 1;
396-
IF i >= n THEN EXIT; END IF;
397-
END LOOP;
398-
RETURN res;
399-
END
400-
$$ LANGUAGE PLpgSQL;
401-
402-
# Return the node with the minimum distance from the source node known so far
403-
# out of the nodes that don't already have a shortest path calculated.
404-
statement ok
405-
CREATE FUNCTION min_distance(dist INT[], spt_set INT[]) RETURNS INT AS $$
406-
DECLARE
407-
n INT := vertexes();
408-
i INT;
409-
min INT := max_int();
410-
min_index INT := 0;
411-
BEGIN
412-
i := 0;
413-
LOOP
414-
IF spt_set[i+1] = 0 AND dist[i+1] <= min THEN
415-
min := dist[i+1];
416-
min_index := i;
417-
END IF;
418-
i := i + 1;
419-
IF i >= n THEN EXIT; END IF;
420-
END LOOP;
421-
RETURN min_index;
422-
END
423-
$$ LANGUAGE PLPGSQL;
424-
425-
# Implement dijkstra's algorithm using the "edges" table.
426-
statement ok
427-
CREATE FUNCTION dijkstra(src INT) RETURNS INT[] AS $$
428-
DECLARE
429-
n INT := vertexes();
430-
i INT;
431-
count INT;
432-
dist INT[] := ARRAY[]::INT[];
433-
spt_set INT[] := ARRAY[]::INT[];
434-
u INT;
435-
BEGIN
436-
i := 0;
437-
LOOP
438-
dist := dist || max_int();
439-
spt_set := spt_set || 0;
440-
i := i + 1;
441-
IF i >= n THEN EXIT; END IF;
442-
END LOOP;
443-
dist := replace(dist, src, 0);
444-
count := 0;
445-
LOOP
446-
u := min_distance(dist, spt_set);
447-
spt_set := replace(spt_set, u, 1);
448-
i := 0;
449-
LOOP
450-
IF
451-
spt_set[i+1] = 0 AND
452-
graph(u, i) > 0 AND
453-
dist[u+1] <> max_int() AND
454-
dist[u+1] + graph(u, i) < dist[i+1]
455-
THEN
456-
dist := replace(dist, i, dist[u+1] + graph(u, i));
457-
END IF;
458-
i := i + 1;
459-
IF i >= n THEN EXIT; END IF;
460-
END LOOP;
461-
count := count + 1;
462-
IF count >= n THEN EXIT; END IF;
463-
END LOOP;
464-
RETURN dist;
465-
END
466-
$$ LANGUAGE PLPGSQL;
328+
# TODO(drewk): add back the dijkstra test once UDFs calling other UDFs is
329+
# allowed.
467330

468-
# Run dijkstra's algorithm using node 0 as the source.
469-
query II nosort,colnames
470-
SELECT i AS "Vertex", dist[i+1] AS "Distance From Source"
471-
FROM generate_series(0, vertexes() - 1) f(i), dijkstra(0) g(dist);
472-
----
473-
Vertex Distance From Source
474-
0 0
475-
1 4
476-
2 12
477-
3 19
478-
4 21
479-
5 11
480-
6 9
481-
7 8
482-
8 14
483-
484-
statement ok
331+
statement error pgcode 2F005 control reached end of function without RETURN
485332
CREATE OR REPLACE FUNCTION f(a INT, b INT) RETURNS INT AS $$
486333
BEGIN
487334
END
488335
$$ LANGUAGE PLpgSQL;
489336

490337
statement error pgcode 2F005 control reached end of function without RETURN
491-
SELECT f(1, 2);
492-
493-
statement ok
494338
CREATE OR REPLACE FUNCTION f(a INT, b INT) RETURNS INT AS $$
495339
DECLARE
496340
i INT;
@@ -500,9 +344,6 @@ CREATE OR REPLACE FUNCTION f(a INT, b INT) RETURNS INT AS $$
500344
$$ LANGUAGE PLpgSQL;
501345

502346
statement error pgcode 2F005 control reached end of function without RETURN
503-
SELECT f(1, 2);
504-
505-
statement ok
506347
CREATE OR REPLACE FUNCTION f(a INT, b INT) RETURNS INT AS $$
507348
BEGIN
508349
IF a < b THEN
@@ -512,9 +353,6 @@ CREATE OR REPLACE FUNCTION f(a INT, b INT) RETURNS INT AS $$
512353
$$ LANGUAGE PLpgSQL;
513354

514355
statement error pgcode 2F005 control reached end of function without RETURN
515-
SELECT f(1, 2);
516-
517-
statement ok
518356
CREATE OR REPLACE FUNCTION f(a INT, b INT) RETURNS INT AS $$
519357
DECLARE
520358
i INT;
@@ -528,9 +366,6 @@ CREATE OR REPLACE FUNCTION f(a INT, b INT) RETURNS INT AS $$
528366
$$ LANGUAGE PLpgSQL;
529367

530368
statement error pgcode 2F005 control reached end of function without RETURN
531-
SELECT f(1, 2);
532-
533-
statement ok
534369
CREATE OR REPLACE FUNCTION f(a INT, b INT) RETURNS INT AS $$
535370
BEGIN
536371
LOOP
@@ -540,9 +375,6 @@ CREATE OR REPLACE FUNCTION f(a INT, b INT) RETURNS INT AS $$
540375
$$ LANGUAGE PLpgSQL;
541376

542377
statement error pgcode 2F005 control reached end of function without RETURN
543-
SELECT f(1, 2);
544-
545-
statement ok
546378
CREATE OR REPLACE FUNCTION f(a INT, b INT) RETURNS INT AS $$
547379
BEGIN
548380
LOOP
@@ -554,9 +386,6 @@ CREATE OR REPLACE FUNCTION f(a INT, b INT) RETURNS INT AS $$
554386
END
555387
$$ LANGUAGE PLpgSQL;
556388

557-
statement error pgcode 2F005 control reached end of function without RETURN
558-
SELECT f(1, 2);
559-
560389
statement error pgcode 0A000 PL/pgSQL functions with RECORD input arguments are not yet supported
561390
CREATE FUNCTION f_err(p1 RECORD) RETURNS RECORD AS $$
562391
BEGIN
@@ -831,3 +660,151 @@ $$ LANGUAGE PLpgSQL;
831660

832661
query error pgcode P0001 pq: foo
833662
SELECT f();
663+
664+
statement error pgcode 42601 pq: too few parameters specified for RAISE
665+
CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$
666+
BEGIN
667+
RAISE 'foo% % %', 1, 2;
668+
RETURN 0;
669+
END
670+
$$ LANGUAGE PLpgSQL;
671+
672+
statement error pgcode 42601 pq: too many parameters specified for RAISE
673+
CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$
674+
BEGIN
675+
RAISE 'foo%', 1, 2;
676+
RETURN 0;
677+
END
678+
$$ LANGUAGE PLpgSQL;
679+
680+
statement error pgcode 42601 pq: RAISE option already specified: ERRCODE
681+
CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$
682+
BEGIN
683+
RAISE EXCEPTION USING ERRCODE = '22012', ERRCODE = '22013';
684+
return 0;
685+
END
686+
$$ LANGUAGE PLpgSQL;
687+
688+
statement error pgcode 42601 pq: \"i\" is not a known variable
689+
CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$
690+
BEGIN
691+
i := 0;
692+
RETURN i;
693+
END
694+
$$ LANGUAGE PLpgSQL;
695+
696+
statement error pgcode 42601 CONTINUE cannot be used outside a loop
697+
CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$
698+
BEGIN
699+
CONTINUE;
700+
RETURN i;
701+
END
702+
$$ LANGUAGE PLpgSQL;
703+
704+
statement error pgcode 42601 EXIT cannot be used outside a loop, unless it has a label
705+
CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$
706+
BEGIN
707+
EXIT;
708+
RETURN i;
709+
END
710+
$$ LANGUAGE PLpgSQL;
711+
712+
statement ok
713+
CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$
714+
DECLARE
715+
i CONSTANT INT;
716+
BEGIN
717+
RETURN i;
718+
END
719+
$$ LANGUAGE PLpgSQL;
720+
721+
query I
722+
SELECT f();
723+
----
724+
NULL
725+
726+
statement ok
727+
CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$
728+
DECLARE
729+
i CONSTANT INT := 0;
730+
BEGIN
731+
RETURN i;
732+
END
733+
$$ LANGUAGE PLpgSQL;
734+
735+
query I
736+
SELECT f();
737+
----
738+
0
739+
740+
statement ok
741+
CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$
742+
DECLARE
743+
i CONSTANT INT := (SELECT x FROM xy ORDER BY x LIMIT 1);
744+
BEGIN
745+
RETURN i;
746+
END
747+
$$ LANGUAGE PLpgSQL;
748+
749+
query I
750+
SELECT f();
751+
----
752+
1
753+
754+
statement ok
755+
CREATE OR REPLACE FUNCTION f(n INT) RETURNS INT AS $$
756+
DECLARE
757+
i CONSTANT INT := n;
758+
BEGIN
759+
RETURN i;
760+
END
761+
$$ LANGUAGE PLpgSQL;
762+
763+
query IIIIII
764+
SELECT f(-100), f(-1), f(0), f(1), f(100), f(NULL);
765+
----
766+
-100 -1 0 1 100 NULL
767+
768+
statement error pgcode 22005 pq: variable \"i\" is declared CONSTANT
769+
CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$
770+
DECLARE
771+
i CONSTANT INT;
772+
BEGIN
773+
i := i + 1;
774+
RETURN i;
775+
END
776+
$$ LANGUAGE PLpgSQL;
777+
778+
statement error pgcode 22005 pq: variable \"i\" is declared CONSTANT
779+
CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$
780+
DECLARE
781+
i CONSTANT INT := 0;
782+
BEGIN
783+
i := i + 1;
784+
RETURN i;
785+
END
786+
$$ LANGUAGE PLpgSQL;
787+
788+
statement error pgcode 22005 pq: variable \"i\" is declared CONSTANT
789+
CREATE OR REPLACE FUNCTION f(n INT) RETURNS INT AS $$
790+
DECLARE
791+
i CONSTANT INT := 0;
792+
BEGIN
793+
IF n > 0 THEN
794+
i := i + 1;
795+
END IF;
796+
RETURN i;
797+
END
798+
$$ LANGUAGE PLpgSQL;
799+
800+
statement error pgcode 22005 pq: variable \"i\" is declared CONSTANT
801+
CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$
802+
DECLARE
803+
i CONSTANT INT := 0;
804+
BEGIN
805+
LOOP IF i >= 10 THEN EXIT; END IF;
806+
i := i + 1;
807+
END LOOP;
808+
RETURN i;
809+
END
810+
$$ LANGUAGE PLpgSQL;

0 commit comments

Comments
 (0)