Skip to content

Commit 2a6b47c

Browse files
committed
Improve plpgsql's error messages for incorrect %TYPE and %ROWTYPE.
If one of these constructs referenced a nonexistent object, we'd fall through to feeding the whole construct to the core parser, which would reject it with a "syntax error" message. That's pretty unhelpful and misleading. There's no good reason for plpgsql_parse_wordtype and friends not to throw a useful error for incorrect input, so make them do that instead of returning NULL. Discussion: https://postgr.es/m/1964516.1708977740@sss.pgh.pa.us
1 parent 363eb05 commit 2a6b47c

File tree

4 files changed

+83
-57
lines changed

4 files changed

+83
-57
lines changed

src/pl/plpgsql/src/expected/plpgsql_misc.out

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,39 @@ CREATE OR REPLACE PROCEDURE public.test2(IN x integer)
2929
BEGIN ATOMIC
3030
SELECT (x + 2);
3131
END
32+
-- Test %TYPE and %ROWTYPE error cases
33+
create table misc_table(f1 int);
34+
do $$ declare x foo%type; begin end $$;
35+
ERROR: variable "foo" does not exist
36+
CONTEXT: compilation of PL/pgSQL function "inline_code_block" near line 1
37+
do $$ declare x notice%type; begin end $$; -- covers unreserved-keyword case
38+
ERROR: variable "notice" does not exist
39+
CONTEXT: compilation of PL/pgSQL function "inline_code_block" near line 1
40+
do $$ declare x foo.bar%type; begin end $$;
41+
ERROR: relation "foo" does not exist
42+
CONTEXT: compilation of PL/pgSQL function "inline_code_block" near line 1
43+
do $$ declare x foo.bar.baz%type; begin end $$;
44+
ERROR: schema "foo" does not exist
45+
CONTEXT: compilation of PL/pgSQL function "inline_code_block" near line 1
46+
do $$ declare x public.foo.bar%type; begin end $$;
47+
ERROR: relation "public.foo" does not exist
48+
CONTEXT: compilation of PL/pgSQL function "inline_code_block" near line 1
49+
do $$ declare x public.misc_table.zed%type; begin end $$;
50+
ERROR: column "zed" of relation "misc_table" does not exist
51+
CONTEXT: compilation of PL/pgSQL function "inline_code_block" near line 1
52+
do $$ declare x foo%rowtype; begin end $$;
53+
ERROR: relation "foo" does not exist
54+
CONTEXT: compilation of PL/pgSQL function "inline_code_block" near line 1
55+
do $$ declare x notice%rowtype; begin end $$; -- covers unreserved-keyword case
56+
ERROR: relation "notice" does not exist
57+
CONTEXT: compilation of PL/pgSQL function "inline_code_block" near line 1
58+
do $$ declare x foo.bar%rowtype; begin end $$;
59+
ERROR: schema "foo" does not exist
60+
CONTEXT: compilation of PL/pgSQL function "inline_code_block" near line 1
61+
do $$ declare x foo.bar.baz%rowtype; begin end $$;
62+
ERROR: cross-database references are not implemented: "foo.bar.baz"
63+
CONTEXT: compilation of PL/pgSQL function "inline_code_block" near line 1
64+
do $$ declare x public.foo%rowtype; begin end $$;
65+
ERROR: relation "public.foo" does not exist
66+
CONTEXT: compilation of PL/pgSQL function "inline_code_block" near line 1
67+
do $$ declare x public.misc_table%rowtype; begin end $$;

src/pl/plpgsql/src/pl_comp.c

Lines changed: 29 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1599,7 +1599,7 @@ plpgsql_parse_tripword(char *word1, char *word2, char *word3,
15991599
* plpgsql_parse_wordtype The scanner found word%TYPE. word should be
16001600
* a pre-existing variable name.
16011601
*
1602-
* Returns datatype struct, or NULL if no match found for word.
1602+
* Returns datatype struct. Throws error if no match found for word.
16031603
* ----------
16041604
*/
16051605
PLpgSQL_type *
@@ -1623,23 +1623,24 @@ plpgsql_parse_wordtype(char *ident)
16231623
case PLPGSQL_NSTYPE_REC:
16241624
return ((PLpgSQL_rec *) (plpgsql_Datums[nse->itemno]))->datatype;
16251625
default:
1626-
return NULL;
1626+
break;
16271627
}
16281628
}
16291629

1630-
/*
1631-
* Nothing found - up to now it's a word without any special meaning for
1632-
* us.
1633-
*/
1634-
return NULL;
1630+
/* No match, complain */
1631+
ereport(ERROR,
1632+
(errcode(ERRCODE_UNDEFINED_OBJECT),
1633+
errmsg("variable \"%s\" does not exist", ident)));
1634+
return NULL; /* keep compiler quiet */
16351635
}
16361636

16371637

16381638
/* ----------
16391639
* plpgsql_parse_cwordtype Same lookup for compositeword%TYPE
16401640
*
16411641
* Here, we allow either a block-qualified variable name, or a reference
1642-
* to a column of some table.
1642+
* to a column of some table. (If we must throw error, we assume that the
1643+
* latter case was intended.)
16431644
* ----------
16441645
*/
16451646
PLpgSQL_type *
@@ -1648,12 +1649,11 @@ plpgsql_parse_cwordtype(List *idents)
16481649
PLpgSQL_type *dtype = NULL;
16491650
PLpgSQL_nsitem *nse;
16501651
int nnames;
1651-
const char *fldname;
1652+
RangeVar *relvar = NULL;
1653+
const char *fldname = NULL;
16521654
Oid classOid;
1653-
HeapTuple classtup = NULL;
16541655
HeapTuple attrtup = NULL;
16551656
HeapTuple typetup = NULL;
1656-
Form_pg_class classStruct;
16571657
Form_pg_attribute attrStruct;
16581658
MemoryContext oldCxt;
16591659

@@ -1688,57 +1688,39 @@ plpgsql_parse_cwordtype(List *idents)
16881688
/*
16891689
* First word could also be a table name
16901690
*/
1691-
classOid = RelnameGetRelid(strVal(linitial(idents)));
1692-
if (!OidIsValid(classOid))
1693-
goto done;
1691+
relvar = makeRangeVar(NULL,
1692+
strVal(linitial(idents)),
1693+
-1);
16941694
fldname = strVal(lsecond(idents));
16951695
}
1696-
else if (list_length(idents) == 3)
1696+
else
16971697
{
1698-
RangeVar *relvar;
1699-
17001698
/*
17011699
* We could check for a block-qualified reference to a field of a
17021700
* record variable, but %TYPE is documented as applying to variables,
17031701
* not fields of variables. Things would get rather ambiguous if we
17041702
* allowed either interpretation.
17051703
*/
1706-
relvar = makeRangeVar(strVal(linitial(idents)),
1707-
strVal(lsecond(idents)),
1708-
-1);
1709-
/* Can't lock relation - we might not have privileges. */
1710-
classOid = RangeVarGetRelid(relvar, NoLock, true);
1711-
if (!OidIsValid(classOid))
1712-
goto done;
1713-
fldname = strVal(lthird(idents));
1714-
}
1715-
else
1716-
goto done;
1704+
List *rvnames;
17171705

1718-
classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(classOid));
1719-
if (!HeapTupleIsValid(classtup))
1720-
goto done;
1721-
classStruct = (Form_pg_class) GETSTRUCT(classtup);
1706+
Assert(list_length(idents) > 2);
1707+
rvnames = list_delete_last(list_copy(idents));
1708+
relvar = makeRangeVarFromNameList(rvnames);
1709+
fldname = strVal(llast(idents));
1710+
}
17221711

1723-
/*
1724-
* It must be a relation, sequence, view, materialized view, composite
1725-
* type, or foreign table
1726-
*/
1727-
if (classStruct->relkind != RELKIND_RELATION &&
1728-
classStruct->relkind != RELKIND_SEQUENCE &&
1729-
classStruct->relkind != RELKIND_VIEW &&
1730-
classStruct->relkind != RELKIND_MATVIEW &&
1731-
classStruct->relkind != RELKIND_COMPOSITE_TYPE &&
1732-
classStruct->relkind != RELKIND_FOREIGN_TABLE &&
1733-
classStruct->relkind != RELKIND_PARTITIONED_TABLE)
1734-
goto done;
1712+
/* Look up relation name. Can't lock it - we might not have privileges. */
1713+
classOid = RangeVarGetRelid(relvar, NoLock, false);
17351714

17361715
/*
17371716
* Fetch the named table field and its type
17381717
*/
17391718
attrtup = SearchSysCacheAttName(classOid, fldname);
17401719
if (!HeapTupleIsValid(attrtup))
1741-
goto done;
1720+
ereport(ERROR,
1721+
(errcode(ERRCODE_UNDEFINED_COLUMN),
1722+
errmsg("column \"%s\" of relation \"%s\" does not exist",
1723+
fldname, relvar->relname)));
17421724
attrStruct = (Form_pg_attribute) GETSTRUCT(attrtup);
17431725

17441726
typetup = SearchSysCache1(TYPEOID,
@@ -1759,8 +1741,6 @@ plpgsql_parse_cwordtype(List *idents)
17591741
MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
17601742

17611743
done:
1762-
if (HeapTupleIsValid(classtup))
1763-
ReleaseSysCache(classtup);
17641744
if (HeapTupleIsValid(attrtup))
17651745
ReleaseSysCache(attrtup);
17661746
if (HeapTupleIsValid(typetup))
@@ -1824,16 +1804,12 @@ plpgsql_parse_cwordrowtype(List *idents)
18241804
* As above, this is a relation lookup but could be a type lookup if we
18251805
* weren't being backwards-compatible about error wording.
18261806
*/
1827-
if (list_length(idents) != 2)
1828-
return NULL;
18291807

18301808
/* Avoid memory leaks in long-term function context */
18311809
oldCxt = MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
18321810

18331811
/* Look up relation name. Can't lock it - we might not have privileges. */
1834-
relvar = makeRangeVar(strVal(linitial(idents)),
1835-
strVal(lsecond(idents)),
1836-
-1);
1812+
relvar = makeRangeVarFromNameList(idents);
18371813
classOid = RangeVarGetRelid(relvar, NoLock, false);
18381814

18391815
/* Some relkinds lack type OIDs */
@@ -1842,7 +1818,7 @@ plpgsql_parse_cwordrowtype(List *idents)
18421818
ereport(ERROR,
18431819
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
18441820
errmsg("relation \"%s\" does not have a composite type",
1845-
strVal(lsecond(idents)))));
1821+
relvar->relname)));
18461822

18471823
MemoryContextSwitchTo(oldCxt);
18481824

src/pl/plpgsql/src/pl_gram.y

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2810,10 +2810,7 @@ read_datatype(int tok)
28102810

28112811
/*
28122812
* If we have a simple or composite identifier, check for %TYPE and
2813-
* %ROWTYPE constructs. (Note that if plpgsql_parse_wordtype et al fail
2814-
* to recognize the identifier, we'll fall through and pass the whole
2815-
* string to parse_datatype, which will assuredly give an unhelpful
2816-
* "syntax error". Should we try to give a more specific error?)
2813+
* %ROWTYPE constructs.
28172814
*/
28182815
if (tok == T_WORD)
28192816
{

src/pl/plpgsql/src/sql/plpgsql_misc.sql

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,20 @@ $$;
2020

2121
\sf test1
2222
\sf test2
23+
24+
-- Test %TYPE and %ROWTYPE error cases
25+
create table misc_table(f1 int);
26+
27+
do $$ declare x foo%type; begin end $$;
28+
do $$ declare x notice%type; begin end $$; -- covers unreserved-keyword case
29+
do $$ declare x foo.bar%type; begin end $$;
30+
do $$ declare x foo.bar.baz%type; begin end $$;
31+
do $$ declare x public.foo.bar%type; begin end $$;
32+
do $$ declare x public.misc_table.zed%type; begin end $$;
33+
34+
do $$ declare x foo%rowtype; begin end $$;
35+
do $$ declare x notice%rowtype; begin end $$; -- covers unreserved-keyword case
36+
do $$ declare x foo.bar%rowtype; begin end $$;
37+
do $$ declare x foo.bar.baz%rowtype; begin end $$;
38+
do $$ declare x public.foo%rowtype; begin end $$;
39+
do $$ declare x public.misc_table%rowtype; begin end $$;

0 commit comments

Comments
 (0)