v1-0005-add-docs-and-tests.patch
text/x-diff
Filename: v1-0005-add-docs-and-tests.patch
Type: text/x-diff
Part: 4
Patch
Format: unified
Series: patch v1-0005
| File | + | − |
|---|---|---|
| contrib/hstore/expected/hstore.out | 4 | 0 |
| contrib/hstore/sql/hstore.sql | 4 | 0 |
| doc/src/sgml/plpgsql.sgml | 19 | 2 |
| src/pl/plpgsql/src/expected/plpgsql_array.out | 94 | 0 |
| src/pl/plpgsql/src/expected/plpgsql_record.out | 83 | 0 |
| src/pl/plpgsql/src/Makefile | 1 | 1 |
| src/pl/plpgsql/src/pl_comp.c | 33 | 16 |
| src/pl/plpgsql/src/pl_gram.y | 5 | 3 |
| src/pl/plpgsql/src/sql/plpgsql_array.sql | 79 | 0 |
| src/pl/plpgsql/src/sql/plpgsql_record.sql | 42 | 0 |
diff --git a/contrib/hstore/expected/hstore.out b/contrib/hstore/expected/hstore.out
index fdcc3920ce..64a3272b9c 100644
--- a/contrib/hstore/expected/hstore.out
+++ b/contrib/hstore/expected/hstore.out
@@ -1583,6 +1583,10 @@ select f2 from test_json_agg;
"d"=>NULL, "x"=>"xyzzy"
(3 rows)
+-- Test subscripting in plpgsql
+do $$ declare h hstore;
+begin h['a'] := 'b'; raise notice 'h = %, h[a] = %', h, h['a']; end $$;
+NOTICE: h = "a"=>"b", h[a] = b
-- Check the hstore_hash() and hstore_hash_extended() function explicitly.
SELECT v as value, hstore_hash(v)::bit(32) as standard,
hstore_hash_extended(v, 0)::bit(32) as extended0,
diff --git a/contrib/hstore/sql/hstore.sql b/contrib/hstore/sql/hstore.sql
index 8d96e30403..a59db66b0a 100644
--- a/contrib/hstore/sql/hstore.sql
+++ b/contrib/hstore/sql/hstore.sql
@@ -372,6 +372,10 @@ select f2['d':'e'] from test_json_agg; -- error
update test_json_agg set f2['d'] = f2['e'], f2['x'] = 'xyzzy';
select f2 from test_json_agg;
+-- Test subscripting in plpgsql
+do $$ declare h hstore;
+begin h['a'] := 'b'; raise notice 'h = %, h[a] = %', h, h['a']; end $$;
+
-- Check the hstore_hash() and hstore_hash_extended() function explicitly.
SELECT v as value, hstore_hash(v)::bit(32) as standard,
hstore_hash_extended(v, 0)::bit(32) as extended0,
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index 9ec168b0c4..32c466eaa5 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -946,8 +946,8 @@ PREPARE <replaceable>statement_name</replaceable>(integer, integer) AS SELECT $1
database engine. The expression must yield a single value (possibly
a row value, if the variable is a row or record variable). The target
variable can be a simple variable (optionally qualified with a block
- name), a field of a row or record variable, or an element of an array
- that is a simple variable or field. Equal (<literal>=</literal>) can be
+ name), a field of a row or record target, or an element or slice of
+ an array target. Equal (<literal>=</literal>) can be
used instead of PL/SQL-compliant <literal>:=</literal>.
</para>
@@ -968,8 +968,25 @@ PREPARE <replaceable>statement_name</replaceable>(integer, integer) AS SELECT $1
<programlisting>
tax := subtotal * 0.06;
my_record.user_id := 20;
+my_array[j] := 20;
+my_array[1:3] := array[1,2,3];
+complex_array[n].realpart = 12.3;
</programlisting>
</para>
+
+ <para>
+ It's useful to know that what follows the assignment operator is
+ essentially treated as a <literal>SELECT</literal> command; as long
+ as it returns a single row and column, it will work. Thus for example
+ one can write something like
+<programlisting>
+total_sales := sum(quantity) from sales;
+</programlisting>
+ This provides an effect similar to the single-row <literal>SELECT
+ ... INTO</literal> syntax described in
+ <xref linkend="plpgsql-statements-sql-onerow"/>. However, that syntax
+ is more portable.
+ </para>
</sect2>
<sect2 id="plpgsql-statements-sql-noresult">
diff --git a/src/pl/plpgsql/src/Makefile b/src/pl/plpgsql/src/Makefile
index 193df8a010..9946abbc1d 100644
--- a/src/pl/plpgsql/src/Makefile
+++ b/src/pl/plpgsql/src/Makefile
@@ -32,7 +32,7 @@ DATA = plpgsql.control plpgsql--1.0.sql
REGRESS_OPTS = --dbname=$(PL_TESTDB)
-REGRESS = plpgsql_call plpgsql_control plpgsql_copy plpgsql_domain \
+REGRESS = plpgsql_array plpgsql_call plpgsql_control plpgsql_copy plpgsql_domain \
plpgsql_record plpgsql_cache plpgsql_simple plpgsql_transaction \
plpgsql_trap plpgsql_trigger plpgsql_varprops
diff --git a/src/pl/plpgsql/src/expected/plpgsql_array.out b/src/pl/plpgsql/src/expected/plpgsql_array.out
new file mode 100644
index 0000000000..5f28b4f685
--- /dev/null
+++ b/src/pl/plpgsql/src/expected/plpgsql_array.out
@@ -0,0 +1,94 @@
+--
+-- Tests for PL/pgSQL handling of array variables
+--
+-- We also check arrays of composites here, so this has some overlap
+-- with the plpgsql_record tests.
+--
+create type complex as (r float8, i float8);
+create type quadarray as (c1 complex[], c2 complex);
+do $$ declare a int[];
+begin a := array[1,2]; a[3] := 4; raise notice 'a = %', a; end$$;
+NOTICE: a = {1,2,4}
+do $$ declare a int[];
+begin a[3] := 4; raise notice 'a = %', a; end$$;
+NOTICE: a = [3:3]={4}
+do $$ declare a int[];
+begin a[1][4] := 4; raise notice 'a = %', a; end$$;
+NOTICE: a = [1:1][4:4]={{4}}
+do $$ declare a int[];
+begin a[1] := 23::text; raise notice 'a = %', a; end$$; -- lax typing
+NOTICE: a = {23}
+do $$ declare a int[];
+begin a := array[1,2]; a[2:3] := array[3,4]; raise notice 'a = %', a; end$$;
+NOTICE: a = {1,3,4}
+do $$ declare a int[];
+begin a := array[1,2]; a[2] := a[2] + 1; raise notice 'a = %', a; end$$;
+NOTICE: a = {1,3}
+do $$ declare a int[];
+begin a[1:2] := array[3,4]; raise notice 'a = %', a; end$$;
+NOTICE: a = {3,4}
+do $$ declare a int[];
+begin a[1:2] := 4; raise notice 'a = %', a; end$$; -- error
+ERROR: malformed array literal: "4"
+DETAIL: Array value must start with "{" or dimension information.
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
+do $$ declare a complex[];
+begin a[1] := (1,2); a[1].i := 11; raise notice 'a = %', a; end$$;
+NOTICE: a = {"(1,11)"}
+do $$ declare a complex[];
+begin a[1].i := 11; raise notice 'a = %, a[1].i = %', a, a[1].i; end$$;
+NOTICE: a = {"(,11)"}, a[1].i = 11
+-- perhaps this ought to work, but for now it doesn't:
+do $$ declare a complex[];
+begin a[1:2].i := array[11,12]; raise notice 'a = %', a; end$$;
+ERROR: cannot assign to field "i" of column "a" because its type complex[] is not a composite type
+LINE 1: a[1:2].i := array[11,12]
+ ^
+QUERY: a[1:2].i := array[11,12]
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
+do $$ declare a quadarray;
+begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
+NOTICE: a = ("{""(,11)""}",), a.c1[1].i = 11
+do $$ declare a int[];
+begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
+NOTICE: a = {1,2,3}
+create temp table onecol as select array[1,2] as f1;
+do $$ declare a int[];
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+NOTICE: a = {1,2}
+do $$ declare a int[];
+begin a := * from onecol for update; raise notice 'a = %', a; end$$;
+NOTICE: a = {1,2}
+-- error cases:
+do $$ declare a int[];
+begin a := from onecol; raise notice 'a = %', a; end$$;
+ERROR: assignment source returned 0 columns
+CONTEXT: PL/pgSQL assignment "a := from onecol"
+PL/pgSQL function inline_code_block line 2 at assignment
+do $$ declare a int[];
+begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
+ERROR: assignment source returned 2 columns
+CONTEXT: PL/pgSQL assignment "a := f1, f1 from onecol"
+PL/pgSQL function inline_code_block line 2 at assignment
+insert into onecol values(array[11]);
+do $$ declare a int[];
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+ERROR: query "a := f1 from onecol" returned more than one row
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
+do $$ declare a int[];
+begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
+NOTICE: a = {1,2}
+do $$ declare a real;
+begin a[1] := 2; raise notice 'a = %', a; end$$;
+ERROR: cannot subscript type real because it does not support subscripting
+LINE 1: a[1] := 2
+ ^
+QUERY: a[1] := 2
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
+do $$ declare a complex;
+begin a.r[1] := 2; raise notice 'a = %', a; end$$;
+ERROR: cannot subscript type double precision because it does not support subscripting
+LINE 1: a.r[1] := 2
+ ^
+QUERY: a.r[1] := 2
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
diff --git a/src/pl/plpgsql/src/expected/plpgsql_record.out b/src/pl/plpgsql/src/expected/plpgsql_record.out
index 52207e9b10..6e835c0751 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_record.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_record.out
@@ -3,6 +3,7 @@
--
create type two_int4s as (f1 int4, f2 int4);
create type two_int8s as (q1 int8, q2 int8);
+create type nested_int8s as (c1 two_int8s, c2 two_int8s);
-- base-case return of a composite type
create function retc(int) returns two_int8s language plpgsql as
$$ begin return row($1,1)::two_int8s; end $$;
@@ -82,6 +83,88 @@ begin
end$$;
NOTICE: c4 = (1,2)
NOTICE: c8 = (1,2)
+do $$ declare c two_int8s; d nested_int8s;
+begin
+ c := row(1,2);
+ d := row(c, row(c.q1, c.q2+1));
+ raise notice 'c = %, d = %', c, d;
+ c.q1 := 10;
+ d.c1 := row(11,12);
+ d.c2.q2 := 42;
+ raise notice 'c = %, d = %', c, d;
+ raise notice 'c.q1 = %, d.c2 = %', c.q1, d.c2;
+ raise notice '(d).c2.q2 = %', (d).c2.q2; -- doesn't work without parens
+ raise notice '(d.c2).q2 = %', (d.c2).q2; -- doesn't work without parens
+end$$;
+NOTICE: c = (1,2), d = ("(1,2)","(1,3)")
+NOTICE: c = (10,2), d = ("(11,12)","(1,42)")
+NOTICE: c.q1 = 10, d.c2 = (1,42)
+NOTICE: (d).c2.q2 = 42
+NOTICE: (d.c2).q2 = 42
+-- block-qualified naming
+do $$ <<b>> declare c two_int8s; d nested_int8s;
+begin
+ b.c := row(1,2);
+ b.d := row(b.c, row(b.c.q1, b.c.q2+1));
+ raise notice 'b.c = %, b.d = %', b.c, b.d;
+ b.c.q1 := 10;
+ b.d.c1 := row(11,12);
+ b.d.c2.q2 := 42;
+ raise notice 'b.c = %, b.d = %', b.c, b.d;
+ raise notice 'b.c.q1 = %, b.d.c2 = %', b.c.q1, b.d.c2;
+ raise notice '(b.d).c2.q2 = %', (b.d).c2.q2; -- doesn't work without parens
+ raise notice '(b.d.c2).q2 = %', (b.d.c2).q2; -- doesn't work without parens
+end$$;
+NOTICE: b.c = (1,2), b.d = ("(1,2)","(1,3)")
+NOTICE: b.c = (10,2), b.d = ("(11,12)","(1,42)")
+NOTICE: b.c.q1 = 10, b.d.c2 = (1,42)
+NOTICE: (b.d).c2.q2 = 42
+NOTICE: (b.d.c2).q2 = 42
+-- error cases
+do $$ declare c two_int8s; begin c.x = 1; end $$;
+ERROR: record "c" has no field "x"
+CONTEXT: PL/pgSQL assignment "c.x = 1"
+PL/pgSQL function inline_code_block line 1 at assignment
+do $$ declare c nested_int8s; begin c.x = 1; end $$;
+ERROR: record "c" has no field "x"
+CONTEXT: PL/pgSQL assignment "c.x = 1"
+PL/pgSQL function inline_code_block line 1 at assignment
+do $$ declare c nested_int8s; begin c.x.q1 = 1; end $$;
+ERROR: record "c" has no field "x"
+CONTEXT: PL/pgSQL assignment "c.x.q1 = 1"
+PL/pgSQL function inline_code_block line 1 at assignment
+do $$ declare c nested_int8s; begin c.c2.x = 1; end $$;
+ERROR: cannot assign to field "x" of column "c" because there is no such column in data type two_int8s
+LINE 1: c.c2.x = 1
+ ^
+QUERY: c.c2.x = 1
+CONTEXT: PL/pgSQL function inline_code_block line 1 at assignment
+do $$ declare c nested_int8s; begin d.c2.x = 1; end $$;
+ERROR: "d.c2.x" is not a known variable
+LINE 1: do $$ declare c nested_int8s; begin d.c2.x = 1; end $$;
+ ^
+do $$ <<b>> declare c two_int8s; begin b.c.x = 1; end $$;
+ERROR: record "c" has no field "x"
+CONTEXT: PL/pgSQL assignment "b.c.x = 1"
+PL/pgSQL function inline_code_block line 1 at assignment
+do $$ <<b>> declare c nested_int8s; begin b.c.x = 1; end $$;
+ERROR: record "c" has no field "x"
+CONTEXT: PL/pgSQL assignment "b.c.x = 1"
+PL/pgSQL function inline_code_block line 1 at assignment
+do $$ <<b>> declare c nested_int8s; begin b.c.x.q1 = 1; end $$;
+ERROR: record "c" has no field "x"
+CONTEXT: PL/pgSQL assignment "b.c.x.q1 = 1"
+PL/pgSQL function inline_code_block line 1 at assignment
+do $$ <<b>> declare c nested_int8s; begin b.c.c2.x = 1; end $$;
+ERROR: cannot assign to field "x" of column "b" because there is no such column in data type two_int8s
+LINE 1: b.c.c2.x = 1
+ ^
+QUERY: b.c.c2.x = 1
+CONTEXT: PL/pgSQL function inline_code_block line 1 at assignment
+do $$ <<b>> declare c nested_int8s; begin b.d.c2.x = 1; end $$;
+ERROR: "b.d.c2" is not a known variable
+LINE 1: do $$ <<b>> declare c nested_int8s; begin b.d.c2.x = 1; end ...
+ ^
-- check passing composite result to another function
create function getq1(two_int8s) returns int8 language plpgsql as $$
declare r two_int8s; begin r := $1; return r.q1; end $$;
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index b610b28d70..0225f5911d 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -1456,7 +1456,8 @@ plpgsql_parse_dblword(char *word1, char *word2,
/*
* We should do nothing in DECLARE sections. In SQL expressions, we
* really only need to make sure that RECFIELD datums are created when
- * needed.
+ * needed. In all the cases handled by this function, returning a T_DATUM
+ * with a two-word idents string is the right thing.
*/
if (plpgsql_IdentifierLookup != IDENTIFIER_LOOKUP_DECLARE)
{
@@ -1530,40 +1531,53 @@ plpgsql_parse_tripword(char *word1, char *word2, char *word3,
List *idents;
int nnames;
- idents = list_make3(makeString(word1),
- makeString(word2),
- makeString(word3));
-
/*
- * We should do nothing in DECLARE sections. In SQL expressions, we
- * really only need to make sure that RECFIELD datums are created when
- * needed.
+ * We should do nothing in DECLARE sections. In SQL expressions, we need
+ * to make sure that RECFIELD datums are created when needed, and we need
+ * to be careful about how many names are reported as belonging to the
+ * T_DATUM: the third word could be a sub-field reference, which we don't
+ * care about here.
*/
if (plpgsql_IdentifierLookup != IDENTIFIER_LOOKUP_DECLARE)
{
/*
- * Do a lookup in the current namespace stack. Must find a qualified
+ * Do a lookup in the current namespace stack. Must find a record
* reference, else ignore.
*/
ns = plpgsql_ns_lookup(plpgsql_ns_top(), false,
word1, word2, word3,
&nnames);
- if (ns != NULL && nnames == 2)
+ if (ns != NULL)
{
switch (ns->itemtype)
{
case PLPGSQL_NSTYPE_REC:
{
- /*
- * words 1/2 are a record name, so third word could be
- * a field in this record.
- */
PLpgSQL_rec *rec;
PLpgSQL_recfield *new;
rec = (PLpgSQL_rec *) (plpgsql_Datums[ns->itemno]);
- new = plpgsql_build_recfield(rec, word3);
-
+ if (nnames == 1)
+ {
+ /*
+ * First word is a record name, so second word
+ * could be a field in this record (and the third,
+ * a sub-field). We build a RECFIELD datum
+ * whether it is or not --- any error will be
+ * detected later.
+ */
+ new = plpgsql_build_recfield(rec, word2);
+ idents = list_make2(makeString(word1),
+ makeString(word2));
+ }
+ else
+ {
+ /* Block-qualified reference to record variable. */
+ new = plpgsql_build_recfield(rec, word3);
+ idents = list_make3(makeString(word1),
+ makeString(word2),
+ makeString(word3));
+ }
wdatum->datum = (PLpgSQL_datum *) new;
wdatum->ident = NULL;
wdatum->quoted = false; /* not used */
@@ -1578,6 +1592,9 @@ plpgsql_parse_tripword(char *word1, char *word2, char *word3,
}
/* Nothing found */
+ idents = list_make3(makeString(word1),
+ makeString(word2),
+ makeString(word3));
cword->idents = idents;
return false;
}
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 051544a3b4..dece013e23 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -2004,7 +2004,7 @@ loop_body : proc_sect K_END K_LOOP opt_label ';'
* variable. (The composite case is probably a syntax error, but we'll let
* the core parser decide that.) Normally, we should assume that such a
* word is a SQL statement keyword that isn't also a plpgsql keyword.
- * However, if the next token is assignment or '[', it can't be a valid
+ * However, if the next token is assignment or '[' or '.', it can't be a valid
* SQL statement, and what we're probably looking at is an intended variable
* assignment. Give an appropriate complaint for that, instead of letting
* the core parser throw an unhelpful "syntax error".
@@ -2023,7 +2023,8 @@ stmt_execsql : K_IMPORT
tok = yylex();
plpgsql_push_back_token(tok);
- if (tok == '=' || tok == COLON_EQUALS || tok == '[')
+ if (tok == '=' || tok == COLON_EQUALS ||
+ tok == '[' || tok == '.')
word_is_not_variable(&($1), @1);
$$ = make_execsql_stmt(T_WORD, @1);
}
@@ -2033,7 +2034,8 @@ stmt_execsql : K_IMPORT
tok = yylex();
plpgsql_push_back_token(tok);
- if (tok == '=' || tok == COLON_EQUALS || tok == '[')
+ if (tok == '=' || tok == COLON_EQUALS ||
+ tok == '[' || tok == '.')
cword_is_not_variable(&($1), @1);
$$ = make_execsql_stmt(T_CWORD, @1);
}
diff --git a/src/pl/plpgsql/src/sql/plpgsql_array.sql b/src/pl/plpgsql/src/sql/plpgsql_array.sql
new file mode 100644
index 0000000000..4c3f26be10
--- /dev/null
+++ b/src/pl/plpgsql/src/sql/plpgsql_array.sql
@@ -0,0 +1,79 @@
+--
+-- Tests for PL/pgSQL handling of array variables
+--
+-- We also check arrays of composites here, so this has some overlap
+-- with the plpgsql_record tests.
+--
+
+create type complex as (r float8, i float8);
+create type quadarray as (c1 complex[], c2 complex);
+
+do $$ declare a int[];
+begin a := array[1,2]; a[3] := 4; raise notice 'a = %', a; end$$;
+
+do $$ declare a int[];
+begin a[3] := 4; raise notice 'a = %', a; end$$;
+
+do $$ declare a int[];
+begin a[1][4] := 4; raise notice 'a = %', a; end$$;
+
+do $$ declare a int[];
+begin a[1] := 23::text; raise notice 'a = %', a; end$$; -- lax typing
+
+do $$ declare a int[];
+begin a := array[1,2]; a[2:3] := array[3,4]; raise notice 'a = %', a; end$$;
+
+do $$ declare a int[];
+begin a := array[1,2]; a[2] := a[2] + 1; raise notice 'a = %', a; end$$;
+
+do $$ declare a int[];
+begin a[1:2] := array[3,4]; raise notice 'a = %', a; end$$;
+
+do $$ declare a int[];
+begin a[1:2] := 4; raise notice 'a = %', a; end$$; -- error
+
+do $$ declare a complex[];
+begin a[1] := (1,2); a[1].i := 11; raise notice 'a = %', a; end$$;
+
+do $$ declare a complex[];
+begin a[1].i := 11; raise notice 'a = %, a[1].i = %', a, a[1].i; end$$;
+
+-- perhaps this ought to work, but for now it doesn't:
+do $$ declare a complex[];
+begin a[1:2].i := array[11,12]; raise notice 'a = %', a; end$$;
+
+do $$ declare a quadarray;
+begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
+
+do $$ declare a int[];
+begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
+
+create temp table onecol as select array[1,2] as f1;
+
+do $$ declare a int[];
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+
+do $$ declare a int[];
+begin a := * from onecol for update; raise notice 'a = %', a; end$$;
+
+-- error cases:
+
+do $$ declare a int[];
+begin a := from onecol; raise notice 'a = %', a; end$$;
+
+do $$ declare a int[];
+begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
+
+insert into onecol values(array[11]);
+
+do $$ declare a int[];
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+
+do $$ declare a int[];
+begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
+
+do $$ declare a real;
+begin a[1] := 2; raise notice 'a = %', a; end$$;
+
+do $$ declare a complex;
+begin a.r[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/pl/plpgsql/src/sql/plpgsql_record.sql b/src/pl/plpgsql/src/sql/plpgsql_record.sql
index 128846e610..be10f00b1e 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_record.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_record.sql
@@ -4,6 +4,7 @@
create type two_int4s as (f1 int4, f2 int4);
create type two_int8s as (q1 int8, q2 int8);
+create type nested_int8s as (c1 two_int8s, c2 two_int8s);
-- base-case return of a composite type
create function retc(int) returns two_int8s language plpgsql as
@@ -59,6 +60,47 @@ begin
raise notice 'c8 = %', c8;
end$$;
+do $$ declare c two_int8s; d nested_int8s;
+begin
+ c := row(1,2);
+ d := row(c, row(c.q1, c.q2+1));
+ raise notice 'c = %, d = %', c, d;
+ c.q1 := 10;
+ d.c1 := row(11,12);
+ d.c2.q2 := 42;
+ raise notice 'c = %, d = %', c, d;
+ raise notice 'c.q1 = %, d.c2 = %', c.q1, d.c2;
+ raise notice '(d).c2.q2 = %', (d).c2.q2; -- doesn't work without parens
+ raise notice '(d.c2).q2 = %', (d.c2).q2; -- doesn't work without parens
+end$$;
+
+-- block-qualified naming
+do $$ <<b>> declare c two_int8s; d nested_int8s;
+begin
+ b.c := row(1,2);
+ b.d := row(b.c, row(b.c.q1, b.c.q2+1));
+ raise notice 'b.c = %, b.d = %', b.c, b.d;
+ b.c.q1 := 10;
+ b.d.c1 := row(11,12);
+ b.d.c2.q2 := 42;
+ raise notice 'b.c = %, b.d = %', b.c, b.d;
+ raise notice 'b.c.q1 = %, b.d.c2 = %', b.c.q1, b.d.c2;
+ raise notice '(b.d).c2.q2 = %', (b.d).c2.q2; -- doesn't work without parens
+ raise notice '(b.d.c2).q2 = %', (b.d.c2).q2; -- doesn't work without parens
+end$$;
+
+-- error cases
+do $$ declare c two_int8s; begin c.x = 1; end $$;
+do $$ declare c nested_int8s; begin c.x = 1; end $$;
+do $$ declare c nested_int8s; begin c.x.q1 = 1; end $$;
+do $$ declare c nested_int8s; begin c.c2.x = 1; end $$;
+do $$ declare c nested_int8s; begin d.c2.x = 1; end $$;
+do $$ <<b>> declare c two_int8s; begin b.c.x = 1; end $$;
+do $$ <<b>> declare c nested_int8s; begin b.c.x = 1; end $$;
+do $$ <<b>> declare c nested_int8s; begin b.c.x.q1 = 1; end $$;
+do $$ <<b>> declare c nested_int8s; begin b.c.c2.x = 1; end $$;
+do $$ <<b>> declare c nested_int8s; begin b.d.c2.x = 1; end $$;
+
-- check passing composite result to another function
create function getq1(two_int8s) returns int8 language plpgsql as $$
declare r two_int8s; begin r := $1; return r.q1; end $$;