Support CONSTANT/NOT NULL/initial value for plpgsql composite variables.

These features were never implemented previously for composite or record
variables ... not that the documentation admitted it, so there's no doc
updates here.

This also fixes some issues concerning enforcing DOMAIN NOT NULL
constraints against plpgsql variables, although I'm not sure that
that topic is completely dealt with.

I created a new plpgsql test file for these features, and moved the
one relevant existing test case into that file.

Tom Lane, reviewed by Daniel Gustafsson

Discussion: https://postgr.es/m/18362.1514605650@sss.pgh.pa.us
This commit is contained in:
Tom Lane 2018-02-13 22:15:08 -05:00
parent fd333bc763
commit f9263006d8
10 changed files with 686 additions and 134 deletions

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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;
}

View File

@ -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",

View File

@ -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;

View File

@ -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,

View File

@ -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$$;

View File

@ -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 $$

View File

@ -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;