From b3f52320f6cb8374f3db5397e63b82f595c90681 Mon Sep 17 00:00:00 2001
From: Bruce Momjian <bruce@momjian.us>
Date: Thu, 12 Sep 2002 00:24:10 +0000
Subject: [PATCH] > Sean Chittenden <sean@chittenden.org> writes: > >>::sigh::
 Is it me or does it look like all >>of pl/pgsql is schema un-aware (ie, all
 of the declarations).  -sc > > > Yeah.  The group of routines parse_word,
 parse_dblword, etc that are > called by the lexer certainly all need work. 
 There are some > definitional issues to think about, too --- plpgsql
 presently relies on > the number of names to give it some idea of what to
 look for, and those > rules are probably all toast now.  Please come up with
 a sketch of what > you think the behavior should be before you start hacking
 code.

Attached is a diff -c format proposal to fix this. I've also attached a short
test script. Seems to work OK and passes all regression tests.

Here's a breakdown of how I understand plpgsql's "Special word rules" -- I
think it illustrates the behavior reasonably well. New functions added by this
patch are plpgsql_parse_tripwordtype and plpgsql_parse_dblwordrowtype:

Joe Conway
---
 src/pl/plpgsql/src/pl_comp.c                 | 162 ++++++++++++++++++-
 src/pl/plpgsql/src/plpgsql.h                 |   4 +-
 src/pl/plpgsql/src/scan.l                    |   6 +-
 src/test/regress/sql/plpgsql-nsp-testing.sql |  47 ++++++
 4 files changed, 216 insertions(+), 3 deletions(-)
 create mode 100644 src/test/regress/sql/plpgsql-nsp-testing.sql

diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index c25f964f7c6..00f2997ae3e 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -3,7 +3,7 @@
  *			  procedural language
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.51 2002/09/04 20:31:47 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.52 2002/09/12 00:24:09 momjian Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -1092,6 +1092,126 @@ plpgsql_parse_dblwordtype(char *word)
 	return T_DTYPE;
 }
 
+/* ----------
+ * plpgsql_parse_tripwordtype		Same lookup for word.word.word%TYPE
+ * ----------
+ */
+#define TYPE_JUNK_LEN	5
+
+int
+plpgsql_parse_tripwordtype(char *word)
+{
+	Oid			classOid;
+	HeapTuple	classtup;
+	Form_pg_class classStruct;
+	HeapTuple	attrtup;
+	Form_pg_attribute attrStruct;
+	HeapTuple	typetup;
+	Form_pg_type typeStruct;
+	PLpgSQL_type *typ;
+	char	   *cp[2];
+	int			qualified_att_len;
+	int			numdots = 0;
+	int			i;
+	RangeVar   *relvar;
+
+	/* Do case conversion and word separation */
+	qualified_att_len = strlen(word) - TYPE_JUNK_LEN;
+	Assert(word[qualified_att_len] == '%');
+
+	for (i = 0; i < qualified_att_len; i++)
+	{
+		if (word[i] == '.' && ++numdots == 2)
+		{
+			cp[0] = (char *) palloc((i + 1) * sizeof(char));
+			memset(cp[0], 0, (i + 1) * sizeof(char));
+			memcpy(cp[0], word, i * sizeof(char));
+
+			/* qualified_att_len - one based position + 1 (null terminator) */
+			cp[1] = (char *) palloc((qualified_att_len - i) * sizeof(char));
+			memset(cp[1], 0, (qualified_att_len - i) * sizeof(char));
+			memcpy(cp[1], &word[i + 1], (qualified_att_len - i - 1) * sizeof(char));
+
+			break;
+		}
+	}
+
+	relvar = makeRangeVarFromNameList(stringToQualifiedNameList(cp[0], "plpgsql_parse_dblwordtype"));
+	classOid = RangeVarGetRelid(relvar, true);
+	if (!OidIsValid(classOid))
+	{
+		pfree(cp[0]);
+		pfree(cp[1]);
+		return T_ERROR;
+	}
+	classtup = SearchSysCache(RELOID,
+							  ObjectIdGetDatum(classOid),
+							  0, 0, 0);
+	if (!HeapTupleIsValid(classtup))
+	{
+		pfree(cp[0]);
+		pfree(cp[1]);
+		return T_ERROR;
+	}
+
+	/*
+	 * It must be a relation, sequence, view, or type
+	 */
+	classStruct = (Form_pg_class) GETSTRUCT(classtup);
+	if (classStruct->relkind != RELKIND_RELATION &&
+		classStruct->relkind != RELKIND_SEQUENCE &&
+		classStruct->relkind != RELKIND_VIEW &&
+		classStruct->relkind != RELKIND_COMPOSITE_TYPE)
+	{
+		ReleaseSysCache(classtup);
+		pfree(cp[0]);
+		pfree(cp[1]);
+		return T_ERROR;
+	}
+
+	/*
+	 * Fetch the named table field and it's type
+	 */
+	attrtup = SearchSysCacheAttName(classOid, cp[1]);
+	if (!HeapTupleIsValid(attrtup))
+	{
+		ReleaseSysCache(classtup);
+		pfree(cp[0]);
+		pfree(cp[1]);
+		return T_ERROR;
+	}
+	attrStruct = (Form_pg_attribute) GETSTRUCT(attrtup);
+
+	typetup = SearchSysCache(TYPEOID,
+							 ObjectIdGetDatum(attrStruct->atttypid),
+							 0, 0, 0);
+	if (!HeapTupleIsValid(typetup))
+		elog(ERROR, "cache lookup for type %u of %s.%s failed",
+			 attrStruct->atttypid, cp[0], cp[1]);
+	typeStruct = (Form_pg_type) GETSTRUCT(typetup);
+
+	/*
+	 * Found that - build a compiler type struct and return it
+	 */
+	typ = (PLpgSQL_type *) malloc(sizeof(PLpgSQL_type));
+
+	typ->typname = strdup(NameStr(typeStruct->typname));
+	typ->typoid = attrStruct->atttypid;
+	perm_fmgr_info(typeStruct->typinput, &(typ->typinput));
+	typ->typelem = typeStruct->typelem;
+	typ->typbyval = typeStruct->typbyval;
+	typ->typlen = typeStruct->typlen;
+	typ->atttypmod = attrStruct->atttypmod;
+
+	plpgsql_yylval.dtype = typ;
+
+	ReleaseSysCache(classtup);
+	ReleaseSysCache(attrtup);
+	ReleaseSysCache(typetup);
+	pfree(cp[0]);
+	pfree(cp[1]);
+	return T_DTYPE;
+}
 
 /* ----------
  * plpgsql_parse_wordrowtype		Scanner found word%ROWTYPE.
@@ -1129,6 +1249,46 @@ plpgsql_parse_wordrowtype(char *word)
 	return T_ROW;
 }
 
+/* ----------
+ * plpgsql_parse_dblwordrowtype		Scanner found word.word%ROWTYPE.
+ *			So word must be namespace qualified a table name.
+ * ----------
+ */
+#define ROWTYPE_JUNK_LEN	8
+
+int
+plpgsql_parse_dblwordrowtype(char *word)
+{
+	Oid			classOid;
+	char	   *cp;
+	int			i;
+	RangeVar   *relvar;
+
+	/* Do case conversion and word separation */
+	/* We convert %rowtype to .rowtype momentarily to keep converter happy */
+	i = strlen(word) - ROWTYPE_JUNK_LEN;
+	Assert(word[i] == '%');
+
+	cp = (char *) palloc((i + 1) * sizeof(char));
+	memset(cp, 0, (i + 1) * sizeof(char));
+	memcpy(cp, word, i * sizeof(char));
+
+	/* Lookup the relation */
+	relvar = makeRangeVarFromNameList(stringToQualifiedNameList(cp, "plpgsql_parse_dblwordtype"));
+	classOid = RangeVarGetRelid(relvar, true);
+	if (!OidIsValid(classOid))
+		elog(ERROR, "%s: no such class", cp);
+
+	/*
+	 * Build and return the complete row definition
+	 */
+	plpgsql_yylval.row = build_rowtype(classOid);
+
+	pfree(cp);
+
+	return T_ROW;
+}
+
 /*
  * Build a rowtype data structure given the pg_class OID.
  */
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 1d5ab78a328..cf3e1942d8a 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -3,7 +3,7 @@
  *			  procedural language
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.27 2002/09/04 20:31:47 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.28 2002/09/12 00:24:09 momjian Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -568,7 +568,9 @@ extern int	plpgsql_parse_dblword(char *word);
 extern int	plpgsql_parse_tripword(char *word);
 extern int	plpgsql_parse_wordtype(char *word);
 extern int	plpgsql_parse_dblwordtype(char *word);
+extern int	plpgsql_parse_tripwordtype(char *word);
 extern int	plpgsql_parse_wordrowtype(char *word);
+extern int	plpgsql_parse_dblwordrowtype(char *word);
 extern PLpgSQL_type *plpgsql_parse_datatype(char *string);
 extern void plpgsql_adddatum(PLpgSQL_datum * new);
 extern int	plpgsql_add_initdatums(int **varnos);
diff --git a/src/pl/plpgsql/src/scan.l b/src/pl/plpgsql/src/scan.l
index 3976b542756..697be513930 100644
--- a/src/pl/plpgsql/src/scan.l
+++ b/src/pl/plpgsql/src/scan.l
@@ -4,7 +4,7 @@
  *			  procedural language
  *
  * IDENTIFICATION
- *    $Header: /cvsroot/pgsql/src/pl/plpgsql/src/Attic/scan.l,v 1.22 2002/08/30 00:28:41 tgl Exp $
+ *    $Header: /cvsroot/pgsql/src/pl/plpgsql/src/Attic/scan.l,v 1.23 2002/09/12 00:24:09 momjian Exp $
  *
  *    This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -170,14 +170,18 @@ dump			{ return O_DUMP;			}
 {identifier}{space}*\.{space}*{identifier}{space}*\.{space}*{identifier}	{ return plpgsql_parse_tripword(yytext); }
 {identifier}{space}*%TYPE		{ return plpgsql_parse_wordtype(yytext);	}
 {identifier}{space}*\.{space}*{identifier}{space}*%TYPE	{ return plpgsql_parse_dblwordtype(yytext); }
+{identifier}{space}*\.{space}*{identifier}{space}*\.{space}*{identifier}{space}*%TYPE	{ return plpgsql_parse_tripwordtype(yytext); }
 {identifier}{space}*%ROWTYPE	{ return plpgsql_parse_wordrowtype(yytext);	}
+{identifier}{space}*\.{space}*{identifier}{space}*%ROWTYPE	{ return plpgsql_parse_dblwordrowtype(yytext);	}
 
 \${digit}+						{ return plpgsql_parse_word(yytext);	}
 \${digit}+{space}*\.{space}*{identifier}	{ return plpgsql_parse_dblword(yytext);	}
 \${digit}+{space}*\.{space}*{identifier}{space}*\.{space}*{identifier}	{ return plpgsql_parse_tripword(yytext); }
 \${digit}+{space}*%TYPE			{ return plpgsql_parse_wordtype(yytext);	}
 \${digit}+{space}*\.{space}*{identifier}{space}*%TYPE	{ return plpgsql_parse_dblwordtype(yytext); }
+\${digit}+{space}*\.{space}*{identifier}{space}*\.{space}*{identifier}{space}*%TYPE	{ return plpgsql_parse_tripwordtype(yytext); }
 \${digit}+{space}*%ROWTYPE		{ return plpgsql_parse_wordrowtype(yytext);	}
+\${digit}+{space}*\.{space}*{identifier}{space}*%ROWTYPE	{ return plpgsql_parse_dblwordrowtype(yytext);	}
 
 {digit}+		{ return T_NUMBER;			}
 
diff --git a/src/test/regress/sql/plpgsql-nsp-testing.sql b/src/test/regress/sql/plpgsql-nsp-testing.sql
new file mode 100644
index 00000000000..8694b8db649
--- /dev/null
+++ b/src/test/regress/sql/plpgsql-nsp-testing.sql
@@ -0,0 +1,47 @@
+-- nspname.relname.attname%TYPE
+DROP FUNCTION t();
+CREATE OR REPLACE FUNCTION t() RETURNS TEXT AS '
+DECLARE
+    col_name pg_catalog.pg_attribute.attname%TYPE;
+BEGIN
+    col_name := ''uga'';
+    RETURN col_name;
+END;
+' LANGUAGE 'plpgsql';
+SELECT t();
+
+-- nspname.relname%ROWTYPE
+DROP FUNCTION t();
+CREATE OR REPLACE FUNCTION t() RETURNS pg_catalog.pg_attribute AS '
+DECLARE
+    rec pg_catalog.pg_attribute%ROWTYPE;
+BEGIN
+    SELECT INTO rec * FROM pg_catalog.pg_attribute WHERE attrelid = 1247 AND attname = ''typname'';
+    RETURN rec;
+END;
+' LANGUAGE 'plpgsql';
+SELECT * FROM t();
+
+-- nspname.relname.attname%TYPE
+DROP FUNCTION t();
+CREATE OR REPLACE FUNCTION t() RETURNS pg_catalog.pg_attribute.attname%TYPE AS '
+DECLARE
+    rec pg_catalog.pg_attribute.attname%TYPE;
+BEGIN
+    SELECT INTO rec pg_catalog.pg_attribute.attname FROM pg_catalog.pg_attribute WHERE attrelid = 1247 AND attname = ''typname'';
+    RETURN rec;
+END;
+' LANGUAGE 'plpgsql';
+SELECT t();
+
+-- nspname.relname%ROWTYPE
+DROP FUNCTION t();
+CREATE OR REPLACE FUNCTION t() RETURNS pg_catalog.pg_attribute AS '
+DECLARE
+    rec pg_catalog.pg_attribute%ROWTYPE;
+BEGIN
+    SELECT INTO rec * FROM pg_catalog.pg_attribute WHERE attrelid = 1247 AND attname = ''typname'';
+    RETURN rec;
+END;
+' LANGUAGE 'plpgsql';
+SELECT * FROM t();
-- 
GitLab