Skip to content

Commit a181794

Browse files
committed
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.
1 parent 3b0da62 commit a181794

File tree

4 files changed

+115
-14
lines changed

4 files changed

+115
-14
lines changed

pkg/sql/logictest/testdata/logic_test/udf_plpgsql

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,3 +708,103 @@ CREATE OR REPLACE FUNCTION f() RETURNS INT AS $$
708708
RETURN i;
709709
END
710710
$$ 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;

pkg/sql/opt/optbuilder/plpgsql.go

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ type plpgsqlBuilder struct {
121121
// varTypes maps from the name of each variable to its type.
122122
varTypes map[tree.Name]*types.T
123123

124+
// constants tracks the variables that were declared as constant.
125+
constants map[tree.Name]struct{}
126+
124127
// returnType is the return type of the PL/pgSQL function.
125128
returnType *types.T
126129

@@ -164,12 +167,6 @@ func (b *plpgsqlBuilder) init(
164167
"not-null PL/pgSQL variables are not yet supported",
165168
))
166169
}
167-
if dec.Constant {
168-
panic(unimplemented.NewWithIssueDetail(105241,
169-
"constant variable",
170-
"constant PL/pgSQL variables are not yet supported",
171-
))
172-
}
173170
if dec.Collate != "" {
174171
panic(unimplemented.NewWithIssueDetail(105245,
175172
"variable collation",
@@ -185,14 +182,20 @@ func (b *plpgsqlBuilder) build(block *plpgsqltree.PLpgSQLStmtBlock, s *scope) *s
185182
s = s.push()
186183
b.ensureScopeHasExpr(s)
187184

188-
// Some variable declarations initialize the variable.
185+
b.constants = make(map[tree.Name]struct{})
189186
for _, dec := range b.decls {
190187
if dec.Expr != nil {
188+
// Some variable declarations initialize the variable.
191189
s = b.addPLpgSQLAssign(s, dec.Var, dec.Expr)
192190
} else {
193191
// Uninitialized variables are null.
194192
s = b.addPLpgSQLAssign(s, dec.Var, &tree.CastExpr{Expr: tree.DNull, Type: dec.Typ})
195193
}
194+
if dec.Constant {
195+
// Add to the constants map after initializing the variable, since
196+
// constant variables only prevent assignment, not initialization.
197+
b.constants[dec.Var] = struct{}{}
198+
}
196199
}
197200
if s = b.buildPLpgSQLStatements(block.Body, s); s != nil {
198201
return s
@@ -399,6 +402,11 @@ func (b *plpgsqlBuilder) buildPLpgSQLStatements(
399402
func (b *plpgsqlBuilder) addPLpgSQLAssign(
400403
inScope *scope, ident plpgsqltree.PLpgSQLVariable, val plpgsqltree.PLpgSQLExpr,
401404
) *scope {
405+
if b.constants != nil {
406+
if _, ok := b.constants[ident]; ok {
407+
panic(pgerror.Newf(pgcode.ErrorInAssignment, "variable \"%s\" is declared CONSTANT", ident))
408+
}
409+
}
402410
typ, ok := b.varTypes[ident]
403411
if !ok {
404412
panic(pgerror.Newf(pgcode.Syntax, "\"%s\" is not a known variable", ident))

pkg/sql/sem/plpgsqltree/statements.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ type PLpgSQLStmtBlock struct {
6060
Decls []PLpgSQLDecl
6161
Body []PLpgSQLStatement
6262
Exceptions *PLpgSQLExceptionBlock
63-
Scope VariableScope
6463
}
6564

6665
// TODO(drewk): format Label and Exceptions fields.

pkg/sql/sem/plpgsqltree/variable.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,3 @@ package plpgsqltree
1313
import "github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
1414

1515
type PLpgSQLVariable = tree.Name
16-
17-
// Scope contains all the variables defined in the DECLARE section of current statement block.
18-
type VariableScope struct {
19-
Variables []*PLpgSQLVariable
20-
VarNameToIdx map[string]int // mapping from variable
21-
}

0 commit comments

Comments
 (0)