Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtp (Exim 4.84_2) (envelope-from ) id 1eV87X-0003vN-0U for pgsql-hackers@arkaria.postgresql.org; Sat, 30 Dec 2017 03:47:39 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.84_2) (envelope-from ) id 1eV87W-0000Bm-AS for pgsql-hackers@arkaria.postgresql.org; Sat, 30 Dec 2017 03:47:38 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA384:256) (Exim 4.84_2) (envelope-from ) id 1eV87W-0000BX-19 for pgsql-hackers@lists.postgresql.org; Sat, 30 Dec 2017 03:47:38 +0000 Received: from sss.pgh.pa.us ([66.207.139.130]) by magus.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA384:256) (Exim 4.89) (envelope-from ) id 1eV87Q-00044N-Eh for pgsql-hackers@lists.postgresql.org; Sat, 30 Dec 2017 03:47:37 +0000 Received: from sss1.sss.pgh.pa.us (localhost [127.0.0.1]) by sss.pgh.pa.us (8.14.4/8.14.4) with ESMTP id vBU3lUBm018363 for ; Fri, 29 Dec 2017 22:47:30 -0500 From: Tom Lane To: pgsql-hackers@lists.postgresql.org Subject: CONSTANT/NOT NULL/initializer properties for plpgsql record variables MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0" Content-ID: <18229.1514605458.0@sss.pgh.pa.us> Date: Fri, 29 Dec 2017 22:47:30 -0500 Message-ID: <18362.1514605650@sss.pgh.pa.us> List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Precedence: bulk ------- =_aaaaaaaaaa0 Content-Type: text/plain; charset="us-ascii" Content-ID: <18229.1514605458.1@sss.pgh.pa.us> 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/23537.1514589378@sss.pgh.pa.us and https://postgr.es/m/11986.1514407114@sss.pgh.pa.us 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 ------- =_aaaaaaaaaa0 Content-Type: text/x-diff; name="plpgsql-record-var-properties-1.patch"; charset="us-ascii" Content-ID: <18229.1514605458.2@sss.pgh.pa.us> Content-Description: plpgsql-record-var-properties-1.patch Content-Transfer-Encoding: quoted-printable 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 =3D plpgsql.control plpgsql--1.0.sql = *** 26,32 **** = REGRESS_OPTS =3D --dbname=3D$(PL_TESTDB) = ! REGRESS =3D plpgsql_call plpgsql_record = all: all-lib = --- 26,32 ---- = REGRESS_OPTS =3D --dbname=3D$(PL_TESTDB) = ! REGRESS =3D plpgsql_call plpgsql_record plpgsql_varprops = all: all-lib = diff --git a/src/pl/plpgsql/src/expected/plpgsql_varprops.out b/src/pl/plp= gsql/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, initializ= ers + -- + 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 nul= l); + -- CONSTANT + do $$ + declare x constant int :=3D 42; + begin + raise notice 'x =3D %', x; + end$$; + NOTICE: x =3D 42 + do $$ + declare x constant int; + begin + x :=3D 42; -- fail + end$$; + ERROR: variable "x" is declared CONSTANT + LINE 4: x :=3D 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] :=3D 42; -- fail + end$$; + ERROR: variable "x" is declared CONSTANT + LINE 4: x[1] :=3D 42; -- fail + ^ + do $$ + declare x constant int[]; y int; + begin + for x[1], y in select 1, 2 loop -- fail (currently, unsupported synta= x) + 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 :=3D 42; -- fail + end$$; + ERROR: variable "x" is declared CONSTANT + LINE 4: x.f1 :=3D 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 :=3D sin(0); + begin + raise notice 'x =3D %', x; + end$$; + NOTICE: x =3D 0 + do $$ + declare x int :=3D 1/0; -- fail + begin + raise notice 'x =3D %', 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[] :=3D array[1,3,5]; + begin + raise notice 'x =3D %', x; + end$$; + NOTICE: x =3D {1,3,5} + do $$ + declare x record :=3D row(1,2,3); + begin + raise notice 'x =3D %', x; + end$$; + NOTICE: x =3D (1,2,3) + do $$ + declare x var_record :=3D row(1,2); + begin + raise notice 'x =3D %', x; + end$$; + NOTICE: x =3D (1,2) + -- NOT NULL + do $$ + declare x int not null; -- fail + begin + raise notice 'x =3D %', 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 :=3D 42; + begin + raise notice 'x =3D %', x; + x :=3D null; -- fail + end$$; + NOTICE: x =3D 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 :=3D null; -- fail + begin + raise notice 'x =3D %', 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 bl= ock local variable initialization + do $$ + declare x record not null; -- fail + begin + raise notice 'x =3D %', 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 :=3D row(42); + begin + raise notice 'x =3D %', x; + x :=3D row(null); -- ok + raise notice 'x =3D %', x; + x :=3D null; -- fail + end$$; + NOTICE: x =3D (42) + NOTICE: x =3D () + 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 :=3D null; -- fail + begin + raise notice 'x =3D %', 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 bl= ock local variable initialization + do $$ + declare x var_record not null; -- fail + begin + raise notice 'x =3D %', 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 :=3D row(41,42); + begin + raise notice 'x =3D %', x; + x :=3D row(null,null); -- ok + raise notice 'x =3D %', x; + x :=3D null; -- fail + end$$; + NOTICE: x =3D (41,42) + NOTICE: x =3D (,) + 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 :=3D null; -- fail + begin + raise notice 'x =3D %', 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 bl= ock 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 :=3D i; + r record; + c var_record; + begin + if i =3D 1 then + x :=3D 42; + r :=3D row(i, i+1); + c :=3D row(i, i+1); + end if; + raise notice 'x =3D %', x; + raise notice 'y =3D %', y; + raise notice 'r =3D %', r; + raise notice 'c =3D %', c; + end; + end loop; + end$$; + NOTICE: x =3D 42 + NOTICE: y =3D 1 + NOTICE: r =3D (1,2) + NOTICE: c =3D (1,2) + NOTICE: x =3D + NOTICE: y =3D 2 + NOTICE: r =3D + NOTICE: c =3D + NOTICE: x =3D + NOTICE: y =3D 3 + NOTICE: r =3D + NOTICE: c =3D + \set VERBOSITY default + -- Check enforcement of domain constraints during initialization + do $$ + declare x int_nn; -- fail + begin + raise notice 'x =3D %', x; + end$$; + ERROR: domain int_nn does not allow null values + CONTEXT: PL/pgSQL function inline_code_block line 3 during statement bl= ock local variable initialization + do $$ + declare x int_nn :=3D null; -- fail + begin + raise notice 'x =3D %', x; + end$$; + ERROR: domain int_nn does not allow null values + CONTEXT: PL/pgSQL function inline_code_block line 3 during statement bl= ock local variable initialization + do $$ + declare x int_nn :=3D 42; + begin + raise notice 'x =3D %', x; + x :=3D null; -- fail + end$$; + NOTICE: x =3D 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 =3D %', x; + end$$; + ERROR: domain var_record_nn does not allow null values + CONTEXT: PL/pgSQL function inline_code_block line 3 during statement bl= ock local variable initialization + do $$ + declare x var_record_nn :=3D null; -- fail + begin + raise notice 'x =3D %', x; + end$$; + ERROR: domain var_record_nn does not allow null values + CONTEXT: PL/pgSQL function inline_code_block line 3 during statement bl= ock local variable initialization + do $$ + declare x var_record_nn :=3D row(1,2); + begin + raise notice 'x =3D %', x; + x :=3D row(null,null); -- ok + x :=3D null; -- fail + end$$; + NOTICE: x =3D (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 =3D %', 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 bl= ock local variable initialization + do $$ + declare x var_record_colnn :=3D null; -- fail + begin + raise notice 'x =3D %', 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 bl= ock local variable initialization + do $$ + declare x var_record_colnn :=3D row(1,null); -- fail + begin + raise notice 'x =3D %', 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 bl= ock local variable initialization + do $$ + declare x var_record_colnn :=3D row(1,2); + begin + raise notice 'x =3D %', x; + x :=3D null; -- fail + end$$; + NOTICE: x =3D (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 :=3D row(1,2); + begin + raise notice 'x =3D %', x; + x :=3D row(null,null); -- fail + end$$; + NOTICE: x =3D (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 =3D plpgsql_build_record("new", 0, RECORDOID, true); function->new_varno =3D rec->dno; = /* Add the record for referencing OLD ROW */ ! rec =3D plpgsql_build_record("old", 0, RECORDOID, true); function->old_varno =3D 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 =3D plpgsql_build_record("new", 0, NULL, RECORDOID, true); function->new_varno =3D rec->dno; = /* Add the record for referencing OLD ROW */ ! rec =3D plpgsql_build_record("old", 0, NULL, RECORDOID, true); function->old_varno =3D rec->dno; = /* Add the variable tg_name */ *************** plpgsql_build_variable(const char *refna *** 1811,1817 **** var->refname =3D pstrdup(refname); var->lineno =3D lineno; var->datatype =3D dtype; ! /* other fields might be filled by caller */ = /* preset to NULL */ var->value =3D 0; --- 1811,1817 ---- var->refname =3D pstrdup(refname); var->lineno =3D lineno; var->datatype =3D dtype; ! /* other fields are left as 0, might be changed by caller */ = /* preset to NULL */ var->value =3D 0; *************** plpgsql_build_variable(const char *refna *** 1831,1837 **** /* Composite type -- build a record variable */ PLpgSQL_rec *rec; = ! rec =3D plpgsql_build_record(refname, lineno, dtype->typoid, add2namespace); result =3D (PLpgSQL_variable *) rec; break; --- 1831,1838 ---- /* Composite type -- build a record variable */ PLpgSQL_rec *rec; = ! rec =3D plpgsql_build_record(refname, lineno, ! dtype, dtype->typoid, add2namespace); result =3D (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 =3D PLPGSQL_DTYPE_REC; rec->refname =3D pstrdup(refname); rec->lineno =3D lineno; + /* other fields are left as 0, might be changed by caller */ + rec->datatype =3D dtype; rec->rectypeid =3D rectypeid; rec->firstfield =3D -1; rec->erh =3D 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 =3D=3D 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 =3D=3D 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 NU= LL", ! 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 =3D (PLpgSQL_rec *) datum; = ! if (rec->erh) ! DeleteExpandedObject(ExpandedRecordGetDatum(rec->erh)); ! rec->erh =3D NULL; } break; = --- 1488,1515 ---- { PLpgSQL_rec *rec =3D (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 =3D=3D 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\" declar= ed 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 =3D=3D NULL) { ! if (rec->erh) ! DeleteExpandedObject(ExpandedRecordGetDatum(rec->erh)); ! rec->erh =3D NULL; return; } = --- 6462,6488 ---- */ if (tupdesc =3D=3D NULL) { ! if (rec->datatype && ! rec->datatype->typtype =3D=3D 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 =3D 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 =3D 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 =3D (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 !=3D 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 =3D plpgsql_build_variable($1.name, $1.lineno, $3, true); ! if ($2) ! { ! if (var->dtype =3D=3D PLPGSQL_DTYPE_VAR) ! ((PLpgSQL_var *) var)->isconst =3D $2; ! else ! ereport(ERROR, ! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("record variable cannot be CONSTANT"), ! parser_errposition(@2))); ! } ! if ($5) ! { ! if (var->dtype =3D=3D PLPGSQL_DTYPE_VAR) ! ((PLpgSQL_var *) var)->notnull =3D $5; ! else ! ereport(ERROR, ! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("record variable cannot be NOT NULL"), ! parser_errposition(@4))); = ! } ! if ($6 !=3D NULL) ! { ! if (var->dtype =3D=3D PLPGSQL_DTYPE_VAR) ! ((PLpgSQL_var *) var)->default_val =3D $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 =3D plpgsql_build_variable($1.name, $1.lineno, $3, true); ! var->isconst =3D $2; ! var->notnull =3D $5; ! var->default_val =3D $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 =3D=3D 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 =3D (PLpgSQL_variable *) lfirst(l); + Assert(!arg->isconst); new->fieldnames[i] =3D arg->refname; new->varnos[i] =3D arg->dno; i++; *************** for_control : for_variable K_IN *** 1378,1383 **** --- 1362,1368 ---- new->var =3D (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 =3D true; new->sqlstate_varno =3D var->dno; = var =3D plpgsql_build_variable("sqlerrm", lineno, --- 2190,2196 ---- -1, plpgsql_curr_compile->fn_input_collation), true); ! var->isconst =3D true; new->sqlstate_varno =3D var->dno; = var =3D plpgsql_build_variable("sqlerrm", lineno, *************** exception_sect : *** 2213,2219 **** -1, plpgsql_curr_compile->fn_input_collation), true); ! ((PLpgSQL_var *) var)->isconst =3D true; new->sqlerrm_varno =3D var->dno; = $$ =3D new; --- 2198,2204 ---- -1, plpgsql_curr_compile->fn_input_collation), true); ! var->isconst =3D true; new->sqlerrm_varno =3D var->dno; = $$ =3D 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)->recpare= ntno], ! location); break; case PLPGSQL_DTYPE_ARRAYELEM: ! /* assignable if parent array is */ ! check_assignable(plpgsql_Datums[((PLpgSQL_arrayelem *) datum)->arrayp= arentno], ! 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 =3D palloc(sizeof(PLpgSQL_row)); row->dtype =3D PLPGSQL_DTYPE_ROW; - row->refname =3D pstrdup("*internal*"); row->lineno =3D plpgsql_location_to_lineno(initial_location); row->rowtupdesc =3D NULL; row->nfields =3D nfields; --- 3416,3423 ---- */ plpgsql_push_back_token(tok); = ! row =3D palloc0(sizeof(PLpgSQL_row)); row->dtype =3D PLPGSQL_DTYPE_ROW; row->lineno =3D plpgsql_location_to_lineno(initial_location); row->rowtupdesc =3D NULL; row->nfields =3D nfields; *************** make_scalar_list1(char *initial_name, *** 3464,3472 **** = check_assignable(initial_datum, location); = ! row =3D palloc(sizeof(PLpgSQL_row)); row->dtype =3D PLPGSQL_DTYPE_ROW; - row->refname =3D pstrdup("*internal*"); row->lineno =3D lineno; row->rowtupdesc =3D NULL; row->nfields =3D 1; --- 3450,3457 ---- = check_assignable(initial_datum, location); = ! row =3D palloc0(sizeof(PLpgSQL_row)); row->dtype =3D PLPGSQL_DTYPE_ROW; row->lineno =3D lineno; row->rowtupdesc =3D NULL; row->nfields =3D 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 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 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 ha= ve + * 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, initializ= ers + -- + = + 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 nul= l); + = + -- CONSTANT + = + do $$ + declare x constant int :=3D 42; + begin + raise notice 'x =3D %', x; + end$$; + = + do $$ + declare x constant int; + begin + x :=3D 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] :=3D 42; -- fail + end$$; + = + do $$ + declare x constant int[]; y int; + begin + for x[1], y in select 1, 2 loop -- fail (currently, unsupported synta= x) + end loop; + end$$; + = + do $$ + declare x constant var_record; + begin + x.f1 :=3D 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 :=3D sin(0); + begin + raise notice 'x =3D %', x; + end$$; + = + do $$ + declare x int :=3D 1/0; -- fail + begin + raise notice 'x =3D %', x; + end$$; + = + do $$ + declare x bigint[] :=3D array[1,3,5]; + begin + raise notice 'x =3D %', x; + end$$; + = + do $$ + declare x record :=3D row(1,2,3); + begin + raise notice 'x =3D %', x; + end$$; + = + do $$ + declare x var_record :=3D row(1,2); + begin + raise notice 'x =3D %', x; + end$$; + = + -- NOT NULL + = + do $$ + declare x int not null; -- fail + begin + raise notice 'x =3D %', x; + end$$; + = + do $$ + declare x int not null :=3D 42; + begin + raise notice 'x =3D %', x; + x :=3D null; -- fail + end$$; + = + do $$ + declare x int not null :=3D null; -- fail + begin + raise notice 'x =3D %', x; + end$$; + = + do $$ + declare x record not null; -- fail + begin + raise notice 'x =3D %', x; + end$$; + = + do $$ + declare x record not null :=3D row(42); + begin + raise notice 'x =3D %', x; + x :=3D row(null); -- ok + raise notice 'x =3D %', x; + x :=3D null; -- fail + end$$; + = + do $$ + declare x record not null :=3D null; -- fail + begin + raise notice 'x =3D %', x; + end$$; + = + do $$ + declare x var_record not null; -- fail + begin + raise notice 'x =3D %', x; + end$$; + = + do $$ + declare x var_record not null :=3D row(41,42); + begin + raise notice 'x =3D %', x; + x :=3D row(null,null); -- ok + raise notice 'x =3D %', x; + x :=3D null; -- fail + end$$; + = + do $$ + declare x var_record not null :=3D null; -- fail + begin + raise notice 'x =3D %', 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 :=3D i; + r record; + c var_record; + begin + if i =3D 1 then + x :=3D 42; + r :=3D row(i, i+1); + c :=3D row(i, i+1); + end if; + raise notice 'x =3D %', x; + raise notice 'y =3D %', y; + raise notice 'r =3D %', r; + raise notice 'c =3D %', 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 =3D %', x; + end$$; + = + do $$ + declare x int_nn :=3D null; -- fail + begin + raise notice 'x =3D %', x; + end$$; + = + do $$ + declare x int_nn :=3D 42; + begin + raise notice 'x =3D %', x; + x :=3D null; -- fail + end$$; + = + do $$ + declare x var_record_nn; -- fail + begin + raise notice 'x =3D %', x; + end$$; + = + do $$ + declare x var_record_nn :=3D null; -- fail + begin + raise notice 'x =3D %', x; + end$$; + = + do $$ + declare x var_record_nn :=3D row(1,2); + begin + raise notice 'x =3D %', x; + x :=3D row(null,null); -- ok + x :=3D null; -- fail + end$$; + = + do $$ + declare x var_record_colnn; -- fail + begin + raise notice 'x =3D %', x; + end$$; + = + do $$ + declare x var_record_colnn :=3D null; -- fail + begin + raise notice 'x =3D %', x; + end$$; + = + do $$ + declare x var_record_colnn :=3D row(1,null); -- fail + begin + raise notice 'x =3D %', x; + end$$; + = + do $$ + declare x var_record_colnn :=3D row(1,2); + begin + raise notice 'x =3D %', x; + x :=3D null; -- fail + end$$; + = + do $$ + declare x var_record_colnn :=3D row(1,2); + begin + raise notice 'x =3D %', x; + x :=3D row(null,null); -- fail + end$$; diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expe= cted/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 :=3D i; - r record; - c int8_tbl; - begin - if i =3D 1 then - x :=3D 42; - r :=3D row(i, i+1); - c :=3D row(i, i+1); - end if; - raise notice 'x =3D %', x; - raise notice 'y =3D %', y; - raise notice 'r =3D %', r; - raise notice 'c =3D %', c; - end; - end loop; - end$$; - NOTICE: x =3D 42 - NOTICE: y =3D 1 - NOTICE: r =3D (1,2) - NOTICE: c =3D (1,2) - NOTICE: x =3D - NOTICE: y =3D 2 - NOTICE: r =3D - NOTICE: c =3D - NOTICE: x =3D - NOTICE: y =3D 3 - NOTICE: r =3D - NOTICE: c =3D - \set VERBOSITY default -- Check handling of conflicts between plpgsql vars and table columns. set plpgsql.variable_conflict =3D 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/plpgs= ql.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 :=3D i; - r record; - c int8_tbl; - begin - if i =3D 1 then - x :=3D 42; - r :=3D row(i, i+1); - c :=3D row(i, i+1); - end if; - raise notice 'x =3D %', x; - raise notice 'y =3D %', y; - raise notice 'r =3D %', r; - raise notice 'c =3D %', c; - end; - end loop; - end$$; - \set VERBOSITY default - = -- Check handling of conflicts between plpgsql vars and table columns. = set plpgsql.variable_conflict =3D error; --- 4014,4019 ---- ------- =_aaaaaaaaaa0--