summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/pl/plpgsql/src/Makefile3
-rw-r--r--src/pl/plpgsql/src/expected/plpgsql_varprops.out300
-rw-r--r--src/pl/plpgsql/src/pl_comp.c17
-rw-r--r--src/pl/plpgsql/src/pl_exec.c72
-rw-r--r--src/pl/plpgsql/src/pl_funcs.c15
-rw-r--r--src/pl/plpgsql/src/pl_gram.y75
-rw-r--r--src/pl/plpgsql/src/plpgsql.h27
-rw-r--r--src/pl/plpgsql/src/sql/plpgsql_varprops.sql249
-rw-r--r--src/test/regress/expected/plpgsql.out36
-rw-r--r--src/test/regress/sql/plpgsql.sql26
10 files changed, 686 insertions, 134 deletions
diff --git a/src/pl/plpgsql/src/Makefile b/src/pl/plpgsql/src/Makefile
index 2190eab6169..3ac64e2d447 100644
--- a/src/pl/plpgsql/src/Makefile
+++ b/src/pl/plpgsql/src/Makefile
@@ -26,7 +26,8 @@ DATA = plpgsql.control plpgsql--1.0.sql plpgsql--unpackaged--1.0.sql
REGRESS_OPTS = --dbname=$(PL_TESTDB)
-REGRESS = plpgsql_call plpgsql_control plpgsql_record plpgsql_transaction
+REGRESS = plpgsql_call plpgsql_control plpgsql_record \
+ plpgsql_transaction plpgsql_varprops
all: all-lib
diff --git a/src/pl/plpgsql/src/expected/plpgsql_varprops.out b/src/pl/plpgsql/src/expected/plpgsql_varprops.out
new file mode 100644
index 00000000000..109056c0540
--- /dev/null
+++ b/src/pl/plpgsql/src/expected/plpgsql_varprops.out
@@ -0,0 +1,300 @@
+--
+-- Tests for PL/pgSQL variable properties: CONSTANT, NOT NULL, initializers
+--
+create type var_record as (f1 int4, f2 int4);
+create domain int_nn as int not null;
+create domain var_record_nn as var_record not null;
+create domain var_record_colnn as var_record check((value).f2 is not null);
+-- CONSTANT
+do $$
+declare x constant int := 42;
+begin
+ raise notice 'x = %', x;
+end$$;
+NOTICE: x = 42
+do $$
+declare x constant int;
+begin
+ x := 42; -- fail
+end$$;
+ERROR: variable "x" is declared CONSTANT
+LINE 4: x := 42; -- fail
+ ^
+do $$
+declare x constant int; y int;
+begin
+ for x, y in select 1, 2 loop -- fail
+ end loop;
+end$$;
+ERROR: variable "x" is declared CONSTANT
+LINE 4: for x, y in select 1, 2 loop -- fail
+ ^
+do $$
+declare x constant int[];
+begin
+ x[1] := 42; -- fail
+end$$;
+ERROR: variable "x" is declared CONSTANT
+LINE 4: x[1] := 42; -- fail
+ ^
+do $$
+declare x constant int[]; y int;
+begin
+ for x[1], y in select 1, 2 loop -- fail (currently, unsupported syntax)
+ end loop;
+end$$;
+ERROR: syntax error at or near "["
+LINE 4: for x[1], y in select 1, 2 loop -- fail (currently, unsup...
+ ^
+do $$
+declare x constant var_record;
+begin
+ x.f1 := 42; -- fail
+end$$;
+ERROR: variable "x" is declared CONSTANT
+LINE 4: x.f1 := 42; -- fail
+ ^
+do $$
+declare x constant var_record; y int;
+begin
+ for x.f1, y in select 1, 2 loop -- fail
+ end loop;
+end$$;
+ERROR: variable "x" is declared CONSTANT
+LINE 4: for x.f1, y in select 1, 2 loop -- fail
+ ^
+-- initializer expressions
+do $$
+declare x int := sin(0);
+begin
+ raise notice 'x = %', x;
+end$$;
+NOTICE: x = 0
+do $$
+declare x int := 1/0; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+ERROR: division by zero
+CONTEXT: SQL statement "SELECT 1/0"
+PL/pgSQL function inline_code_block line 3 during statement block local variable initialization
+do $$
+declare x bigint[] := array[1,3,5];
+begin
+ raise notice 'x = %', x;
+end$$;
+NOTICE: x = {1,3,5}
+do $$
+declare x record := row(1,2,3);
+begin
+ raise notice 'x = %', x;
+end$$;
+NOTICE: x = (1,2,3)
+do $$
+declare x var_record := row(1,2);
+begin
+ raise notice 'x = %', x;
+end$$;
+NOTICE: x = (1,2)
+-- NOT NULL
+do $$
+declare x int not null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+ERROR: variable "x" must have a default value, since it's declared NOT NULL
+LINE 2: declare x int not null; -- fail
+ ^
+do $$
+declare x int not null := 42;
+begin
+ raise notice 'x = %', x;
+ x := null; -- fail
+end$$;
+NOTICE: x = 42
+ERROR: null value cannot be assigned to variable "x" declared NOT NULL
+CONTEXT: PL/pgSQL function inline_code_block line 5 at assignment
+do $$
+declare x int not null := null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+ERROR: null value cannot be assigned to variable "x" declared NOT NULL
+CONTEXT: PL/pgSQL function inline_code_block line 3 during statement block local variable initialization
+do $$
+declare x record not null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+ERROR: variable "x" must have a default value, since it's declared NOT NULL
+LINE 2: declare x record not null; -- fail
+ ^
+do $$
+declare x record not null := row(42);
+begin
+ raise notice 'x = %', x;
+ x := row(null); -- ok
+ raise notice 'x = %', x;
+ x := null; -- fail
+end$$;
+NOTICE: x = (42)
+NOTICE: x = ()
+ERROR: null value cannot be assigned to variable "x" declared NOT NULL
+CONTEXT: PL/pgSQL function inline_code_block line 7 at assignment
+do $$
+declare x record not null := null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+ERROR: null value cannot be assigned to variable "x" declared NOT NULL
+CONTEXT: PL/pgSQL function inline_code_block line 3 during statement block local variable initialization
+do $$
+declare x var_record not null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+ERROR: variable "x" must have a default value, since it's declared NOT NULL
+LINE 2: declare x var_record not null; -- fail
+ ^
+do $$
+declare x var_record not null := row(41,42);
+begin
+ raise notice 'x = %', x;
+ x := row(null,null); -- ok
+ raise notice 'x = %', x;
+ x := null; -- fail
+end$$;
+NOTICE: x = (41,42)
+NOTICE: x = (,)
+ERROR: null value cannot be assigned to variable "x" declared NOT NULL
+CONTEXT: PL/pgSQL function inline_code_block line 7 at assignment
+do $$
+declare x var_record not null := null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+ERROR: null value cannot be assigned to variable "x" declared NOT NULL
+CONTEXT: PL/pgSQL function inline_code_block line 3 during statement block local variable initialization
+-- Check that variables are reinitialized on block re-entry.
+\set VERBOSITY terse \\ -- needed for output stability
+do $$
+begin
+ for i in 1..3 loop
+ declare
+ x int;
+ y int := i;
+ r record;
+ c var_record;
+ begin
+ if i = 1 then
+ x := 42;
+ r := row(i, i+1);
+ c := row(i, i+1);
+ end if;
+ raise notice 'x = %', x;
+ raise notice 'y = %', y;
+ raise notice 'r = %', r;
+ raise notice 'c = %', c;
+ end;
+ end loop;
+end$$;
+NOTICE: x = 42
+NOTICE: y = 1
+NOTICE: r = (1,2)
+NOTICE: c = (1,2)
+NOTICE: x = <NULL>
+NOTICE: y = 2
+NOTICE: r = <NULL>
+NOTICE: c = <NULL>
+NOTICE: x = <NULL>
+NOTICE: y = 3
+NOTICE: r = <NULL>
+NOTICE: c = <NULL>
+\set VERBOSITY default
+-- Check enforcement of domain constraints during initialization
+do $$
+declare x int_nn; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+ERROR: domain int_nn does not allow null values
+CONTEXT: PL/pgSQL function inline_code_block line 3 during statement block local variable initialization
+do $$
+declare x int_nn := null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+ERROR: domain int_nn does not allow null values
+CONTEXT: PL/pgSQL function inline_code_block line 3 during statement block local variable initialization
+do $$
+declare x int_nn := 42;
+begin
+ raise notice 'x = %', x;
+ x := null; -- fail
+end$$;
+NOTICE: x = 42
+ERROR: domain int_nn does not allow null values
+CONTEXT: PL/pgSQL function inline_code_block line 5 at assignment
+do $$
+declare x var_record_nn; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+ERROR: domain var_record_nn does not allow null values
+CONTEXT: PL/pgSQL function inline_code_block line 3 during statement block local variable initialization
+do $$
+declare x var_record_nn := null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+ERROR: domain var_record_nn does not allow null values
+CONTEXT: PL/pgSQL function inline_code_block line 3 during statement block local variable initialization
+do $$
+declare x var_record_nn := row(1,2);
+begin
+ raise notice 'x = %', x;
+ x := row(null,null); -- ok
+ x := null; -- fail
+end$$;
+NOTICE: x = (1,2)
+ERROR: domain var_record_nn does not allow null values
+CONTEXT: PL/pgSQL function inline_code_block line 6 at assignment
+do $$
+declare x var_record_colnn; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+ERROR: value for domain var_record_colnn violates check constraint "var_record_colnn_check"
+CONTEXT: PL/pgSQL function inline_code_block line 3 during statement block local variable initialization
+do $$
+declare x var_record_colnn := null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+ERROR: value for domain var_record_colnn violates check constraint "var_record_colnn_check"
+CONTEXT: PL/pgSQL function inline_code_block line 3 during statement block local variable initialization
+do $$
+declare x var_record_colnn := row(1,null); -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+ERROR: value for domain var_record_colnn violates check constraint "var_record_colnn_check"
+CONTEXT: PL/pgSQL function inline_code_block line 3 during statement block local variable initialization
+do $$
+declare x var_record_colnn := row(1,2);
+begin
+ raise notice 'x = %', x;
+ x := null; -- fail
+end$$;
+NOTICE: x = (1,2)
+ERROR: value for domain var_record_colnn violates check constraint "var_record_colnn_check"
+CONTEXT: PL/pgSQL function inline_code_block line 5 at assignment
+do $$
+declare x var_record_colnn := row(1,2);
+begin
+ raise notice 'x = %', x;
+ x := row(null,null); -- fail
+end$$;
+NOTICE: x = (1,2)
+ERROR: value for domain var_record_colnn violates check constraint "var_record_colnn_check"
+CONTEXT: PL/pgSQL function inline_code_block line 5 at assignment
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index 526aa8f6219..aab92c4711c 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -594,11 +594,11 @@ do_compile(FunctionCallInfo fcinfo,
errhint("The arguments of the trigger can be accessed through TG_NARGS and TG_ARGV instead.")));
/* Add the record for referencing NEW ROW */
- rec = plpgsql_build_record("new", 0, RECORDOID, true);
+ rec = plpgsql_build_record("new", 0, NULL, RECORDOID, true);
function->new_varno = rec->dno;
/* Add the record for referencing OLD ROW */
- rec = plpgsql_build_record("old", 0, RECORDOID, true);
+ rec = plpgsql_build_record("old", 0, NULL, RECORDOID, true);
function->old_varno = rec->dno;
/* Add the variable tg_name */
@@ -1811,7 +1811,7 @@ plpgsql_build_variable(const char *refname, int lineno, PLpgSQL_type *dtype,
var->refname = pstrdup(refname);
var->lineno = lineno;
var->datatype = dtype;
- /* other fields might be filled by caller */
+ /* other fields are left as 0, might be changed by caller */
/* preset to NULL */
var->value = 0;
@@ -1831,7 +1831,8 @@ plpgsql_build_variable(const char *refname, int lineno, PLpgSQL_type *dtype,
/* Composite type -- build a record variable */
PLpgSQL_rec *rec;
- rec = plpgsql_build_record(refname, lineno, dtype->typoid,
+ rec = plpgsql_build_record(refname, lineno,
+ dtype, dtype->typoid,
add2namespace);
result = (PLpgSQL_variable *) rec;
break;
@@ -1856,7 +1857,8 @@ plpgsql_build_variable(const char *refname, int lineno, PLpgSQL_type *dtype,
* Build empty named record variable, and optionally add it to namespace
*/
PLpgSQL_rec *
-plpgsql_build_record(const char *refname, int lineno, Oid rectypeid,
+plpgsql_build_record(const char *refname, int lineno,
+ PLpgSQL_type *dtype, Oid rectypeid,
bool add2namespace)
{
PLpgSQL_rec *rec;
@@ -1865,6 +1867,8 @@ plpgsql_build_record(const char *refname, int lineno, Oid rectypeid,
rec->dtype = PLPGSQL_DTYPE_REC;
rec->refname = pstrdup(refname);
rec->lineno = lineno;
+ /* other fields are left as 0, might be changed by caller */
+ rec->datatype = dtype;
rec->rectypeid = rectypeid;
rec->firstfield = -1;
rec->erh = NULL;
@@ -1899,6 +1903,9 @@ build_row_from_vars(PLpgSQL_variable **vars, int numvars)
int32 typmod;
Oid typcoll;
+ /* Member vars of a row should never be const */
+ Assert(!var->isconst);
+
switch (var->dtype)
{
case PLPGSQL_DTYPE_VAR:
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index f6866743ac1..5054d20ab15 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -539,7 +539,7 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo,
}
else
{
- /* If arg is null, treat it as an empty row */
+ /* If arg is null, set variable to null */
exec_move_row(&estate, (PLpgSQL_variable *) rec,
NULL, NULL);
}
@@ -1539,11 +1539,9 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
{
/*
* If needed, give the datatype a chance to reject
- * NULLs, by assigning a NULL to the variable. We
+ * NULLs, by assigning a NULL to the variable. We
* claim the value is of type UNKNOWN, not the var's
- * datatype, else coercion will be skipped. (Do this
- * before the notnull check to be consistent with
- * exec_assign_value.)
+ * datatype, else coercion will be skipped.
*/
if (var->datatype->typtype == TYPTYPE_DOMAIN)
exec_assign_value(estate,
@@ -1553,11 +1551,8 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
UNKNOWNOID,
-1);
- if (var->notnull)
- ereport(ERROR,
- (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
- errmsg("variable \"%s\" declared NOT NULL cannot default to NULL",
- var->refname)));
+ /* parser should have rejected NOT NULL */
+ Assert(!var->notnull);
}
else
{
@@ -1571,9 +1566,28 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
{
PLpgSQL_rec *rec = (PLpgSQL_rec *) datum;
- if (rec->erh)
- DeleteExpandedObject(ExpandedRecordGetDatum(rec->erh));
- rec->erh = NULL;
+ /*
+ * Deletion of any existing object will be handled during
+ * the assignments below, and in some cases it's more
+ * efficient for us not to get rid of it beforehand.
+ */
+ if (rec->default_val == NULL)
+ {
+ /*
+ * If needed, give the datatype a chance to reject
+ * NULLs, by assigning a NULL to the variable.
+ */
+ exec_move_row(estate, (PLpgSQL_variable *) rec,
+ NULL, NULL);
+
+ /* parser should have rejected NOT NULL */
+ Assert(!rec->notnull);
+ }
+ else
+ {
+ exec_assign_expr(estate, (PLpgSQL_datum *) rec,
+ rec->default_val);
+ }
}
break;
@@ -4725,7 +4739,13 @@ exec_assign_value(PLpgSQL_execstate *estate,
if (isNull)
{
- /* If source is null, just assign nulls to the record */
+ if (rec->notnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("null value cannot be assigned to variable \"%s\" declared NOT NULL",
+ rec->refname)));
+
+ /* Set variable to a simple NULL */
exec_move_row(estate, (PLpgSQL_variable *) rec,
NULL, NULL);
}
@@ -6375,9 +6395,27 @@ exec_move_row(PLpgSQL_execstate *estate,
*/
if (tupdesc == NULL)
{
- if (rec->erh)
- DeleteExpandedObject(ExpandedRecordGetDatum(rec->erh));
- rec->erh = NULL;
+ if (rec->datatype &&
+ rec->datatype->typtype == TYPTYPE_DOMAIN)
+ {
+ /*
+ * If it's a composite domain, NULL might not be a legal
+ * value, so we instead need to make an empty expanded record
+ * and ensure that domain type checking gets done. If there
+ * is already an expanded record, piggyback on its lookups.
+ */
+ newerh = make_expanded_record_for_rec(estate, rec,
+ NULL, rec->erh);
+ expanded_record_set_tuple(newerh, NULL, false);
+ assign_record_var(estate, rec, newerh);
+ }
+ else
+ {
+ /* Just clear it to NULL */
+ if (rec->erh)
+ DeleteExpandedObject(ExpandedRecordGetDatum(rec->erh));
+ rec->erh = NULL;
+ }
return;
}
diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c
index 379fd69f44a..b986fc39b38 100644
--- a/src/pl/plpgsql/src/pl_funcs.c
+++ b/src/pl/plpgsql/src/pl_funcs.c
@@ -740,6 +740,11 @@ plpgsql_free_function_memory(PLpgSQL_function *func)
case PLPGSQL_DTYPE_ROW:
break;
case PLPGSQL_DTYPE_REC:
+ {
+ PLpgSQL_rec *rec = (PLpgSQL_rec *) d;
+
+ free_expr(rec->default_val);
+ }
break;
case PLPGSQL_DTYPE_RECFIELD:
break;
@@ -1633,6 +1638,16 @@ plpgsql_dumptree(PLpgSQL_function *func)
printf("REC %-16s typoid %u\n",
((PLpgSQL_rec *) d)->refname,
((PLpgSQL_rec *) d)->rectypeid);
+ if (((PLpgSQL_rec *) d)->isconst)
+ printf(" CONSTANT\n");
+ if (((PLpgSQL_rec *) d)->notnull)
+ printf(" NOT NULL\n");
+ if (((PLpgSQL_rec *) d)->default_val != NULL)
+ {
+ printf(" DEFAULT ");
+ dump_expr(((PLpgSQL_rec *) d)->default_val);
+ printf("\n");
+ }
break;
case PLPGSQL_DTYPE_RECFIELD:
printf("RECFIELD %-16s of REC %d\n",
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 5bf45942a60..688fbd6531e 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -505,37 +505,20 @@ decl_statement : decl_varname decl_const decl_datatype decl_collate decl_notnull
var = plpgsql_build_variable($1.name, $1.lineno,
$3, true);
- if ($2)
- {
- if (var->dtype == PLPGSQL_DTYPE_VAR)
- ((PLpgSQL_var *) var)->isconst = $2;
- else
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("record variable cannot be CONSTANT"),
- parser_errposition(@2)));
- }
- if ($5)
- {
- if (var->dtype == PLPGSQL_DTYPE_VAR)
- ((PLpgSQL_var *) var)->notnull = $5;
- else
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("record variable cannot be NOT NULL"),
- parser_errposition(@4)));
+ var->isconst = $2;
+ var->notnull = $5;
+ var->default_val = $6;
- }
- if ($6 != NULL)
- {
- if (var->dtype == PLPGSQL_DTYPE_VAR)
- ((PLpgSQL_var *) var)->default_val = $6;
- else
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("default value for record variable is not supported"),
- parser_errposition(@5)));
- }
+ /*
+ * The combination of NOT NULL without an initializer
+ * can't work, so let's reject it at compile time.
+ */
+ if (var->notnull && var->default_val == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("variable \"%s\" must have a default value, since it's declared NOT NULL",
+ var->refname),
+ parser_errposition(@5)));
}
| decl_varname K_ALIAS K_FOR decl_aliasitem ';'
{
@@ -635,6 +618,7 @@ decl_cursor_args :
foreach (l, $2)
{
PLpgSQL_variable *arg = (PLpgSQL_variable *) lfirst(l);
+ Assert(!arg->isconst);
new->fieldnames[i] = arg->refname;
new->varnos[i] = arg->dno;
i++;
@@ -1385,6 +1369,7 @@ for_control : for_variable K_IN
new->var = (PLpgSQL_variable *)
plpgsql_build_record($1.name,
$1.lineno,
+ NULL,
RECORDOID,
true);
@@ -2237,7 +2222,7 @@ exception_sect :
-1,
plpgsql_curr_compile->fn_input_collation),
true);
- ((PLpgSQL_var *) var)->isconst = true;
+ var->isconst = true;
new->sqlstate_varno = var->dno;
var = plpgsql_build_variable("sqlerrm", lineno,
@@ -2245,7 +2230,7 @@ exception_sect :
-1,
plpgsql_curr_compile->fn_input_collation),
true);
- ((PLpgSQL_var *) var)->isconst = true;
+ var->isconst = true;
new->sqlerrm_varno = var->dno;
$<exception_block>$ = new;
@@ -3321,24 +3306,26 @@ check_assignable(PLpgSQL_datum *datum, int location)
{
case PLPGSQL_DTYPE_VAR:
case PLPGSQL_DTYPE_PROMISE:
- if (((PLpgSQL_var *) datum)->isconst)
+ case PLPGSQL_DTYPE_REC:
+ if (((PLpgSQL_variable *) datum)->isconst)
ereport(ERROR,
(errcode(ERRCODE_ERROR_IN_ASSIGNMENT),
- errmsg("\"%s\" is declared CONSTANT",
- ((PLpgSQL_var *) datum)->refname),
+ errmsg("variable \"%s\" is declared CONSTANT",
+ ((PLpgSQL_variable *) datum)->refname),
parser_errposition(location)));
break;
case PLPGSQL_DTYPE_ROW:
- /* always assignable? Shouldn't we check member vars? */
- break;
- case PLPGSQL_DTYPE_REC:
- /* always assignable? What about NEW/OLD? */
+ /* always assignable; member vars were checked at compile time */
break;
case PLPGSQL_DTYPE_RECFIELD:
- /* always assignable? */
+ /* assignable if parent record is */
+ check_assignable(plpgsql_Datums[((PLpgSQL_recfield *) datum)->recparentno],
+ location);
break;
case PLPGSQL_DTYPE_ARRAYELEM:
- /* always assignable? */
+ /* assignable if parent array is */
+ check_assignable(plpgsql_Datums[((PLpgSQL_arrayelem *) datum)->arrayparentno],
+ location);
break;
default:
elog(ERROR, "unrecognized dtype: %d", datum->dtype);
@@ -3463,9 +3450,8 @@ read_into_scalar_list(char *initial_name,
*/
plpgsql_push_back_token(tok);
- row = palloc(sizeof(PLpgSQL_row));
+ row = palloc0(sizeof(PLpgSQL_row));
row->dtype = PLPGSQL_DTYPE_ROW;
- row->refname = pstrdup("*internal*");
row->lineno = plpgsql_location_to_lineno(initial_location);
row->rowtupdesc = NULL;
row->nfields = nfields;
@@ -3498,9 +3484,8 @@ make_scalar_list1(char *initial_name,
check_assignable(initial_datum, location);
- row = palloc(sizeof(PLpgSQL_row));
+ row = palloc0(sizeof(PLpgSQL_row));
row->dtype = PLPGSQL_DTYPE_ROW;
- row->refname = pstrdup("*internal*");
row->lineno = lineno;
row->rowtupdesc = NULL;
row->nfields = 1;
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 01b89a5ffa3..c2449f03cf9 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -264,6 +264,9 @@ typedef struct PLpgSQL_variable
int dno;
char *refname;
int lineno;
+ bool isconst;
+ bool notnull;
+ PLpgSQL_expr *default_val;
} PLpgSQL_variable;
/*
@@ -283,12 +286,12 @@ typedef struct PLpgSQL_var
int dno;
char *refname;
int lineno;
- /* end of PLpgSQL_variable fields */
-
bool isconst;
bool notnull;
- PLpgSQL_type *datatype;
PLpgSQL_expr *default_val;
+ /* end of PLpgSQL_variable fields */
+
+ PLpgSQL_type *datatype;
/*
* Variables declared as CURSOR FOR <query> are mostly like ordinary
@@ -320,6 +323,11 @@ typedef struct PLpgSQL_var
*
* Note that there's no way to name the row as such from PL/pgSQL code,
* so many functions don't need to support these.
+ *
+ * refname, isconst, notnull, and default_val are unsupported (and hence
+ * always zero/null) for a row. The member variables of a row should have
+ * been checked to be writable at compile time, so isconst is correctly set
+ * to false. notnull and default_val aren't applicable.
*/
typedef struct PLpgSQL_row
{
@@ -327,6 +335,9 @@ typedef struct PLpgSQL_row
int dno;
char *refname;
int lineno;
+ bool isconst;
+ bool notnull;
+ PLpgSQL_expr *default_val;
/* end of PLpgSQL_variable fields */
/*
@@ -350,11 +361,18 @@ typedef struct PLpgSQL_rec
int dno;
char *refname;
int lineno;
+ bool isconst;
+ bool notnull;
+ PLpgSQL_expr *default_val;
/* end of PLpgSQL_variable fields */
+ PLpgSQL_type *datatype; /* can be NULL, if rectypeid is RECORDOID */
Oid rectypeid; /* declared type of variable */
/* RECFIELDs for this record are chained together for easy access */
int firstfield; /* dno of first RECFIELD, or -1 if none */
+
+ /* Fields below here can change at runtime */
+
/* We always store record variables as "expanded" records */
ExpandedRecordHeader *erh;
} PLpgSQL_rec;
@@ -1141,7 +1159,8 @@ extern PLpgSQL_variable *plpgsql_build_variable(const char *refname, int lineno,
PLpgSQL_type *dtype,
bool add2namespace);
extern PLpgSQL_rec *plpgsql_build_record(const char *refname, int lineno,
- Oid rectypeid, bool add2namespace);
+ PLpgSQL_type *dtype, Oid rectypeid,
+ bool add2namespace);
extern PLpgSQL_recfield *plpgsql_build_recfield(PLpgSQL_rec *rec,
const char *fldname);
extern int plpgsql_recognize_err_condition(const char *condname,
diff --git a/src/pl/plpgsql/src/sql/plpgsql_varprops.sql b/src/pl/plpgsql/src/sql/plpgsql_varprops.sql
new file mode 100644
index 00000000000..c0e7f95f4e2
--- /dev/null
+++ b/src/pl/plpgsql/src/sql/plpgsql_varprops.sql
@@ -0,0 +1,249 @@
+--
+-- Tests for PL/pgSQL variable properties: CONSTANT, NOT NULL, initializers
+--
+
+create type var_record as (f1 int4, f2 int4);
+create domain int_nn as int not null;
+create domain var_record_nn as var_record not null;
+create domain var_record_colnn as var_record check((value).f2 is not null);
+
+-- CONSTANT
+
+do $$
+declare x constant int := 42;
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x constant int;
+begin
+ x := 42; -- fail
+end$$;
+
+do $$
+declare x constant int; y int;
+begin
+ for x, y in select 1, 2 loop -- fail
+ end loop;
+end$$;
+
+do $$
+declare x constant int[];
+begin
+ x[1] := 42; -- fail
+end$$;
+
+do $$
+declare x constant int[]; y int;
+begin
+ for x[1], y in select 1, 2 loop -- fail (currently, unsupported syntax)
+ end loop;
+end$$;
+
+do $$
+declare x constant var_record;
+begin
+ x.f1 := 42; -- fail
+end$$;
+
+do $$
+declare x constant var_record; y int;
+begin
+ for x.f1, y in select 1, 2 loop -- fail
+ end loop;
+end$$;
+
+-- initializer expressions
+
+do $$
+declare x int := sin(0);
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x int := 1/0; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x bigint[] := array[1,3,5];
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x record := row(1,2,3);
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x var_record := row(1,2);
+begin
+ raise notice 'x = %', x;
+end$$;
+
+-- NOT NULL
+
+do $$
+declare x int not null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x int not null := 42;
+begin
+ raise notice 'x = %', x;
+ x := null; -- fail
+end$$;
+
+do $$
+declare x int not null := null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x record not null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x record not null := row(42);
+begin
+ raise notice 'x = %', x;
+ x := row(null); -- ok
+ raise notice 'x = %', x;
+ x := null; -- fail
+end$$;
+
+do $$
+declare x record not null := null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x var_record not null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x var_record not null := row(41,42);
+begin
+ raise notice 'x = %', x;
+ x := row(null,null); -- ok
+ raise notice 'x = %', x;
+ x := null; -- fail
+end$$;
+
+do $$
+declare x var_record not null := null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+
+-- Check that variables are reinitialized on block re-entry.
+
+\set VERBOSITY terse \\ -- needed for output stability
+do $$
+begin
+ for i in 1..3 loop
+ declare
+ x int;
+ y int := i;
+ r record;
+ c var_record;
+ begin
+ if i = 1 then
+ x := 42;
+ r := row(i, i+1);
+ c := row(i, i+1);
+ end if;
+ raise notice 'x = %', x;
+ raise notice 'y = %', y;
+ raise notice 'r = %', r;
+ raise notice 'c = %', c;
+ end;
+ end loop;
+end$$;
+\set VERBOSITY default
+
+-- Check enforcement of domain constraints during initialization
+
+do $$
+declare x int_nn; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x int_nn := null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x int_nn := 42;
+begin
+ raise notice 'x = %', x;
+ x := null; -- fail
+end$$;
+
+do $$
+declare x var_record_nn; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x var_record_nn := null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x var_record_nn := row(1,2);
+begin
+ raise notice 'x = %', x;
+ x := row(null,null); -- ok
+ x := null; -- fail
+end$$;
+
+do $$
+declare x var_record_colnn; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x var_record_colnn := null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x var_record_colnn := row(1,null); -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x var_record_colnn := row(1,2);
+begin
+ raise notice 'x = %', x;
+ x := null; -- fail
+end$$;
+
+do $$
+declare x var_record_colnn := row(1,2);
+begin
+ raise notice 'x = %', x;
+ x := row(null,null); -- fail
+end$$;
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 0c1da088697..d294e536345 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -4586,42 +4586,6 @@ select scope_test();
(1 row)
drop function scope_test();
--- Check that variables are reinitialized on block re-entry.
-\set VERBOSITY terse \\ -- needed for output stability
-do $$
-begin
- for i in 1..3 loop
- declare
- x int;
- y int := i;
- r record;
- c int8_tbl;
- begin
- if i = 1 then
- x := 42;
- r := row(i, i+1);
- c := row(i, i+1);
- end if;
- raise notice 'x = %', x;
- raise notice 'y = %', y;
- raise notice 'r = %', r;
- raise notice 'c = %', c;
- end;
- end loop;
-end$$;
-NOTICE: x = 42
-NOTICE: y = 1
-NOTICE: r = (1,2)
-NOTICE: c = (1,2)
-NOTICE: x = <NULL>
-NOTICE: y = 2
-NOTICE: r = <NULL>
-NOTICE: c = <NULL>
-NOTICE: x = <NULL>
-NOTICE: y = 3
-NOTICE: r = <NULL>
-NOTICE: c = <NULL>
-\set VERBOSITY default
-- Check handling of conflicts between plpgsql vars and table columns.
set plpgsql.variable_conflict = error;
create function conflict_test() returns setof int8_tbl as $$
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index 6bdcfe7cc58..f17cf0b49bf 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -3735,32 +3735,6 @@ select scope_test();
drop function scope_test();
--- Check that variables are reinitialized on block re-entry.
-
-\set VERBOSITY terse \\ -- needed for output stability
-do $$
-begin
- for i in 1..3 loop
- declare
- x int;
- y int := i;
- r record;
- c int8_tbl;
- begin
- if i = 1 then
- x := 42;
- r := row(i, i+1);
- c := row(i, i+1);
- end if;
- raise notice 'x = %', x;
- raise notice 'y = %', y;
- raise notice 'r = %', r;
- raise notice 'c = %', c;
- end;
- end loop;
-end$$;
-\set VERBOSITY default
-
-- Check handling of conflicts between plpgsql vars and table columns.
set plpgsql.variable_conflict = error;