Re: CONSTANT/NOT NULL/initializer properties for plpgsql record variables

Поиск
Список
Период
Сортировка
От Tom Lane
Тема Re: CONSTANT/NOT NULL/initializer properties for plpgsql record variables
Дата
Msg-id 13781.1516836760@sss.pgh.pa.us
обсуждение исходный текст
Ответ на CONSTANT/NOT NULL/initializer properties for plpgsql record variables  (Tom Lane <tgl@sss.pgh.pa.us>)
Ответы Re: CONSTANT/NOT NULL/initializer properties for plpgsql record variables
Список pgsql-hackers
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/833.1516834367@sss.pgh.pa.us
and
https://postgr.es/m/8376.1516835784@sss.pgh.pa.us

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

В списке pgsql-hackers по дате отправления:

Предыдущее
От: Robert Haas
Дата:
Сообщение: Re: reducing isolation tests runtime
Следующее
От: Tom Lane
Дата:
Сообщение: Re: reducing isolation tests runtime