I wrote:
> 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.
Here's a version rebased up to HEAD, with a trivial merge conflict fixed.
This now needs to be applied over the patches in
https://postgr.es/m/[email protected]
and
https://postgr.es/m/[email protected]
regards, tom lane
diff --git a/src/pl/plpgsql/src/Makefile b/src/pl/plpgsql/src/Makefile
index 2190eab..3ac64e2 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_control plpgsql_record plpgsql_transaction
all: all-lib
--- 26,33 ----
REGRESS_OPTS = --dbname=$(PL_TESTDB)
! 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
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 526aa8f..aab92c4 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 d578c56..62010d8 100644
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
*************** plpgsql_exec_function(PLpgSQL_function *
*** 539,545 ****
}
else
{
! /* If arg is null, treat it as an empty row */
exec_move_row(&estate, (PLpgSQL_variable *) rec,
NULL, NULL);
}
--- 539,545 ----
}
else
{
! /* If arg is null, set variable to null */
exec_move_row(&estate, (PLpgSQL_variable *) rec,
NULL, NULL);
}
*************** exec_stmt_block(PLpgSQL_execstate *estat
*** 1539,1549 ****
{
/*
* 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,
--- 1539,1547 ----
{
/*
* 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
*** 1553,1563 ****
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
{
--- 1551,1558 ----
UNKNOWNOID,
-1);
! /* parser should have rejected NOT NULL */
! Assert(!var->notnull);
}
else
{
*************** exec_stmt_block(PLpgSQL_execstate *estat
*** 1571,1579 ****
{
PLpgSQL_rec *rec = (PLpgSQL_rec *) datum;
! if (rec->erh)
! DeleteExpandedObject(ExpandedRecordGetDatum(rec->erh));
! rec->erh = NULL;
}
break;
--- 1566,1593 ----
{
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
*** 4725,4731 ****
if (isNull)
{
! /* If source is null, just assign nulls to the record */
exec_move_row(estate, (PLpgSQL_variable *) rec,
NULL, NULL);
}
--- 4739,4751 ----
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,
*** 6375,6383 ****
*/
if (tupdesc == NULL)
{
! if (rec->erh)
! DeleteExpandedObject(ExpandedRecordGetDatum(rec->erh));
! rec->erh = NULL;
return;
}
--- 6395,6421 ----
*/
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 379fd69..b986fc3 100644
*** a/src/pl/plpgsql/src/pl_funcs.c
--- b/src/pl/plpgsql/src/pl_funcs.c
*************** plpgsql_free_function_memory(PLpgSQL_fun
*** 740,745 ****
--- 740,750 ----
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)
*** 1633,1638 ****
--- 1638,1653 ----
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 5bf4594..688fbd6 100644
*** a/src/pl/plpgsql/src/pl_gram.y
--- b/src/pl/plpgsql/src/pl_gram.y
*************** decl_statement : decl_varname decl_const
*** 505,541 ****
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 ';'
{
--- 505,524 ----
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 :
*** 635,640 ****
--- 618,624 ----
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
*** 1385,1390 ****
--- 1369,1375 ----
new->var = (PLpgSQL_variable *)
plpgsql_build_record($1.name,
$1.lineno,
+ NULL,
RECORDOID,
true);
*************** exception_sect :
*** 2237,2243 ****
-1,
plpgsql_curr_compile->fn_input_collation),
true);
! ((PLpgSQL_var *) var)->isconst = true;
new->sqlstate_varno = var->dno;
var = plpgsql_build_variable("sqlerrm", lineno,
--- 2222,2228 ----
-1,
plpgsql_curr_compile->fn_input_collation),
true);
! var->isconst = true;
new->sqlstate_varno = var->dno;
var = plpgsql_build_variable("sqlerrm", lineno,
*************** exception_sect :
*** 2245,2251 ****
-1,
plpgsql_curr_compile->fn_input_collation),
true);
! ((PLpgSQL_var *) var)->isconst = true;
new->sqlerrm_varno = var->dno;
$<exception_block>$ = new;
--- 2230,2236 ----
-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
*** 3321,3344 ****
{
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);
--- 3306,3331 ----
{
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
*** 3463,3471 ****
*/
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;
--- 3450,3457 ----
*/
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,
*** 3498,3506 ****
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;
--- 3484,3491 ----
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 01b89a5..c2449f0 100644
*** a/src/pl/plpgsql/src/plpgsql.h
--- b/src/pl/plpgsql/src/plpgsql.h
*************** typedef struct PLpgSQL_variable
*** 264,269 ****
--- 264,272 ----
int dno;
char *refname;
int lineno;
+ bool isconst;
+ bool notnull;
+ PLpgSQL_expr *default_val;
} PLpgSQL_variable;
/*
*************** typedef struct PLpgSQL_var
*** 283,294 ****
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
--- 286,297 ----
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
*** 320,325 ****
--- 323,333 ----
*
* 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
*** 327,332 ****
--- 335,343 ----
int dno;
char *refname;
int lineno;
+ bool isconst;
+ bool notnull;
+ PLpgSQL_expr *default_val;
/* end of PLpgSQL_variable fields */
/*
*************** typedef struct PLpgSQL_rec
*** 350,360 ****
--- 361,378 ----
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
*** 1141,1147 ****
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,
--- 1159,1166 ----
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 0c1da08..d294e53 100644
*** a/src/test/regress/expected/plpgsql.out
--- b/src/test/regress/expected/plpgsql.out
*************** select scope_test();
*** 4586,4627 ****
(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 $$
--- 4586,4591 ----
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index 6bdcfe7..f17cf0b 100644
*** a/src/test/regress/sql/plpgsql.sql
--- b/src/test/regress/sql/plpgsql.sql
*************** select scope_test();
*** 3735,3766 ****
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;
--- 3735,3740 ----