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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy