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
This commit is contained in:
parent
363eb05996
commit
2a6b47cb50
@ -29,3 +29,39 @@ CREATE OR REPLACE PROCEDURE public.test2(IN x integer)
|
|||||||
BEGIN ATOMIC
|
BEGIN ATOMIC
|
||||||
SELECT (x + 2);
|
SELECT (x + 2);
|
||||||
END
|
END
|
||||||
|
-- Test %TYPE and %ROWTYPE error cases
|
||||||
|
create table misc_table(f1 int);
|
||||||
|
do $$ declare x foo%type; begin end $$;
|
||||||
|
ERROR: variable "foo" does not exist
|
||||||
|
CONTEXT: compilation of PL/pgSQL function "inline_code_block" near line 1
|
||||||
|
do $$ declare x notice%type; begin end $$; -- covers unreserved-keyword case
|
||||||
|
ERROR: variable "notice" does not exist
|
||||||
|
CONTEXT: compilation of PL/pgSQL function "inline_code_block" near line 1
|
||||||
|
do $$ declare x foo.bar%type; begin end $$;
|
||||||
|
ERROR: relation "foo" does not exist
|
||||||
|
CONTEXT: compilation of PL/pgSQL function "inline_code_block" near line 1
|
||||||
|
do $$ declare x foo.bar.baz%type; begin end $$;
|
||||||
|
ERROR: schema "foo" does not exist
|
||||||
|
CONTEXT: compilation of PL/pgSQL function "inline_code_block" near line 1
|
||||||
|
do $$ declare x public.foo.bar%type; begin end $$;
|
||||||
|
ERROR: relation "public.foo" does not exist
|
||||||
|
CONTEXT: compilation of PL/pgSQL function "inline_code_block" near line 1
|
||||||
|
do $$ declare x public.misc_table.zed%type; begin end $$;
|
||||||
|
ERROR: column "zed" of relation "misc_table" does not exist
|
||||||
|
CONTEXT: compilation of PL/pgSQL function "inline_code_block" near line 1
|
||||||
|
do $$ declare x foo%rowtype; begin end $$;
|
||||||
|
ERROR: relation "foo" does not exist
|
||||||
|
CONTEXT: compilation of PL/pgSQL function "inline_code_block" near line 1
|
||||||
|
do $$ declare x notice%rowtype; begin end $$; -- covers unreserved-keyword case
|
||||||
|
ERROR: relation "notice" does not exist
|
||||||
|
CONTEXT: compilation of PL/pgSQL function "inline_code_block" near line 1
|
||||||
|
do $$ declare x foo.bar%rowtype; begin end $$;
|
||||||
|
ERROR: schema "foo" does not exist
|
||||||
|
CONTEXT: compilation of PL/pgSQL function "inline_code_block" near line 1
|
||||||
|
do $$ declare x foo.bar.baz%rowtype; begin end $$;
|
||||||
|
ERROR: cross-database references are not implemented: "foo.bar.baz"
|
||||||
|
CONTEXT: compilation of PL/pgSQL function "inline_code_block" near line 1
|
||||||
|
do $$ declare x public.foo%rowtype; begin end $$;
|
||||||
|
ERROR: relation "public.foo" does not exist
|
||||||
|
CONTEXT: compilation of PL/pgSQL function "inline_code_block" near line 1
|
||||||
|
do $$ declare x public.misc_table%rowtype; begin end $$;
|
||||||
|
@ -1599,7 +1599,7 @@ plpgsql_parse_tripword(char *word1, char *word2, char *word3,
|
|||||||
* plpgsql_parse_wordtype The scanner found word%TYPE. word should be
|
* plpgsql_parse_wordtype The scanner found word%TYPE. word should be
|
||||||
* a pre-existing variable name.
|
* a pre-existing variable name.
|
||||||
*
|
*
|
||||||
* Returns datatype struct, or NULL if no match found for word.
|
* Returns datatype struct. Throws error if no match found for word.
|
||||||
* ----------
|
* ----------
|
||||||
*/
|
*/
|
||||||
PLpgSQL_type *
|
PLpgSQL_type *
|
||||||
@ -1623,15 +1623,15 @@ plpgsql_parse_wordtype(char *ident)
|
|||||||
case PLPGSQL_NSTYPE_REC:
|
case PLPGSQL_NSTYPE_REC:
|
||||||
return ((PLpgSQL_rec *) (plpgsql_Datums[nse->itemno]))->datatype;
|
return ((PLpgSQL_rec *) (plpgsql_Datums[nse->itemno]))->datatype;
|
||||||
default:
|
default:
|
||||||
return NULL;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/* No match, complain */
|
||||||
* Nothing found - up to now it's a word without any special meaning for
|
ereport(ERROR,
|
||||||
* us.
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
||||||
*/
|
errmsg("variable \"%s\" does not exist", ident)));
|
||||||
return NULL;
|
return NULL; /* keep compiler quiet */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1639,7 +1639,8 @@ plpgsql_parse_wordtype(char *ident)
|
|||||||
* plpgsql_parse_cwordtype Same lookup for compositeword%TYPE
|
* plpgsql_parse_cwordtype Same lookup for compositeword%TYPE
|
||||||
*
|
*
|
||||||
* Here, we allow either a block-qualified variable name, or a reference
|
* Here, we allow either a block-qualified variable name, or a reference
|
||||||
* to a column of some table.
|
* to a column of some table. (If we must throw error, we assume that the
|
||||||
|
* latter case was intended.)
|
||||||
* ----------
|
* ----------
|
||||||
*/
|
*/
|
||||||
PLpgSQL_type *
|
PLpgSQL_type *
|
||||||
@ -1648,12 +1649,11 @@ plpgsql_parse_cwordtype(List *idents)
|
|||||||
PLpgSQL_type *dtype = NULL;
|
PLpgSQL_type *dtype = NULL;
|
||||||
PLpgSQL_nsitem *nse;
|
PLpgSQL_nsitem *nse;
|
||||||
int nnames;
|
int nnames;
|
||||||
const char *fldname;
|
RangeVar *relvar = NULL;
|
||||||
|
const char *fldname = NULL;
|
||||||
Oid classOid;
|
Oid classOid;
|
||||||
HeapTuple classtup = NULL;
|
|
||||||
HeapTuple attrtup = NULL;
|
HeapTuple attrtup = NULL;
|
||||||
HeapTuple typetup = NULL;
|
HeapTuple typetup = NULL;
|
||||||
Form_pg_class classStruct;
|
|
||||||
Form_pg_attribute attrStruct;
|
Form_pg_attribute attrStruct;
|
||||||
MemoryContext oldCxt;
|
MemoryContext oldCxt;
|
||||||
|
|
||||||
@ -1688,57 +1688,39 @@ plpgsql_parse_cwordtype(List *idents)
|
|||||||
/*
|
/*
|
||||||
* First word could also be a table name
|
* First word could also be a table name
|
||||||
*/
|
*/
|
||||||
classOid = RelnameGetRelid(strVal(linitial(idents)));
|
relvar = makeRangeVar(NULL,
|
||||||
if (!OidIsValid(classOid))
|
strVal(linitial(idents)),
|
||||||
goto done;
|
-1);
|
||||||
fldname = strVal(lsecond(idents));
|
fldname = strVal(lsecond(idents));
|
||||||
}
|
}
|
||||||
else if (list_length(idents) == 3)
|
else
|
||||||
{
|
{
|
||||||
RangeVar *relvar;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We could check for a block-qualified reference to a field of a
|
* We could check for a block-qualified reference to a field of a
|
||||||
* record variable, but %TYPE is documented as applying to variables,
|
* record variable, but %TYPE is documented as applying to variables,
|
||||||
* not fields of variables. Things would get rather ambiguous if we
|
* not fields of variables. Things would get rather ambiguous if we
|
||||||
* allowed either interpretation.
|
* allowed either interpretation.
|
||||||
*/
|
*/
|
||||||
relvar = makeRangeVar(strVal(linitial(idents)),
|
List *rvnames;
|
||||||
strVal(lsecond(idents)),
|
|
||||||
-1);
|
Assert(list_length(idents) > 2);
|
||||||
/* Can't lock relation - we might not have privileges. */
|
rvnames = list_delete_last(list_copy(idents));
|
||||||
classOid = RangeVarGetRelid(relvar, NoLock, true);
|
relvar = makeRangeVarFromNameList(rvnames);
|
||||||
if (!OidIsValid(classOid))
|
fldname = strVal(llast(idents));
|
||||||
goto done;
|
|
||||||
fldname = strVal(lthird(idents));
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
goto done;
|
|
||||||
|
|
||||||
classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(classOid));
|
/* Look up relation name. Can't lock it - we might not have privileges. */
|
||||||
if (!HeapTupleIsValid(classtup))
|
classOid = RangeVarGetRelid(relvar, NoLock, false);
|
||||||
goto done;
|
|
||||||
classStruct = (Form_pg_class) GETSTRUCT(classtup);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* It must be a relation, sequence, view, materialized view, composite
|
|
||||||
* type, or foreign table
|
|
||||||
*/
|
|
||||||
if (classStruct->relkind != RELKIND_RELATION &&
|
|
||||||
classStruct->relkind != RELKIND_SEQUENCE &&
|
|
||||||
classStruct->relkind != RELKIND_VIEW &&
|
|
||||||
classStruct->relkind != RELKIND_MATVIEW &&
|
|
||||||
classStruct->relkind != RELKIND_COMPOSITE_TYPE &&
|
|
||||||
classStruct->relkind != RELKIND_FOREIGN_TABLE &&
|
|
||||||
classStruct->relkind != RELKIND_PARTITIONED_TABLE)
|
|
||||||
goto done;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Fetch the named table field and its type
|
* Fetch the named table field and its type
|
||||||
*/
|
*/
|
||||||
attrtup = SearchSysCacheAttName(classOid, fldname);
|
attrtup = SearchSysCacheAttName(classOid, fldname);
|
||||||
if (!HeapTupleIsValid(attrtup))
|
if (!HeapTupleIsValid(attrtup))
|
||||||
goto done;
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
||||||
|
errmsg("column \"%s\" of relation \"%s\" does not exist",
|
||||||
|
fldname, relvar->relname)));
|
||||||
attrStruct = (Form_pg_attribute) GETSTRUCT(attrtup);
|
attrStruct = (Form_pg_attribute) GETSTRUCT(attrtup);
|
||||||
|
|
||||||
typetup = SearchSysCache1(TYPEOID,
|
typetup = SearchSysCache1(TYPEOID,
|
||||||
@ -1759,8 +1741,6 @@ plpgsql_parse_cwordtype(List *idents)
|
|||||||
MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
|
MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
|
||||||
|
|
||||||
done:
|
done:
|
||||||
if (HeapTupleIsValid(classtup))
|
|
||||||
ReleaseSysCache(classtup);
|
|
||||||
if (HeapTupleIsValid(attrtup))
|
if (HeapTupleIsValid(attrtup))
|
||||||
ReleaseSysCache(attrtup);
|
ReleaseSysCache(attrtup);
|
||||||
if (HeapTupleIsValid(typetup))
|
if (HeapTupleIsValid(typetup))
|
||||||
@ -1824,16 +1804,12 @@ plpgsql_parse_cwordrowtype(List *idents)
|
|||||||
* As above, this is a relation lookup but could be a type lookup if we
|
* As above, this is a relation lookup but could be a type lookup if we
|
||||||
* weren't being backwards-compatible about error wording.
|
* weren't being backwards-compatible about error wording.
|
||||||
*/
|
*/
|
||||||
if (list_length(idents) != 2)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
/* Avoid memory leaks in long-term function context */
|
/* Avoid memory leaks in long-term function context */
|
||||||
oldCxt = MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
|
oldCxt = MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
|
||||||
|
|
||||||
/* Look up relation name. Can't lock it - we might not have privileges. */
|
/* Look up relation name. Can't lock it - we might not have privileges. */
|
||||||
relvar = makeRangeVar(strVal(linitial(idents)),
|
relvar = makeRangeVarFromNameList(idents);
|
||||||
strVal(lsecond(idents)),
|
|
||||||
-1);
|
|
||||||
classOid = RangeVarGetRelid(relvar, NoLock, false);
|
classOid = RangeVarGetRelid(relvar, NoLock, false);
|
||||||
|
|
||||||
/* Some relkinds lack type OIDs */
|
/* Some relkinds lack type OIDs */
|
||||||
@ -1842,7 +1818,7 @@ plpgsql_parse_cwordrowtype(List *idents)
|
|||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||||
errmsg("relation \"%s\" does not have a composite type",
|
errmsg("relation \"%s\" does not have a composite type",
|
||||||
strVal(lsecond(idents)))));
|
relvar->relname)));
|
||||||
|
|
||||||
MemoryContextSwitchTo(oldCxt);
|
MemoryContextSwitchTo(oldCxt);
|
||||||
|
|
||||||
|
@ -2810,10 +2810,7 @@ read_datatype(int tok)
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* If we have a simple or composite identifier, check for %TYPE and
|
* If we have a simple or composite identifier, check for %TYPE and
|
||||||
* %ROWTYPE constructs. (Note that if plpgsql_parse_wordtype et al fail
|
* %ROWTYPE constructs.
|
||||||
* to recognize the identifier, we'll fall through and pass the whole
|
|
||||||
* string to parse_datatype, which will assuredly give an unhelpful
|
|
||||||
* "syntax error". Should we try to give a more specific error?)
|
|
||||||
*/
|
*/
|
||||||
if (tok == T_WORD)
|
if (tok == T_WORD)
|
||||||
{
|
{
|
||||||
|
@ -20,3 +20,20 @@ $$;
|
|||||||
|
|
||||||
\sf test1
|
\sf test1
|
||||||
\sf test2
|
\sf test2
|
||||||
|
|
||||||
|
-- Test %TYPE and %ROWTYPE error cases
|
||||||
|
create table misc_table(f1 int);
|
||||||
|
|
||||||
|
do $$ declare x foo%type; begin end $$;
|
||||||
|
do $$ declare x notice%type; begin end $$; -- covers unreserved-keyword case
|
||||||
|
do $$ declare x foo.bar%type; begin end $$;
|
||||||
|
do $$ declare x foo.bar.baz%type; begin end $$;
|
||||||
|
do $$ declare x public.foo.bar%type; begin end $$;
|
||||||
|
do $$ declare x public.misc_table.zed%type; begin end $$;
|
||||||
|
|
||||||
|
do $$ declare x foo%rowtype; begin end $$;
|
||||||
|
do $$ declare x notice%rowtype; begin end $$; -- covers unreserved-keyword case
|
||||||
|
do $$ declare x foo.bar%rowtype; begin end $$;
|
||||||
|
do $$ declare x foo.bar.baz%rowtype; begin end $$;
|
||||||
|
do $$ declare x public.foo%rowtype; begin end $$;
|
||||||
|
do $$ declare x public.misc_table%rowtype; begin end $$;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user