summaryrefslogtreecommitdiff
path: root/contrib/plpgsql/src/pl_comp.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/plpgsql/src/pl_comp.c')
-rw-r--r--contrib/plpgsql/src/pl_comp.c1313
1 files changed, 1313 insertions, 0 deletions
diff --git a/contrib/plpgsql/src/pl_comp.c b/contrib/plpgsql/src/pl_comp.c
new file mode 100644
index 00000000000..3d894f81b9f
--- /dev/null
+++ b/contrib/plpgsql/src/pl_comp.c
@@ -0,0 +1,1313 @@
+/**********************************************************************
+ * pl_comp.c - Compiler part of the PL/pgSQL
+ * procedural language
+ *
+ * IDENTIFICATION
+ * $Header: /cvsroot/pgsql/contrib/plpgsql/src/Attic/pl_comp.c,v 1.1 1998/08/22 12:38:32 momjian Exp $
+ *
+ * This software is copyrighted by Jan Wieck - Hamburg.
+ *
+ * The author hereby grants permission to use, copy, modify,
+ * distribute, and license this software and its documentation
+ * for any purpose, provided that existing copyright notices are
+ * retained in all copies and that this notice is included
+ * verbatim in any distributions. No written agreement, license,
+ * or royalty fee is required for any of the authorized uses.
+ * Modifications to this software may be copyrighted by their
+ * author and need not follow the licensing terms described
+ * here, provided that the new terms are clearly indicated on
+ * the first page of each file where they apply.
+ *
+ * IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY
+ * PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS
+ * SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES THEREOF, EVEN
+ * IF THE AUTHOR HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * THE AUTHOR AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON
+ * AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAVE NO
+ * OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
+ * ENHANCEMENTS, OR MODIFICATIONS.
+ *
+ **********************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "plpgsql.h"
+#include "pl.tab.h"
+
+#include "executor/spi.h"
+#include "commands/trigger.h"
+#include "utils/elog.h"
+#include "utils/builtins.h"
+#include "fmgr.h"
+#include "access/heapam.h"
+
+#include "utils/syscache.h"
+#include "utils/catcache.h"
+#include "catalog/catname.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_attribute.h"
+#include "catalog/pg_attrdef.h"
+
+
+/* ----------
+ * Variables in the parser that shouldn't go into plpgsql.h
+ * ----------
+ */
+extern PLPGSQL_YYSTYPE plpgsql_yylval;
+extern int plpgsql_yylineno;
+extern char plpgsql_yytext[];
+
+void plpgsql_yyerror(const char *s);
+
+/* ----------
+ * Our own local and global variables
+ * ----------
+ */
+static int datums_alloc;
+int plpgsql_nDatums;
+PLpgSQL_datum **plpgsql_Datums;
+static int datums_last = 0;
+
+int plpgsql_error_lineno;
+char *plpgsql_error_funcname;
+int plpgsql_DumpExecTree = 0;
+
+PLpgSQL_function *plpgsql_curr_compile;
+
+
+/* ----------
+ * Local function declarations
+ * ----------
+ */
+static char *xlateSqlType(char *name);
+
+
+/* ----------
+ * plpgsql_compile Given a pg_proc's oid, make
+ * an execution tree for it.
+ * ----------
+ */
+PLpgSQL_function *plpgsql_compile(Oid fn_oid, int functype)
+{
+ int parse_rc;
+ HeapTuple procTup;
+ Form_pg_proc procStruct;
+ HeapTuple typeTup;
+ TypeTupleForm typeStruct;
+ char *proc_source;
+ PLpgSQL_function *function;
+ PLpgSQL_var *var;
+ PLpgSQL_row *row;
+ PLpgSQL_rec *rec;
+ int i;
+ int arg_varnos[MAXFMGRARGS];
+
+ /* ----------
+ * Initialize the compiler
+ * ----------
+ */
+ plpgsql_ns_init();
+ plpgsql_ns_push(NULL);
+ plpgsql_DumpExecTree = 0;
+
+ datums_alloc = 128;
+ plpgsql_nDatums = 0;
+ plpgsql_Datums = palloc(sizeof(PLpgSQL_datum *) * datums_alloc);
+ datums_last = 0;
+
+ /* ----------
+ * Lookup the pg_proc tuple by Oid
+ * ----------
+ */
+ procTup = SearchSysCacheTuple(PROOID,
+ ObjectIdGetDatum(fn_oid),
+ 0, 0, 0);
+ if (!HeapTupleIsValid(procTup)) {
+ elog(ERROR, "plpgsql: cache lookup from pg_proc failed");
+ }
+
+ /* ----------
+ * Setup the scanner input and error info
+ * ----------
+ */
+ procStruct = (Form_pg_proc) GETSTRUCT(procTup);
+ proc_source = textout(&(procStruct->prosrc));
+ plpgsql_setinput(proc_source, functype);
+ plpgsql_error_funcname = nameout(&(procStruct->proname));
+ plpgsql_error_lineno = 0;
+
+ /* ----------
+ * Create the new function node
+ * ----------
+ */
+ function = malloc(sizeof(PLpgSQL_function));
+ memset(function, 0, sizeof(PLpgSQL_function));
+ plpgsql_curr_compile = function;
+
+ function->fn_functype = functype;
+ function->fn_oid = fn_oid;
+ function->fn_name = strdup(nameout(&(procStruct->proname)));
+
+ switch (functype) {
+ case T_FUNCTION:
+ /* ----------
+ * Normal function has a defined returntype
+ * ----------
+ */
+ function->fn_rettype = procStruct->prorettype;
+ function->fn_retset = procStruct->proretset;
+
+ /* ----------
+ * Lookup the functions return type
+ * ----------
+ */
+ typeTup = SearchSysCacheTuple(TYPOID,
+ ObjectIdGetDatum(procStruct->prorettype), 0, 0, 0);
+
+ if (!HeapTupleIsValid(typeTup)) {
+ plpgsql_comperrinfo();
+ elog(ERROR, "cache lookup for return type %d failed",
+ procStruct->prorettype);
+ }
+ typeStruct = (TypeTupleForm) GETSTRUCT(typeTup);
+ if (typeStruct->typrelid != InvalidOid) {
+ function->fn_retistuple = true;
+ } else {
+ function->fn_retbyval = typeStruct->typbyval;
+ function->fn_rettyplen = typeStruct->typlen;
+ fmgr_info(typeStruct->typinput, &(function->fn_retinput));
+ }
+
+ /* ----------
+ * Create the variables for the procedures parameters
+ * ----------
+ */
+ for (i = 0; i < procStruct->pronargs; i++) {
+ char buf[256];
+
+ /* ----------
+ * Get the parameters type
+ * ----------
+ */
+ typeTup = SearchSysCacheTuple(TYPOID,
+ ObjectIdGetDatum(procStruct->proargtypes[i]), 0, 0, 0);
+
+ if (!HeapTupleIsValid(typeTup)) {
+ plpgsql_comperrinfo();
+ elog(ERROR, "cache lookup for argument type %d failed",
+ procStruct->proargtypes[i]);
+ }
+ typeStruct = (TypeTupleForm) GETSTRUCT(typeTup);
+
+ if (typeStruct->typrelid != InvalidOid) {
+ /* ----------
+ * For tuple type parameters, we set up a record
+ * of that type
+ * ----------
+ */
+ sprintf(buf, "%s%%rowtype", nameout(&(typeStruct->typname)));
+ if (plpgsql_parse_wordrowtype(buf) != T_ROW) {
+ plpgsql_comperrinfo();
+ elog(ERROR, "cannot get tuple struct of argument %d", i + 1);
+ }
+
+ row = plpgsql_yylval.row;
+ sprintf(buf, "$%d", i + 1);
+
+ row->refname = strdup(buf);
+
+ plpgsql_adddatum((PLpgSQL_datum *)row);
+ plpgsql_ns_additem(PLPGSQL_NSTYPE_ROW, row->rowno, buf);
+
+ arg_varnos[i] = row->rowno;
+ } else {
+ /* ----------
+ * Normal parameters get a var node
+ * ----------
+ */
+ var = malloc(sizeof(PLpgSQL_var));
+ memset(var, 0, sizeof(PLpgSQL_var));
+ var->datatype = malloc(sizeof(PLpgSQL_type));
+ memset(var->datatype, 0, sizeof(PLpgSQL_type));
+
+ sprintf(buf, "$%d", i + 1);
+ var->dtype = PLPGSQL_DTYPE_VAR;
+ var->refname = strdup(buf);
+ var->lineno = 0;
+ var->datatype->typname = strdup(nameout(&(typeStruct->typname)));
+ var->datatype->typoid = procStruct->proargtypes[i];
+ fmgr_info(typeStruct->typinput, &(var->datatype->typinput));
+ var->datatype->typbyval = typeStruct->typbyval;
+ var->datatype->atttypmod = -1;
+ var->isconst = true;
+ var->notnull = false;
+ var->default_val = NULL;
+
+ plpgsql_adddatum((PLpgSQL_datum *)var);
+ plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, buf);
+
+ arg_varnos[i] = var->varno;
+ }
+ }
+ break;
+
+ case T_TRIGGER:
+ /* ----------
+ * Trigger procedures return type is unknown yet
+ * ----------
+ */
+ function->fn_rettype = InvalidOid;
+ function->fn_retbyval = false;
+ function->fn_retistuple = true;
+ function->fn_retset = false;
+
+ /* ----------
+ * Add the record for referencing NEW
+ * ----------
+ */
+ rec = malloc(sizeof(PLpgSQL_rec));
+ memset(rec, 0, sizeof(PLpgSQL_rec));
+ rec->dtype = PLPGSQL_DTYPE_REC;
+ rec->refname = strdup("new");
+ plpgsql_adddatum((PLpgSQL_datum *)rec);
+ plpgsql_ns_additem(PLPGSQL_NSTYPE_REC, rec->recno, rec->refname);
+ function->new_varno = rec->recno;
+
+ /* ----------
+ * Add the record for referencing OLD
+ * ----------
+ */
+ rec = malloc(sizeof(PLpgSQL_rec));
+ memset(rec, 0, sizeof(PLpgSQL_rec));
+ rec->dtype = PLPGSQL_DTYPE_REC;
+ rec->refname = strdup("old");
+ plpgsql_adddatum((PLpgSQL_datum *)rec);
+ plpgsql_ns_additem(PLPGSQL_NSTYPE_REC, rec->recno, rec->refname);
+ function->old_varno = rec->recno;
+
+ /* ----------
+ * Add the variable tg_name
+ * ----------
+ */
+ var = malloc(sizeof(PLpgSQL_var));
+ memset(var, 0, sizeof(PLpgSQL_var));
+
+ var->dtype = PLPGSQL_DTYPE_VAR;
+ var->refname = strdup("tg_name");
+ var->lineno = 0;
+ plpgsql_parse_word("name");
+ var->datatype = plpgsql_yylval.dtype;
+ var->isconst = false;
+ var->notnull = false;
+ var->default_val = NULL;
+
+ plpgsql_adddatum((PLpgSQL_datum *)var);
+ plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname);
+ function->tg_name_varno = var->varno;
+
+ /* ----------
+ * Add the variable tg_when
+ * ----------
+ */
+ var = malloc(sizeof(PLpgSQL_var));
+ memset(var, 0, sizeof(PLpgSQL_var));
+
+ var->dtype = PLPGSQL_DTYPE_VAR;
+ var->refname = strdup("tg_when");
+ var->lineno = 0;
+ plpgsql_parse_word("text");
+ var->datatype = plpgsql_yylval.dtype;
+ var->isconst = false;
+ var->notnull = false;
+ var->default_val = NULL;
+
+ plpgsql_adddatum((PLpgSQL_datum *)var);
+ plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname);
+ function->tg_when_varno = var->varno;
+
+ /* ----------
+ * Add the variable tg_level
+ * ----------
+ */
+ var = malloc(sizeof(PLpgSQL_var));
+ memset(var, 0, sizeof(PLpgSQL_var));
+
+ var->dtype = PLPGSQL_DTYPE_VAR;
+ var->refname = strdup("tg_level");
+ var->lineno = 0;
+ plpgsql_parse_word("text");
+ var->datatype = plpgsql_yylval.dtype;
+ var->isconst = false;
+ var->notnull = false;
+ var->default_val = NULL;
+
+ plpgsql_adddatum((PLpgSQL_datum *)var);
+ plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname);
+ function->tg_level_varno = var->varno;
+
+ /* ----------
+ * Add the variable tg_op
+ * ----------
+ */
+ var = malloc(sizeof(PLpgSQL_var));
+ memset(var, 0, sizeof(PLpgSQL_var));
+
+ var->dtype = PLPGSQL_DTYPE_VAR;
+ var->refname = strdup("tg_op");
+ var->lineno = 0;
+ plpgsql_parse_word("text");
+ var->datatype = plpgsql_yylval.dtype;
+ var->isconst = false;
+ var->notnull = false;
+ var->default_val = NULL;
+
+ plpgsql_adddatum((PLpgSQL_datum *)var);
+ plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname);
+ function->tg_op_varno = var->varno;
+
+ /* ----------
+ * Add the variable tg_relid
+ * ----------
+ */
+ var = malloc(sizeof(PLpgSQL_var));
+ memset(var, 0, sizeof(PLpgSQL_var));
+
+ var->dtype = PLPGSQL_DTYPE_VAR;
+ var->refname = strdup("tg_relid");
+ var->lineno = 0;
+ plpgsql_parse_word("oid");
+ var->datatype = plpgsql_yylval.dtype;
+ var->isconst = false;
+ var->notnull = false;
+ var->default_val = NULL;
+
+ plpgsql_adddatum((PLpgSQL_datum *)var);
+ plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname);
+ function->tg_relid_varno = var->varno;
+
+ /* ----------
+ * Add the variable tg_relname
+ * ----------
+ */
+ var = malloc(sizeof(PLpgSQL_var));
+ memset(var, 0, sizeof(PLpgSQL_var));
+
+ var->dtype = PLPGSQL_DTYPE_VAR;
+ var->refname = strdup("tg_relname");
+ var->lineno = 0;
+ plpgsql_parse_word("name");
+ var->datatype = plpgsql_yylval.dtype;
+ var->isconst = false;
+ var->notnull = false;
+ var->default_val = NULL;
+
+ plpgsql_adddatum((PLpgSQL_datum *)var);
+ plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname);
+ function->tg_relname_varno = var->varno;
+
+ /* ----------
+ * Add the variable tg_nargs
+ * ----------
+ */
+ var = malloc(sizeof(PLpgSQL_var));
+ memset(var, 0, sizeof(PLpgSQL_var));
+
+ var->dtype = PLPGSQL_DTYPE_VAR;
+ var->refname = strdup("tg_nargs");
+ var->lineno = 0;
+ plpgsql_parse_word("int4");
+ var->datatype = plpgsql_yylval.dtype;
+ var->isconst = false;
+ var->notnull = false;
+ var->default_val = NULL;
+
+ plpgsql_adddatum((PLpgSQL_datum *)var);
+ plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, var->refname);
+ function->tg_nargs_varno = var->varno;
+
+ break;
+
+ default:
+ elog(ERROR, "unknown function type %d in plpgsql_compile()",
+ functype);
+ break;
+ }
+
+ /* ----------
+ * Create the magic found variable indicating if the
+ * last FOR or SELECT statement returned data
+ * ----------
+ */
+ var = malloc(sizeof(PLpgSQL_var));
+ memset(var, 0, sizeof(PLpgSQL_var));
+
+ var->dtype = PLPGSQL_DTYPE_VAR;
+ var->refname = strdup("found");
+ var->lineno = 0;
+ plpgsql_parse_word("bool");
+ var->datatype = plpgsql_yylval.dtype;
+ var->isconst = false;
+ var->notnull = false;
+ var->default_val = NULL;
+
+ plpgsql_adddatum((PLpgSQL_datum *)var);
+ plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, var->varno, strdup("found"));
+ function->found_varno = var->varno;
+
+ /* ----------
+ * Forget about the above created variables
+ * ----------
+ */
+ plpgsql_add_initdatums(NULL);
+
+ /* ----------
+ * Now parse the functions text
+ * ----------
+ */
+ parse_rc = plpgsql_yyparse();
+ if (parse_rc != 0) {
+ plpgsql_comperrinfo();
+ elog(ERROR, "plpgsql: parser returned %d ???", parse_rc);
+ }
+
+ /* ----------
+ * If that was successful, complete the functions info.
+ * ----------
+ */
+ function->fn_nargs = procStruct->pronargs;
+ for (i = 0; i < function->fn_nargs; i++) {
+ function->fn_argvarnos[i] = arg_varnos[i];
+ }
+ function->ndatums = plpgsql_nDatums;
+ function->datums = malloc(sizeof(PLpgSQL_datum *) * plpgsql_nDatums);
+ for (i = 0; i < plpgsql_nDatums; i++) {
+ function->datums[i] = plpgsql_Datums[i];
+ }
+ function->action = plpgsql_yylval.program;
+
+
+ /* ----------
+ * Finally return the compiled function
+ * ----------
+ */
+ if (plpgsql_DumpExecTree) {
+ plpgsql_dumptree(function);
+ }
+ return function;
+}
+
+
+/* ----------
+ * plpgsql_parse_word The scanner calls this to postparse
+ * any single word not found by a
+ * keyword rule.
+ * ----------
+ */
+int plpgsql_parse_word(char *word)
+{
+ PLpgSQL_nsitem *nse;
+ char *cp;
+ HeapTuple typeTup;
+ TypeTupleForm typeStruct;
+ char *typeXlated;
+
+ /* ----------
+ * We do our lookups case insensitive
+ * ----------
+ */
+ cp = plpgsql_tolower(pstrdup(word));
+
+ /* ----------
+ * Special handling when compiling triggers
+ * ----------
+ */
+ if (plpgsql_curr_compile->fn_functype == T_TRIGGER) {
+ if (!strcmp(cp, "tg_argv")) {
+ int save_spacescanned = plpgsql_SpaceScanned;
+ PLpgSQL_trigarg *trigarg;
+
+ trigarg = malloc(sizeof(PLpgSQL_trigarg));
+ memset(trigarg, 0, sizeof(PLpgSQL_trigarg));
+ trigarg->dtype = PLPGSQL_DTYPE_TRIGARG;
+
+ if (plpgsql_yylex() != '[') {
+ plpgsql_yyerror("expected [");
+ }
+
+ trigarg->argnum = plpgsql_read_expression(']', "]");
+
+ plpgsql_adddatum((PLpgSQL_datum *)trigarg);
+ plpgsql_yylval.trigarg = trigarg;
+
+ plpgsql_SpaceScanned = save_spacescanned;
+ return T_TGARGV;
+ }
+ }
+
+ /* ----------
+ * Do a lookup on the compilers namestack
+ * ----------
+ */
+ nse = plpgsql_ns_lookup(cp, NULL);
+ if (nse != NULL) {
+ pfree(cp);
+ switch (nse->itemtype) {
+ case PLPGSQL_NSTYPE_LABEL:
+ return T_LABEL;
+
+ case PLPGSQL_NSTYPE_VAR:
+ plpgsql_yylval.var = (PLpgSQL_var *)(plpgsql_Datums[nse->itemno]);
+ return T_VARIABLE;
+
+ case PLPGSQL_NSTYPE_REC:
+ plpgsql_yylval.rec = (PLpgSQL_rec *)(plpgsql_Datums[nse->itemno]);
+ return T_RECORD;
+
+ case PLPGSQL_NSTYPE_ROW:
+ plpgsql_yylval.row = (PLpgSQL_row *)(plpgsql_Datums[nse->itemno]);
+ return T_ROW;
+
+ default:
+ return T_ERROR;
+ }
+ }
+
+ /* ----------
+ * Try to find a data type with that name, but ignore
+ * pg_type entries that are in fact class types.
+ * ----------
+ */
+ typeXlated = xlateSqlType(cp);
+ typeTup = SearchSysCacheTuple(TYPNAME,
+ PointerGetDatum(typeXlated), 0, 0, 0);
+ if (HeapTupleIsValid(typeTup)) {
+ PLpgSQL_type *typ;
+
+ typeStruct = (TypeTupleForm) GETSTRUCT(typeTup);
+
+ if (typeStruct->typrelid != InvalidOid) {
+ pfree(cp);
+ return T_WORD;
+ }
+
+ typ = (PLpgSQL_type *)malloc(sizeof(PLpgSQL_type));
+
+ typ->typname = strdup(nameout(&(typeStruct->typname)));
+ typ->typoid = typeTup->t_oid;
+ fmgr_info(typeStruct->typinput, &(typ->typinput));
+ typ->typbyval = typeStruct->typbyval;
+ typ->atttypmod = -1;
+
+ plpgsql_yylval.dtype = typ;
+
+ pfree(cp);
+ return T_DTYPE;
+ }
+
+ /* ----------
+ * Nothing found - up to now it's a word without any
+ * special meaning for us.
+ * ----------
+ */
+ pfree(cp);
+ return T_WORD;
+}
+
+
+/* ----------
+ * plpgsql_parse_dblword Same lookup for two words
+ * separated by a dot.
+ * ----------
+ */
+int plpgsql_parse_dblword(char *string)
+{
+ char *word1;
+ char *word2;
+ PLpgSQL_nsitem *ns;
+
+ /* ----------
+ * Convert to lower case and separate the words
+ * ----------
+ */
+ word1 = plpgsql_tolower(pstrdup(string));
+ word2 = strchr(word1, '.');
+ *word2++ = '\0';
+
+ /* ----------
+ * Lookup the first word
+ * ----------
+ */
+ ns = plpgsql_ns_lookup(word1, NULL);
+ if (ns == NULL) {
+ pfree(word1);
+ return T_ERROR;
+ }
+
+ switch (ns->itemtype) {
+ case PLPGSQL_NSTYPE_LABEL:
+ /* ----------
+ * First word is a label, so second word could be
+ * a variable, record or row in that bodies namestack.
+ * Anything else could only be something in a query
+ * given to the SPI manager and T_ERROR will get eaten
+ * up by the collector routines.
+ * ----------
+ */
+ ns = plpgsql_ns_lookup(word2, word1);
+ if (ns == NULL) {
+ pfree(word1);
+ return T_ERROR;
+ }
+ switch (ns->itemtype) {
+ case PLPGSQL_NSTYPE_VAR:
+ plpgsql_yylval.var = (PLpgSQL_var *)(plpgsql_Datums[ns->itemno]);
+ pfree(word1);
+ return T_VARIABLE;
+
+ case PLPGSQL_NSTYPE_REC:
+ plpgsql_yylval.rec = (PLpgSQL_rec *)(plpgsql_Datums[ns->itemno]);
+ pfree(word1);
+ return T_RECORD;
+
+ case PLPGSQL_NSTYPE_ROW:
+ plpgsql_yylval.row = (PLpgSQL_row *)(plpgsql_Datums[ns->itemno]);
+ pfree(word1);
+ return T_ROW;
+
+ default:
+ pfree(word1);
+ return T_ERROR;
+ }
+
+ case PLPGSQL_NSTYPE_REC:
+ {
+ /* ----------
+ * First word is a record name, so second word
+ * must be a field in this record.
+ * ----------
+ */
+ PLpgSQL_recfield *new;
+
+ new = malloc(sizeof(PLpgSQL_recfield));
+ new->dtype = PLPGSQL_DTYPE_RECFIELD;
+ new->fieldname = strdup(word2);
+ new->recno = ns->itemno;
+
+ plpgsql_adddatum((PLpgSQL_datum *)new);
+
+ pfree(word1);
+ plpgsql_yylval.recfield = new;
+ return T_RECFIELD;
+ }
+
+ case PLPGSQL_NSTYPE_ROW:
+ {
+ /* ----------
+ * First word is a row name, so second word must
+ * be a field in this row.
+ * ----------
+ */
+ PLpgSQL_row *row;
+ int i;
+
+ row = (PLpgSQL_row *)(plpgsql_Datums[ns->itemno]);
+ for (i = 0; i < row->nfields; i++) {
+ if (!strcmp(row->fieldnames[i], word2)) {
+ plpgsql_yylval.var = (PLpgSQL_var *)(plpgsql_Datums[row->varnos[i]]);
+ pfree(word1);
+ return T_VARIABLE;
+ }
+ }
+ plpgsql_comperrinfo();
+ elog(ERROR, "row %s doesn't have a field %s",
+ word1, word2);
+ }
+
+ default:
+ break;
+ }
+
+ pfree(word1);
+ return T_ERROR;
+}
+
+
+/* ----------
+ * plpgsql_parse_tripword Same lookup for three words
+ * separated by dots.
+ * ----------
+ */
+int plpgsql_parse_tripword(char *string)
+{
+ char *word1;
+ char *word2;
+ char *word3;
+ PLpgSQL_nsitem *ns;
+
+ /* ----------
+ * Convert to lower case and separate the words
+ * ----------
+ */
+ word1 = plpgsql_tolower(pstrdup(string));
+ word2 = strchr(word1, '.');
+ *word2++ = '\0';
+ word3 = strchr(word2, '.');
+ *word3++ = '\0';
+
+ /* ----------
+ * Lookup the first word - it must be a label
+ * ----------
+ */
+ ns = plpgsql_ns_lookup(word1, NULL);
+ if (ns == NULL) {
+ pfree(word1);
+ return T_ERROR;
+ }
+ if (ns->itemtype != PLPGSQL_NSTYPE_LABEL) {
+ pfree(word1);
+ return T_ERROR;
+ }
+
+ /* ----------
+ * First word is a label, so second word could be
+ * a record or row
+ * ----------
+ */
+ ns = plpgsql_ns_lookup(word2, word1);
+ if (ns == NULL) {
+ pfree(word1);
+ return T_ERROR;
+ }
+
+ switch (ns->itemtype) {
+ case PLPGSQL_NSTYPE_REC:
+ {
+ /* ----------
+ * This word is a record name, so third word
+ * must be a field in this record.
+ * ----------
+ */
+ PLpgSQL_recfield *new;
+
+ new = malloc(sizeof(PLpgSQL_recfield));
+ new->dtype = PLPGSQL_DTYPE_RECFIELD;
+ new->fieldname = strdup(word3);
+ new->recno = ns->itemno;
+
+ plpgsql_adddatum((PLpgSQL_datum *)new);
+
+ pfree(word1);
+ plpgsql_yylval.recfield = new;
+ return T_RECFIELD;
+ }
+
+ case PLPGSQL_NSTYPE_ROW:
+ {
+ /* ----------
+ * This word is a row name, so third word must
+ * be a field in this row.
+ * ----------
+ */
+ PLpgSQL_row *row;
+ int i;
+
+ row = (PLpgSQL_row *)(plpgsql_Datums[ns->itemno]);
+ for (i = 0; i < row->nfields; i++) {
+ if (!strcmp(row->fieldnames[i], word3)) {
+ plpgsql_yylval.var = (PLpgSQL_var *)(plpgsql_Datums[row->varnos[i]]);
+ pfree(word1);
+ return T_VARIABLE;
+ }
+ }
+ plpgsql_comperrinfo();
+ elog(ERROR, "row %s.%s doesn't have a field %s",
+ word1, word2, word3);
+ }
+
+ default:
+ break;
+ }
+
+ pfree(word1);
+ return T_ERROR;
+}
+
+
+/* ----------
+ * plpgsql_parse_wordtype The scanner found word%TYPE. word can be
+ * a variable name or a basetype.
+ * ----------
+ */
+int plpgsql_parse_wordtype(char *word)
+{
+ PLpgSQL_nsitem *nse;
+ char *cp;
+ HeapTuple typeTup;
+ TypeTupleForm typeStruct;
+ char *typeXlated;
+ bool old_nsstate;
+
+ /* ----------
+ * We do our lookups case insensitive
+ * ----------
+ */
+ cp = plpgsql_tolower(pstrdup(word));
+ *(strchr(cp, '%')) = '\0';
+
+ /* ----------
+ * Do a lookup on the compilers namestack.
+ * But ensure it moves up to the toplevel.
+ * ----------
+ */
+ old_nsstate = plpgsql_ns_setlocal(false);
+ nse = plpgsql_ns_lookup(cp, NULL);
+ plpgsql_ns_setlocal(old_nsstate);
+
+ if (nse != NULL) {
+ pfree(cp);
+ switch (nse->itemtype) {
+ case PLPGSQL_NSTYPE_VAR:
+ plpgsql_yylval.dtype = ((PLpgSQL_var *)(plpgsql_Datums[nse->itemno]))->datatype;
+ return T_DTYPE;
+
+ default:
+ return T_ERROR;
+ }
+ }
+
+ /* ----------
+ * Word wasn't found on the namestack.
+ * Try to find a data type with that name, but ignore
+ * pg_type entries that are in fact class types.
+ * ----------
+ */
+ typeXlated = xlateSqlType(cp);
+ typeTup = SearchSysCacheTuple(TYPNAME,
+ PointerGetDatum(typeXlated), 0, 0, 0);
+ if (HeapTupleIsValid(typeTup)) {
+ PLpgSQL_type *typ;
+
+ typeStruct = (TypeTupleForm) GETSTRUCT(typeTup);
+
+ if (typeStruct->typrelid != InvalidOid) {
+ pfree(cp);
+ return T_ERROR;
+ }
+
+ typ = (PLpgSQL_type *)malloc(sizeof(PLpgSQL_type));
+
+ typ->typname = strdup(nameout(&(typeStruct->typname)));
+ typ->typoid = typeTup->t_oid;
+ fmgr_info(typeStruct->typinput, &(typ->typinput));
+ typ->typbyval = typeStruct->typbyval;
+ typ->atttypmod = -1;
+
+ plpgsql_yylval.dtype = typ;
+
+ pfree(cp);
+ return T_DTYPE;
+ }
+
+ /* ----------
+ * Nothing found - up to now it's a word without any
+ * special meaning for us.
+ * ----------
+ */
+ pfree(cp);
+ return T_ERROR;
+}
+
+
+/* ----------
+ * plpgsql_parse_dblwordtype Same lookup for word.word%TYPE
+ * ----------
+ */
+int plpgsql_parse_dblwordtype(char *string)
+{
+ char *word1;
+ char *word2;
+ PLpgSQL_nsitem *nse;
+ bool old_nsstate;
+ HeapTuple classtup;
+ Form_pg_class classStruct;
+ HeapTuple attrtup;
+ AttributeTupleForm attrStruct;
+ HeapTuple typetup;
+ TypeTupleForm typeStruct;
+ PLpgSQL_type *typ;
+
+
+ /* ----------
+ * Convert to lower case and separate the words
+ * ----------
+ */
+ word1 = plpgsql_tolower(pstrdup(string));
+ word2 = strchr(word1, '.');
+ *word2++ = '\0';
+ *(strchr(word2, '%')) = '\0';
+
+ /* ----------
+ * Lookup the first word
+ * ----------
+ */
+ nse = plpgsql_ns_lookup(word1, NULL);
+
+ /* ----------
+ * If this is a label lookup the second word in that
+ * labels namestack level
+ * ----------
+ */
+ if (nse != NULL) {
+ if (nse->itemtype == PLPGSQL_NSTYPE_LABEL) {
+ old_nsstate = plpgsql_ns_setlocal(false);
+ nse = plpgsql_ns_lookup(word2, word1);
+ plpgsql_ns_setlocal(old_nsstate);
+
+ pfree(word1);
+
+ if (nse != NULL) {
+ switch (nse->itemtype) {
+ case PLPGSQL_NSTYPE_VAR:
+ plpgsql_yylval.dtype = ((PLpgSQL_var *)(plpgsql_Datums[nse->itemno]))->datatype;
+ return T_DTYPE;
+
+ default:
+ return T_ERROR;
+ }
+ }
+ return T_ERROR;
+ }
+ pfree(word1);
+ return T_ERROR;
+ }
+
+ /* ----------
+ * First word could also be a table name
+ * ----------
+ */
+ classtup = SearchSysCacheTuple(RELNAME,
+ PointerGetDatum(word1), 0, 0, 0);
+ if (!HeapTupleIsValid(classtup)) {
+ pfree(word1);
+ return T_ERROR;
+ }
+
+ /* ----------
+ * It must be a (shared) relation class
+ * ----------
+ */
+ classStruct = (Form_pg_class)GETSTRUCT(classtup);
+ if (classStruct->relkind != 'r' && classStruct->relkind != 's') {
+ pfree(word1);
+ return T_ERROR;
+ }
+
+ /* ----------
+ * Fetch the named table field and it's type
+ * ----------
+ */
+ attrtup = SearchSysCacheTuple(ATTNAME,
+ ObjectIdGetDatum(classtup->t_oid),
+ PointerGetDatum(word2), 0, 0);
+ if (!HeapTupleIsValid(attrtup)) {
+ pfree(word1);
+ return T_ERROR;
+ }
+ attrStruct = (AttributeTupleForm)GETSTRUCT(attrtup);
+
+ typetup = SearchSysCacheTuple(TYPOID,
+ ObjectIdGetDatum(attrStruct->atttypid), 0, 0, 0);
+ if (!HeapTupleIsValid(typetup)) {
+ plpgsql_comperrinfo();
+ elog(ERROR, "cache lookup for type %d of %s.%s failed",
+ attrStruct->atttypid, word1, word2);
+ }
+
+ /* ----------
+ * Found that - build a compiler type struct and return it
+ * ----------
+ */
+ typeStruct = (TypeTupleForm)GETSTRUCT(typetup);
+
+ typ = (PLpgSQL_type *)malloc(sizeof(PLpgSQL_type));
+
+ typ->typname = strdup(nameout(&(typeStruct->typname)));
+ typ->typoid = typetup->t_oid;
+ fmgr_info(typeStruct->typinput, &(typ->typinput));
+ typ->typbyval = typeStruct->typbyval;
+ typ->atttypmod = attrStruct->atttypmod;
+
+ plpgsql_yylval.dtype = typ;
+
+ pfree(word1);
+ return T_DTYPE;
+}
+
+
+/* ----------
+ * plpgsql_parse_wordrowtype Scanner found word%ROWTYPE.
+ * So word must be a table name.
+ * ----------
+ */
+int plpgsql_parse_wordrowtype(char *string)
+{
+ HeapTuple classtup;
+ Form_pg_class classStruct;
+ HeapTuple typetup;
+ TypeTupleForm typeStruct;
+ HeapTuple attrtup;
+ AttributeTupleForm attrStruct;
+ char *word1;
+ char *cp;
+ int i;
+ PLpgSQL_row *row;
+ PLpgSQL_var *var;
+
+ /* ----------
+ * Get the word in lower case and fetch the pg_class tuple.
+ * ----------
+ */
+ word1 = plpgsql_tolower(pstrdup(string));
+ cp = strchr(word1, '%');
+ *cp = '\0';
+
+ classtup = SearchSysCacheTuple(RELNAME,
+ PointerGetDatum(word1), 0, 0, 0);
+ if (!HeapTupleIsValid(classtup)) {
+ plpgsql_comperrinfo();
+ elog(ERROR, "%s: no such class", word1);
+ }
+ classStruct = (Form_pg_class)GETSTRUCT(classtup);
+ if (classStruct->relkind != 'r' && classStruct->relkind != 's') {
+ plpgsql_comperrinfo();
+ elog(ERROR, "%s isn't a table", word1);
+ }
+
+ /* ----------
+ * Fetch the tables pg_type tuple too
+ * ----------
+ */
+ typetup = SearchSysCacheTuple(TYPNAME,
+ PointerGetDatum(word1), 0, 0, 0);
+ if (!HeapTupleIsValid(typetup)) {
+ plpgsql_comperrinfo();
+ elog(ERROR, "cache lookup for %s in pg_type failed", word1);
+ }
+
+ /* ----------
+ * Create a row datum entry and all the required variables
+ * that it will point to.
+ * ----------
+ */
+ row = malloc(sizeof(PLpgSQL_row));
+ memset(row, 0, sizeof(PLpgSQL_row));
+
+ row->dtype = PLPGSQL_DTYPE_ROW;
+ row->nfields = classStruct->relnatts;
+ row->rowtypeclass = typetup->t_oid;
+ row->fieldnames = malloc(sizeof(char *) * row->nfields);
+ row->varnos = malloc(sizeof(int) * row->nfields);
+
+ for (i = 0; i < row->nfields; i++) {
+ /* ----------
+ * Get the attribute and it's type
+ * ----------
+ */
+ attrtup = SearchSysCacheTuple(ATTNUM,
+ ObjectIdGetDatum(classtup->t_oid),
+ (Datum)(i + 1), 0, 0);
+ if (!HeapTupleIsValid(attrtup)) {
+ plpgsql_comperrinfo();
+ elog(ERROR, "cache lookup for attribute %d of class %s failed",
+ i + 1, word1);
+ }
+ attrStruct = (AttributeTupleForm)GETSTRUCT(attrtup);
+
+ typetup = SearchSysCacheTuple(TYPOID,
+ ObjectIdGetDatum(attrStruct->atttypid), 0, 0, 0);
+ if (!HeapTupleIsValid(typetup)) {
+ plpgsql_comperrinfo();
+ elog(ERROR, "cache lookup for type %d of %s.%s failed",
+ attrStruct->atttypid, word1,
+ nameout(&(attrStruct->attname)));
+ }
+ typeStruct = (TypeTupleForm)GETSTRUCT(typetup);
+
+ cp = strdup(nameout(&(attrStruct->attname)));
+
+ /* ----------
+ * Create the internal variable
+ * We know if the table definitions contain a default value
+ * or if the field is declared in the table as NOT NULL. But
+ * it's possible to create a table field as NOT NULL without
+ * a default value and that would lead to problems later when
+ * initializing the variables due to entering a block at
+ * execution time. Thus we ignore this information for now.
+ * ----------
+ */
+ var = malloc(sizeof(PLpgSQL_var));
+ var->dtype = PLPGSQL_DTYPE_VAR;
+ var->refname = malloc(strlen(word1) + strlen(cp) + 2);
+ strcpy(var->refname, word1);
+ strcat(var->refname, ".");
+ strcat(var->refname, cp);
+ var->datatype = malloc(sizeof(PLpgSQL_type));
+ var->datatype->typname = strdup(nameout(&(typeStruct->typname)));
+ var->datatype->typoid = typetup->t_oid;
+ fmgr_info(typeStruct->typinput, &(var->datatype->typinput));
+ var->datatype->typbyval = typeStruct->typbyval;
+ var->datatype->atttypmod = attrStruct->atttypmod;
+ var->isconst = false;
+ var->notnull = false;
+ var->default_val = NULL;
+ var->value = (Datum)0;
+ var->isnull = true;
+ var->shouldfree = false;
+
+ plpgsql_adddatum((PLpgSQL_datum *)var);
+
+ /* ----------
+ * Add the variable to the row.
+ * ----------
+ */
+ row->fieldnames[i] = cp;
+ row->varnos[i] = var->varno;
+ }
+
+ /* ----------
+ * Return the complete row definition
+ * ----------
+ */
+ plpgsql_yylval.row = row;
+
+ return T_ROW;
+}
+
+
+/* ----------
+ * plpgsql_adddatum Add a variable, record or row
+ * to the compilers datum list.
+ * ----------
+ */
+void plpgsql_adddatum(PLpgSQL_datum *new)
+{
+ if (plpgsql_nDatums == datums_alloc) {
+ datums_alloc *= 2;
+ plpgsql_Datums = repalloc(plpgsql_Datums, sizeof(PLpgSQL_datum *) * datums_alloc);
+ }
+
+ new->dno = plpgsql_nDatums;
+ plpgsql_Datums[plpgsql_nDatums++] = new;
+}
+
+
+/* ----------
+ * plpgsql_add_initdatums Put all datum entries created
+ * since the last call into the
+ * finishing code block so the
+ * block knows which variables to
+ * reinitialize when entered.
+ * ----------
+ */
+int plpgsql_add_initdatums(int **varnos)
+{
+ int i;
+ int n = 0;
+
+ for (i = datums_last; i < plpgsql_nDatums; i++) {
+ switch (plpgsql_Datums[i]->dtype) {
+ case PLPGSQL_DTYPE_VAR:
+ n++;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (varnos != NULL) {
+ *varnos = (int *)malloc(sizeof(int) * n);
+
+ n = 0;
+ for (i = datums_last; i < plpgsql_nDatums; i++) {
+ switch (plpgsql_Datums[i]->dtype) {
+ case PLPGSQL_DTYPE_VAR:
+ (*varnos)[n++] = plpgsql_Datums[i]->dno;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ datums_last = plpgsql_nDatums;
+ return n;
+}
+
+
+/* ----------
+ * plpgsql_comperrinfo Called before elog(ERROR, ...)
+ * during compile.
+ * ----------
+ */
+void plpgsql_comperrinfo()
+{
+ elog(NOTICE, "plpgsql: ERROR during compile of %s near line %d",
+ plpgsql_error_funcname, plpgsql_error_lineno);
+}
+
+
+/* ---------
+ * plpgsql_yyerror Handle parser error
+ * ---------
+ */
+
+void plpgsql_yyerror(const char *s)
+{
+ plpgsql_error_lineno = plpgsql_yylineno;
+ plpgsql_comperrinfo();
+ elog(ERROR, "%s at or near \"%s\"", s, plpgsql_yytext);
+}
+
+
+/* ----------
+ * xlateSqlType()
+ * Convert alternate type names to internal Postgres types.
+ *
+ * Stolen from backend's main parser
+ * ----------
+ */
+static char *
+xlateSqlType(char *name)
+{
+ if (!strcasecmp(name,"int")
+ || !strcasecmp(name,"integer"))
+ return "int4";
+ else if (!strcasecmp(name, "smallint"))
+ return "int2";
+ else if (!strcasecmp(name, "real")
+ || !strcasecmp(name, "float"))
+ return "float8";
+ else if (!strcasecmp(name, "interval"))
+ return "timespan";
+ else if (!strcasecmp(name, "boolean"))
+ return "bool";
+ else
+ return name;
+} /* xlateSqlType() */
+
+