plpgsql_type_array.patch
text/x-patch
Filename: plpgsql_type_array.patch
Type: text/x-patch
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
| File | + | − |
|---|---|---|
| doc/src/sgml/plpgsql.sgml | 30 | 6 |
| src/pl/plpgsql/src/gram.y | 89 | 65 |
| src/pl/plpgsql/src/pl_comp.c | 638 | 110 |
| src/pl/plpgsql/src/plpgsql.h | 14 | 4 |
| src/test/regress/expected/plpgsql.out | 791 | 0 |
| src/test/regress/sql/plpgsql.sql | 644 | 0 |
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index c14c34c..563ae08 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -321,6 +321,7 @@ quantity numeric(5);
url varchar;
myrow tablename%ROWTYPE;
myfield tablename.columnname%TYPE;
+total quantity%TYPE[];
arow RECORD;
</programlisting>
</para>
@@ -584,14 +585,15 @@ DECLARE
</synopsis>
<para>
- <literal>%TYPE</literal> provides the data type of a variable or
- table column. You can use this to declare variables that will hold
- database values. For example, let's say you have a column named
- <literal>user_id</literal> in your <literal>users</literal>
- table. To declare a variable with the same data type as
- <literal>users.user_id</> you write:
+ <literal>%TYPE</literal> provides the data type of a variable,
+ function argument or table column. You can use this to declare
+ variables or arrays that will hold database values. For example,
+ let's say you have a column named <literal>user_id</literal> in
+ your <literal>users</literal> table. To declare a variable with
+ the same data type as <literal>users.user_id</> you write:
<programlisting>
user_id users.user_id%TYPE;
+user_id_arr users.user_id%TYPE[];
</programlisting>
</para>
@@ -605,6 +607,28 @@ user_id users.user_id%TYPE;
</para>
<para>
+ <literal>%TYPE</literal> copy also array types, even if elements
+ are row-typed. For example:
+<programlisting>
+array1 integer[]; -- integers array
+copy1 var1%TYPE; -- OK
+
+array2 users[]; -- array of users
+copy2 array2%TYPE; -- OK
+</programlisting>
+ </para>
+
+ <para>
+ However declaring array of arrays with <literal>%TYPE</literal> is
+ not allowed.
+<programlisting>
+var1 integer[][]; -- OK, integers array
+var2 var1%TYPE; -- OK, integers array
+var3 var1%TYPE[]; -- ERROR
+</programlisting>
+ </para>
+
+ <para>
<literal>%TYPE</literal> is particularly valuable in polymorphic
functions, since the data types needed for internal variables can
change from one call to the next. Appropriate variables can be
diff --git a/src/pl/plpgsql/src/gram.y b/src/pl/plpgsql/src/gram.y
index f8e956b..ed9a59d 100644
--- a/src/pl/plpgsql/src/gram.y
+++ b/src/pl/plpgsql/src/gram.y
@@ -75,7 +75,7 @@ static PLpgSQL_expr *read_sql_expression2(int until, int until2,
const char *expected,
int *endtoken);
static PLpgSQL_expr *read_sql_stmt(const char *sqlstart);
-static PLpgSQL_type *read_datatype(int tok);
+static PLpgSQL_type *read_datatype(int startlocation, int tok);
static PLpgSQL_stmt *make_execsql_stmt(int firsttoken, int location);
static PLpgSQL_stmt_fetch *read_fetch_direction(void);
static void complete_direction(PLpgSQL_stmt_fetch *fetch,
@@ -162,6 +162,11 @@ static List *read_raise_options(void);
PLpgSQL_diag_item *diagitem;
PLpgSQL_stmt_fetch *fetch;
PLpgSQL_case_when *casewhen;
+ struct {
+ PLpgSQL_word_attribute attr;
+ bool array;
+ } typearr;
+ bool array;
}
%type <declhdr> decl_sect
@@ -213,6 +218,8 @@ static List *read_raise_options(void);
%type <keyword> unreserved_keyword
+%type <typearr> decl_type
+%type <array> decl_array
/*
* Basic non-keyword token types. These are hard-wired into the core lexer.
@@ -703,14 +710,86 @@ decl_const :
{ $$ = true; }
;
-decl_datatype :
+decl_datatype : T_WORD decl_type
{
- /*
- * If there's a lookahead token, read_datatype
- * should consume it.
- */
- $$ = read_datatype(yychar);
- yyclearin;
+ if ($2.attr == PLPGSQL_WORD) {
+ /*
+ * If there's a lookahead token, read_datatype
+ * should consume it.
+ */
+ $$ = read_datatype(yyloc, yychar);
+ yyclearin;
+ }
+ else {
+ if ($2.attr == PLPGSQL_WORD_TYPE)
+ $$ = plpgsql_parse_wordtype($1.ident, $2.array);
+ else if ($2.attr == PLPGSQL_WORD_ROWTYPE)
+ $$ = plpgsql_parse_wordrowtype($1.ident, $2.array);
+ else
+ elog(FATAL, "wrong value decl_type=%d", $2.attr);
+
+ if ($$ == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("can't determine datatype of \"%s\"", $1.ident),
+ parser_errposition(@1)));
+ }
+ }
+ | T_CWORD decl_type
+ {
+ if ($2.attr == PLPGSQL_WORD) {
+ /*
+ * If there's a lookahead token, read_datatype
+ * should consume it.
+ */
+ $$ = read_datatype(yyloc, yychar);
+ yyclearin;
+ }
+ else {
+ if ($2.attr == PLPGSQL_WORD_TYPE)
+ $$ = plpgsql_parse_cwordtype($1.idents, $2.array);
+ else if ($2.attr == PLPGSQL_WORD_ROWTYPE)
+ $$ = plpgsql_parse_cwordrowtype($1.idents, $2.array);
+ else
+ elog(FATAL, "wrong value decl_type=%d", $2.attr);
+
+ if ($$ == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("can't determine datatype"),
+ parser_errposition(@1)));
+ }
+ }
+ ;
+
+decl_type :
+ {
+ $$.attr = PLPGSQL_WORD;
+ $$.array = false;
+ }
+ | '%' K_TYPE decl_array
+ {
+ $$.attr = PLPGSQL_WORD_TYPE;
+ $$.array = $3;
+ }
+ | '%' K_ROWTYPE decl_array
+ {
+ $$.attr = PLPGSQL_WORD_ROWTYPE;
+ $$.array = $3;
+ }
+ ;
+
+decl_arr_elem : '[' ']'
+ | '[' ICONST ']'
+ ;
+
+decl_array :
+ {
+ $$ = false;
+ }
+ | decl_array decl_arr_elem
+ {
+ $$ = true;
}
;
@@ -2473,11 +2552,10 @@ read_sql_construct(int until,
}
static PLpgSQL_type *
-read_datatype(int tok)
+read_datatype(int startlocation, int tok)
{
StringInfoData ds;
char *type_name;
- int startlocation;
PLpgSQL_type *result;
int parenlevel = 0;
@@ -2488,61 +2566,6 @@ read_datatype(int tok)
if (tok == YYEMPTY)
tok = yylex();
- startlocation = yylloc;
-
- /*
- * If we have a simple or composite identifier, check for %TYPE
- * and %ROWTYPE constructs.
- */
- if (tok == T_WORD)
- {
- char *dtname = yylval.word.ident;
-
- tok = yylex();
- if (tok == '%')
- {
- tok = yylex();
- if (tok_is_keyword(tok, &yylval,
- K_TYPE, "type"))
- {
- result = plpgsql_parse_wordtype(dtname);
- if (result)
- return result;
- }
- else if (tok_is_keyword(tok, &yylval,
- K_ROWTYPE, "rowtype"))
- {
- result = plpgsql_parse_wordrowtype(dtname);
- if (result)
- return result;
- }
- }
- }
- else if (tok == T_CWORD)
- {
- List *dtnames = yylval.cword.idents;
-
- tok = yylex();
- if (tok == '%')
- {
- tok = yylex();
- if (tok_is_keyword(tok, &yylval,
- K_TYPE, "type"))
- {
- result = plpgsql_parse_cwordtype(dtnames);
- if (result)
- return result;
- }
- else if (tok_is_keyword(tok, &yylval,
- K_ROWTYPE, "rowtype"))
- {
- result = plpgsql_parse_cwordrowtype(dtnames);
- if (result)
- return result;
- }
- }
- }
-
while (tok != ';')
{
if (tok == 0)
@@ -2584,6 +2607,7 @@ read_datatype(int tok)
return result;
}
+
static PLpgSQL_stmt *
make_execsql_stmt(int firsttoken, int location)
{
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index 578cae5..853f1af 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -1584,41 +1584,288 @@ plpgsql_parse_tripword(char *word1, char *word2, char *word3,
/* ----------
- * plpgsql_parse_wordtype The scanner found word%TYPE. word can be
- * a variable name or a basetype.
+ * build_arraydatatype Try to construct array type from a type
*
- * Returns datatype struct, or NULL if no match found for word.
+ * Function is used internally by plpgsql_parse_wordtype and
+ * plpgsql_parse_cwordtype when syntax word%TYPE[] is handled.
+ *
+ * Returns datatype struct, or NULL if type can't be used.
* ----------
*/
-PLpgSQL_type *
-plpgsql_parse_wordtype(char *ident)
-{
- PLpgSQL_type *dtype;
- PLpgSQL_nsitem *nse;
- HeapTuple typeTup;
+static PLpgSQL_type *
+build_arraydatatype(Oid typoid, int32 typmod, Oid collation) {
+ Oid elem_typoid;
+
+ if (type_is_array(typoid))
+ /* don't allow to construct array of array */
+ return NULL;
+ else
+ {
+ /* get array type for given scalar type */
+ elem_typoid = get_array_type(typoid);
+ if (elem_typoid != InvalidOid)
+ return plpgsql_build_datatype(elem_typoid, typmod, collation);
+ else
+ /* typoid can't be used as a datatype of array */
+ return NULL;
+ }
+}
+/* ----------
+ * plpgsql_get_variable1 Returns variable from current stack
+ *
+ * Parameter type is used to early filter variables
+ * Used internally by plpgsql_parse_XXX.
+ */
+static PLpgSQL_datum*
+plpgsql_get_variable1(const char* ident, int type, bool* found) {
+ PLpgSQL_nsitem *nse;
+
/*
* Do a lookup in the current namespace stack
*/
nse = plpgsql_ns_lookup(plpgsql_ns_top(), false,
ident, NULL, NULL,
NULL);
+ if (nse) {
+ if (found)
+ *found = true;
- if (nse != NULL)
- {
- switch (nse->itemtype)
- {
- case PLPGSQL_NSTYPE_VAR:
- return ((PLpgSQL_var *) (plpgsql_Datums[nse->itemno]))->datatype;
+ if (type != -1 && type != nse->itemtype)
+ /* variable type do not match */
+ return NULL;
+ else
+ return plpgsql_Datums[nse->itemno];
+ }
+ else {
+ if (found)
+ *found = false;
- /* XXX perhaps allow REC/ROW here? */
+ return NULL;
+ }
+}
- default:
- return NULL;
- }
+
+/* ----------
+ * plpgsql_get_variable2 Returns variable from current stack
+ *
+ * Parameter type is used to early filter variables
+ * Used internally by plpgsql_parse_XXX.
+ */
+static PLpgSQL_datum*
+plpgsql_get_variable2(const char* block_label, const char* ident, int type, bool* found) {
+ PLpgSQL_nsitem *nse;
+ int k;
+
+ /*
+ * Do a lookup in the current namespace stack
+ */
+ nse = plpgsql_ns_lookup(plpgsql_ns_top(), false,
+ block_label, ident, NULL,
+ &k);
+
+ if (nse && k == 2) {
+ if (found)
+ *found = true;
+
+ if (type != -1 && type != nse->itemtype)
+ /* variable type do not match */
+ return NULL;
+ else
+ return plpgsql_Datums[nse->itemno];
}
+ else {
+ if (found)
+ *found = false;
+
+ return NULL;
+ }
+}
+
+
+
+/* plpgsql_get_field_reloid Returns type Oid of field from relation
+ *
+ * Used internally in plpgsql_parse_XXX
+ *
+ */
+static Oid
+plpgsql_get_field_reloid(Oid classOid, const char* fldname, int32* typmod, Oid* typcoll) {
+ HeapTuple classtup = NULL;
+ HeapTuple attrtup = NULL;
+ HeapTuple typetup = NULL;
+ Form_pg_class classStruct;
+ Form_pg_attribute attrStruct;
+ Oid typoid = InvalidOid;
+ MemoryContext oldCxt;
+
+ if (typmod)
+ *typmod = -1;
+ if (typcoll)
+ *typcoll = plpgsql_curr_compile->fn_input_collation;
+
+ /* Avoid memory leaks in the long-term function context */
+ oldCxt = MemoryContextSwitchTo(compile_tmp_cxt);
+
+ /*
+ * Fetch class info
+ */
+ classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(classOid));
+ if (!HeapTupleIsValid(classtup))
+ goto done;
+ classStruct = (Form_pg_class) GETSTRUCT(classtup);
/*
+ * It must be a relation, sequence, view, or type
+ */
+ if (classStruct->relkind != RELKIND_RELATION &&
+ classStruct->relkind != RELKIND_SEQUENCE &&
+ classStruct->relkind != RELKIND_VIEW &&
+ classStruct->relkind != RELKIND_COMPOSITE_TYPE)
+ goto done;
+
+ /*
+ * Fetch the named table field and its type
+ */
+ attrtup = SearchSysCacheAttName(classOid, fldname);
+ if (!HeapTupleIsValid(attrtup))
+ goto done;
+ attrStruct = (Form_pg_attribute) GETSTRUCT(attrtup);
+
+ typoid = attrStruct->atttypid;
+ if (typmod)
+ *typmod = attrStruct->atttypmod;
+ if (typcoll)
+ *typcoll = attrStruct->attcollation;
+
+done:
+ if (HeapTupleIsValid(classtup))
+ ReleaseSysCache(classtup);
+ if (HeapTupleIsValid(attrtup))
+ ReleaseSysCache(attrtup);
+ if (HeapTupleIsValid(typetup))
+ ReleaseSysCache(typetup);
+
+ MemoryContextSwitchTo(oldCxt);
+ return typoid;
+}
+
+
+/* plpgsql_get_field_typoid Returns type Oid of field from composite type
+ *
+ * Used internally in plpgsql_parse_XXX
+ *
+ */
+static Oid
+plpgsql_get_field_typoid(Oid comp_typoid, const char* fldname, int32* typmod, Oid* typcoll) {
+
+ return plpgsql_get_field_reloid(
+ get_typ_typrelid(comp_typoid),
+ fldname,
+ typmod,
+ typcoll);
+}
+
+
+/* plpgsql_get_field2_typoid Returns type Oid of field1.field2 from composite type
+ *
+ * Used internally in plpgsql_parse_XXX
+ *
+ */
+static Oid
+plpgsql_get_field2_typoid(
+ Oid comp_typoid,
+ const char* fldname1,
+ const char* fldname2,
+ int32* typmod,
+ Oid* typcoll)
+{
+ Oid typoid;
+ typoid = plpgsql_get_field_reloid(
+ get_typ_typrelid(comp_typoid),
+ fldname1,
+ NULL,
+ NULL);
+
+ if (type_is_rowtype(typoid))
+ return plpgsql_get_field_reloid(
+ get_typ_typrelid(typoid),
+ fldname2,
+ typmod,
+ typcoll);
+ else
+ return InvalidOid;
+}
+
+
+/* plpgsql_get_field2_reloid Returns type Oid of field1.field2 from relation
+ *
+ * Used internally in plpgsql_parse_XXX
+ *
+ */
+static Oid
+plpgsql_get_field2_reloid(
+ Oid relid,
+ const char* fldname1,
+ const char* fldname2,
+ int32* typmod,
+ Oid* typcoll)
+{
+ Oid typoid;
+ typoid = plpgsql_get_field_reloid(
+ relid,
+ fldname1,
+ NULL,
+ NULL);
+
+ if (type_is_rowtype(typoid))
+ return plpgsql_get_field_reloid(
+ get_typ_typrelid(typoid),
+ fldname2,
+ typmod,
+ typcoll);
+ else
+ return InvalidOid;
+}
+
+
+/* ----------
+ * plpgsql_parse_wordtype The scanner found word%TYPE or word%TYPE[].
+ * word can be a variable name or a basetype.
+ *
+ * Returns datatype struct, or NULL if no match found for word.
+ * ----------
+ */
+PLpgSQL_type *
+plpgsql_parse_wordtype(char *ident, bool array)
+{
+ PLpgSQL_type* dtype;
+ PLpgSQL_var *var;
+ bool exists;
+ HeapTuple typeTup;
+
+ /*
+ * Do a lookup in the current namespace stack
+ */
+ var = (PLpgSQL_var*) plpgsql_get_variable1(
+ ident,
+ PLPGSQL_NSTYPE_VAR,
+ &exists);
+ if (var) {
+ /* found scalar variable */
+ if (array)
+ return build_arraydatatype(
+ var->datatype->typoid,
+ -1,
+ plpgsql_curr_compile->fn_input_collation);
+ else
+ return var->datatype;
+ }
+ else if (exists)
+ /* non-scalar variable found */
+ return NULL;
+
+ /*
* Word wasn't found in the namespace stack. Try to find a data type with
* that name, but ignore shell types and complex types.
*/
@@ -1634,7 +1881,11 @@ plpgsql_parse_wordtype(char *ident)
return NULL;
}
- dtype = build_datatype(typeTup, -1,
+ if (array)
+ dtype = build_datatype(typeTup, -1,
+ plpgsql_curr_compile->fn_input_collation);
+ else
+ dtype = build_arraydatatype(HeapTupleGetOid(typeTup), -1,
plpgsql_curr_compile->fn_input_collation);
ReleaseSysCache(typeTup);
@@ -1650,128 +1901,241 @@ plpgsql_parse_wordtype(char *ident)
/* ----------
- * plpgsql_parse_cwordtype Same lookup for compositeword%TYPE
+ * plpgsql_parse_cwordtype Same lookup for compositeword%TYPE or %TYPE[]
* ----------
*/
PLpgSQL_type *
-plpgsql_parse_cwordtype(List *idents)
+plpgsql_parse_cwordtype(List *idents, bool array)
{
PLpgSQL_type *dtype = NULL;
- PLpgSQL_nsitem *nse;
- const char *fldname;
- Oid classOid;
- HeapTuple classtup = NULL;
- HeapTuple attrtup = NULL;
- HeapTuple typetup = NULL;
- Form_pg_class classStruct;
- Form_pg_attribute attrStruct;
+ PLpgSQL_row *row;
+ PLpgSQL_var *var;
MemoryContext oldCxt;
+ Oid relid;
+ Oid typoid;
+ int32 typmod;
+ Oid typcoll;
+
+#define invalidate_type_info \
+ typoid = InvalidOid; \
+ typmod = -1; \
+ typcoll = plpgsql_curr_compile->fn_input_collation;
+
+#define word1 strVal(linitial(idents))
+#define word2 strVal(lsecond(idents))
+#define word3 strVal(lthird(idents))
+
+ invalidate_type_info;
+
/* Avoid memory leaks in the long-term function context */
oldCxt = MemoryContextSwitchTo(compile_tmp_cxt);
if (list_length(idents) == 2)
{
/*
- * Do a lookup in the current namespace stack. We don't need to check
+ * case 1: block_label.scalar_variable
+ */
+
+ /* Do a lookup in the current namespace stack. We don't need to check
* number of names matched, because we will only consider scalar
* variables.
*/
- nse = plpgsql_ns_lookup(plpgsql_ns_top(), false,
- strVal(linitial(idents)),
- strVal(lsecond(idents)),
- NULL,
+ var = (PLpgSQL_var *) plpgsql_get_variable2(
+ word1,
+ word2,
+ PLPGSQL_NSTYPE_VAR,
+ NULL);
+ if (var) {
+ dtype = var->datatype;
+ if (!type_is_rowtype(dtype->typoid)) {
+ if (array) {
+ typoid = dtype->typoid;
+ goto have_typoid;
+ }
+ else
+ goto have_dtype;
+ }
+ }
+
+ /*
+ * case 2: row_variable.scalar_field
+ */
+ row = (PLpgSQL_row *)plpgsql_get_variable1(
+ word1,
+ PLPGSQL_NSTYPE_ROW,
NULL);
- if (nse != NULL && nse->itemtype == PLPGSQL_NSTYPE_VAR)
- {
- dtype = ((PLpgSQL_var *) (plpgsql_Datums[nse->itemno]))->datatype;
- goto done;
+ if (row && row->rowtupdesc) {
+ typoid = plpgsql_get_field_typoid(
+ row->rowtupdesc->tdtypeid,
+ word2,
+ &typmod,
+ &typcoll);
+
+ if (!type_is_rowtype(typoid))
+ goto have_typoid;
+
+ invalidate_type_info;
}
/*
- * First word could also be a table name
+ * case 3: First word could also be a table name
*/
- classOid = RelnameGetRelid(strVal(linitial(idents)));
- if (!OidIsValid(classOid))
- goto done;
- fldname = strVal(lsecond(idents));
+ relid = RelnameGetRelid(word1);
+ typoid = plpgsql_get_field_reloid(relid, word2, &typmod, &typcoll);
+
+ if (type_is_rowtype(typoid)) {
+ /* field is composite type, mark as invalid */
+ invalidate_type_info;
+ }
+
+ goto have_typoid;
}
else if (list_length(idents) == 3)
{
RangeVar *relvar;
- relvar = makeRangeVar(strVal(linitial(idents)),
- strVal(lsecond(idents)),
- -1);
- /* Can't lock relation - we might not have privileges. */
- classOid = RangeVarGetRelid(relvar, NoLock, true, false);
- if (!OidIsValid(classOid))
- goto done;
- fldname = strVal(lthird(idents));
- }
- else
- goto done;
+ /*
+ * case 1: row_variable.composite_type.scalar_field
+ */
+ row = (PLpgSQL_row *)plpgsql_get_variable1(
+ word1,
+ PLPGSQL_NSTYPE_ROW,
+ NULL);
- classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(classOid));
- if (!HeapTupleIsValid(classtup))
- goto done;
- classStruct = (Form_pg_class) GETSTRUCT(classtup);
+ if (row && row->rowtupdesc) {
+ typoid = plpgsql_get_field2_typoid(
+ row->rowtupdesc->tdtypeid,
+ word2, word3,
+ &typmod, &typcoll);
- /*
- * It must be a relation, sequence, view, or type
- */
- if (classStruct->relkind != RELKIND_RELATION &&
- classStruct->relkind != RELKIND_SEQUENCE &&
- classStruct->relkind != RELKIND_VIEW &&
- classStruct->relkind != RELKIND_COMPOSITE_TYPE)
- goto done;
+ if (!type_is_rowtype(typoid))
+ goto have_typoid;
- /*
- * Fetch the named table field and its type
- */
- attrtup = SearchSysCacheAttName(classOid, fldname);
- if (!HeapTupleIsValid(attrtup))
- goto done;
- attrStruct = (Form_pg_attribute) GETSTRUCT(attrtup);
+ invalidate_type_info;
+ }
- typetup = SearchSysCache1(TYPEOID,
- ObjectIdGetDatum(attrStruct->atttypid));
- if (!HeapTupleIsValid(typetup))
- elog(ERROR, "cache lookup failed for type %u", attrStruct->atttypid);
+ /*
+ * case 2: block_label.row_variable.scalar_field
+ */
+ row = (PLpgSQL_row *)plpgsql_get_variable2(
+ word1,
+ word2,
+ PLPGSQL_NSTYPE_ROW,
+ NULL);
- /*
- * Found that - build a compiler type struct in the caller's cxt and
- * return it
- */
+ if (row && row->rowtupdesc) {
+ typoid = plpgsql_get_field_typoid(
+ row->rowtupdesc->tdtypeid,
+ word3,
+ &typmod,
+ &typcoll);
+
+ if (!type_is_rowtype(typoid))
+ goto have_typoid;
+
+ invalidate_type_info;
+ }
+
+ /*
+ * case 3: relation.row_field.scalar_field
+ */
+ relid = RelnameGetRelid(word1);
+ if (OidIsValid(relid)) {
+ typoid = plpgsql_get_field2_reloid(
+ relid,
+ word2,
+ word3,
+ &typmod,
+ &typcoll);
+
+ if (!type_is_rowtype(typoid))
+ goto have_typoid;
+
+ invalidate_type_info;
+ }
+
+ /*
+ * case 4: schema.relation.scalar_field
+ */
+ relvar = makeRangeVar(word1, word2, -1);
+ /* Can't lock relation - we might not have privileges. */
+ relid = RangeVarGetRelid(relvar, NoLock, true, false);
+ typoid = plpgsql_get_field_reloid(
+ relid,
+ word3,
+ &typmod,
+ &typcoll);
+
+ if (type_is_rowtype(typoid)) {
+ invalidate_type_info;
+ }
+
+ goto have_typoid;
+ }
+
+have_typoid:
MemoryContextSwitchTo(oldCxt);
- dtype = build_datatype(typetup,
- attrStruct->atttypmod,
- attrStruct->attcollation);
- MemoryContextSwitchTo(compile_tmp_cxt);
-done:
- if (HeapTupleIsValid(classtup))
- ReleaseSysCache(classtup);
- if (HeapTupleIsValid(attrtup))
- ReleaseSysCache(attrtup);
- if (HeapTupleIsValid(typetup))
- ReleaseSysCache(typetup);
+ if (OidIsValid(typoid)) {
+ if (array)
+ return build_arraydatatype(typoid, typmod, typcoll);
+ else
+ return plpgsql_build_datatype(typoid, typmod, typcoll);
+ }
+ else
+ return NULL;
+have_dtype:
MemoryContextSwitchTo(oldCxt);
return dtype;
+
+#undef invalidate_type_info
+#undef word1
+#undef word2
+#undef word3
}
/* ----------
- * plpgsql_parse_wordrowtype Scanner found word%ROWTYPE.
- * So word must be a table name.
+ * plpgsql_parse_wordrowtype Scanner found word%ROWTYPE or word%ROWTYPE[].
+ * So word must be a table name or parameter/variable of record type.
* ----------
*/
PLpgSQL_type *
-plpgsql_parse_wordrowtype(char *ident)
+plpgsql_parse_wordrowtype(char *ident, bool array)
{
+ PLpgSQL_row *row;
+ bool exists;
Oid classOid;
+ /*
+ * Do a lookup in the current namespace stack
+ */
+ row = (PLpgSQL_row *)plpgsql_get_variable1(
+ ident,
+ PLPGSQL_NSTYPE_ROW,
+ &exists);
+
+ if (row && row->rowtupdesc) {
+ /* row variable */
+ TupleDesc tuple = row->rowtupdesc;
+
+ if (array)
+ return build_arraydatatype(
+ tuple->tdtypeid,
+ tuple->tdtypmod,
+ plpgsql_curr_compile->fn_input_collation);
+ else
+ return plpgsql_build_datatype(
+ tuple->tdtypeid,
+ tuple->tdtypmod,
+ plpgsql_curr_compile->fn_input_collation);
+ }
+ else if (exists)
+ /* scalar variable found */
+ return NULL;
+
/* Lookup the relation */
classOid = RelnameGetRelid(ident);
if (!OidIsValid(classOid))
@@ -1780,37 +2144,201 @@ plpgsql_parse_wordrowtype(char *ident)
errmsg("relation \"%s\" does not exist", ident)));
/* Build and return the row type struct */
- return plpgsql_build_datatype(get_rel_type_id(classOid), -1, InvalidOid);
+ if (array)
+ return build_arraydatatype(get_rel_type_id(classOid), -1, InvalidOid);
+ else
+ return plpgsql_build_datatype(get_rel_type_id(classOid), -1, InvalidOid);
}
/* ----------
- * plpgsql_parse_cwordrowtype Scanner found compositeword%ROWTYPE.
- * So word must be a namespace qualified table name.
+ * plpgsql_parse_cwordrowtype Scanner found compositeword%ROWTYPE or %ROWTYPE[].
* ----------
*/
PLpgSQL_type *
-plpgsql_parse_cwordrowtype(List *idents)
+plpgsql_parse_cwordrowtype(List *idents, bool array)
{
- Oid classOid;
+ PLpgSQL_row* row;
RangeVar *relvar;
MemoryContext oldCxt;
+
+ Oid relid;
+ Oid typoid;
+ int32 typmod;
+ Oid typcoll;
- if (list_length(idents) != 2)
- return NULL;
+#define invalidate_type_info \
+ typoid = InvalidOid; \
+ typmod = -1; \
+ typcoll = plpgsql_curr_compile->fn_input_collation;
+
+#define word1 strVal(linitial(idents))
+#define word2 strVal(lsecond(idents))
+#define word3 strVal(lthird(idents))
+
+ invalidate_type_info;
/* Avoid memory leaks in long-term function context */
oldCxt = MemoryContextSwitchTo(compile_tmp_cxt);
- /* Look up relation name. Can't lock it - we might not have privileges. */
- relvar = makeRangeVar(strVal(linitial(idents)),
- strVal(lsecond(idents)),
- -1);
- classOid = RangeVarGetRelid(relvar, NoLock, false, false);
+ if (list_length(idents) == 2) {
+ PLpgSQL_row* row;
+ /*
+ * case 1: row_variable.row_field
+ */
+ row = (PLpgSQL_row *)plpgsql_get_variable1(
+ word1,
+ PLPGSQL_NSTYPE_ROW,
+ NULL);
+ if (row && row->rowtupdesc) {
+ typoid = plpgsql_get_field_typoid(
+ row->rowtupdesc->tdtypeid,
+ word2,
+ &typmod,
+ &typcoll);
+
+ if (type_is_rowtype(typoid))
+ goto done;
+
+ invalidate_type_info;
+ }
+
+ /*
+ * case 2: block_label.row_variable
+ */
+ row = (PLpgSQL_row *)plpgsql_get_variable2(
+ word1,
+ word2,
+ PLPGSQL_NSTYPE_ROW,
+ NULL);
+ if (row && row->rowtupdesc) {
+ typoid = row->rowtupdesc->tdtypeid;
+ typmod = row->rowtupdesc->tdtypmod;
+ goto done;
+ }
+
+ /*
+ * case 3: relation.row_field
+ *
+ */
+ relid = RelnameGetRelid(strVal(linitial(idents)));
+ if (OidIsValid(relid)) {
+ typoid = plpgsql_get_field_reloid(
+ relid,
+ word2,
+ &typmod,
+ &typcoll);
+
+ if (type_is_rowtype(typoid))
+ goto done;
+
+ invalidate_type_info;
+ }
+ /*
+ * case 4: schema.relation
+ */
+ /* Look up relation name. Can't lock it - we might not have privileges. */
+ relvar = makeRangeVar(word1, word2, -1);
+ relid = RangeVarGetRelid(relvar, NoLock, false, false);
+ typoid = get_rel_type_id(relid);
+ goto done;
+ }
+ else if (list_length(idents) == 3) {
+ /*
+ * case 1: row_variable.composite_type.field
+ */
+ row = (PLpgSQL_row *)plpgsql_get_variable1(
+ word1,
+ PLPGSQL_NSTYPE_ROW,
+ NULL);
+ if (row && row->rowtupdesc) {
+ typoid = plpgsql_get_field2_typoid(
+ row->rowtupdesc->tdtypeid,
+ word2,
+ word3,
+ &typmod,
+ &typcoll);
+
+ if (type_is_rowtype(typoid))
+ goto done;
+
+ invalidate_type_info;
+ }
+
+ /*
+ * case 2: block_label.row_variable.row_field
+ */
+ row = (PLpgSQL_row *)plpgsql_get_variable2(
+ word1,
+ word2,
+ PLPGSQL_NSTYPE_ROW,
+ NULL);
+
+ if (row && row->rowtupdesc) {
+ typoid = plpgsql_get_field_typoid(
+ row->rowtupdesc->tdtypeid,
+ word3,
+ &typmod,
+ &typcoll);
+
+ if (type_is_rowtype(typoid))
+ goto done;
+
+ invalidate_type_info;
+ }
+
+ /*
+ * case 3: composite_type.row_field.row_field
+ */
+ relid = RelnameGetRelid(word1);
+ if (OidIsValid(relid)) {
+ typoid = plpgsql_get_field2_reloid(
+ relid,
+ word2,
+ word3,
+ &typmod,
+ &typcoll);
+
+ if (type_is_rowtype(typoid))
+ goto done;
+
+ invalidate_type_info;
+ }
+
+ /*
+ * case 4: schema.relation.row_field
+ */
+ relvar = makeRangeVar(word1, word2, -1);
+ relid = RangeVarGetRelid(relvar, NoLock, false, false);
+ if (OidIsValid(relid)) {
+ typoid = plpgsql_get_field_reloid(
+ relid,
+ word3,
+ &typmod,
+ &typcoll);
+ }
+
+ if (!type_is_rowtype(typoid)) {
+ invalidate_type_info;
+ }
+ }
+
+done:
MemoryContextSwitchTo(oldCxt);
- /* Build and return the row type struct */
- return plpgsql_build_datatype(get_rel_type_id(classOid), -1, InvalidOid);
+ if (OidIsValid(typoid)) {
+ if (array)
+ return build_arraydatatype(typoid, typmod, typcoll);
+ else
+ return plpgsql_build_datatype(typoid, typmod, typcoll);
+ }
+ else
+ return NULL;
+
+#undef invalidate_type_info
+#undef word1
+#undef word2
+#undef word3
}
/*
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 61503f1..71015ba 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -153,6 +153,16 @@ typedef enum
PLPGSQL_RESOLVE_COLUMN /* prefer table column to plpgsql var */
} PLpgSQL_resolve_option;
+/* --------
+ * Information about %TYPE|%ROWTYPE attribute
+ * in a data type declaration (rule decl_datatype)
+ * --------
+ */
+typedef enum {
+ PLPGSQL_WORD, /* word - T_WORD or T_CWORD */
+ PLPGSQL_WORD_TYPE, /* word%TYPE */
+ PLPGSQL_WORD_ROWTYPE /* word%ROWTYPE */
+} PLpgSQL_word_attribute;
/**********************************************************************
* Node and structure definitions
@@ -877,10 +887,10 @@ extern bool plpgsql_parse_dblword(char *word1, char *word2,
PLwdatum *wdatum, PLcword *cword);
extern bool plpgsql_parse_tripword(char *word1, char *word2, char *word3,
PLwdatum *wdatum, PLcword *cword);
-extern PLpgSQL_type *plpgsql_parse_wordtype(char *ident);
-extern PLpgSQL_type *plpgsql_parse_cwordtype(List *idents);
-extern PLpgSQL_type *plpgsql_parse_wordrowtype(char *ident);
-extern PLpgSQL_type *plpgsql_parse_cwordrowtype(List *idents);
+extern PLpgSQL_type *plpgsql_parse_wordtype(char *ident, bool array);
+extern PLpgSQL_type *plpgsql_parse_cwordtype(List *idents, bool array);
+extern PLpgSQL_type *plpgsql_parse_wordrowtype(char *ident, bool array);
+extern PLpgSQL_type *plpgsql_parse_cwordrowtype(List *idents, bool array);
extern PLpgSQL_type *plpgsql_build_datatype(Oid typeOid, int32 typmod,
Oid collation);
extern PLpgSQL_variable *plpgsql_build_variable(const char *refname, int lineno,
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 238bf5f..2d75f26 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -4315,6 +4315,797 @@ select unreserved_test();
(1 row)
drop function unreserved_test();
+--
+-- Test attributes %TYPE and %ROWTYPE
+-- and also declaring arrays from copied types.
+--
+create type field_type2 as (
+ scalar_field text
+);
+create type field_type as (
+ scalar_field boolean,
+ comp_field field_type2
+);
+create type comp_type as (
+ scalar_field text,
+ comp_field field_type
+);
+create schema test_plpgsql;
+create type test_plpgsql.field_type as (
+ field boolean
+);
+create type test_plpgsql.comp_type as (
+ scalar_field integer,
+ comp_field test_plpgsql.field_type
+);
+create function test_type_and_rowtype_attribute(param1 integer, param2 comp_type)
+returns void as $$
+declare
+ -- scalar types
+ var1 boolean;
+ var2 integer;
+ var3 varchar(5);
+
+ -- record type
+ rec1 comp_type%ROWTYPE;
+
+ -- copy types
+ copy_var1 var1%TYPE;
+ copy_var2 var2%TYPE;
+ copy_var3 var3%TYPE;
+ copy_rec1 rec1%ROWTYPE;
+ copy_param1 param1%TYPE;
+ copy_param2 param2%ROWTYPE;
+ copy_field comp_type.scalar_field%TYPE;
+
+ -- declare arrays of copied types
+ -- many [] or [NUM] can be used, but all are folded during type construction
+ arr_var1 var1%TYPE[];
+ arr_var2 var2%TYPE[1][2][3];
+ arr_var3 var3%TYPE[][];
+ arr_rec1 rec1%ROWTYPE[];
+ arr_rec2 comp_type%ROWTYPE[];
+ arr_param1 param1%TYPE[];
+ arr_param2 param2%ROWTYPE[][][][][];
+ arr_field comp_type.scalar_field%TYPE[][];
+begin
+ -- finally check types
+ raise notice '%', pg_typeof(copy_var1);
+ raise notice '%', pg_typeof(copy_var2);
+ raise notice '%', pg_typeof(copy_var3);
+ raise notice '%', pg_typeof(copy_rec1);
+ raise notice '%', pg_typeof(copy_param1);
+ raise notice '%', pg_typeof(copy_param2);
+ raise notice '%', pg_typeof(copy_field);
+
+ raise notice '%', pg_typeof(arr_var1);
+ raise notice '%', pg_typeof(arr_var2);
+ raise notice '%', pg_typeof(arr_var3);
+ raise notice '%', pg_typeof(arr_rec1);
+ raise notice '%', pg_typeof(arr_param1);
+ raise notice '%', pg_typeof(arr_param2);
+ raise notice '%', pg_typeof(arr_field);
+end
+$$ language plpgsql;
+SELECT test_type_and_rowtype_attribute(NULL, NULL);
+NOTICE: boolean
+NOTICE: integer
+NOTICE: character varying
+NOTICE: comp_type
+NOTICE: integer
+NOTICE: comp_type
+NOTICE: text
+NOTICE: boolean[]
+NOTICE: integer[]
+NOTICE: character varying[]
+NOTICE: comp_type[]
+NOTICE: integer[]
+NOTICE: comp_type[]
+NOTICE: text[]
+ test_type_and_rowtype_attribute
+---------------------------------
+
+(1 row)
+
+-- compilation should fail, because we don't allow
+-- to declare array of arrays
+create function test_type_attribute_array_of_array()
+returns void as $$
+declare
+ arr integer[];
+ arrarr arr%TYPE[]; -- <= error
+begin
+ null;
+end;
+$$ language plpgsql;
+ERROR: can't determine datatype of "arr"
+LINE 5: arrarr arr%TYPE[]; -- <= error
+ ^
+-- compilation should fail, because we don't allow
+-- to declare array of arrays
+create or replace function test_rowtype_attribute_array_of_array()
+returns void as $$
+declare
+ arr comp_type%ROWTYPE[];
+ arr1 arr%TYPE; -- <= ok, type of 'arr' is comp_type[]
+ arrarr arr%ROWTYPE[]; -- <= error
+begin
+ null;
+end;
+$$ language plpgsql;
+ERROR: can't determine datatype of "arr"
+LINE 6: arrarr arr%ROWTYPE[]; -- <= error
+ ^
+--
+-- Tests syntax: word %TYPE/%ROWTYPE []
+--
+-- test variable%TYPE
+create or replace function test_declare1_1()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 integer;
+ var2 var1%TYPE;
+ var3 var2%TYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ raise notice 'var3 - %', pg_typeof(var3);
+ end;
+$$;
+select test_declare1_1();
+NOTICE: var1 - integer
+NOTICE: var2 - integer
+NOTICE: var3 - integer[]
+ test_declare1_1
+-----------------
+
+(1 row)
+
+-- test scalar_variable%ROWTYPE - ERROR
+create or replace function test_declare1_2()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 integer;
+ var2 var1%ROWTYPE;
+ begin
+ null;
+ end;
+$$;
+ERROR: can't determine datatype of "var1"
+LINE 7: var2 var1%ROWTYPE;
+ ^
+-- test record_variable%ROWTYPE - ERROR
+create or replace function test_declare1_3()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 record;
+ var2 var1%ROWTYPE;
+ begin
+ null;
+ end;
+$$;
+ERROR: can't determine datatype of "var1"
+LINE 7: var2 var1%ROWTYPE;
+ ^
+-- test relation%ROWTYPE - OK
+create or replace function test_declare1_4()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type;
+ var2 comp_type%ROWTYPE;
+ var3 comp_type%ROWTYPE[];
+ var4 var2%ROWTYPE;
+ var5 var2%ROWTYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ raise notice 'var3 - %', pg_typeof(var3);
+ raise notice 'var4 - %', pg_typeof(var4);
+ raise notice 'var5 - %', pg_typeof(var5);
+ end;
+$$;
+select test_declare1_4();
+NOTICE: var1 - comp_type
+NOTICE: var2 - comp_type
+NOTICE: var3 - comp_type[]
+NOTICE: var4 - comp_type
+NOTICE: var5 - comp_type[]
+ test_declare1_4
+-----------------
+
+(1 row)
+
+--
+-- Tests syntax: word1.word2 %TYPE/%ROWTYPE []
+--
+-- test: block_label.scalar_variable %TYPE
+create or replace function test_declare2_1()
+ returns void
+ language plpgsql
+as $$
+ <<block>>
+ declare
+ var1 integer;
+ begin
+ declare
+ var2 block.var1%TYPE;
+ var3 block.var1%TYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ raise notice 'var3 - %', pg_typeof(var3);
+ end;
+ end;
+$$;
+select test_declare2_1();
+NOTICE: var1 - integer
+NOTICE: var2 - integer
+NOTICE: var3 - integer[]
+ test_declare2_1
+-----------------
+
+(1 row)
+
+-- test: block_label.scalar_variable %ROWTYPE - ERROR
+create or replace function test_declare2_2()
+ returns void
+ language plpgsql
+as $$
+ <<block>>
+ declare
+ var1 integer;
+ begin
+ declare
+ var2 block.var1%ROWTYPE;
+ begin
+ end;
+ end;
+$$;
+ERROR: schema "block" does not exist
+CONTEXT: compilation of PL/pgSQL function "test_declare2_2" near line 7
+-- test: block_label.row_variable %TYPE - ERROR
+create or replace function test_declare2_3()
+ returns void
+ language plpgsql
+as $$
+ <<block>>
+ declare
+ var1 comp_type;
+ begin
+ declare
+ var2 block.var1%TYPE;
+ begin
+ end;
+ end;
+$$;
+ERROR: can't determine datatype
+LINE 10: var2 block.var1%TYPE;
+ ^
+-- test: block_label.row_variable %ROWTYPE
+create or replace function test_declare2_4()
+ returns void
+ language plpgsql
+as $$
+ <<block>>
+ declare
+ var1 comp_type;
+ begin
+ declare
+ var2 block.var1%ROWTYPE;
+ var3 block.var1%ROWTYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ raise notice 'var3 - %', pg_typeof(var3);
+ end;
+ end;
+$$;
+select test_declare2_4();
+NOTICE: var1 - comp_type
+NOTICE: var2 - comp_type
+NOTICE: var3 - comp_type[]
+ test_declare2_4
+-----------------
+
+(1 row)
+
+-- test: row_variable.scalar_field % TYPE
+create or replace function test_declare2_5()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type;
+ var2 var1.scalar_field%TYPE;
+ var3 var1.scalar_field%TYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ raise notice 'var3 - %', pg_typeof(var3);
+ end;
+$$;
+select test_declare2_5();
+NOTICE: var1 - comp_type
+NOTICE: var2 - text
+NOTICE: var3 - text[]
+ test_declare2_5
+-----------------
+
+(1 row)
+
+-- test: row_variable.scalar_field % ROWTYPE - ERROR
+create or replace function test_declare2_6()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type;
+ var2 var1.scalar_field%ROWTYPE;
+ begin
+ end;
+$$;
+ERROR: schema "var1" does not exist
+CONTEXT: compilation of PL/pgSQL function "test_declare2_6" near line 4
+-- test: row_variable.row_field % TYPE - ERROR
+create or replace function test_declare2_7()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type;
+ var2 var1.comp_field%TYPE;
+ begin
+ end;
+$$;
+ERROR: can't determine datatype
+LINE 7: var2 var1.comp_field%TYPE;
+ ^
+-- test: row_variable.row_field % ROWTYPE
+create or replace function test_declare2_8()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type;
+ var2 var1.comp_field%ROWTYPE;
+ var3 var1.comp_field%ROWTYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ raise notice 'var3 - %', pg_typeof(var3);
+ end;
+$$;
+select test_declare2_8();
+NOTICE: var1 - comp_type
+NOTICE: var2 - field_type
+NOTICE: var3 - field_type[]
+ test_declare2_8
+-----------------
+
+(1 row)
+
+-- test: relation.scalar_field % TYPE
+create or replace function test_declare2_9()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type.scalar_field%TYPE;
+ var2 comp_type.scalar_field%TYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ end;
+$$;
+select test_declare2_9();
+NOTICE: var1 - text
+NOTICE: var2 - text[]
+ test_declare2_9
+-----------------
+
+(1 row)
+
+-- test: relation.scalar_field % ROWTYPE - ERROR
+create or replace function test_declare2_9()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type.scalar_field%ROWTYPE;
+ begin
+ end;
+$$;
+ERROR: schema "comp_type" does not exist
+CONTEXT: compilation of PL/pgSQL function "test_declare2_9" near line 3
+-- test: relation.row_field % TYPE - ERROR
+create or replace function test_declare2_10()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type.comp_field%TYPE;
+ begin
+ end;
+$$;
+ERROR: can't determine datatype
+LINE 6: var1 comp_type.comp_field%TYPE;
+ ^
+-- test: relation.row_field % ROWTYPE
+create or replace function test_declare2_11()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type.comp_field%ROWTYPE;
+ var2 comp_type.comp_field%ROWTYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ end;
+$$;
+select test_declare2_11();
+NOTICE: var1 - field_type
+NOTICE: var2 - field_type[]
+ test_declare2_11
+------------------
+
+(1 row)
+
+-- test: schama.relation % TYPE - ERROR
+create or replace function test_declare2_12()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 test_plpgsql.comp_type%TYPE;
+ begin
+ end;
+$$;
+ERROR: can't determine datatype
+LINE 6: var1 test_plpgsql.comp_type%TYPE;
+ ^
+-- test: schama.relation % ROWTYPE
+create or replace function test_declare2_13()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 test_plpgsql.comp_type%ROWTYPE;
+ var2 test_plpgsql.comp_type%ROWTYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ end;
+$$;
+select test_declare2_13();
+NOTICE: var1 - test_plpgsql.comp_type
+NOTICE: var2 - test_plpgsql.comp_type[]
+ test_declare2_13
+------------------
+
+(1 row)
+
+--
+-- Test syntax: word1.word2.word3 %TYPE / %ROWTYPE []
+--
+-- test: block_label.row_variable.scalar_field % TYPE
+create or replace function test_declare3_1()
+ returns void
+ language plpgsql
+as $$
+ <<block>>
+ declare
+ var1 comp_type;
+ begin
+ declare
+ var2 block.var1.scalar_field%TYPE;
+ var3 block.var1.scalar_field%TYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ raise notice 'var3 - %', pg_typeof(var3);
+ end;
+ end;
+$$;
+select test_declare3_1();
+NOTICE: var1 - comp_type
+NOTICE: var2 - text
+NOTICE: var3 - text[]
+ test_declare3_1
+-----------------
+
+(1 row)
+
+-- test: block_label.row_variable.scalar_field % ROWTYPE - ERROR
+create or replace function test_declare3_2()
+ returns void
+ language plpgsql
+as $$
+ <<block>>
+ declare
+ var1 comp_type;
+ begin
+ declare
+ var2 block.var1.scalar_field%ROWTYPE;
+ begin
+ end;
+ end;
+$$;
+ERROR: schema "block" does not exist
+CONTEXT: compilation of PL/pgSQL function "test_declare3_2" near line 7
+-- test: block_label.row_variable.row_field % TYPE - ERROR
+create or replace function test_declare3_3()
+ returns void
+ language plpgsql
+as $$
+ <<block>>
+ declare
+ var1 comp_type;
+ begin
+ declare
+ var2 block.var1.comp_field%TYPE;
+ begin
+ end;
+ end;
+$$;
+ERROR: schema "block" does not exist
+CONTEXT: compilation of PL/pgSQL function "test_declare3_3" near line 7
+-- test: block_label.row_variable.row_field % ROWTYPE
+create or replace function test_declare3_4()
+ returns void
+ language plpgsql
+as $$
+ <<block>>
+ declare
+ var1 comp_type;
+ begin
+ declare
+ var2 block.var1.comp_field%ROWTYPE;
+ var3 block.var1.comp_field%ROWTYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ raise notice 'var3 - %', pg_typeof(var3);
+ end;
+ end;
+$$;
+select test_declare3_4();
+NOTICE: var1 - comp_type
+NOTICE: var2 - field_type
+NOTICE: var3 - field_type[]
+ test_declare3_4
+-----------------
+
+(1 row)
+
+-- test: row_variable.row_field.scalar_field % TYPE
+create or replace function test_declare3_5()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type;
+ begin
+ declare
+ var2 var1.comp_field.scalar_field%TYPE;
+ var3 var1.comp_field.scalar_field%TYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ raise notice 'var3 - %', pg_typeof(var3);
+ end;
+ end;
+$$;
+select test_declare3_5();
+NOTICE: var1 - comp_type
+NOTICE: var2 - boolean
+NOTICE: var3 - boolean[]
+ test_declare3_5
+-----------------
+
+(1 row)
+
+-- test: row_variable.row_field.scalar_field % ROWTYPE - ERROR
+create or replace function test_declare3_6()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type;
+ begin
+ declare
+ var2 var1.comp_field.scalar_field%ROWTYPE;
+ begin
+ end;
+ end;
+$$;
+ERROR: schema "var1" does not exist
+CONTEXT: compilation of PL/pgSQL function "test_declare3_6" near line 6
+-- test: row_variable.row_field.row_field % TYPE - ERROR
+create or replace function test_declare3_7()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type;
+ begin
+ declare
+ var2 var1.comp_field.comp_field%TYPE;
+ begin
+ end;
+ end;
+$$;
+ERROR: schema "var1" does not exist
+CONTEXT: compilation of PL/pgSQL function "test_declare3_7" near line 6
+-- test: row_variable.row_field.row_field % ROWTYPE
+create or replace function test_declare3_8()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type;
+ begin
+ declare
+ var2 var1.comp_field.comp_field%ROWTYPE;
+ var3 var1.comp_field.comp_field%ROWTYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ raise notice 'var3 - %', pg_typeof(var3);
+ end;
+ end;
+$$;
+select test_declare3_8();
+NOTICE: var1 - comp_type
+NOTICE: var2 - field_type2
+NOTICE: var3 - field_type2[]
+ test_declare3_8
+-----------------
+
+(1 row)
+
+-- test: relation.row_field.scalar_field % TYPE
+create or replace function test_declare3_9()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type.comp_field.scalar_field%TYPE;
+ var2 comp_type.comp_field.scalar_field%TYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ end;
+$$;
+select test_declare3_9();
+NOTICE: var1 - boolean
+NOTICE: var2 - boolean[]
+ test_declare3_9
+-----------------
+
+(1 row)
+
+-- test: relation.row_field.scalar_field % ROWTYPE - ERROR
+create or replace function test_declare3_10()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type.comp_field.scalar_field%ROWTYPE;
+ begin
+ end;
+$$;
+ERROR: schema "comp_type" does not exist
+CONTEXT: compilation of PL/pgSQL function "test_declare3_10" near line 3
+-- test: relation.row_field.row_field % TYPE - ERROR
+create or replace function test_declare3_11()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type.comp_field.comp_field%TYPE;
+ begin
+ end;
+$$;
+ERROR: schema "comp_type" does not exist
+CONTEXT: compilation of PL/pgSQL function "test_declare3_11" near line 3
+-- test: relation.row_field.row_field % ROWTYPE
+create or replace function test_declare3_12()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type.comp_field.comp_field%ROWTYPE;
+ var2 comp_type.comp_field.comp_field%ROWTYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ end;
+$$;
+select test_declare3_12();
+NOTICE: var1 - field_type2
+NOTICE: var2 - field_type2[]
+ test_declare3_12
+------------------
+
+(1 row)
+
+-- test: schema.relation.scalar_field % TYPE
+create or replace function test_declare3_13()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 test_plpgsql.comp_type.scalar_field%TYPE;
+ var2 test_plpgsql.comp_type.scalar_field%TYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ end;
+$$;
+select test_declare3_13();
+NOTICE: var1 - integer
+NOTICE: var2 - integer[]
+ test_declare3_13
+------------------
+
+(1 row)
+
+-- test: schema.relation.scalar_field % ROWTYPE - ERROR
+create or replace function test_declare3_14()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 test_plpgsql.comp_type.scalar_field%ROWTYPE;
+ begin
+ end;
+$$;
+ERROR: can't determine datatype
+LINE 6: var1 test_plpgsql.comp_type.scalar_field%ROWTYPE;
+ ^
+-- test: schema.relation.row_field % TYPE - ERROR
+create or replace function test_declare3_15()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 test_plpgsql.comp_type.comp_field%TYPE;
+ begin
+ end;
+$$;
+ERROR: can't determine datatype
+LINE 6: var1 test_plpgsql.comp_type.comp_field%TYPE;
+ ^
+-- test: schema.relation.row_field % ROWTYPE
+create or replace function test_declare3_16()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 test_plpgsql.comp_type.comp_field%ROWTYPE;
+ var2 test_plpgsql.comp_type.comp_field%ROWTYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ end;
+$$;
+select test_declare3_16();
+NOTICE: var1 - test_plpgsql.field_type
+NOTICE: var2 - test_plpgsql.field_type[]
+ test_declare3_16
+------------------
+
+(1 row)
+
--
-- Test FOREACH over arrays
--
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index b47c2de..e558c9a 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -3446,6 +3446,650 @@ select unreserved_test();
drop function unreserved_test();
+--
+-- Test attributes %TYPE and %ROWTYPE
+-- and also declaring arrays from copied types.
+--
+
+create type field_type2 as (
+ scalar_field text
+);
+
+create type field_type as (
+ scalar_field boolean,
+ comp_field field_type2
+);
+
+create type comp_type as (
+ scalar_field text,
+ comp_field field_type
+);
+
+create schema test_plpgsql;
+
+create type test_plpgsql.field_type as (
+ field boolean
+);
+
+create type test_plpgsql.comp_type as (
+ scalar_field integer,
+ comp_field test_plpgsql.field_type
+);
+
+create function test_type_and_rowtype_attribute(param1 integer, param2 comp_type)
+returns void as $$
+declare
+ -- scalar types
+ var1 boolean;
+ var2 integer;
+ var3 varchar(5);
+
+ -- record type
+ rec1 comp_type%ROWTYPE;
+
+ -- copy types
+ copy_var1 var1%TYPE;
+ copy_var2 var2%TYPE;
+ copy_var3 var3%TYPE;
+ copy_rec1 rec1%ROWTYPE;
+ copy_param1 param1%TYPE;
+ copy_param2 param2%ROWTYPE;
+ copy_field comp_type.scalar_field%TYPE;
+
+ -- declare arrays of copied types
+ -- many [] or [NUM] can be used, but all are folded during type construction
+ arr_var1 var1%TYPE[];
+ arr_var2 var2%TYPE[1][2][3];
+ arr_var3 var3%TYPE[][];
+ arr_rec1 rec1%ROWTYPE[];
+ arr_rec2 comp_type%ROWTYPE[];
+ arr_param1 param1%TYPE[];
+ arr_param2 param2%ROWTYPE[][][][][];
+ arr_field comp_type.scalar_field%TYPE[][];
+begin
+ -- finally check types
+ raise notice '%', pg_typeof(copy_var1);
+ raise notice '%', pg_typeof(copy_var2);
+ raise notice '%', pg_typeof(copy_var3);
+ raise notice '%', pg_typeof(copy_rec1);
+ raise notice '%', pg_typeof(copy_param1);
+ raise notice '%', pg_typeof(copy_param2);
+ raise notice '%', pg_typeof(copy_field);
+
+ raise notice '%', pg_typeof(arr_var1);
+ raise notice '%', pg_typeof(arr_var2);
+ raise notice '%', pg_typeof(arr_var3);
+ raise notice '%', pg_typeof(arr_rec1);
+ raise notice '%', pg_typeof(arr_param1);
+ raise notice '%', pg_typeof(arr_param2);
+ raise notice '%', pg_typeof(arr_field);
+end
+$$ language plpgsql;
+
+SELECT test_type_and_rowtype_attribute(NULL, NULL);
+
+
+-- compilation should fail, because we don't allow
+-- to declare array of arrays
+create function test_type_attribute_array_of_array()
+returns void as $$
+declare
+ arr integer[];
+ arrarr arr%TYPE[]; -- <= error
+begin
+ null;
+end;
+$$ language plpgsql;
+
+
+-- compilation should fail, because we don't allow
+-- to declare array of arrays
+create or replace function test_rowtype_attribute_array_of_array()
+returns void as $$
+declare
+ arr comp_type%ROWTYPE[];
+ arr1 arr%TYPE; -- <= ok, type of 'arr' is comp_type[]
+ arrarr arr%ROWTYPE[]; -- <= error
+begin
+ null;
+end;
+$$ language plpgsql;
+
+--
+-- Tests syntax: word %TYPE/%ROWTYPE []
+--
+
+-- test variable%TYPE
+create or replace function test_declare1_1()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 integer;
+ var2 var1%TYPE;
+ var3 var2%TYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ raise notice 'var3 - %', pg_typeof(var3);
+ end;
+$$;
+select test_declare1_1();
+
+-- test scalar_variable%ROWTYPE - ERROR
+create or replace function test_declare1_2()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 integer;
+ var2 var1%ROWTYPE;
+ begin
+ null;
+ end;
+$$;
+
+-- test record_variable%ROWTYPE - ERROR
+create or replace function test_declare1_3()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 record;
+ var2 var1%ROWTYPE;
+ begin
+ null;
+ end;
+$$;
+
+-- test relation%ROWTYPE - OK
+create or replace function test_declare1_4()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type;
+ var2 comp_type%ROWTYPE;
+ var3 comp_type%ROWTYPE[];
+ var4 var2%ROWTYPE;
+ var5 var2%ROWTYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ raise notice 'var3 - %', pg_typeof(var3);
+ raise notice 'var4 - %', pg_typeof(var4);
+ raise notice 'var5 - %', pg_typeof(var5);
+ end;
+$$;
+select test_declare1_4();
+
+--
+-- Tests syntax: word1.word2 %TYPE/%ROWTYPE []
+--
+
+-- test: block_label.scalar_variable %TYPE
+create or replace function test_declare2_1()
+ returns void
+ language plpgsql
+as $$
+ <<block>>
+ declare
+ var1 integer;
+ begin
+ declare
+ var2 block.var1%TYPE;
+ var3 block.var1%TYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ raise notice 'var3 - %', pg_typeof(var3);
+ end;
+ end;
+$$;
+select test_declare2_1();
+
+-- test: block_label.scalar_variable %ROWTYPE - ERROR
+create or replace function test_declare2_2()
+ returns void
+ language plpgsql
+as $$
+ <<block>>
+ declare
+ var1 integer;
+ begin
+ declare
+ var2 block.var1%ROWTYPE;
+ begin
+ end;
+ end;
+$$;
+
+-- test: block_label.row_variable %TYPE - ERROR
+create or replace function test_declare2_3()
+ returns void
+ language plpgsql
+as $$
+ <<block>>
+ declare
+ var1 comp_type;
+ begin
+ declare
+ var2 block.var1%TYPE;
+ begin
+ end;
+ end;
+$$;
+
+-- test: block_label.row_variable %ROWTYPE
+create or replace function test_declare2_4()
+ returns void
+ language plpgsql
+as $$
+ <<block>>
+ declare
+ var1 comp_type;
+ begin
+ declare
+ var2 block.var1%ROWTYPE;
+ var3 block.var1%ROWTYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ raise notice 'var3 - %', pg_typeof(var3);
+ end;
+ end;
+$$;
+select test_declare2_4();
+
+-- test: row_variable.scalar_field % TYPE
+create or replace function test_declare2_5()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type;
+ var2 var1.scalar_field%TYPE;
+ var3 var1.scalar_field%TYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ raise notice 'var3 - %', pg_typeof(var3);
+ end;
+$$;
+select test_declare2_5();
+
+-- test: row_variable.scalar_field % ROWTYPE - ERROR
+create or replace function test_declare2_6()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type;
+ var2 var1.scalar_field%ROWTYPE;
+ begin
+ end;
+$$;
+
+-- test: row_variable.row_field % TYPE - ERROR
+create or replace function test_declare2_7()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type;
+ var2 var1.comp_field%TYPE;
+ begin
+ end;
+$$;
+
+-- test: row_variable.row_field % ROWTYPE
+create or replace function test_declare2_8()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type;
+ var2 var1.comp_field%ROWTYPE;
+ var3 var1.comp_field%ROWTYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ raise notice 'var3 - %', pg_typeof(var3);
+ end;
+$$;
+select test_declare2_8();
+
+-- test: relation.scalar_field % TYPE
+create or replace function test_declare2_9()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type.scalar_field%TYPE;
+ var2 comp_type.scalar_field%TYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ end;
+$$;
+select test_declare2_9();
+
+-- test: relation.scalar_field % ROWTYPE - ERROR
+create or replace function test_declare2_9()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type.scalar_field%ROWTYPE;
+ begin
+ end;
+$$;
+
+-- test: relation.row_field % TYPE - ERROR
+create or replace function test_declare2_10()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type.comp_field%TYPE;
+ begin
+ end;
+$$;
+
+-- test: relation.row_field % ROWTYPE
+create or replace function test_declare2_11()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type.comp_field%ROWTYPE;
+ var2 comp_type.comp_field%ROWTYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ end;
+$$;
+select test_declare2_11();
+
+-- test: schama.relation % TYPE - ERROR
+create or replace function test_declare2_12()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 test_plpgsql.comp_type%TYPE;
+ begin
+ end;
+$$;
+
+-- test: schama.relation % ROWTYPE
+create or replace function test_declare2_13()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 test_plpgsql.comp_type%ROWTYPE;
+ var2 test_plpgsql.comp_type%ROWTYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ end;
+$$;
+select test_declare2_13();
+
+--
+-- Test syntax: word1.word2.word3 %TYPE / %ROWTYPE []
+--
+
+-- test: block_label.row_variable.scalar_field % TYPE
+create or replace function test_declare3_1()
+ returns void
+ language plpgsql
+as $$
+ <<block>>
+ declare
+ var1 comp_type;
+ begin
+ declare
+ var2 block.var1.scalar_field%TYPE;
+ var3 block.var1.scalar_field%TYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ raise notice 'var3 - %', pg_typeof(var3);
+ end;
+ end;
+$$;
+select test_declare3_1();
+
+-- test: block_label.row_variable.scalar_field % ROWTYPE - ERROR
+create or replace function test_declare3_2()
+ returns void
+ language plpgsql
+as $$
+ <<block>>
+ declare
+ var1 comp_type;
+ begin
+ declare
+ var2 block.var1.scalar_field%ROWTYPE;
+ begin
+ end;
+ end;
+$$;
+
+-- test: block_label.row_variable.row_field % TYPE - ERROR
+create or replace function test_declare3_3()
+ returns void
+ language plpgsql
+as $$
+ <<block>>
+ declare
+ var1 comp_type;
+ begin
+ declare
+ var2 block.var1.comp_field%TYPE;
+ begin
+ end;
+ end;
+$$;
+
+-- test: block_label.row_variable.row_field % ROWTYPE
+create or replace function test_declare3_4()
+ returns void
+ language plpgsql
+as $$
+ <<block>>
+ declare
+ var1 comp_type;
+ begin
+ declare
+ var2 block.var1.comp_field%ROWTYPE;
+ var3 block.var1.comp_field%ROWTYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ raise notice 'var3 - %', pg_typeof(var3);
+ end;
+ end;
+$$;
+select test_declare3_4();
+
+-- test: row_variable.row_field.scalar_field % TYPE
+create or replace function test_declare3_5()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type;
+ begin
+ declare
+ var2 var1.comp_field.scalar_field%TYPE;
+ var3 var1.comp_field.scalar_field%TYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ raise notice 'var3 - %', pg_typeof(var3);
+ end;
+ end;
+$$;
+select test_declare3_5();
+
+-- test: row_variable.row_field.scalar_field % ROWTYPE - ERROR
+create or replace function test_declare3_6()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type;
+ begin
+ declare
+ var2 var1.comp_field.scalar_field%ROWTYPE;
+ begin
+ end;
+ end;
+$$;
+
+-- test: row_variable.row_field.row_field % TYPE - ERROR
+create or replace function test_declare3_7()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type;
+ begin
+ declare
+ var2 var1.comp_field.comp_field%TYPE;
+ begin
+ end;
+ end;
+$$;
+
+-- test: row_variable.row_field.row_field % ROWTYPE
+create or replace function test_declare3_8()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type;
+ begin
+ declare
+ var2 var1.comp_field.comp_field%ROWTYPE;
+ var3 var1.comp_field.comp_field%ROWTYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ raise notice 'var3 - %', pg_typeof(var3);
+ end;
+ end;
+$$;
+select test_declare3_8();
+
+-- test: relation.row_field.scalar_field % TYPE
+create or replace function test_declare3_9()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type.comp_field.scalar_field%TYPE;
+ var2 comp_type.comp_field.scalar_field%TYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ end;
+$$;
+select test_declare3_9();
+
+-- test: relation.row_field.scalar_field % ROWTYPE - ERROR
+create or replace function test_declare3_10()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type.comp_field.scalar_field%ROWTYPE;
+ begin
+ end;
+$$;
+
+-- test: relation.row_field.row_field % TYPE - ERROR
+create or replace function test_declare3_11()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type.comp_field.comp_field%TYPE;
+ begin
+ end;
+$$;
+
+-- test: relation.row_field.row_field % ROWTYPE
+create or replace function test_declare3_12()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 comp_type.comp_field.comp_field%ROWTYPE;
+ var2 comp_type.comp_field.comp_field%ROWTYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ end;
+$$;
+select test_declare3_12();
+
+-- test: schema.relation.scalar_field % TYPE
+create or replace function test_declare3_13()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 test_plpgsql.comp_type.scalar_field%TYPE;
+ var2 test_plpgsql.comp_type.scalar_field%TYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ end;
+$$;
+select test_declare3_13();
+
+-- test: schema.relation.scalar_field % ROWTYPE - ERROR
+create or replace function test_declare3_14()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 test_plpgsql.comp_type.scalar_field%ROWTYPE;
+ begin
+ end;
+$$;
+
+-- test: schema.relation.row_field % TYPE - ERROR
+create or replace function test_declare3_15()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 test_plpgsql.comp_type.comp_field%TYPE;
+ begin
+ end;
+$$;
+
+-- test: schema.relation.row_field % ROWTYPE
+create or replace function test_declare3_16()
+ returns void
+ language plpgsql
+as $$
+ declare
+ var1 test_plpgsql.comp_type.comp_field%ROWTYPE;
+ var2 test_plpgsql.comp_type.comp_field%ROWTYPE[];
+ begin
+ raise notice 'var1 - %', pg_typeof(var1);
+ raise notice 'var2 - %', pg_typeof(var2);
+ end;
+$$;
+select test_declare3_16();
+
+
--
-- Test FOREACH over arrays
--