mirror of
				https://github.com/postgres/postgres.git
				synced 2025-11-03 09:13:20 +03:00 
			
		
		
		
	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:
		@@ -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 $$;
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user