I said a couple of times in recent threads that it wouldn't be too hard
to implement $SUBJECT given the other patches I've been working on.
Attached is proof of the pudding. This needs to be applied on top of
the patches in
https://postgr.es/m/[email protected]
and
https://postgr.es/m/[email protected]
It's pretty straightforward really. Worth noting is that this also
fixes the null-domain-value issues I mentioned as being a loose end
in the first of the above-referenced messages. Also, I created a
new plpgsql test file for these features, and moved the one relevant
existing test case into that file.
I was a bit disappointed to find that no documentation changes seem
needed, because the SGML docs fail to acknowledge that these cases
didn't work ...
regards, tom lane
diff --git a/src/pl/plpgsql/src/Makefile b/src/pl/plpgsql/src/Makefile
index f9a3616..ad93e3e 100644
*** a/src/pl/plpgsql/src/Makefile
--- b/src/pl/plpgsql/src/Makefile
*************** DATA = plpgsql.control plpgsql--1.0.sql
*** 26,32 ****
REGRESS_OPTS = --dbname=$(PL_TESTDB)
! REGRESS = plpgsql_call plpgsql_record
all: all-lib
--- 26,32 ----
REGRESS_OPTS = --dbname=$(PL_TESTDB)
! REGRESS = plpgsql_call plpgsql_record 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
index ...109056c .
*** a/src/pl/plpgsql/src/expected/plpgsql_varprops.out
--- b/src/pl/plpgsql/src/expected/plpgsql_varprops.out
***************
*** 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 5c2e463..576924f 100644
*** a/src/pl/plpgsql/src/pl_comp.c
--- b/src/pl/plpgsql/src/pl_comp.c
*************** do_compile(FunctionCallInfo fcinfo,
*** 594,604 ****
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);
function->new_varno = rec->dno;
/* Add the record for referencing OLD ROW */
! rec = plpgsql_build_record("old", 0, RECORDOID, true);
function->old_varno = rec->dno;
/* Add the variable tg_name */
--- 594,604 ----
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, NULL, RECORDOID, true);
function->new_varno = rec->dno;
/* Add the record for referencing OLD ROW */
! rec = plpgsql_build_record("old", 0, NULL, RECORDOID, true);
function->old_varno = rec->dno;
/* Add the variable tg_name */
*************** plpgsql_build_variable(const char *refna
*** 1811,1817 ****
var->refname = pstrdup(refname);
var->lineno = lineno;
var->datatype = dtype;
! /* other fields might be filled by caller */
/* preset to NULL */
var->value = 0;
--- 1811,1817 ----
var->refname = pstrdup(refname);
var->lineno = lineno;
var->datatype = dtype;
! /* other fields are left as 0, might be changed by caller */
/* preset to NULL */
var->value = 0;
*************** plpgsql_build_variable(const char *refna
*** 1831,1837 ****
/* Composite type -- build a record variable */
PLpgSQL_rec *rec;
! rec = plpgsql_build_record(refname, lineno, dtype->typoid,
add2namespace);
result = (PLpgSQL_variable *) rec;
break;
--- 1831,1838 ----
/* Composite type -- build a record variable */
PLpgSQL_rec *rec;
! rec = plpgsql_build_record(refname, lineno,
! dtype, dtype->typoid,
add2namespace);
result = (PLpgSQL_variable *) rec;
break;
*************** plpgsql_build_variable(const char *refna
*** 1856,1862 ****
* Build empty named record variable, and optionally add it to namespace
*/
PLpgSQL_rec *
! plpgsql_build_record(const char *refname, int lineno, Oid rectypeid,
bool add2namespace)
{
PLpgSQL_rec *rec;
--- 1857,1864 ----
* Build empty named record variable, and optionally add it to namespace
*/
PLpgSQL_rec *
! plpgsql_build_record(const char *refname, int lineno,
! PLpgSQL_type *dtype, Oid rectypeid,
bool add2namespace)
{
PLpgSQL_rec *rec;
*************** plpgsql_build_record(const char *refname
*** 1865,1870 ****
--- 1867,1874 ----
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;
*************** build_row_from_vars(PLpgSQL_variable **v
*** 1899,1904 ****
--- 1903,1911 ----
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 c9fbd83..e97be40 100644
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
*************** plpgsql_exec_function(PLpgSQL_function *
*** 461,467 ****
}
else
{
! /* If arg is null, treat it as an empty row */
exec_move_row(&estate, (PLpgSQL_variable *) rec,
NULL, NULL);
}
--- 461,467 ----
}
else
{
! /* If arg is null, set variable to null */
exec_move_row(&estate, (PLpgSQL_variable *) rec,
NULL, NULL);
}
*************** exec_stmt_block(PLpgSQL_execstate *estat
*** 1461,1471 ****
{
/*
* If needed, give the datatype a chance to reject
! * 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.)
*/
if (var->datatype->typtype == TYPTYPE_DOMAIN)
exec_assign_value(estate,
--- 1461,1469 ----
{
/*
* If needed, give the datatype a chance to reject
! * 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.
*/
if (var->datatype->typtype == TYPTYPE_DOMAIN)
exec_assign_value(estate,
*************** exec_stmt_block(PLpgSQL_execstate *estat
*** 1475,1485 ****
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)));
}
else
{
--- 1473,1480 ----
UNKNOWNOID,
-1);
! /* parser should have rejected NOT NULL */
! Assert(!var->notnull);
}
else
{
*************** exec_stmt_block(PLpgSQL_execstate *estat
*** 1493,1501 ****
{
PLpgSQL_rec *rec = (PLpgSQL_rec *) datum;
! if (rec->erh)
! DeleteExpandedObject(ExpandedRecordGetDatum(rec->erh));
! rec->erh = NULL;
}
break;
--- 1488,1515 ----
{
PLpgSQL_rec *rec = (PLpgSQL_rec *) datum;
! /*
! * 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;
*************** exec_assign_value(PLpgSQL_execstate *est
*** 4739,4745 ****
if (isNull)
{
! /* If source is null, just assign nulls to the record */
exec_move_row(estate, (PLpgSQL_variable *) rec,
NULL, NULL);
}
--- 4753,4765 ----
if (isNull)
{
! 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);
}
*************** exec_move_row(PLpgSQL_execstate *estate,
*** 6442,6450 ****
*/
if (tupdesc == NULL)
{
! if (rec->erh)
! DeleteExpandedObject(ExpandedRecordGetDatum(rec->erh));
! rec->erh = NULL;
return;
}
--- 6462,6488 ----
*/
if (tupdesc == 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 f499d96..a2634c4 100644
*** a/src/pl/plpgsql/src/pl_funcs.c
--- b/src/pl/plpgsql/src/pl_funcs.c
*************** plpgsql_free_function_memory(PLpgSQL_fun
*** 718,723 ****
--- 718,728 ----
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;
*************** plpgsql_dumptree(PLpgSQL_function *func)
*** 1589,1594 ****
--- 1594,1609 ----
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 df88ba3..e37f548 100644
*** a/src/pl/plpgsql/src/pl_gram.y
--- b/src/pl/plpgsql/src/pl_gram.y
*************** decl_statement : decl_varname decl_const
*** 502,538 ****
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)));
! }
! 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)));
! }
}
| decl_varname K_ALIAS K_FOR decl_aliasitem ';'
{
--- 502,521 ----
var = plpgsql_build_variable($1.name, $1.lineno,
$3, true);
! var->isconst = $2;
! var->notnull = $5;
! var->default_val = $6;
! /*
! * 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 ';'
{
*************** decl_cursor_args :
*** 632,637 ****
--- 615,621 ----
foreach (l, $2)
{
PLpgSQL_variable *arg = (PLpgSQL_variable *) lfirst(l);
+ Assert(!arg->isconst);
new->fieldnames[i] = arg->refname;
new->varnos[i] = arg->dno;
i++;
*************** for_control : for_variable K_IN
*** 1378,1383 ****
--- 1362,1368 ----
new->var = (PLpgSQL_variable *)
plpgsql_build_record($1.name,
$1.lineno,
+ NULL,
RECORDOID,
true);
*************** exception_sect :
*** 2205,2211 ****
-1,
plpgsql_curr_compile->fn_input_collation),
true);
! ((PLpgSQL_var *) var)->isconst = true;
new->sqlstate_varno = var->dno;
var = plpgsql_build_variable("sqlerrm", lineno,
--- 2190,2196 ----
-1,
plpgsql_curr_compile->fn_input_collation),
true);
! var->isconst = true;
new->sqlstate_varno = var->dno;
var = plpgsql_build_variable("sqlerrm", lineno,
*************** exception_sect :
*** 2213,2219 ****
-1,
plpgsql_curr_compile->fn_input_collation),
true);
! ((PLpgSQL_var *) var)->isconst = true;
new->sqlerrm_varno = var->dno;
$<exception_block>$ = new;
--- 2198,2204 ----
-1,
plpgsql_curr_compile->fn_input_collation),
true);
! var->isconst = true;
new->sqlerrm_varno = var->dno;
$<exception_block>$ = new;
*************** check_assignable(PLpgSQL_datum *datum, i
*** 3287,3310 ****
{
case PLPGSQL_DTYPE_VAR:
case PLPGSQL_DTYPE_PROMISE:
! if (((PLpgSQL_var *) datum)->isconst)
ereport(ERROR,
(errcode(ERRCODE_ERROR_IN_ASSIGNMENT),
! errmsg("\"%s\" is declared CONSTANT",
! ((PLpgSQL_var *) 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? */
break;
case PLPGSQL_DTYPE_RECFIELD:
! /* always assignable? */
break;
case PLPGSQL_DTYPE_ARRAYELEM:
! /* always assignable? */
break;
default:
elog(ERROR, "unrecognized dtype: %d", datum->dtype);
--- 3272,3297 ----
{
case PLPGSQL_DTYPE_VAR:
case PLPGSQL_DTYPE_PROMISE:
! case PLPGSQL_DTYPE_REC:
! if (((PLpgSQL_variable *) datum)->isconst)
ereport(ERROR,
(errcode(ERRCODE_ERROR_IN_ASSIGNMENT),
! errmsg("variable \"%s\" is declared CONSTANT",
! ((PLpgSQL_variable *) datum)->refname),
parser_errposition(location)));
break;
case PLPGSQL_DTYPE_ROW:
! /* always assignable; member vars were checked at compile time */
break;
case PLPGSQL_DTYPE_RECFIELD:
! /* assignable if parent record is */
! check_assignable(plpgsql_Datums[((PLpgSQL_recfield *) datum)->recparentno],
! location);
break;
case PLPGSQL_DTYPE_ARRAYELEM:
! /* assignable if parent array is */
! check_assignable(plpgsql_Datums[((PLpgSQL_arrayelem *) datum)->arrayparentno],
! location);
break;
default:
elog(ERROR, "unrecognized dtype: %d", datum->dtype);
*************** read_into_scalar_list(char *initial_name
*** 3429,3437 ****
*/
plpgsql_push_back_token(tok);
! row = palloc(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;
--- 3416,3423 ----
*/
plpgsql_push_back_token(tok);
! row = palloc0(sizeof(PLpgSQL_row));
row->dtype = PLPGSQL_DTYPE_ROW;
row->lineno = plpgsql_location_to_lineno(initial_location);
row->rowtupdesc = NULL;
row->nfields = nfields;
*************** make_scalar_list1(char *initial_name,
*** 3464,3472 ****
check_assignable(initial_datum, location);
! row = palloc(sizeof(PLpgSQL_row));
row->dtype = PLPGSQL_DTYPE_ROW;
- row->refname = pstrdup("*internal*");
row->lineno = lineno;
row->rowtupdesc = NULL;
row->nfields = 1;
--- 3450,3457 ----
check_assignable(initial_datum, location);
! row = palloc0(sizeof(PLpgSQL_row));
row->dtype = PLPGSQL_DTYPE_ROW;
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 96e92b2..c1c2b40 100644
*** a/src/pl/plpgsql/src/plpgsql.h
--- b/src/pl/plpgsql/src/plpgsql.h
*************** typedef struct PLpgSQL_variable
*** 262,267 ****
--- 262,270 ----
int dno;
char *refname;
int lineno;
+ bool isconst;
+ bool notnull;
+ PLpgSQL_expr *default_val;
} PLpgSQL_variable;
/*
*************** typedef struct PLpgSQL_var
*** 281,292 ****
int dno;
char *refname;
int lineno;
- /* end of PLpgSQL_variable fields */
-
bool isconst;
bool notnull;
- PLpgSQL_type *datatype;
PLpgSQL_expr *default_val;
/*
* Variables declared as CURSOR FOR <query> are mostly like ordinary
--- 284,295 ----
int dno;
char *refname;
int lineno;
bool isconst;
bool notnull;
PLpgSQL_expr *default_val;
+ /* end of PLpgSQL_variable fields */
+
+ PLpgSQL_type *datatype;
/*
* Variables declared as CURSOR FOR <query> are mostly like ordinary
*************** typedef struct PLpgSQL_var
*** 318,323 ****
--- 321,331 ----
*
* 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
{
*************** typedef struct PLpgSQL_row
*** 325,330 ****
--- 333,341 ----
int dno;
char *refname;
int lineno;
+ bool isconst;
+ bool notnull;
+ PLpgSQL_expr *default_val;
/* end of PLpgSQL_variable fields */
/*
*************** typedef struct PLpgSQL_rec
*** 348,358 ****
--- 359,376 ----
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;
*************** extern PLpgSQL_variable *plpgsql_build_v
*** 1121,1127 ****
PLpgSQL_type *dtype,
bool add2namespace);
extern PLpgSQL_rec *plpgsql_build_record(const char *refname, int lineno,
! 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,
--- 1139,1146 ----
PLpgSQL_type *dtype,
bool add2namespace);
extern PLpgSQL_rec *plpgsql_build_record(const char *refname, int lineno,
! 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
index ...c0e7f95 .
*** a/src/pl/plpgsql/src/sql/plpgsql_varprops.sql
--- b/src/pl/plpgsql/src/sql/plpgsql_varprops.sql
***************
*** 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 e38fe90..bb35326 100644
*** a/src/test/regress/expected/plpgsql.out
--- b/src/test/regress/expected/plpgsql.out
*************** select scope_test();
*** 5025,5066 ****
(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 $$
--- 5025,5030 ----
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index 132f912..6620ea6 100644
*** a/src/test/regress/sql/plpgsql.sql
--- b/src/test/regress/sql/plpgsql.sql
*************** select scope_test();
*** 4014,4045 ****
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;
--- 4014,4019 ----