summaryrefslogtreecommitdiff
path: root/contrib/plpgsql/src
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/plpgsql/src')
-rw-r--r--contrib/plpgsql/src/INSTALL22
-rw-r--r--contrib/plpgsql/src/gram.y1460
-rw-r--r--contrib/plpgsql/src/mklang.sql14
-rw-r--r--contrib/plpgsql/src/pl_comp.c1313
-rw-r--r--contrib/plpgsql/src/pl_exec.c2241
-rw-r--r--contrib/plpgsql/src/pl_funcs.c675
-rw-r--r--contrib/plpgsql/src/pl_handler.c193
-rw-r--r--contrib/plpgsql/src/plpgsql.h475
-rw-r--r--contrib/plpgsql/src/scan.l227
9 files changed, 6620 insertions, 0 deletions
diff --git a/contrib/plpgsql/src/INSTALL b/contrib/plpgsql/src/INSTALL
new file mode 100644
index 00000000000..d46576e6853
--- /dev/null
+++ b/contrib/plpgsql/src/INSTALL
@@ -0,0 +1,22 @@
+Installation of PL/pgSQL
+
+
+1) Type 'make' to build the shared plpgsql object.
+
+2) Type 'make install' to install the shared object in
+ the PostgreSQL library directory.
+
+3) Declare the PL/pgSQL procedural language in your
+ database by
+
+ psql dbname <mklang.sql
+
+ If the PostgreSQL library directory is different from
+ /usr/local/pgsql/lib you must edit mklang.sql prior.
+
+ If you declare the language in the template1 database,
+ any subsequently created database will have PL/pgSQL
+ support installed automatically.
+
+
+
diff --git a/contrib/plpgsql/src/gram.y b/contrib/plpgsql/src/gram.y
new file mode 100644
index 00000000000..12e2fc167ab
--- /dev/null
+++ b/contrib/plpgsql/src/gram.y
@@ -0,0 +1,1460 @@
+%{
+/**********************************************************************
+ * gram.y - Parser for the PL/pgSQL
+ * procedural language
+ *
+ * IDENTIFICATION
+ * $Header: /cvsroot/pgsql/contrib/plpgsql/src/Attic/gram.y,v 1.1 1998/08/22 12:38:30 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 "string.h"
+#include "plpgsql.h"
+
+extern int yylineno;
+extern char yytext[];
+
+static PLpgSQL_expr *read_sqlstmt(int until, char *s, char *sqlstart);
+static PLpgSQL_stmt *make_select_stmt(void);
+static PLpgSQL_expr *make_tupret_expr(PLpgSQL_row *row);
+
+%}
+
+%union {
+ int32 ival;
+ char *str;
+ struct {
+ char *name;
+ int lineno;
+ } varname;
+ struct {
+ int nalloc;
+ int nused;
+ int *dtnums;
+ } dtlist;
+ struct {
+ int reverse;
+ PLpgSQL_expr *expr;
+ } forilow;
+ struct {
+ char *label;
+ int n_initvars;
+ int *initvarnos;
+ } declhdr;
+ PLpgSQL_type *dtype;
+ PLpgSQL_var *var;
+ PLpgSQL_row *row;
+ PLpgSQL_rec *rec;
+ PLpgSQL_recfield *recfield;
+ PLpgSQL_trigarg *trigarg;
+ PLpgSQL_expr *expr;
+ PLpgSQL_stmt *stmt;
+ PLpgSQL_stmts *stmts;
+ PLpgSQL_stmt_block *program;
+ PLpgSQL_nsitem *nsitem;
+}
+
+%type <declhdr> decl_sect
+%type <varname> decl_varname
+%type <str> decl_renname
+%type <ival> decl_const, decl_notnull, decl_atttypmod, decl_atttypmodval
+%type <expr> decl_defval
+%type <dtype> decl_datatype, decl_dtypename
+%type <row> decl_rowtype
+%type <nsitem> decl_aliasitem
+%type <str> decl_stmts, decl_stmt
+
+%type <expr> expr_until_semi, expr_until_then, expr_until_loop
+%type <expr> opt_exitcond
+
+%type <ival> assign_var
+%type <var> fori_var
+%type <varname> fori_varname
+%type <forilow> fori_lower
+%type <rec> fors_target
+
+%type <str> opt_lblname, opt_label
+%type <str> opt_exitlabel
+%type <str> execsql_start
+
+%type <stmts> proc_sect, proc_stmts, stmt_else, loop_body
+%type <stmt> proc_stmt, pl_block
+%type <stmt> stmt_assign, stmt_if, stmt_loop, stmt_while, stmt_exit
+%type <stmt> stmt_return, stmt_raise, stmt_execsql, stmt_fori
+%type <stmt> stmt_fors, stmt_select, stmt_perform
+
+%type <dtlist> raise_params
+%type <ival> raise_level, raise_param
+%type <str> raise_msg
+
+%type <ival> lno
+
+ /*
+ * Keyword tokens
+ */
+%token K_ALIAS
+%token K_ASSIGN
+%token K_BEGIN
+%token K_CONSTANT
+%token K_DEBUG
+%token K_DECLARE
+%token K_DEFAULT
+%token K_DOTDOT
+%token K_ELSE
+%token K_END
+%token K_EXCEPTION
+%token K_EXIT
+%token K_FOR
+%token K_FROM
+%token K_IF
+%token K_IN
+%token K_INTO
+%token K_LOOP
+%token K_NOT
+%token K_NOTICE
+%token K_NULL
+%token K_PERFORM
+%token K_RAISE
+%token K_RECORD
+%token K_RENAME
+%token K_RETURN
+%token K_REVERSE
+%token K_SELECT
+%token K_THEN
+%token K_TO
+%token K_TYPE
+%token K_WHEN
+%token K_WHILE
+
+ /*
+ * Other tokens
+ */
+%token T_FUNCTION
+%token T_TRIGGER
+%token T_CHAR
+%token T_BPCHAR
+%token T_VARCHAR
+%token T_LABEL
+%token T_STRING
+%token T_VARIABLE
+%token T_ROW
+%token T_ROWTYPE
+%token T_RECORD
+%token T_RECFIELD
+%token T_TGARGV
+%token T_DTYPE
+%token T_WORD
+%token T_NUMBER
+%token T_ERROR
+
+%token O_OPTION
+%token O_DUMP
+
+%%
+
+pl_function : T_FUNCTION comp_optsect pl_block
+ {
+ yylval.program = (PLpgSQL_stmt_block *)$3;
+ }
+ | T_TRIGGER comp_optsect pl_block
+ {
+ yylval.program = (PLpgSQL_stmt_block *)$3;
+ }
+ ;
+
+comp_optsect :
+ | comp_options
+ ;
+
+comp_options : comp_options comp_option
+ | comp_option
+ ;
+
+comp_option : O_OPTION O_DUMP
+ {
+ plpgsql_DumpExecTree = 1;
+ }
+ ;
+
+pl_block : decl_sect K_BEGIN lno proc_sect K_END ';'
+ {
+ PLpgSQL_stmt_block *new;
+
+ new = malloc(sizeof(PLpgSQL_stmt_block));
+ memset(new, 0, sizeof(PLpgSQL_stmt_block));
+
+ new->cmd_type = PLPGSQL_STMT_BLOCK;
+ new->lineno = $3;
+ new->label = $1.label;
+ new->n_initvars = $1.n_initvars;
+ new->initvarnos = $1.initvarnos;
+ new->body = $4;
+
+ plpgsql_ns_pop();
+
+ $$ = (PLpgSQL_stmt *)new;
+ }
+ ;
+
+
+decl_sect : opt_label
+ {
+ plpgsql_ns_setlocal(false);
+ $$.label = $1;
+ $$.n_initvars = 0;
+ $$.initvarnos = NULL;
+ plpgsql_add_initdatums(NULL);
+ }
+ | opt_label decl_start
+ {
+ plpgsql_ns_setlocal(false);
+ $$.label = $1;
+ $$.n_initvars = 0;
+ $$.initvarnos = NULL;
+ plpgsql_add_initdatums(NULL);
+ }
+ | opt_label decl_start decl_stmts
+ {
+ plpgsql_ns_setlocal(false);
+ if ($3 != NULL) {
+ $$.label = $3;
+ } else {
+ $$.label = $1;
+ }
+ $$.n_initvars = plpgsql_add_initdatums(&($$.initvarnos));
+ }
+ ;
+
+decl_start : K_DECLARE
+ {
+ plpgsql_ns_setlocal(true);
+ }
+ ;
+
+decl_stmts : decl_stmts decl_stmt
+ {
+ $$ = $2;
+ }
+ | decl_stmt
+ {
+ $$ = $1;
+ }
+ ;
+
+decl_stmt : '<' '<' opt_lblname '>' '>'
+ {
+ $$ = $3;
+ }
+ | K_DECLARE
+ {
+ $$ = NULL;
+ }
+ | decl_statement
+ {
+ $$ = NULL;
+ }
+ ;
+
+decl_statement : decl_varname decl_const decl_datatype decl_notnull decl_defval
+ {
+ PLpgSQL_var *new;
+
+ new = malloc(sizeof(PLpgSQL_var));
+
+ new->dtype = PLPGSQL_DTYPE_VAR;
+ new->refname = $1.name;
+ new->lineno = $1.lineno;
+
+ new->datatype = $3;
+ new->isconst = $2;
+ new->notnull = $4;
+ new->default_val = $5;
+
+ plpgsql_adddatum((PLpgSQL_datum *)new);
+ plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, new->varno,
+ $1.name);
+ }
+ | decl_varname K_RECORD ';'
+ {
+ PLpgSQL_rec *new;
+
+ new = malloc(sizeof(PLpgSQL_var));
+
+ new->dtype = PLPGSQL_DTYPE_REC;
+ new->refname = $1.name;
+ new->lineno = $1.lineno;
+
+ plpgsql_adddatum((PLpgSQL_datum *)new);
+ plpgsql_ns_additem(PLPGSQL_NSTYPE_REC, new->recno,
+ $1.name);
+ }
+ | decl_varname decl_rowtype ';'
+ {
+ $2->dtype = PLPGSQL_DTYPE_ROW;
+ $2->refname = $1.name;
+ $2->lineno = $1.lineno;
+
+ plpgsql_adddatum((PLpgSQL_datum *)$2);
+ plpgsql_ns_additem(PLPGSQL_NSTYPE_ROW, $2->rowno,
+ $1.name);
+ }
+ | decl_varname K_ALIAS K_FOR decl_aliasitem ';'
+ {
+ plpgsql_ns_additem($4->itemtype,
+ $4->itemno, $1.name);
+ }
+ | K_RENAME decl_renname K_TO decl_renname ';'
+ {
+ plpgsql_ns_rename($2, $4);
+ }
+ ;
+
+decl_aliasitem : T_WORD
+ {
+ PLpgSQL_nsitem *nsi;
+ char *name;
+
+ plpgsql_ns_setlocal(false);
+ name = plpgsql_tolower(pstrdup(yytext));
+ if (name[0] != '$') {
+ elog(ERROR, "can only alias positional parameters");
+ }
+ nsi = plpgsql_ns_lookup(name, NULL);
+ if (nsi == NULL) {
+ elog(ERROR, "function has no parameter %s", name);
+ }
+
+ plpgsql_ns_setlocal(true);
+
+ $$ = nsi;
+ }
+ ;
+
+decl_rowtype : T_ROW
+ {
+ $$ = yylval.row;
+ }
+ ;
+
+decl_varname : T_WORD
+ {
+ $$.name = strdup(yytext);
+ $$.lineno = yylineno;
+ }
+ ;
+
+decl_renname : T_WORD
+ {
+ $$ = plpgsql_tolower(pstrdup(yytext));
+ }
+ ;
+
+decl_const :
+ { $$ = 0; }
+ | K_CONSTANT
+ { $$ = 1; }
+ ;
+
+decl_datatype : decl_dtypename
+ {
+ $$ = $1;
+ }
+ ;
+
+decl_dtypename : T_DTYPE
+ {
+ $$ = yylval.dtype;
+ }
+ | T_CHAR decl_atttypmod
+ {
+ if ($2 < 0) {
+ plpgsql_parse_word("char");
+ $$ = yylval.dtype;
+ } else {
+ plpgsql_parse_word("bpchar");
+ $$ = yylval.dtype;
+ $$->atttypmod = $2;
+ }
+ }
+ | T_VARCHAR decl_atttypmod
+ {
+ plpgsql_parse_word("varchar");
+ $$ = yylval.dtype;
+ $$->atttypmod = $2;
+ }
+ | T_BPCHAR '(' decl_atttypmodval ')'
+ {
+ plpgsql_parse_word("bpchar");
+ $$ = yylval.dtype;
+ $$->atttypmod = $3;
+ }
+ ;
+
+decl_atttypmod :
+ {
+ $$ = -1;
+ }
+ | '(' decl_atttypmodval ')'
+ {
+ $$ = $2;
+ }
+ ;
+
+decl_atttypmodval : T_NUMBER
+ {
+ $$ = int2in(yytext) + VARHDRSZ;
+ }
+ ;
+
+decl_notnull :
+ { $$ = 0; }
+ | K_NOT K_NULL
+ { $$ = 1; }
+ ;
+
+decl_defval : ';'
+ { $$ = NULL; }
+ | decl_defkey
+ {
+ int tok;
+ int lno;
+ PLpgSQL_dstring ds;
+ PLpgSQL_expr *expr;
+
+ lno = yylineno;
+ expr = malloc(sizeof(PLpgSQL_expr));
+ plpgsql_dstring_init(&ds);
+ plpgsql_dstring_append(&ds, "SELECT ");
+
+ expr->dtype = PLPGSQL_DTYPE_EXPR;
+ expr->plan = NULL;
+ expr->nparams = 0;
+
+ tok = yylex();
+ switch (tok) {
+ case 0:
+ plpgsql_error_lineno = lno;
+ plpgsql_comperrinfo();
+ elog(ERROR, "unexpected end of file");
+ case K_NULL:
+ if (yylex() != ';') {
+ plpgsql_error_lineno = lno;
+ plpgsql_comperrinfo();
+ elog(ERROR, "expectec ; after NULL");
+ }
+ free(expr);
+ plpgsql_dstring_free(&ds);
+
+ $$ = NULL;
+ break;
+
+ default:
+ plpgsql_dstring_append(&ds, yytext);
+ while ((tok = yylex()) != ';') {
+ if (tok == 0) {
+ plpgsql_error_lineno = lno;
+ plpgsql_comperrinfo();
+ elog(ERROR, "unterminated default value");
+ }
+ if (plpgsql_SpaceScanned) {
+ plpgsql_dstring_append(&ds, " ");
+ }
+ plpgsql_dstring_append(&ds, yytext);
+ }
+ expr->query = strdup(plpgsql_dstring_get(&ds));
+ plpgsql_dstring_free(&ds);
+
+ $$ = expr;
+ break;
+ }
+ }
+ ;
+
+decl_defkey : K_ASSIGN
+ | K_DEFAULT
+
+proc_sect :
+ {
+ PLpgSQL_stmts *new;
+
+ new = malloc(sizeof(PLpgSQL_stmts));
+ memset(new, 0, sizeof(PLpgSQL_stmts));
+ $$ = new;
+ }
+ | proc_stmts
+ {
+ $$ = $1;
+ }
+ ;
+
+proc_stmts : proc_stmts proc_stmt
+ {
+ if ($1->stmts_used == $1->stmts_alloc) {
+ $1->stmts_alloc *= 2;
+ $1->stmts = realloc($1->stmts, sizeof(PLpgSQL_stmt *) * $1->stmts_alloc);
+ }
+ $1->stmts[$1->stmts_used++] = (struct PLpgSQL_stmt *)$2;
+
+ $$ = $1;
+ }
+ | proc_stmt
+ {
+ PLpgSQL_stmts *new;
+
+ new = malloc(sizeof(PLpgSQL_stmts));
+ memset(new, 0, sizeof(PLpgSQL_stmts));
+
+ new->stmts_alloc = 64;
+ new->stmts_used = 1;
+ new->stmts = malloc(sizeof(PLpgSQL_stmt *) * new->stmts_alloc);
+ new->stmts[0] = (struct PLpgSQL_stmt *)$1;
+
+ $$ = new;
+ }
+ ;
+
+proc_stmt : pl_block
+ { $$ = $1; }
+ | stmt_assign
+ { $$ = $1; }
+ | stmt_if
+ { $$ = $1; }
+ | stmt_loop
+ { $$ = $1; }
+ | stmt_while
+ { $$ = $1; }
+ | stmt_fori
+ { $$ = $1; }
+ | stmt_fors
+ { $$ = $1; }
+ | stmt_select
+ { $$ = $1; }
+ | stmt_exit
+ { $$ = $1; }
+ | stmt_return
+ { $$ = $1; }
+ | stmt_raise
+ { $$ = $1; }
+ | stmt_execsql
+ { $$ = $1; }
+ | stmt_perform
+ { $$ = $1; }
+ ;
+
+stmt_perform : K_PERFORM lno expr_until_semi
+ {
+ PLpgSQL_stmt_assign *new;
+
+ new = malloc(sizeof(PLpgSQL_stmt_assign));
+ memset(new, 0, sizeof(PLpgSQL_stmt_assign));
+
+ new->cmd_type = PLPGSQL_STMT_ASSIGN;
+ new->lineno = $2;
+ new->varno = -1;
+ new->expr = $3;
+
+ $$ = (PLpgSQL_stmt *)new;
+ }
+ ;
+
+stmt_assign : assign_var lno K_ASSIGN expr_until_semi
+ {
+ PLpgSQL_stmt_assign *new;
+
+ new = malloc(sizeof(PLpgSQL_stmt_assign));
+ memset(new, 0, sizeof(PLpgSQL_stmt_assign));
+
+ new->cmd_type = PLPGSQL_STMT_ASSIGN;
+ new->lineno = $2;
+ new->varno = $1;
+ new->expr = $4;
+
+ $$ = (PLpgSQL_stmt *)new;
+ }
+ ;
+
+assign_var : T_VARIABLE
+ {
+ if (yylval.var->isconst) {
+ plpgsql_comperrinfo();
+ elog(ERROR, "%s is declared CONSTANT", yylval.var->refname);
+ }
+ $$ = yylval.var->varno;
+ }
+ | T_RECFIELD
+ {
+ $$ = yylval.recfield->rfno;
+ }
+ ;
+
+stmt_if : K_IF lno expr_until_then proc_sect stmt_else K_END K_IF ';'
+ {
+ PLpgSQL_stmt_if *new;
+
+ new = malloc(sizeof(PLpgSQL_stmt_if));
+ memset(new, 0, sizeof(PLpgSQL_stmt_if));
+
+ new->cmd_type = PLPGSQL_STMT_IF;
+ new->lineno = $2;
+ new->cond = $3;
+ new->true_body = $4;
+ new->false_body = $5;
+
+ $$ = (PLpgSQL_stmt *)new;
+ }
+ ;
+
+stmt_else :
+ {
+ PLpgSQL_stmts *new;
+
+ new = malloc(sizeof(PLpgSQL_stmts));
+ memset(new, 0, sizeof(PLpgSQL_stmts));
+ $$ = new;
+ }
+ | K_ELSE proc_sect
+ { $$ = $2; }
+ ;
+
+stmt_loop : opt_label K_LOOP lno loop_body
+ {
+ PLpgSQL_stmt_loop *new;
+
+ new = malloc(sizeof(PLpgSQL_stmt_loop));
+ memset(new, 0, sizeof(PLpgSQL_stmt_loop));
+
+ new->cmd_type = PLPGSQL_STMT_LOOP;
+ new->lineno = $3;
+ new->label = $1;
+ new->body = $4;
+
+ plpgsql_ns_pop();
+
+ $$ = (PLpgSQL_stmt *)new;
+ }
+ ;
+
+stmt_while : opt_label K_WHILE lno expr_until_loop loop_body
+ {
+ PLpgSQL_stmt_while *new;
+
+ new = malloc(sizeof(PLpgSQL_stmt_while));
+ memset(new, 0, sizeof(PLpgSQL_stmt_while));
+
+ new->cmd_type = PLPGSQL_STMT_WHILE;
+ new->lineno = $3;
+ new->label = $1;
+ new->cond = $4;
+ new->body = $5;
+
+ plpgsql_ns_pop();
+
+ $$ = (PLpgSQL_stmt *)new;
+ }
+ ;
+
+stmt_fori : opt_label K_FOR lno fori_var K_IN fori_lower expr_until_loop loop_body
+ {
+ PLpgSQL_stmt_fori *new;
+
+ new = malloc(sizeof(PLpgSQL_stmt_fori));
+ memset(new, 0, sizeof(PLpgSQL_stmt_fori));
+
+ new->cmd_type = PLPGSQL_STMT_FORI;
+ new->lineno = $3;
+ new->label = $1;
+ new->var = $4;
+ new->reverse = $6.reverse;
+ new->lower = $6.expr;
+ new->upper = $7;
+ new->body = $8;
+
+ plpgsql_ns_pop();
+
+ $$ = (PLpgSQL_stmt *)new;
+ }
+ ;
+
+fori_var : fori_varname
+ {
+ PLpgSQL_var *new;
+
+ new = malloc(sizeof(PLpgSQL_var));
+
+ new->dtype = PLPGSQL_DTYPE_VAR;
+ new->refname = $1.name;
+ new->lineno = $1.lineno;
+
+ plpgsql_parse_word("integer");
+
+ new->datatype = yylval.dtype;
+ new->isconst = false;
+ new->notnull = false;
+ new->default_val = NULL;
+
+ plpgsql_adddatum((PLpgSQL_datum *)new);
+ plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, new->varno,
+ $1.name);
+
+ plpgsql_add_initdatums(NULL);
+
+ $$ = new;
+ }
+ ;
+
+fori_varname : T_VARIABLE
+ {
+ $$.name = strdup(yytext);
+ $$.lineno = yylineno;
+ }
+ | T_WORD
+ {
+ $$.name = strdup(yytext);
+ $$.lineno = yylineno;
+ }
+ ;
+
+fori_lower :
+ {
+ int tok;
+ int lno;
+ PLpgSQL_dstring ds;
+ int nparams = 0;
+ int params[1024];
+ char buf[32];
+ PLpgSQL_expr *expr;
+ int firsttok = 1;
+
+ lno = yylineno;
+ plpgsql_dstring_init(&ds);
+ plpgsql_dstring_append(&ds, "SELECT ");
+
+ $$.reverse = 0;
+ while((tok = yylex()) != K_DOTDOT) {
+ if (firsttok) {
+ firsttok = 0;
+ if (tok == K_REVERSE) {
+ $$.reverse = 1;
+ continue;
+ }
+ }
+ if (tok == ';') break;
+ if (plpgsql_SpaceScanned) {
+ plpgsql_dstring_append(&ds, " ");
+ }
+ switch (tok) {
+ case T_VARIABLE:
+ params[nparams] = yylval.var->varno;
+ sprintf(buf, "$%d", ++nparams);
+ plpgsql_dstring_append(&ds, buf);
+ break;
+
+ case T_RECFIELD:
+ params[nparams] = yylval.recfield->rfno;
+ sprintf(buf, "$%d", ++nparams);
+ plpgsql_dstring_append(&ds, buf);
+ break;
+
+ case T_TGARGV:
+ params[nparams] = yylval.trigarg->dno;
+ sprintf(buf, "$%d", ++nparams);
+ plpgsql_dstring_append(&ds, buf);
+ break;
+
+ default:
+ if (tok == 0) {
+ plpgsql_error_lineno = lno;
+ plpgsql_comperrinfo();
+ elog(ERROR, "missing .. to terminate lower bound of for loop");
+ }
+ plpgsql_dstring_append(&ds, yytext);
+ break;
+ }
+ }
+
+ expr = malloc(sizeof(PLpgSQL_expr) + sizeof(int) * nparams - 1);
+ expr->dtype = PLPGSQL_DTYPE_EXPR;
+ expr->query = strdup(plpgsql_dstring_get(&ds));
+ expr->plan = NULL;
+ expr->nparams = nparams;
+ while(nparams-- > 0) {
+ expr->params[nparams] = params[nparams];
+ }
+ plpgsql_dstring_free(&ds);
+ $$.expr = expr;
+ }
+
+stmt_fors : opt_label K_FOR lno fors_target K_IN K_SELECT expr_until_loop loop_body
+ {
+ PLpgSQL_stmt_fors *new;
+
+ new = malloc(sizeof(PLpgSQL_stmt_fors));
+ memset(new, 0, sizeof(PLpgSQL_stmt_fors));
+
+ new->cmd_type = PLPGSQL_STMT_FORS;
+ new->lineno = $3;
+ new->label = $1;
+ switch ($4->dtype) {
+ case PLPGSQL_DTYPE_REC:
+ new->rec = $4;
+ break;
+ case PLPGSQL_DTYPE_ROW:
+ new->row = (PLpgSQL_row *)$4;
+ break;
+ default:
+ plpgsql_comperrinfo();
+ elog(ERROR, "unknown dtype %d in stmt_fors", $4->dtype);
+ }
+ new->query = $7;
+ new->body = $8;
+
+ plpgsql_ns_pop();
+
+ $$ = (PLpgSQL_stmt *)new;
+ }
+
+fors_target : T_RECORD
+ {
+ $$ = yylval.rec;
+ }
+ | T_ROW
+ {
+ $$ = (PLpgSQL_rec *)(yylval.row);
+ }
+ ;
+
+stmt_select : K_SELECT lno
+ {
+ $$ = make_select_stmt();
+ $$->lineno = $2;
+ }
+ ;
+
+stmt_exit : K_EXIT lno opt_exitlabel opt_exitcond
+ {
+ PLpgSQL_stmt_exit *new;
+
+ new = malloc(sizeof(PLpgSQL_stmt_exit));
+ memset(new, 0, sizeof(PLpgSQL_stmt_exit));
+
+ new->cmd_type = PLPGSQL_STMT_EXIT;
+ new->lineno = $2;
+ new->label = $3;
+ new->cond = $4;
+
+ $$ = (PLpgSQL_stmt *)new;
+ }
+ ;
+
+stmt_return : K_RETURN lno
+ {
+ PLpgSQL_stmt_return *new;
+ PLpgSQL_expr *expr = NULL;
+ int tok;
+
+ new = malloc(sizeof(PLpgSQL_stmt_return));
+ memset(new, 0, sizeof(PLpgSQL_stmt_return));
+
+ if (plpgsql_curr_compile->fn_retistuple) {
+ new->retistuple = true;
+ new->retrecno = -1;
+ switch (tok = yylex()) {
+ case K_NULL:
+ expr = NULL;
+ break;
+
+ case T_ROW:
+ expr = make_tupret_expr(yylval.row);
+ break;
+
+ case T_RECORD:
+ new->retrecno = yylval.rec->recno;
+ expr = NULL;
+ break;
+
+ default:
+ yyerror("return type mismatch in function returning table row");
+ break;
+ }
+ if (yylex() != ';') {
+ yyerror("expected ';'");
+ }
+ } else {
+ new->retistuple = false;
+ expr = plpgsql_read_expression(';', ";");
+ }
+
+ new->cmd_type = PLPGSQL_STMT_RETURN;
+ new->lineno = $2;
+ new->expr = expr;
+
+ $$ = (PLpgSQL_stmt *)new;
+ }
+ ;
+
+stmt_raise : K_RAISE lno raise_level raise_msg raise_params ';'
+ {
+ PLpgSQL_stmt_raise *new;
+
+ new = malloc(sizeof(PLpgSQL_stmt_raise));
+
+ new->cmd_type = PLPGSQL_STMT_RAISE;
+ new->lineno = $2;
+ new->elog_level = $3;
+ new->message = $4;
+ new->nparams = $5.nused;
+ new->params = malloc(sizeof(int) * $5.nused);
+ memcpy(new->params, $5.dtnums, sizeof(int) * $5.nused);
+
+ $$ = (PLpgSQL_stmt *)new;
+ }
+ | K_RAISE lno raise_level raise_msg ';'
+ {
+ PLpgSQL_stmt_raise *new;
+
+ new = malloc(sizeof(PLpgSQL_stmt_raise));
+
+ new->cmd_type = PLPGSQL_STMT_RAISE;
+ new->lineno = $2;
+ new->elog_level = $3;
+ new->message = $4;
+ new->nparams = 0;
+ new->params = NULL;
+
+ $$ = (PLpgSQL_stmt *)new;
+ }
+ ;
+
+raise_msg : T_STRING
+ {
+ $$ = strdup(yytext);
+ }
+ ;
+
+raise_level : K_EXCEPTION
+ {
+ $$ = ERROR;
+ }
+ | K_NOTICE
+ {
+ $$ = NOTICE;
+ }
+ | K_DEBUG
+ {
+ $$ = DEBUG;
+ }
+ ;
+
+raise_params : raise_params raise_param
+ {
+ if ($1.nused == $1.nalloc) {
+ $1.nalloc *= 2;
+ $1.dtnums = repalloc($1.dtnums, sizeof(int) * $1.nalloc);
+ }
+ $1.dtnums[$1.nused++] = $2;
+
+ $$.nalloc = $1.nalloc;
+ $$.nused = $1.nused;
+ $$.dtnums = $1.dtnums;
+ }
+ | raise_param
+ {
+ $$.nalloc = 1;
+ $$.nused = 1;
+ $$.dtnums = palloc(sizeof(int) * $$.nalloc);
+ $$.dtnums[0] = $1;
+ }
+ ;
+
+raise_param : ',' T_VARIABLE
+ {
+ $$ = yylval.var->varno;
+ }
+ | ',' T_RECFIELD
+ {
+ $$ = yylval.recfield->rfno;
+ }
+ | ',' T_TGARGV
+ {
+ $$ = yylval.trigarg->dno;
+ }
+ ;
+
+loop_body : proc_sect K_END K_LOOP ';'
+ { $$ = $1; }
+ ;
+
+stmt_execsql : execsql_start lno
+ {
+ PLpgSQL_stmt_execsql *new;
+
+ new = malloc(sizeof(PLpgSQL_stmt_execsql));
+ new->cmd_type = PLPGSQL_STMT_EXECSQL;
+ new->lineno = $2;
+ new->sqlstmt = read_sqlstmt(';', ";", $1);
+
+ $$ = (PLpgSQL_stmt *)new;
+ }
+ ;
+
+execsql_start : T_WORD
+ { $$ = strdup(yytext); }
+ | T_ERROR
+ { $$ = strdup(yytext); }
+ ;
+
+expr_until_semi :
+ { $$ = plpgsql_read_expression(';', ";"); }
+ ;
+
+expr_until_then :
+ { $$ = plpgsql_read_expression(K_THEN, "THEN"); }
+ ;
+
+expr_until_loop :
+ { $$ = plpgsql_read_expression(K_LOOP, "LOOP"); }
+ ;
+
+opt_label :
+ {
+ plpgsql_ns_push(NULL);
+ $$ = NULL;
+ }
+ | '<' '<' opt_lblname '>' '>'
+ {
+ plpgsql_ns_push($3);
+ $$ = $3;
+ }
+ ;
+
+opt_exitlabel :
+ { $$ = NULL; }
+ | T_LABEL
+ { $$ = strdup(yytext); }
+ ;
+
+opt_exitcond : ';'
+ { $$ = NULL; }
+ | K_WHEN expr_until_semi
+ { $$ = $2; }
+ ;
+
+opt_lblname : T_WORD
+ { $$ = strdup(yytext); }
+ ;
+
+lno :
+ {
+ plpgsql_error_lineno = yylineno;
+ $$ = yylineno;
+ }
+ ;
+
+%%
+
+PLpgSQL_expr *
+plpgsql_read_expression (int until, char *s)
+{
+ return read_sqlstmt(until, s, "SELECT ");
+}
+
+
+static PLpgSQL_expr *
+read_sqlstmt (int until, char *s, char *sqlstart)
+{
+ int tok;
+ int lno;
+ PLpgSQL_dstring ds;
+ int nparams = 0;
+ int params[1024];
+ char buf[32];
+ PLpgSQL_expr *expr;
+
+ lno = yylineno;
+ plpgsql_dstring_init(&ds);
+ plpgsql_dstring_append(&ds, sqlstart);
+
+ while((tok = yylex()) != until) {
+ if (tok == ';') break;
+ if (plpgsql_SpaceScanned) {
+ plpgsql_dstring_append(&ds, " ");
+ }
+ switch (tok) {
+ case T_VARIABLE:
+ params[nparams] = yylval.var->varno;
+ sprintf(buf, "$%d", ++nparams);
+ plpgsql_dstring_append(&ds, buf);
+ break;
+
+ case T_RECFIELD:
+ params[nparams] = yylval.recfield->rfno;
+ sprintf(buf, "$%d", ++nparams);
+ plpgsql_dstring_append(&ds, buf);
+ break;
+
+ case T_TGARGV:
+ params[nparams] = yylval.trigarg->dno;
+ sprintf(buf, "$%d", ++nparams);
+ plpgsql_dstring_append(&ds, buf);
+ break;
+
+ default:
+ if (tok == 0) {
+ plpgsql_error_lineno = lno;
+ plpgsql_comperrinfo();
+ elog(ERROR, "missing %s at end of SQL statement", s);
+ }
+ plpgsql_dstring_append(&ds, yytext);
+ break;
+ }
+ }
+
+ expr = malloc(sizeof(PLpgSQL_expr) + sizeof(int) * nparams - 1);
+ expr->dtype = PLPGSQL_DTYPE_EXPR;
+ expr->query = strdup(plpgsql_dstring_get(&ds));
+ expr->plan = NULL;
+ expr->nparams = nparams;
+ while(nparams-- > 0) {
+ expr->params[nparams] = params[nparams];
+ }
+ plpgsql_dstring_free(&ds);
+
+ return expr;
+}
+
+
+static PLpgSQL_stmt *
+make_select_stmt()
+{
+ int tok;
+ int lno;
+ PLpgSQL_dstring ds;
+ int nparams = 0;
+ int params[1024];
+ char buf[32];
+ PLpgSQL_expr *expr;
+ PLpgSQL_row *row = NULL;
+ PLpgSQL_rec *rec = NULL;
+ PLpgSQL_stmt_select *select;
+ int have_nexttok = 0;
+
+ lno = yylineno;
+ plpgsql_dstring_init(&ds);
+ plpgsql_dstring_append(&ds, "SELECT ");
+
+ while((tok = yylex()) != K_INTO) {
+ if (tok == ';') {
+ PLpgSQL_stmt_execsql *execsql;
+
+ expr = malloc(sizeof(PLpgSQL_expr) + sizeof(int) * nparams - 1);
+ expr->dtype = PLPGSQL_DTYPE_EXPR;
+ expr->query = strdup(plpgsql_dstring_get(&ds));
+ expr->plan = NULL;
+ expr->nparams = nparams;
+ while(nparams-- > 0) {
+ expr->params[nparams] = params[nparams];
+ }
+ plpgsql_dstring_free(&ds);
+
+ execsql = malloc(sizeof(PLpgSQL_stmt_execsql));
+ execsql->cmd_type = PLPGSQL_STMT_EXECSQL;
+ execsql->sqlstmt = expr;
+
+ return (PLpgSQL_stmt *)execsql;
+ }
+
+ if (plpgsql_SpaceScanned) {
+ plpgsql_dstring_append(&ds, " ");
+ }
+ switch (tok) {
+ case T_VARIABLE:
+ params[nparams] = yylval.var->varno;
+ sprintf(buf, "$%d", ++nparams);
+ plpgsql_dstring_append(&ds, buf);
+ break;
+
+ case T_RECFIELD:
+ params[nparams] = yylval.recfield->rfno;
+ sprintf(buf, "$%d", ++nparams);
+ plpgsql_dstring_append(&ds, buf);
+ break;
+
+ case T_TGARGV:
+ params[nparams] = yylval.trigarg->dno;
+ sprintf(buf, "$%d", ++nparams);
+ plpgsql_dstring_append(&ds, buf);
+ break;
+
+ default:
+ if (tok == 0) {
+ plpgsql_error_lineno = yylineno;
+ plpgsql_comperrinfo();
+ elog(ERROR, "unexpected end of file");
+ }
+ plpgsql_dstring_append(&ds, yytext);
+ break;
+ }
+ }
+
+ tok = yylex();
+ switch (tok) {
+ case T_ROW:
+ row = yylval.row;
+ break;
+
+ case T_RECORD:
+ rec = yylval.rec;
+ break;
+
+ case T_VARIABLE:
+ case T_RECFIELD:
+ {
+ PLpgSQL_var *var;
+ PLpgSQL_recfield *recfield;
+ int nfields = 1;
+ char *fieldnames[1024];
+ int varnos[1024];
+
+ switch (tok) {
+ case T_VARIABLE:
+ var = yylval.var;
+ fieldnames[0] = strdup(yytext);
+ varnos[0] = var->varno;
+ break;
+
+ case T_RECFIELD:
+ recfield = yylval.recfield;
+ fieldnames[0] = strdup(yytext);
+ varnos[0] = recfield->rfno;
+ break;
+ }
+
+ while ((tok = yylex()) == ',') {
+ tok = yylex();
+ switch(tok) {
+ case T_VARIABLE:
+ var = yylval.var;
+ fieldnames[nfields] = strdup(yytext);
+ varnos[nfields++] = var->varno;
+ break;
+
+ case T_RECFIELD:
+ recfield = yylval.recfield;
+ fieldnames[0] = strdup(yytext);
+ varnos[0] = recfield->rfno;
+ break;
+
+ default:
+ elog(ERROR, "plpgsql: %s is not a variable or record field", yytext);
+ }
+ }
+ row = malloc(sizeof(PLpgSQL_row));
+ row->dtype = PLPGSQL_DTYPE_ROW;
+ row->refname = strdup("*internal*");
+ row->lineno = yylineno;
+ row->rowtypeclass = InvalidOid;
+ row->nfields = nfields;
+ row->fieldnames = malloc(sizeof(char *) * nfields);
+ row->varnos = malloc(sizeof(int) * nfields);
+ while (--nfields >= 0) {
+ row->fieldnames[nfields] = fieldnames[nfields];
+ row->varnos[nfields] = varnos[nfields];
+ }
+
+ plpgsql_adddatum((PLpgSQL_datum *)row);
+
+ have_nexttok = 1;
+ }
+ break;
+
+ default:
+ {
+ if (plpgsql_SpaceScanned) {
+ plpgsql_dstring_append(&ds, " ");
+ }
+ plpgsql_dstring_append(&ds, yytext);
+
+ while(1) {
+ tok = yylex();
+ if (tok == ';') {
+ PLpgSQL_stmt_execsql *execsql;
+
+ expr = malloc(sizeof(PLpgSQL_expr) + sizeof(int) * nparams - 1);
+ expr->dtype = PLPGSQL_DTYPE_EXPR;
+ expr->query = strdup(plpgsql_dstring_get(&ds));
+ expr->plan = NULL;
+ expr->nparams = nparams;
+ while(nparams-- > 0) {
+ expr->params[nparams] = params[nparams];
+ }
+ plpgsql_dstring_free(&ds);
+
+ execsql = malloc(sizeof(PLpgSQL_stmt_execsql));
+ execsql->cmd_type = PLPGSQL_STMT_EXECSQL;
+ execsql->sqlstmt = expr;
+
+ return (PLpgSQL_stmt *)execsql;
+ }
+
+ if (plpgsql_SpaceScanned) {
+ plpgsql_dstring_append(&ds, " ");
+ }
+ switch (tok) {
+ case T_VARIABLE:
+ params[nparams] = yylval.var->varno;
+ sprintf(buf, "$%d", ++nparams);
+ plpgsql_dstring_append(&ds, buf);
+ break;
+
+ case T_RECFIELD:
+ params[nparams] = yylval.recfield->rfno;
+ sprintf(buf, "$%d", ++nparams);
+ plpgsql_dstring_append(&ds, buf);
+ break;
+
+ case T_TGARGV:
+ params[nparams] = yylval.trigarg->dno;
+ sprintf(buf, "$%d", ++nparams);
+ plpgsql_dstring_append(&ds, buf);
+ break;
+
+ default:
+ if (tok == 0) {
+ plpgsql_error_lineno = yylineno;
+ plpgsql_comperrinfo();
+ elog(ERROR, "unexpected end of file");
+ }
+ plpgsql_dstring_append(&ds, yytext);
+ break;
+ }
+ }
+ }
+ }
+
+ /************************************************************
+ * Eat up the rest of the statement after the target fields
+ ************************************************************/
+ while(1) {
+ if (!have_nexttok) {
+ tok = yylex();
+ }
+ have_nexttok = 0;
+ if (tok == ';') {
+ break;
+ }
+
+ if (plpgsql_SpaceScanned) {
+ plpgsql_dstring_append(&ds, " ");
+ }
+ switch (tok) {
+ case T_VARIABLE:
+ params[nparams] = yylval.var->varno;
+ sprintf(buf, "$%d", ++nparams);
+ plpgsql_dstring_append(&ds, buf);
+ break;
+
+ case T_RECFIELD:
+ params[nparams] = yylval.recfield->rfno;
+ sprintf(buf, "$%d", ++nparams);
+ plpgsql_dstring_append(&ds, buf);
+ break;
+
+ case T_TGARGV:
+ params[nparams] = yylval.trigarg->dno;
+ sprintf(buf, "$%d", ++nparams);
+ plpgsql_dstring_append(&ds, buf);
+ break;
+
+ default:
+ if (tok == 0) {
+ plpgsql_error_lineno = yylineno;
+ plpgsql_comperrinfo();
+ elog(ERROR, "unexpected end of file");
+ }
+ plpgsql_dstring_append(&ds, yytext);
+ break;
+ }
+ }
+
+ expr = malloc(sizeof(PLpgSQL_expr) + sizeof(int) * (nparams - 1));
+ expr->dtype = PLPGSQL_DTYPE_EXPR;
+ expr->query = strdup(plpgsql_dstring_get(&ds));
+ expr->plan = NULL;
+ expr->nparams = nparams;
+ while(nparams-- > 0) {
+ expr->params[nparams] = params[nparams];
+ }
+ plpgsql_dstring_free(&ds);
+
+ select = malloc(sizeof(PLpgSQL_stmt_select));
+ memset(select, 0, sizeof(PLpgSQL_stmt_select));
+ select->cmd_type = PLPGSQL_STMT_SELECT;
+ select->rec = rec;
+ select->row = row;
+ select->query = expr;
+
+ return (PLpgSQL_stmt *)select;
+}
+
+
+static PLpgSQL_expr *
+make_tupret_expr(PLpgSQL_row *row)
+{
+ PLpgSQL_dstring ds;
+ PLpgSQL_expr *expr;
+ int i;
+ char buf[16];
+
+ expr = malloc(sizeof(PLpgSQL_expr) + sizeof(int) * (row->nfields - 1));
+ expr->dtype = PLPGSQL_DTYPE_EXPR;
+
+ plpgsql_dstring_init(&ds);
+ plpgsql_dstring_append(&ds, "SELECT ");
+
+ for (i = 0; i < row->nfields; i++) {
+ sprintf(buf, "%s$%d", (i > 0) ? "," : "", i + 1);
+ plpgsql_dstring_append(&ds, buf);
+ expr->params[i] = row->varnos[i];
+ }
+
+ expr->query = strdup(plpgsql_dstring_get(&ds));
+ expr->plan = NULL;
+ expr->plan_argtypes = NULL;
+ expr->nparams = row->nfields;
+
+ plpgsql_dstring_free(&ds);
+ return expr;
+}
+
+
+
+#include "pl_scan.c"
diff --git a/contrib/plpgsql/src/mklang.sql b/contrib/plpgsql/src/mklang.sql
new file mode 100644
index 00000000000..7bfe0e04d0c
--- /dev/null
+++ b/contrib/plpgsql/src/mklang.sql
@@ -0,0 +1,14 @@
+--
+-- PL/pgSQL language declaration
+--
+-- $Header: /cvsroot/pgsql/contrib/plpgsql/src/Attic/mklang.sql,v 1.1 1998/08/22 12:38:31 momjian Exp $
+--
+
+create function plpgsql_call_handler() returns opaque
+ as '/usr/local/pgsql/lib/plpgsql.so'
+ language 'C';
+
+create trusted procedural language 'plpgsql'
+ handler plpgsql_call_handler
+ lancompiler 'PL/pgSQL';
+
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() */
+
+
diff --git a/contrib/plpgsql/src/pl_exec.c b/contrib/plpgsql/src/pl_exec.c
new file mode 100644
index 00000000000..306169fc95c
--- /dev/null
+++ b/contrib/plpgsql/src/pl_exec.c
@@ -0,0 +1,2241 @@
+/**********************************************************************
+ * pl_exec.c - Executor for the PL/pgSQL
+ * procedural language
+ *
+ * IDENTIFICATION
+ * $Header: /cvsroot/pgsql/contrib/plpgsql/src/Attic/pl_exec.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 <setjmp.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 "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+
+
+/************************************************************
+ * Make Warn_restart from tcop/postgres.c visible for us.
+ * The longjmp() mechanism of the elog(ERROR,...) makes it
+ * impossible for us to call exceptions. But at least I
+ * would like some suggestions about where in the PL function
+ * the error occured.
+ *
+ * It's ugly - Jan
+ ************************************************************/
+#if defined(nextstep)
+#define sigjmp_buf jmp_buf
+#define sigsetjmp(x,y) setjmp(x)
+#define siglongjmp longjmp
+#endif
+
+extern sigjmp_buf Warn_restart; /* in tcop/postgres.c */
+
+static PLpgSQL_function *error_info_func = NULL;
+static PLpgSQL_stmt *error_info_stmt = NULL;
+static char *error_info_text = NULL;
+
+
+/************************************************************
+ * Local function forward declarations
+ ************************************************************/
+static PLpgSQL_var *copy_var(PLpgSQL_var *var);
+static PLpgSQL_rec *copy_rec(PLpgSQL_rec *rec);
+
+static int exec_stmt_block(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_block *block);
+static int exec_stmts(PLpgSQL_execstate *estate,
+ PLpgSQL_stmts *stmts);
+static int exec_stmt(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt *stmt);
+static int exec_stmt_assign(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_assign *stmt);
+static int exec_stmt_if(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_if *stmt);
+static int exec_stmt_loop(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_loop *stmt);
+static int exec_stmt_while(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_while *stmt);
+static int exec_stmt_fori(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_fori *stmt);
+static int exec_stmt_fors(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_fors *stmt);
+static int exec_stmt_select(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_select *stmt);
+static int exec_stmt_exit(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_exit *stmt);
+static int exec_stmt_return(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_return *stmt);
+static int exec_stmt_raise(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_raise *stmt);
+static int exec_stmt_execsql(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_execsql *stmt);
+
+static void exec_assign_expr(PLpgSQL_execstate *estate,
+ PLpgSQL_datum *target,
+ PLpgSQL_expr *expr);
+static void exec_assign_value(PLpgSQL_execstate *estate,
+ PLpgSQL_datum *target,
+ Datum value, Oid valtype, bool *isNull);
+static Datum exec_eval_expr(PLpgSQL_execstate *estate,
+ PLpgSQL_expr *expr,
+ bool *isNull,
+ Oid *rettype);
+static int exec_run_select(PLpgSQL_execstate *estate,
+ PLpgSQL_expr *expr, int maxtuples);
+static void exec_move_row(PLpgSQL_execstate *estate,
+ PLpgSQL_rec *rec,
+ PLpgSQL_row *row,
+ HeapTuple tup, TupleDesc tupdesc);
+static Datum exec_cast_value(Datum value, Oid valtype,
+ Oid reqtype,
+ FmgrInfo *reqinput,
+ int16 reqtypmod,
+ bool *isnull);
+static void exec_set_found(PLpgSQL_execstate *estate, bool state);
+
+
+/* ----------
+ * plpgsql_exec_function Called by the call handler for
+ * function execution.
+ * ----------
+ */
+Datum plpgsql_exec_function(PLpgSQL_function *func,
+ FmgrValues *args, bool *isNull)
+{
+ PLpgSQL_execstate estate;
+ int i;
+ sigjmp_buf save_restart;
+ PLpgSQL_function *save_efunc;
+ PLpgSQL_stmt *save_estmt;
+ char *save_etext;
+
+ /* ----------
+ * Setup debug error info and catch elog()
+ * ----------
+ */
+ save_efunc = error_info_func;
+ save_estmt = error_info_stmt;
+ save_etext = error_info_text;
+
+ error_info_func = func;
+ error_info_stmt = NULL;
+ error_info_text = "while initialization of execution state";
+
+ memcpy(&save_restart, &Warn_restart, sizeof(save_restart));
+ if (sigsetjmp(Warn_restart, 1) != 0) {
+ memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
+
+ /* ----------
+ * If we are the first of cascaded error catchings,
+ * print where this happened
+ * ----------
+ */
+ if (error_info_func != NULL) {
+ elog(DEBUG, "Last error occured while executing PL/pgSQL function %s",
+ error_info_func->fn_name);
+ if (error_info_stmt != NULL) {
+ char *stmttype;
+ switch (error_info_stmt->cmd_type) {
+ case PLPGSQL_STMT_BLOCK:
+ stmttype = "blocks variable initialization";
+ break;
+ case PLPGSQL_STMT_ASSIGN:
+ stmttype = "assignment";
+ break;
+ case PLPGSQL_STMT_IF:
+ stmttype = "if";
+ break;
+ case PLPGSQL_STMT_LOOP:
+ stmttype = "loop";
+ break;
+ case PLPGSQL_STMT_WHILE:
+ stmttype = "while";
+ break;
+ case PLPGSQL_STMT_FORI:
+ stmttype = "for with integer loopvar";
+ break;
+ case PLPGSQL_STMT_FORS:
+ stmttype = "for over select rows";
+ break;
+ case PLPGSQL_STMT_SELECT:
+ stmttype = "select into variables";
+ break;
+ case PLPGSQL_STMT_EXIT:
+ stmttype = "exit";
+ break;
+ case PLPGSQL_STMT_RETURN:
+ stmttype = "return";
+ break;
+ case PLPGSQL_STMT_RAISE:
+ stmttype = "raise";
+ break;
+ case PLPGSQL_STMT_EXECSQL:
+ stmttype = "SQL statement";
+ break;
+ default:
+ stmttype = "unknown";
+ break;
+ }
+ elog(DEBUG, "line %d at %s", error_info_stmt->lineno,
+ stmttype);
+ } else {
+ if (error_info_text != NULL) {
+ elog(DEBUG, "%s", error_info_text);
+ } else {
+ elog(DEBUG, "no more error information available");
+ }
+ }
+
+ error_info_func = NULL;
+ error_info_stmt = NULL;
+ error_info_text = NULL;
+ }
+
+ siglongjmp(Warn_restart, 1);
+ }
+
+
+ /* ----------
+ * Setup the execution state
+ * ----------
+ */
+ estate.retval = 0;
+ estate.retisnull = false;
+ estate.rettype = InvalidOid;
+ estate.retistuple = func->fn_retistuple;
+ estate.retisset = func->fn_retset;
+ estate.exitlabel = NULL;
+
+ estate.found_varno = func->found_varno;
+ estate.ndatums = func->ndatums;
+ estate.datums = palloc(sizeof(PLpgSQL_datum *) * estate.ndatums);
+
+ /* ----------
+ * Make local execution copies of all the datums
+ * ----------
+ */
+ for (i = 0; i < func->ndatums; i++) {
+ switch(func->datums[i]->dtype) {
+ case PLPGSQL_DTYPE_VAR:
+ estate.datums[i] = (PLpgSQL_datum *)
+ copy_var((PLpgSQL_var *)(func->datums[i]));
+ break;
+
+ case PLPGSQL_DTYPE_REC:
+ estate.datums[i] = (PLpgSQL_datum *)
+ copy_rec((PLpgSQL_rec *)(func->datums[i]));
+ break;
+
+ case PLPGSQL_DTYPE_ROW:
+ case PLPGSQL_DTYPE_RECFIELD:
+ estate.datums[i] = func->datums[i];
+ break;
+
+ default:
+ elog(ERROR, "unknown dtype %d in plpgsql_exec_function()",
+ func->datums[i]->dtype);
+ }
+ }
+
+ /* ----------
+ * Put the actual call argument values into the variables
+ * ----------
+ */
+ error_info_text = "while putting call arguments to local variables";
+ for (i = 0; i < func->fn_nargs; i++) {
+ int n = func->fn_argvarnos[i];
+ switch(estate.datums[n]->dtype) {
+ case PLPGSQL_DTYPE_VAR:
+ {
+ PLpgSQL_var *var = (PLpgSQL_var *)estate.datums[n];
+ var->value = (Datum)(args->data[i]);
+ var->isnull = *isNull;
+ var->shouldfree = false;
+ }
+ break;
+
+ case PLPGSQL_DTYPE_ROW:
+ {
+ HeapTuple tup;
+ TupleDesc tupdesc;
+ PLpgSQL_row *row = (PLpgSQL_row *)estate.datums[n];
+
+ tup = ((TupleTableSlot *)(args->data[i]))->val;
+ tupdesc = ((TupleTableSlot *)(args->data[i]))->ttc_tupleDescriptor;
+
+ exec_move_row(&estate, NULL, row, tup, tupdesc);
+ }
+ break;
+
+ default:
+ elog(ERROR, "unknown dtype %d in plpgsql_exec_function()",
+ func->datums[i]->dtype);
+ }
+ }
+
+ /* ----------
+ * Initialize the other variables to NULL values for now.
+ * The default values are set when the blocks are entered.
+ * ----------
+ */
+ error_info_text = "while initializing local variables to NULL";
+ for (i = estate.found_varno; i < estate.ndatums; i++) {
+ switch(estate.datums[i]->dtype) {
+ case PLPGSQL_DTYPE_VAR:
+ {
+ PLpgSQL_var *var = (PLpgSQL_var *)estate.datums[i];
+ var->value = 0;
+ var->isnull = true;
+ var->shouldfree = false;
+ }
+ break;
+
+ case PLPGSQL_DTYPE_ROW:
+ case PLPGSQL_DTYPE_REC:
+ case PLPGSQL_DTYPE_RECFIELD:
+ break;
+
+ default:
+ elog(ERROR, "unknown dtype %d in plpgsql_exec_function()",
+ func->datums[i]->dtype);
+ }
+ }
+
+ /* ----------
+ * Set the magic variable FOUND to false
+ * ----------
+ */
+ exec_set_found(&estate, false);
+
+ /* ----------
+ * Now call the toplevel block of statements
+ * ----------
+ */
+ error_info_text = NULL;
+ error_info_stmt = (PLpgSQL_stmt *)(func->action);
+ if (exec_stmt_block(&estate, func->action) != PLPGSQL_RC_RETURN) {
+ error_info_stmt = NULL;
+ error_info_text = "at END of toplevel PL block";
+ elog(ERROR, "control reaches end of function without RETURN");
+ }
+
+ /* ----------
+ * We got a return value - process it
+ * ----------
+ */
+ error_info_stmt = NULL;
+ error_info_text = "while casting return value to functions return type";
+
+ *isNull = estate.retisnull;
+
+ if (!estate.retistuple) {
+ estate.retval = exec_cast_value(estate.retval, estate.rettype,
+ func->fn_rettype, &(func->fn_retinput), -1,
+ isNull);
+
+ /* ----------
+ * If the functions return type isn't by value,
+ * copy the value into upper executor memory context.
+ * ----------
+ */
+ if (!*isNull && !func->fn_retbyval) {
+ int len;
+ Datum tmp;
+
+ if (func->fn_rettyplen < 0) {
+ len = VARSIZE(estate.retval);
+ } else {
+ len = func->fn_rettyplen;
+ }
+
+ tmp = (Datum)SPI_palloc(len);
+ memcpy((void *)tmp, (void *)estate.retval, len);
+ estate.retval = tmp;
+ }
+ }
+
+ /* ----------
+ * Restore the previous error info and elog() jump target
+ * ----------
+ */
+ error_info_func = save_efunc;
+ error_info_stmt = save_estmt;
+ error_info_text = save_etext;
+ memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
+
+ /* ----------
+ * Return the functions result
+ * ----------
+ */
+ return estate.retval;
+}
+
+
+/* ----------
+ * plpgsql_exec_trigger Called by the call handler for
+ * trigger execution.
+ * ----------
+ */
+HeapTuple plpgsql_exec_trigger(PLpgSQL_function *func,
+ TriggerData *trigdata)
+{
+ PLpgSQL_execstate estate;
+ int i;
+ sigjmp_buf save_restart;
+ PLpgSQL_function *save_efunc;
+ PLpgSQL_stmt *save_estmt;
+ char *save_etext;
+ PLpgSQL_rec *rec_new;
+ PLpgSQL_rec *rec_old;
+ PLpgSQL_var *var;
+ HeapTuple rettup;
+
+ /* ----------
+ * Setup debug error info and catch elog()
+ * ----------
+ */
+ save_efunc = error_info_func;
+ save_estmt = error_info_stmt;
+ save_etext = error_info_text;
+
+ error_info_func = func;
+ error_info_stmt = NULL;
+ error_info_text = "while initialization of execution state";
+
+ memcpy(&save_restart, &Warn_restart, sizeof(save_restart));
+ if (sigsetjmp(Warn_restart, 1) != 0) {
+ memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
+
+ /* ----------
+ * If we are the first of cascaded error catchings,
+ * print where this happened
+ * ----------
+ */
+ if (error_info_func != NULL) {
+ elog(DEBUG, "Last error occured while executing PL/pgSQL function %s",
+ error_info_func->fn_name);
+ if (error_info_stmt != NULL) {
+ char *stmttype;
+ switch (error_info_stmt->cmd_type) {
+ case PLPGSQL_STMT_BLOCK:
+ stmttype = "blocks variable initialization";
+ break;
+ case PLPGSQL_STMT_ASSIGN:
+ stmttype = "assignment";
+ break;
+ case PLPGSQL_STMT_IF:
+ stmttype = "if";
+ break;
+ case PLPGSQL_STMT_LOOP:
+ stmttype = "loop";
+ break;
+ case PLPGSQL_STMT_WHILE:
+ stmttype = "while";
+ break;
+ case PLPGSQL_STMT_FORI:
+ stmttype = "for with integer loopvar";
+ break;
+ case PLPGSQL_STMT_FORS:
+ stmttype = "for over select rows";
+ break;
+ case PLPGSQL_STMT_SELECT:
+ stmttype = "select into variables";
+ break;
+ case PLPGSQL_STMT_EXIT:
+ stmttype = "exit";
+ break;
+ case PLPGSQL_STMT_RETURN:
+ stmttype = "return";
+ break;
+ case PLPGSQL_STMT_RAISE:
+ stmttype = "raise";
+ break;
+ case PLPGSQL_STMT_EXECSQL:
+ stmttype = "SQL statement";
+ break;
+ default:
+ stmttype = "unknown";
+ break;
+ }
+ elog(DEBUG, "line %d at %s", error_info_stmt->lineno,
+ stmttype);
+ } else {
+ if (error_info_text != NULL) {
+ elog(DEBUG, "%s", error_info_text);
+ } else {
+ elog(DEBUG, "no more error information available");
+ }
+ }
+
+ error_info_func = NULL;
+ error_info_stmt = NULL;
+ error_info_text = NULL;
+ }
+
+ siglongjmp(Warn_restart, 1);
+ }
+
+
+ /* ----------
+ * Setup the execution state
+ * ----------
+ */
+ estate.retval = 0;
+ estate.retisnull = false;
+ estate.rettype = InvalidOid;
+ estate.retistuple = func->fn_retistuple;
+ estate.retisset = func->fn_retset;
+ estate.exitlabel = NULL;
+
+ estate.found_varno = func->found_varno;
+ estate.ndatums = func->ndatums;
+ estate.datums = palloc(sizeof(PLpgSQL_datum *) * estate.ndatums);
+
+ /* ----------
+ * Make local execution copies of all the datums
+ * ----------
+ */
+ for (i = 0; i < func->ndatums; i++) {
+ switch(func->datums[i]->dtype) {
+ case PLPGSQL_DTYPE_VAR:
+ estate.datums[i] = (PLpgSQL_datum *)
+ copy_var((PLpgSQL_var *)(func->datums[i]));
+ break;
+
+ case PLPGSQL_DTYPE_REC:
+ estate.datums[i] = (PLpgSQL_datum *)
+ copy_rec((PLpgSQL_rec *)(func->datums[i]));
+ break;
+
+ case PLPGSQL_DTYPE_ROW:
+ case PLPGSQL_DTYPE_RECFIELD:
+ case PLPGSQL_DTYPE_TRIGARG:
+ estate.datums[i] = func->datums[i];
+ break;
+
+ default:
+ elog(ERROR, "unknown dtype %d in plpgsql_exec_function()",
+ func->datums[i]->dtype);
+ }
+ }
+
+ /* ----------
+ * Put the trig and new tuples into the records
+ * and set the tg_op variable
+ * ----------
+ */
+ rec_new = (PLpgSQL_rec *)(estate.datums[func->new_varno]);
+ rec_old = (PLpgSQL_rec *)(estate.datums[func->old_varno]);
+ var = (PLpgSQL_var *)(estate.datums[func->tg_op_varno]);
+ var->isnull = false;
+
+ if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) {
+ rec_new->tup = trigdata->tg_trigtuple;
+ rec_new->tupdesc = trigdata->tg_relation->rd_att;
+ rec_old->tup = NULL;
+ rec_old->tupdesc = NULL;
+ var->value = (Datum)textin("INSERT");
+ } else
+ if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) {
+ rec_new->tup = trigdata->tg_newtuple;
+ rec_new->tupdesc = trigdata->tg_relation->rd_att;
+ rec_old->tup = trigdata->tg_trigtuple;
+ rec_old->tupdesc = trigdata->tg_relation->rd_att;
+ var->value = (Datum)textin("UPDATE");
+ } else
+ if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event)) {
+ rec_new->tup = NULL;
+ rec_new->tupdesc = NULL;
+ rec_old->tup = trigdata->tg_trigtuple;
+ rec_old->tupdesc = trigdata->tg_relation->rd_att;
+ var->value = (Datum)textin("DELETE");
+ } else {
+ rec_new->tup = NULL;
+ rec_new->tupdesc = NULL;
+ var->value = (Datum)textin("UNKNOWN");
+ }
+
+ /* ----------
+ * Fill all the other special tg_ variables
+ * ----------
+ */
+ var = (PLpgSQL_var *)(estate.datums[func->tg_name_varno]);
+ var->isnull = false;
+ var->value = (Datum)namein(trigdata->tg_trigger->tgname);
+
+ var = (PLpgSQL_var *)(estate.datums[func->tg_when_varno]);
+ var->isnull = false;
+ if (TRIGGER_FIRED_BEFORE(trigdata->tg_event)) {
+ var->value = (Datum)textin("BEFORE");
+ } else
+ if (TRIGGER_FIRED_AFTER(trigdata->tg_event)) {
+ var->value = (Datum)textin("AFTER");
+ } else {
+ var->value = (Datum)textin("UNKNOWN");
+ }
+
+ var = (PLpgSQL_var *)(estate.datums[func->tg_level_varno]);
+ var->isnull = false;
+ if (TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) {
+ var->value = (Datum)textin("ROW");
+ } else
+ if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event)) {
+ var->value = (Datum)textin("STATEMENT");
+ } else {
+ var->value = (Datum)textin("UNKNOWN");
+ }
+
+ var = (PLpgSQL_var *)(estate.datums[func->tg_relid_varno]);
+ var->isnull = false;
+ var->value = (Datum)(trigdata->tg_relation->rd_id);
+
+ var = (PLpgSQL_var *)(estate.datums[func->tg_relname_varno]);
+ var->isnull = false;
+ var->value = (Datum)namein(nameout(&(trigdata->tg_relation->rd_rel->relname)));
+
+ var = (PLpgSQL_var *)(estate.datums[func->tg_nargs_varno]);
+ var->isnull = false;
+ var->value = (Datum)(trigdata->tg_trigger->tgnargs);
+
+ /* ----------
+ * Put the actual call argument values into the special
+ * execution state variables
+ * ----------
+ */
+ error_info_text = "while putting call arguments to local variables";
+ estate.trig_nargs = trigdata->tg_trigger->tgnargs;
+ if (estate.trig_nargs == 0) {
+ estate.trig_argv = NULL;
+ } else {
+ estate.trig_argv = palloc(sizeof(Datum) * estate.trig_nargs);
+ for (i = 0; i < trigdata->tg_trigger->tgnargs; i++) {
+ estate.trig_argv[i] = (Datum)textin(trigdata->tg_trigger->tgargs[i]);
+ }
+ }
+
+ /* ----------
+ * Initialize the other variables to NULL values for now.
+ * The default values are set when the blocks are entered.
+ * ----------
+ */
+ error_info_text = "while initializing local variables to NULL";
+ for (i = estate.found_varno; i < estate.ndatums; i++) {
+ switch(estate.datums[i]->dtype) {
+ case PLPGSQL_DTYPE_VAR:
+ {
+ PLpgSQL_var *var = (PLpgSQL_var *)estate.datums[i];
+ var->value = 0;
+ var->isnull = true;
+ var->shouldfree = false;
+ }
+ break;
+
+ case PLPGSQL_DTYPE_ROW:
+ case PLPGSQL_DTYPE_REC:
+ case PLPGSQL_DTYPE_RECFIELD:
+ case PLPGSQL_DTYPE_TRIGARG:
+ break;
+
+ default:
+ elog(ERROR, "unknown dtype %d in plpgsql_exec_trigger()",
+ func->datums[i]->dtype);
+ }
+ }
+
+ /* ----------
+ * Set the magic variable FOUND to false
+ * ----------
+ */
+ exec_set_found(&estate, false);
+
+ /* ----------
+ * Now call the toplevel block of statements
+ * ----------
+ */
+ error_info_text = NULL;
+ error_info_stmt = (PLpgSQL_stmt *)(func->action);
+ if (exec_stmt_block(&estate, func->action) != PLPGSQL_RC_RETURN) {
+ error_info_stmt = NULL;
+ error_info_text = "at END of toplevel PL block";
+ elog(ERROR, "control reaches end of trigger procedure without RETURN");
+ }
+
+ /* ----------
+ * Check that the returned tuple structure has the same attributes,
+ * the relation that fired the trigger has.
+ *
+ * XXX This way it is possible, that the trigger returns a tuple
+ * where attributes don't have the correct atttypmod's length.
+ * It's up to the trigger's programmer to ensure that this
+ * doesn't happen. Jan
+ * ----------
+ */
+ if (estate.retisnull) {
+ rettup = NULL;
+ } else {
+ TupleDesc td1 = trigdata->tg_relation->rd_att;
+ TupleDesc td2 = estate.rettupdesc;
+ int i;
+
+ if (td1->natts != td2->natts) {
+ elog(ERROR, "returned tuple structure doesn't match table of trigger event");
+ }
+ for (i = 1; i <= td1->natts; i++) {
+ if (SPI_gettypeid(td1, i) != SPI_gettypeid(td2, i)) {
+ elog(ERROR, "returned tuple structure doesn't match table of trigger event");
+ }
+ }
+
+ rettup = SPI_copytuple((HeapTuple)(estate.retval));
+ }
+
+ /* ----------
+ * Restore the previous error info and elog() jump target
+ * ----------
+ */
+ error_info_func = save_efunc;
+ error_info_stmt = save_estmt;
+ error_info_text = save_etext;
+ memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart));
+
+ /* ----------
+ * Return the triggers result
+ * ----------
+ */
+ return rettup;
+}
+
+
+/* ----------
+ * Support functions for copying local execution variables
+ * ----------
+ */
+static PLpgSQL_var *copy_var(PLpgSQL_var *var)
+{
+ PLpgSQL_var *new = palloc(sizeof(PLpgSQL_var));
+ memcpy(new, var, sizeof(PLpgSQL_var));
+
+ return new;
+}
+
+
+static PLpgSQL_rec *copy_rec(PLpgSQL_rec *rec)
+{
+ PLpgSQL_rec *new = palloc(sizeof(PLpgSQL_rec));
+ memcpy(new, rec, sizeof(PLpgSQL_rec));
+
+ return new;
+}
+
+
+/* ----------
+ * exec_stmt_block Execute a block of statements
+ * ----------
+ */
+static int exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
+{
+ int rc;
+ int i;
+ int n;
+
+ /* ----------
+ * First initialize all variables declared in this block
+ * ----------
+ */
+ for (i = 0; i < block->n_initvars; i++) {
+ n = block->initvarnos[i];
+
+ switch (estate->datums[n]->dtype) {
+ case PLPGSQL_DTYPE_VAR:
+ {
+ PLpgSQL_var *var = (PLpgSQL_var *)(estate->datums[n]);
+
+ if (!var->isconst || var->isnull) {
+ if (var->default_val == NULL) {
+ var->value = (Datum)0;
+ var->isnull = true;
+ if (var->notnull) {
+ elog(ERROR, "variable '%s' declared NOT NULL cannot default to NULL", var->refname);
+ }
+ } else {
+ exec_assign_expr(estate, (PLpgSQL_datum *)var,
+ var->default_val);
+ }
+ }
+ }
+ break;
+
+ case PLPGSQL_DTYPE_REC:
+ {
+ PLpgSQL_rec *rec = (PLpgSQL_rec *)(estate->datums[n]);
+
+ rec->tup = NULL;
+ rec->tupdesc = NULL;
+ }
+ break;
+
+ case PLPGSQL_DTYPE_RECFIELD:
+ break;
+
+ default:
+ elog(ERROR, "unknown dtype %d in exec_stmt_block()", estate->datums[n]->dtype);
+ }
+
+ }
+
+ /* ----------
+ * Execute the statements in the block's body
+ * ----------
+ */
+ rc = exec_stmts(estate, block->body);
+
+ /* ----------
+ * Handle the return code.
+ * ----------
+ */
+ switch(rc) {
+ case PLPGSQL_RC_OK:
+ return PLPGSQL_RC_OK;
+
+ case PLPGSQL_RC_EXIT:
+ if (estate->exitlabel == NULL) {
+ return PLPGSQL_RC_OK;
+ }
+ if (block->label == NULL) {
+ return PLPGSQL_RC_EXIT;
+ }
+ if (strcmp(block->label, estate->exitlabel)) {
+ return PLPGSQL_RC_EXIT;
+ }
+ estate->exitlabel = NULL;
+ return PLPGSQL_RC_OK;
+
+ case PLPGSQL_RC_RETURN:
+ return PLPGSQL_RC_RETURN;
+
+ default:
+ elog(ERROR, "unknown rc %d from exec_stmt()", rc);
+ }
+
+ return PLPGSQL_RC_OK;
+}
+
+
+/* ----------
+ * exec_stmts Iterate over a list of statements
+ * as long as their return code is OK
+ * ----------
+ */
+static int exec_stmts(PLpgSQL_execstate *estate, PLpgSQL_stmts *stmts)
+{
+ int rc;
+ int i;
+
+ for (i = 0; i < stmts->stmts_used; i++) {
+ rc = exec_stmt(estate, (PLpgSQL_stmt *)(stmts->stmts[i]));
+ if (rc != PLPGSQL_RC_OK) {
+ return rc;
+ }
+ }
+
+ return PLPGSQL_RC_OK;
+}
+
+
+/* ----------
+ * exec_stmt Distribute one statement to the statements
+ * type specific execution function.
+ * ----------
+ */
+static int exec_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
+{
+ PLpgSQL_stmt *save_estmt;
+ int rc = -1;
+
+ save_estmt = error_info_stmt;
+ error_info_stmt = stmt;
+
+ switch (stmt->cmd_type) {
+ case PLPGSQL_STMT_BLOCK:
+ rc = exec_stmt_block(estate, (PLpgSQL_stmt_block *)stmt);
+ break;
+
+ case PLPGSQL_STMT_ASSIGN:
+ rc = exec_stmt_assign(estate, (PLpgSQL_stmt_assign *)stmt);
+ break;
+
+ case PLPGSQL_STMT_IF:
+ rc = exec_stmt_if(estate, (PLpgSQL_stmt_if *)stmt);
+ break;
+
+ case PLPGSQL_STMT_LOOP:
+ rc = exec_stmt_loop(estate, (PLpgSQL_stmt_loop *)stmt);
+ break;
+
+ case PLPGSQL_STMT_WHILE:
+ rc = exec_stmt_while(estate, (PLpgSQL_stmt_while *)stmt);
+ break;
+
+ case PLPGSQL_STMT_FORI:
+ rc = exec_stmt_fori(estate, (PLpgSQL_stmt_fori *)stmt);
+ break;
+
+ case PLPGSQL_STMT_FORS:
+ rc = exec_stmt_fors(estate, (PLpgSQL_stmt_fors *)stmt);
+ break;
+
+ case PLPGSQL_STMT_SELECT:
+ rc = exec_stmt_select(estate, (PLpgSQL_stmt_select *)stmt);
+ break;
+
+ case PLPGSQL_STMT_EXIT:
+ rc = exec_stmt_exit(estate, (PLpgSQL_stmt_exit *)stmt);
+ break;
+
+ case PLPGSQL_STMT_RETURN:
+ rc = exec_stmt_return(estate, (PLpgSQL_stmt_return *)stmt);
+ break;
+
+ case PLPGSQL_STMT_RAISE:
+ rc = exec_stmt_raise(estate, (PLpgSQL_stmt_raise *)stmt);
+ break;
+
+ case PLPGSQL_STMT_EXECSQL:
+ rc = exec_stmt_execsql(estate, (PLpgSQL_stmt_execsql *)stmt);
+ break;
+
+ default:
+ error_info_stmt = save_estmt;
+ elog(ERROR, "unknown cmdtype %d in exec_stmt",
+ stmt->cmd_type);
+ }
+
+ error_info_stmt = save_estmt;
+
+ return rc;
+}
+
+
+/* ----------
+ * exec_stmt_assign Evaluate an expression and
+ * put the result into a variable.
+ * ----------
+ */
+static int exec_stmt_assign(PLpgSQL_execstate *estate, PLpgSQL_stmt_assign *stmt)
+{
+ if (stmt->varno < 0) {
+ exec_assign_expr(estate, NULL, stmt->expr);
+ } else {
+ exec_assign_expr(estate, estate->datums[stmt->varno], stmt->expr);
+ }
+
+ return PLPGSQL_RC_OK;
+}
+
+
+/* ----------
+ * exec_stmt_if Evaluate a bool expression and
+ * execute the true or false body
+ * conditionally.
+ * ----------
+ */
+static int exec_stmt_if(PLpgSQL_execstate *estate, PLpgSQL_stmt_if *stmt)
+{
+ Datum value;
+ Oid valtype;
+ bool isnull = false;
+
+ value = exec_eval_expr(estate, stmt->cond, &isnull, &valtype);
+
+ if (value) {
+ if (stmt->true_body != NULL) {
+ return exec_stmts(estate, stmt->true_body);
+ }
+ } else {
+ if (stmt->false_body != NULL) {
+ return exec_stmts(estate, stmt->false_body);
+ }
+ }
+
+ return PLPGSQL_RC_OK;
+}
+
+
+/* ----------
+ * exec_stmt_loop Loop over statements until
+ * an exit occurs.
+ * ----------
+ */
+static int exec_stmt_loop(PLpgSQL_execstate *estate, PLpgSQL_stmt_loop *stmt)
+{
+ int rc;
+
+ for (;;) {
+ rc = exec_stmts(estate, stmt->body);
+
+ switch (rc) {
+ case PLPGSQL_RC_OK:
+ break;
+
+ case PLPGSQL_RC_EXIT:
+ if (estate->exitlabel == NULL) {
+ return PLPGSQL_RC_OK;
+ }
+ if (stmt->label == NULL) {
+ return PLPGSQL_RC_EXIT;
+ }
+ if (strcmp(stmt->label, estate->exitlabel)) {
+ return PLPGSQL_RC_EXIT;
+ }
+ estate->exitlabel = NULL;
+ return PLPGSQL_RC_OK;
+
+ case PLPGSQL_RC_RETURN:
+ return PLPGSQL_RC_RETURN;
+
+ default:
+ elog(ERROR, "unknown rc %d from exec_stmts()", rc);
+ }
+ }
+
+ return PLPGSQL_RC_OK;
+}
+
+
+/* ----------
+ * exec_stmt_while Loop over statements as long
+ * as an expression evaluates to
+ * true or an exit occurs.
+ * ----------
+ */
+static int exec_stmt_while(PLpgSQL_execstate *estate, PLpgSQL_stmt_while *stmt)
+{
+ Datum value;
+ Oid valtype;
+ bool isnull = false;
+ int rc;
+
+ for (;;) {
+ value = exec_eval_expr(estate, stmt->cond, &isnull, &valtype);
+ if (!value) {
+ break;
+ }
+
+ rc = exec_stmts(estate, stmt->body);
+
+ switch (rc) {
+ case PLPGSQL_RC_OK:
+ break;
+
+ case PLPGSQL_RC_EXIT:
+ if (estate->exitlabel == NULL) {
+ return PLPGSQL_RC_OK;
+ }
+ if (stmt->label == NULL) {
+ return PLPGSQL_RC_EXIT;
+ }
+ if (strcmp(stmt->label, estate->exitlabel)) {
+ return PLPGSQL_RC_EXIT;
+ }
+ estate->exitlabel = NULL;
+ return PLPGSQL_RC_OK;
+
+ case PLPGSQL_RC_RETURN:
+ return PLPGSQL_RC_RETURN;
+
+ default:
+ elog(ERROR, "unknown rc %d from exec_stmts()", rc);
+ }
+ }
+
+ return PLPGSQL_RC_OK;
+}
+
+
+/* ----------
+ * exec_stmt_fori Iterate an integer variable
+ * from a lower to an upper value.
+ * Loop can be left with exit.
+ * ----------
+ */
+static int exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt)
+{
+ PLpgSQL_var *var;
+ Datum value;
+ Oid valtype;
+ bool isnull = false;
+ int rc;
+
+ /* ----------
+ * Get the value of the lower bound into the loop var
+ * ----------
+ */
+ value = exec_eval_expr(estate, stmt->lower, &isnull, &valtype);
+ var = (PLpgSQL_var *)(estate->datums[stmt->var->varno]);
+
+ value = exec_cast_value(value, valtype, var->datatype->typoid,
+ &(var->datatype->typinput),
+ var->datatype->atttypmod, &isnull);
+ if (isnull) {
+ elog(ERROR, "lower bound of FOR loop cannot be NULL");
+ }
+ var->value = value;
+ var->isnull = false;
+
+ /* ----------
+ * Get the value of the upper bound
+ * ----------
+ */
+ value = exec_eval_expr(estate, stmt->upper, &isnull, &valtype);
+ value = exec_cast_value(value, valtype, var->datatype->typoid,
+ &(var->datatype->typinput),
+ var->datatype->atttypmod, &isnull);
+ if (isnull) {
+ elog(ERROR, "upper bound of FOR loop cannot be NULL");
+ }
+
+ /* ----------
+ * Now do the loop
+ * ----------
+ */
+ exec_set_found(estate, false);
+ for (;;) {
+ /* ----------
+ * Check bounds
+ * ----------
+ */
+ if (stmt->reverse) {
+ if ((int4)(var->value) < (int4)value) {
+ break;
+ }
+ } else {
+ if ((int4)(var->value) > (int4)value) {
+ break;
+ }
+ }
+ exec_set_found(estate, true);
+
+ /* ----------
+ * Execute the statements
+ * ----------
+ */
+ rc = exec_stmts(estate, stmt->body);
+
+ /* ----------
+ * Check returncode
+ * ----------
+ */
+ switch (rc) {
+ case PLPGSQL_RC_OK:
+ break;
+
+ case PLPGSQL_RC_EXIT:
+ if (estate->exitlabel == NULL) {
+ return PLPGSQL_RC_OK;
+ }
+ if (stmt->label == NULL) {
+ return PLPGSQL_RC_EXIT;
+ }
+ if (strcmp(stmt->label, estate->exitlabel)) {
+ return PLPGSQL_RC_EXIT;
+ }
+ estate->exitlabel = NULL;
+ return PLPGSQL_RC_OK;
+
+ case PLPGSQL_RC_RETURN:
+ return PLPGSQL_RC_RETURN;
+
+ default:
+ elog(ERROR, "unknown rc %d from exec_stmts()", rc);
+ }
+
+ /* ----------
+ * Increase/decrease loop var
+ * ----------
+ */
+ if (stmt->reverse) {
+ ((int4)(var->value))--;
+ } else {
+ ((int4)(var->value))++;
+ }
+ }
+
+ return PLPGSQL_RC_OK;
+}
+
+
+/* ----------
+ * exec_stmt_fors Execute a query, assign each
+ * tuple to a record or row and
+ * execute a group of statements
+ * for it.
+ * ----------
+ */
+static int exec_stmt_fors(PLpgSQL_execstate *estate, PLpgSQL_stmt_fors *stmt)
+{
+ PLpgSQL_rec *rec = NULL;
+ PLpgSQL_row *row = NULL;
+ SPITupleTable *tuptab;
+ int rc;
+ int i;
+ int n;
+
+ /* ----------
+ * Initialize the global found variable to false
+ * ----------
+ */
+ exec_set_found(estate, false);
+
+ /* ----------
+ * Determine if we assign to a record or a row
+ * ----------
+ */
+ if (stmt->rec != NULL) {
+ rec = (PLpgSQL_rec *)(estate->datums[stmt->rec->recno]);
+ } else {
+ if (stmt->row != NULL) {
+ row = (PLpgSQL_row *)(estate->datums[stmt->row->rowno]);
+ } else {
+ elog(ERROR, "unsupported target in exec_stmt_fors()");
+ }
+ }
+
+ /* ----------
+ * Run the query
+ * ----------
+ */
+ exec_run_select(estate, stmt->query, 0);
+ n = SPI_processed;
+
+ /* ----------
+ * If the query didn't return any row, set the target
+ * to NULL and return.
+ * ----------
+ */
+ if (n == 0) {
+ exec_move_row(estate, rec, row, NULL, NULL);
+ return PLPGSQL_RC_OK;
+ }
+
+ /* ----------
+ * There are tuples, so set found to true
+ * ----------
+ */
+ exec_set_found(estate, true);
+
+ /* ----------
+ * Now do the loop
+ * ----------
+ */
+ tuptab = SPI_tuptable;
+ SPI_tuptable = NULL;
+
+ for (i = 0; i < n; i++) {
+ /* ----------
+ * Assign the tuple to the target
+ * ----------
+ */
+ exec_move_row(estate, rec, row, tuptab->vals[i], tuptab->tupdesc);
+
+ /* ----------
+ * Execute the statements
+ * ----------
+ */
+ rc = exec_stmts(estate, stmt->body);
+
+ /* ----------
+ * Check returncode
+ * ----------
+ */
+ switch (rc) {
+ case PLPGSQL_RC_OK:
+ break;
+
+ case PLPGSQL_RC_EXIT:
+ if (estate->exitlabel == NULL) {
+ return PLPGSQL_RC_OK;
+ }
+ if (stmt->label == NULL) {
+ return PLPGSQL_RC_EXIT;
+ }
+ if (strcmp(stmt->label, estate->exitlabel)) {
+ return PLPGSQL_RC_EXIT;
+ }
+ estate->exitlabel = NULL;
+ return PLPGSQL_RC_OK;
+
+ case PLPGSQL_RC_RETURN:
+ return PLPGSQL_RC_RETURN;
+
+ default:
+ elog(ERROR, "unknown rc %d from exec_stmts()", rc);
+ }
+ }
+
+ return PLPGSQL_RC_OK;
+}
+
+
+/* ----------
+ * exec_stmt_select Run a query and assign the first
+ * row to a record or rowtype.
+ * ----------
+ */
+static int exec_stmt_select(PLpgSQL_execstate *estate, PLpgSQL_stmt_select *stmt)
+{
+ PLpgSQL_rec *rec = NULL;
+ PLpgSQL_row *row = NULL;
+ SPITupleTable *tuptab;
+ int n;
+
+ /* ----------
+ * Initialize the global found variable to false
+ * ----------
+ */
+ exec_set_found(estate, false);
+
+ /* ----------
+ * Determine if we assign to a record or a row
+ * ----------
+ */
+ if (stmt->rec != NULL) {
+ rec = (PLpgSQL_rec *)(estate->datums[stmt->rec->recno]);
+ } else {
+ if (stmt->row != NULL) {
+ row = (PLpgSQL_row *)(estate->datums[stmt->row->rowno]);
+ } else {
+ elog(ERROR, "unsupported target in exec_stmt_select()");
+ }
+ }
+
+ /* ----------
+ * Run the query
+ * ----------
+ */
+ exec_run_select(estate, stmt->query, 1);
+ n = SPI_processed;
+
+ /* ----------
+ * If the query didn't return any row, set the target
+ * to NULL and return.
+ * ----------
+ */
+ if (n == 0) {
+ exec_move_row(estate, rec, row, NULL, NULL);
+ return PLPGSQL_RC_OK;
+ }
+
+ /* ----------
+ * Put the result into the target and set found to true
+ * ----------
+ */
+ tuptab = SPI_tuptable;
+ SPI_tuptable = NULL;
+
+ exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc);
+
+ exec_set_found(estate, true);
+
+ return PLPGSQL_RC_OK;
+}
+
+
+/* ----------
+ * exec_stmt_exit Start exiting loop(s) or blocks
+ * ----------
+ */
+static int exec_stmt_exit(PLpgSQL_execstate *estate, PLpgSQL_stmt_exit *stmt)
+{
+ Datum value;
+ Oid valtype;
+ bool isnull = false;
+
+ /* ----------
+ * If the exit has a condition, check that it's true
+ * ----------
+ */
+ if (stmt->cond != NULL) {
+ value = exec_eval_expr(estate, stmt->cond, &isnull, &valtype);
+ if (!value) {
+ return PLPGSQL_RC_OK;
+ }
+ }
+
+ estate->exitlabel = stmt->label;
+ return PLPGSQL_RC_EXIT;
+}
+
+
+/* ----------
+ * exec_stmt_return Evaluate an expression and start
+ * returning from the function.
+ * ----------
+ */
+static int exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt)
+{
+ if (estate->retistuple) {
+ if (stmt->retrecno >= 0) {
+ PLpgSQL_rec *rec = (PLpgSQL_rec *)(estate->datums[stmt->retrecno]);
+
+ estate->retval = (Datum)(rec->tup);
+ estate->rettupdesc = rec->tupdesc;
+ estate->retisnull = !HeapTupleIsValid(rec->tup);
+
+ return PLPGSQL_RC_RETURN;
+ }
+
+ if (stmt->expr == NULL) {
+ estate->retval = (Datum)0;
+ estate->rettupdesc = NULL;
+ estate->retisnull = true;
+ } else {
+ exec_run_select(estate, stmt->expr, 1);
+ estate->retval = (Datum) SPI_copytuple(SPI_tuptable->vals[0]);
+ estate->rettupdesc = SPI_tuptable->tupdesc;
+ estate->retisnull = false;
+ }
+ return PLPGSQL_RC_RETURN;
+ }
+
+ estate->retval = exec_eval_expr(estate, stmt->expr,
+ &(estate->retisnull),
+ &(estate->rettype));
+
+ return PLPGSQL_RC_RETURN;
+}
+
+
+/* ----------
+ * exec_stmt_raise Build a message and throw it with
+ * elog()
+ * ----------
+ */
+static int exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
+{
+ HeapTuple typetup;
+ TypeTupleForm typeStruct;
+ FmgrInfo finfo_output;
+ char *extval;
+ int pidx = 0;
+ char c[2] = {0, 0};
+ char *cp;
+ PLpgSQL_dstring ds;
+ PLpgSQL_var *var;
+ PLpgSQL_rec *rec;
+ PLpgSQL_recfield *recfield;
+ int fno;
+
+ plpgsql_dstring_init(&ds);
+
+ for (cp = stmt->message; *cp; cp++) {
+ /* ----------
+ * Occurences of a single % are replaced by the next
+ * arguments external representation. Double %'s are
+ * left as is so elog() will also don't touch them.
+ * ----------
+ */
+ if ((c[0] = *cp) == '%') {
+ cp++;
+ if (*cp == '%') {
+ plpgsql_dstring_append(&ds, c);
+ plpgsql_dstring_append(&ds, c);
+ continue;
+ }
+ cp--;
+ if (pidx >= stmt->nparams) {
+ plpgsql_dstring_append(&ds, c);
+ plpgsql_dstring_append(&ds, c);
+ continue;
+ }
+ switch(estate->datums[stmt->params[pidx]]->dtype) {
+ case PLPGSQL_DTYPE_VAR:
+ var = (PLpgSQL_var *)
+ (estate->datums[stmt->params[pidx]]);
+ if (var->isnull) {
+ extval = "<NULL>";
+ } else {
+ typetup = SearchSysCacheTuple(TYPOID,
+ ObjectIdGetDatum(var->datatype->typoid), 0, 0, 0);
+ if (!HeapTupleIsValid(typetup)) {
+ elog(ERROR, "cache lookup for type %d failed (1)", var->datatype->typoid);
+ }
+ typeStruct = (TypeTupleForm) GETSTRUCT(typetup);
+
+ fmgr_info(typeStruct->typoutput, &finfo_output);
+ extval = (char *)(*fmgr_faddr(&finfo_output))(var->value, &(var->isnull), var->datatype->atttypmod);
+ }
+ plpgsql_dstring_append(&ds, extval);
+ break;
+
+ case PLPGSQL_DTYPE_RECFIELD:
+ recfield = (PLpgSQL_recfield *)
+ (estate->datums[stmt->params[pidx]]);
+ rec = (PLpgSQL_rec *)
+ (estate->datums[recfield->recno]);
+ if (!HeapTupleIsValid(rec->tup)) {
+ extval = "<NULL>";
+ } else {
+ fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
+ if (fno == SPI_ERROR_NOATTRIBUTE) {
+ elog(ERROR, "record %s has no field %s", rec->refname, recfield->fieldname);
+ }
+ extval = SPI_getvalue(rec->tup, rec->tupdesc, fno);
+ }
+ plpgsql_dstring_append(&ds, extval);
+ break;
+
+ case PLPGSQL_DTYPE_TRIGARG:
+ {
+ PLpgSQL_trigarg *trigarg;
+ int value;
+ Oid valtype;
+ bool valisnull = false;
+
+ trigarg = (PLpgSQL_trigarg *)
+ (estate->datums[stmt->params[pidx]]);
+ value = (int)exec_eval_expr(estate, trigarg->argnum,
+ &valisnull, &valtype);
+ if (valisnull) {
+ extval = "<INDEX_IS_NULL>";
+ } else {
+ if (value < 0 || value >= estate->trig_nargs) {
+ extval = "<OUT_OF_RANGE>";
+ } else {
+ extval = textout((text *)(estate->trig_argv[value]));
+ }
+ }
+ plpgsql_dstring_append(&ds, extval);
+ }
+ break;
+
+ default:
+ c[0] = '?';
+ plpgsql_dstring_append(&ds, c);
+ break;
+ }
+ pidx++;
+ continue;
+ }
+
+ /* ----------
+ * Occurences of single ' are removed. double ' are reduced
+ * to single ones.
+ * ----------
+ */
+ if (*cp == '\'') {
+ cp++;
+ if (*cp == '\'') {
+ plpgsql_dstring_append(&ds, c);
+ } else {
+ cp--;
+ }
+ continue;
+ }
+ plpgsql_dstring_append(&ds, c);
+ }
+
+ /* ----------
+ * Now suppress debug info and throw the elog()
+ * ----------
+ */
+ if (stmt->elog_level == ERROR) {
+ error_info_func = NULL;
+ error_info_stmt = NULL;
+ error_info_text = NULL;
+ }
+ elog(stmt->elog_level, "%s", plpgsql_dstring_get(&ds));
+ plpgsql_dstring_free(&ds);
+
+ return PLPGSQL_RC_OK;
+}
+
+
+/* ----------
+ * exec_stmt_execsql Execute an SQL statement not
+ * returning any data.
+ * ----------
+ */
+static int exec_stmt_execsql(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_execsql *stmt)
+{
+ PLpgSQL_var *var;
+ PLpgSQL_rec *rec;
+ PLpgSQL_recfield *recfield;
+ PLpgSQL_trigarg *trigarg;
+ int tgargno;
+ Oid tgargoid;
+ int fno;
+ int i;
+ Datum *values;
+ char *nulls;
+ int rc;
+ PLpgSQL_expr *expr = stmt->sqlstmt;
+ bool isnull;
+
+ /* ----------
+ * On the first call for this expression generate the plan
+ * ----------
+ */
+ if (expr->plan == NULL) {
+ void *plan;
+ Oid *argtypes;
+
+ argtypes = malloc(sizeof(Oid *) * (expr->nparams + 1));
+
+ for (i = 0; i < expr->nparams; i++) {
+ switch (estate->datums[expr->params[i]]->dtype) {
+ case PLPGSQL_DTYPE_VAR:
+ var = (PLpgSQL_var *)(estate->datums[expr->params[i]]);
+ argtypes[i] = var->datatype->typoid;
+ break;
+
+ case PLPGSQL_DTYPE_RECFIELD:
+ recfield = (PLpgSQL_recfield *)(estate->datums[expr->params[i]]);
+ rec = (PLpgSQL_rec *)(estate->datums[recfield->recno]);
+
+ if (!HeapTupleIsValid(rec->tup)) {
+ elog(ERROR, "record %s is unassigned yet", rec->refname);
+ }
+ fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
+ if (fno == SPI_ERROR_NOATTRIBUTE) {
+ elog(ERROR, "record %s has no field %s", rec->refname, recfield->fieldname);
+ }
+ argtypes[i] = SPI_gettypeid(rec->tupdesc, fno);
+ break;
+
+ case PLPGSQL_DTYPE_TRIGARG:
+ argtypes[i] = (Oid)TEXTOID;
+ break;
+
+ default:
+ elog(ERROR, "unknown parameter dtype %d in exec_stmt_execsql()", estate->datums[expr->params[i]]->dtype);
+ }
+ }
+
+ plan = SPI_prepare(expr->query, expr->nparams, argtypes);
+ if (plan == NULL) {
+ elog(ERROR, "SPI_prepare() failed on \"%s\"", expr->query);
+ }
+ expr->plan = SPI_saveplan(plan);
+ expr->plan_argtypes = argtypes;
+ }
+
+ /* ----------
+ * Now build up the values and nulls arguments for SPI_execp()
+ * ----------
+ */
+ values = palloc(sizeof(Datum) * (expr->nparams + 1));
+ nulls = palloc(expr->nparams + 1);
+
+ for (i = 0; i < expr->nparams; i++) {
+ switch (estate->datums[expr->params[i]]->dtype) {
+ case PLPGSQL_DTYPE_VAR:
+ var = (PLpgSQL_var *)(estate->datums[expr->params[i]]);
+ values[i] = var->value;
+ if (var->isnull) {
+ nulls[i] = 'n';
+ } else {
+ nulls[i] = ' ';
+ }
+ break;
+
+ case PLPGSQL_DTYPE_RECFIELD:
+ recfield = (PLpgSQL_recfield *)(estate->datums[expr->params[i]]);
+ rec = (PLpgSQL_rec *)(estate->datums[recfield->recno]);
+
+ if (!HeapTupleIsValid(rec->tup)) {
+ elog(ERROR, "record %s is unassigned yet", rec->refname);
+ }
+ fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
+ if (fno == SPI_ERROR_NOATTRIBUTE) {
+ elog(ERROR, "record %s has no field %s", rec->refname, recfield->fieldname);
+ }
+
+ if (expr->plan_argtypes[i] != SPI_gettypeid(rec->tupdesc, fno)) {
+ elog(ERROR, "type of %s.%s doesn't match that when preparing the plan", rec->refname, recfield->fieldname);
+ }
+
+ values[i] = SPI_getbinval(rec->tup, rec->tupdesc, fno, &isnull);
+ if (isnull) {
+ nulls[i] = 'n';
+ } else {
+ nulls[i] = ' ';
+ }
+ break;
+
+ case PLPGSQL_DTYPE_TRIGARG:
+ trigarg = (PLpgSQL_trigarg *)(estate->datums[expr->params[i]]);
+ tgargno = (int)exec_eval_expr(estate, trigarg->argnum,
+ &isnull, &tgargoid);
+ if (isnull || tgargno < 0 || tgargno >= estate->trig_nargs) {
+ values[i] = 0;
+ nulls[i] = 'n';
+ } else {
+ values[i] = estate->trig_argv[tgargno];
+ nulls[i] = ' ';
+ }
+ break;
+
+ default:
+ elog(ERROR, "unknown parameter dtype %d in exec_stmt_execsql()", estate->datums[expr->params[i]]->dtype);
+ }
+ }
+ nulls[i] = '\0';
+
+ /* ----------
+ * Execute the plan
+ * ----------
+ */
+ rc = SPI_execp(expr->plan, values, nulls, 0);
+ switch(rc) {
+ case SPI_OK_UTILITY:
+ case SPI_OK_SELINTO:
+ case SPI_OK_INSERT:
+ case SPI_OK_DELETE:
+ case SPI_OK_UPDATE:
+ break;
+
+ case SPI_OK_SELECT:
+ elog(ERROR, "unexpected SELECT query in exec_stmt_execsql()");
+
+ default:
+ elog(ERROR, "error executing query \"%s\"",
+ expr->query);
+ }
+ pfree(values);
+ pfree(nulls);
+
+ return PLPGSQL_RC_OK;
+}
+
+
+/* ----------
+ * exec_assign_expr Put an expressions result into
+ * a variable.
+ * ----------
+ */
+static void exec_assign_expr(PLpgSQL_execstate *estate, PLpgSQL_datum *target,
+ PLpgSQL_expr *expr)
+{
+ Datum value;
+ Oid valtype;
+ bool isnull = false;
+
+ value = exec_eval_expr(estate, expr, &isnull, &valtype);
+ if (target != NULL) {
+ exec_assign_value(estate, target, value, valtype, &isnull);
+ }
+}
+
+
+/* ----------
+ * exec_assign_value Put a value into a target field
+ * ----------
+ */
+static void exec_assign_value(PLpgSQL_execstate *estate,
+ PLpgSQL_datum *target,
+ Datum value, Oid valtype, bool *isNull)
+{
+ PLpgSQL_var *var;
+ PLpgSQL_rec *rec;
+ PLpgSQL_recfield *recfield;
+ int fno;
+ int i;
+ int natts;
+ Datum *values;
+ char *nulls;
+ bool attisnull;
+ Oid atttype;
+ int4 atttypmod;
+ HeapTuple typetup;
+ TypeTupleForm typeStruct;
+ FmgrInfo finfo_input;
+
+ switch (target->dtype) {
+ case PLPGSQL_DTYPE_VAR:
+ /* ----------
+ * Target field is a variable - that's easy
+ * ----------
+ */
+ var = (PLpgSQL_var *)target;
+ var->value = exec_cast_value(value, valtype, var->datatype->typoid,
+ &(var->datatype->typinput),
+ var->datatype->atttypmod, isNull);
+
+ if (isNull && var->notnull) {
+ elog(ERROR, "NULL assignment to variable '%s' declared NOT NULL", var->refname);
+ }
+
+ var->isnull = *isNull;
+ break;
+
+ case PLPGSQL_DTYPE_RECFIELD:
+ /* ----------
+ * Target field is a record
+ * ----------
+ */
+ recfield = (PLpgSQL_recfield *)target;
+ rec = (PLpgSQL_rec *)(estate->datums[recfield->recno]);
+
+ /* ----------
+ * Check that there is already a tuple in the record.
+ * We need that because records don't have any predefined
+ * field structure.
+ * ----------
+ */
+ if (!HeapTupleIsValid(rec->tup)) {
+ elog(ERROR, "record %s is unassigned yet - don't know it's tuple structure", rec->refname);
+ }
+
+ /* ----------
+ * Get the number of the records field to change and the
+ * number of attributes in the tuple.
+ * ----------
+ */
+ fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
+ if (fno == SPI_ERROR_NOATTRIBUTE) {
+ elog(ERROR, "record %s has no field %s", rec->refname, recfield->fieldname);
+ }
+ fno--;
+ natts = rec->tupdesc->natts;
+
+ /* ----------
+ * We loop over the attributes of the rec's current tuple
+ * and collect the values in a Datum array along with the
+ * nulls information.
+ * ----------
+ */
+ values = palloc(sizeof(Datum) * natts);
+ nulls = palloc(natts + 1);
+
+ for (i = 0; i < natts; i++) {
+ /* ----------
+ * If this isn't the field we assign to, just use the
+ * value that's already in the tuple.
+ * ----------
+ */
+ if (i != fno) {
+ values[i] = SPI_getbinval(rec->tup, rec->tupdesc,
+ i + 1, &attisnull);
+ if (attisnull) {
+ nulls[i] = 'n';
+ } else {
+ nulls[i] = ' ';
+ }
+ continue;
+ }
+
+ /* ----------
+ * This is the field to change. Get it's type
+ * and cast the value we insert to that type.
+ * ----------
+ */
+ atttype = SPI_gettypeid(rec->tupdesc, i + 1);
+ atttypmod = rec->tupdesc->attrs[i]->atttypmod;
+ typetup = SearchSysCacheTuple(TYPOID,
+ ObjectIdGetDatum(atttype), 0, 0, 0);
+ if (!HeapTupleIsValid(typetup)) {
+ elog(ERROR, "cache lookup for type %d failed", atttype);
+ }
+ typeStruct = (TypeTupleForm) GETSTRUCT(typetup);
+ fmgr_info(typeStruct->typinput, &finfo_input);
+
+ attisnull = *isNull;
+ values[i] = exec_cast_value(value, valtype,
+ atttype, &finfo_input, atttypmod, &attisnull);
+ if (attisnull) {
+ nulls[i] = 'n';
+ } else {
+ nulls[i] = ' ';
+ }
+ }
+
+ /* ----------
+ * Now call heap_formtuple() to create a new tuple
+ * that replaces the old one in the record.
+ * ----------
+ */
+ nulls[i] = '\0';
+ rec->tup = heap_formtuple(rec->tupdesc, values, nulls);
+ pfree(values);
+ pfree(nulls);
+
+ break;
+
+ default:
+ elog(ERROR, "unknown dtype %d in exec_assign_value()",
+ target->dtype);
+ }
+}
+
+
+/* ----------
+ * exec_eval_expr Evaluate an expression and return
+ * the result Datum.
+ * ----------
+ */
+static Datum exec_eval_expr(PLpgSQL_execstate *estate,
+ PLpgSQL_expr *expr,
+ bool *isNull,
+ Oid *rettype)
+{
+ int rc;
+
+ rc = exec_run_select(estate, expr, 2);
+ if (rc != SPI_OK_SELECT) {
+ elog(ERROR, "query \"%s\" didn't return data", expr->query);
+ }
+
+ /* ----------
+ * If there are no rows selected, the result is NULL.
+ * ----------
+ */
+ if (SPI_processed == 0) {
+ *isNull = true;
+ return (Datum)0;
+ }
+
+ /* ----------
+ * Check that the expression returned one single Datum
+ * ----------
+ */
+ if (SPI_processed > 1) {
+ elog(ERROR, "query \"%s\" didn't return a single value", expr->query);
+ }
+ if (SPI_tuptable->tupdesc->natts != 1) {
+ elog(ERROR, "query \"%s\" didn't return a single value", expr->query);
+ }
+
+ /* ----------
+ * Return the result and it's type
+ * ----------
+ */
+ *rettype = SPI_gettypeid(SPI_tuptable->tupdesc, 1);
+ return SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, isNull);
+}
+
+
+/* ----------
+ * exec_run_select Execute a select query
+ * ----------
+ */
+static int exec_run_select(PLpgSQL_execstate *estate,
+ PLpgSQL_expr *expr, int maxtuples)
+{
+ PLpgSQL_var *var;
+ PLpgSQL_rec *rec;
+ PLpgSQL_recfield *recfield;
+ PLpgSQL_trigarg *trigarg;
+ int tgargno;
+ Oid tgargoid;
+ int i;
+ Datum *values;
+ char *nulls;
+ int rc;
+ int fno;
+ bool isnull;
+
+ /* ----------
+ * On the first call for this expression generate the plan
+ * ----------
+ */
+ if (expr->plan == NULL) {
+ void *plan;
+ Oid *argtypes;
+
+ /* ----------
+ * Setup the argtypes array
+ * ----------
+ */
+ argtypes = malloc(sizeof(Oid *) * (expr->nparams + 1));
+
+ for (i = 0; i < expr->nparams; i++) {
+ switch (estate->datums[expr->params[i]]->dtype) {
+ case PLPGSQL_DTYPE_VAR:
+ var = (PLpgSQL_var *)(estate->datums[expr->params[i]]);
+ argtypes[i] = var->datatype->typoid;
+ break;
+
+ case PLPGSQL_DTYPE_RECFIELD:
+ recfield = (PLpgSQL_recfield *)(estate->datums[expr->params[i]]);
+ rec = (PLpgSQL_rec *)(estate->datums[recfield->recno]);
+
+ if (!HeapTupleIsValid(rec->tup)) {
+ elog(ERROR, "record %s is unassigned yet", rec->refname);
+ }
+ fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
+ if (fno == SPI_ERROR_NOATTRIBUTE) {
+ elog(ERROR, "record %s has no field %s", rec->refname, recfield->fieldname);
+ }
+ argtypes[i] = SPI_gettypeid(rec->tupdesc, fno);
+ break;
+
+ case PLPGSQL_DTYPE_TRIGARG:
+ argtypes[i] = (Oid)TEXTOID;
+ break;
+
+ default:
+ elog(ERROR, "unknown parameter dtype %d in exec_run_select()", estate->datums[expr->params[i]]);
+ }
+ }
+
+ /* ----------
+ * Generate and save the plan
+ * ----------
+ */
+ plan = SPI_prepare(expr->query, expr->nparams, argtypes);
+ if (plan == NULL) {
+ elog(ERROR, "SPI_prepare() failed on \"%s\"", expr->query);
+ }
+ expr->plan = SPI_saveplan(plan);
+ expr->plan_argtypes = argtypes;
+ }
+
+ /* ----------
+ * Now build up the values and nulls arguments for SPI_execp()
+ * ----------
+ */
+ values = palloc(sizeof(Datum) * (expr->nparams + 1));
+ nulls = palloc(expr->nparams + 1);
+
+ for (i = 0; i < expr->nparams; i++) {
+ switch (estate->datums[expr->params[i]]->dtype) {
+ case PLPGSQL_DTYPE_VAR:
+ var = (PLpgSQL_var *)(estate->datums[expr->params[i]]);
+ values[i] = var->value;
+ if (var->isnull) {
+ nulls[i] = 'n';
+ } else {
+ nulls[i] = ' ';
+ }
+ break;
+
+ case PLPGSQL_DTYPE_RECFIELD:
+ recfield = (PLpgSQL_recfield *)(estate->datums[expr->params[i]]);
+ rec = (PLpgSQL_rec *)(estate->datums[recfield->recno]);
+
+ if (!HeapTupleIsValid(rec->tup)) {
+ elog(ERROR, "record %s is unassigned yet", rec->refname);
+ }
+ fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
+ if (fno == SPI_ERROR_NOATTRIBUTE) {
+ elog(ERROR, "record %s has no field %s", rec->refname, recfield->fieldname);
+ }
+
+ if (expr->plan_argtypes[i] != SPI_gettypeid(rec->tupdesc, fno)) {
+ elog(ERROR, "type of %s.%s doesn't match that when preparing the plan", rec->refname, recfield->fieldname);
+ }
+
+ values[i] = SPI_getbinval(rec->tup, rec->tupdesc, fno, &isnull);
+ if (isnull) {
+ nulls[i] = 'n';
+ } else {
+ nulls[i] = ' ';
+ }
+ break;
+
+ case PLPGSQL_DTYPE_TRIGARG:
+ trigarg = (PLpgSQL_trigarg *)(estate->datums[expr->params[i]]);
+ tgargno = (int)exec_eval_expr(estate, trigarg->argnum,
+ &isnull, &tgargoid);
+ if (isnull || tgargno < 0 || tgargno >= estate->trig_nargs) {
+ values[i] = 0;
+ nulls[i] = 'n';
+ } else {
+ values[i] = estate->trig_argv[tgargno];
+ nulls[i] = ' ';
+ }
+ break;
+
+ default:
+ elog(ERROR, "unknown parameter dtype %d in exec_eval_expr()", estate->datums[expr->params[i]]);
+ }
+ }
+ nulls[i] = '\0';
+
+ /* ----------
+ * Execute the query
+ * ----------
+ */
+ rc = SPI_execp(expr->plan, values, nulls, maxtuples);
+ if (rc != SPI_OK_SELECT) {
+ elog(ERROR, "query \"%s\" isn't a SELECT", expr->query);
+ }
+ pfree(values);
+ pfree(nulls);
+
+ return rc;
+}
+
+
+/* ----------
+ * exec_move_row Move one tuples values into a
+ * record or row
+ * ----------
+ */
+static void exec_move_row(PLpgSQL_execstate *estate,
+ PLpgSQL_rec *rec,
+ PLpgSQL_row *row,
+ HeapTuple tup, TupleDesc tupdesc)
+{
+ PLpgSQL_var *var;
+ int i;
+ Datum value;
+ Oid valtype;
+ bool isnull;
+
+ /* ----------
+ * Record is simple - just put the tuple and it's descriptor
+ * into the record
+ * ----------
+ */
+ if (rec != NULL) {
+ if (HeapTupleIsValid(tup)) {
+ rec->tup = tup;
+ rec->tupdesc = tupdesc;
+ } else {
+ rec->tup = NULL;
+ rec->tupdesc = NULL;
+ }
+
+ return;
+ }
+
+
+ /* ----------
+ * Row is a bit more complicated in that we assign the single
+ * attributes of the query to the variables the row points to.
+ * ----------
+ */
+ if (row != NULL) {
+ if (HeapTupleIsValid(tup)) {
+ if (row->nfields != tupdesc->natts) {
+ elog(ERROR, "query didn't return correct # of attributes for %s",
+ row->refname);
+ }
+
+ for (i = 0; i < row->nfields; i++) {
+ var = (PLpgSQL_var *)(estate->datums[row->varnos[i]]);
+
+ valtype = SPI_gettypeid(tupdesc, i + 1);
+ value = SPI_getbinval(tup, tupdesc, i + 1, &isnull);
+ exec_assign_value(estate, estate->datums[row->varnos[i]],
+ value, valtype, &isnull);
+
+ }
+ } else {
+ for (i = 0; i < row->nfields; i++) {
+ bool nullval = true;
+
+ exec_assign_value(estate, estate->datums[row->varnos[i]],
+ (Datum) 0, 0, &nullval);
+ }
+ }
+
+ return;
+ }
+
+ elog(ERROR, "unsupported target in exec_move_row()");
+}
+
+
+/* ----------
+ * exec_cast_value Cast a value if required
+ * ----------
+ */
+static Datum exec_cast_value(Datum value, Oid valtype,
+ Oid reqtype,
+ FmgrInfo *reqinput,
+ int16 reqtypmod,
+ bool *isnull)
+{
+ if (!*isnull) {
+ /* ----------
+ * If the type of the queries return value isn't
+ * that of the variable, convert it.
+ * ----------
+ */
+ if (valtype != reqtype || reqtypmod > 0) {
+ HeapTuple typetup;
+ TypeTupleForm typeStruct;
+ FmgrInfo finfo_output;
+ char *extval;
+
+ typetup = SearchSysCacheTuple(TYPOID,
+ ObjectIdGetDatum(valtype), 0, 0, 0);
+ if (!HeapTupleIsValid(typetup)) {
+ elog(ERROR, "cache lookup for type %d failed", valtype);
+ }
+ typeStruct = (TypeTupleForm) GETSTRUCT(typetup);
+
+ fmgr_info(typeStruct->typoutput, &finfo_output);
+ extval = (char *)(*fmgr_faddr(&finfo_output))(value, &isnull, -1);
+ value = (Datum)(*fmgr_faddr(reqinput))(extval, &isnull, reqtypmod);
+ }
+ }
+
+ return value;
+}
+
+
+/* ----------
+ * exec_set_found Set the global found variable
+ * to true/false
+ * ----------
+ */
+static void exec_set_found(PLpgSQL_execstate *estate, bool state)
+{
+ PLpgSQL_var *var;
+
+ var = (PLpgSQL_var *)(estate->datums[estate->found_varno]);
+ var->value = (Datum) state;
+ var->isnull = false;
+}
+
+
diff --git a/contrib/plpgsql/src/pl_funcs.c b/contrib/plpgsql/src/pl_funcs.c
new file mode 100644
index 00000000000..94de9e4c9a6
--- /dev/null
+++ b/contrib/plpgsql/src/pl_funcs.c
@@ -0,0 +1,675 @@
+/**********************************************************************
+ * pl_funcs.c - Misc functins for the PL/pgSQL
+ * procedural language
+ *
+ * IDENTIFICATION
+ * $Header: /cvsroot/pgsql/contrib/plpgsql/src/Attic/pl_funcs.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"
+
+
+/* ----------
+ * Local variables for the namestack handling
+ * ----------
+ */
+static PLpgSQL_ns *ns_current = NULL;
+static bool ns_localmode = false;
+
+
+/* ----------
+ * plpgsql_dstring_init Dynamic string initialization
+ * ----------
+ */
+void plpgsql_dstring_init(PLpgSQL_dstring *ds)
+{
+ ds->value = palloc(ds->alloc = 512);
+ ds->used = 0;
+}
+
+
+/* ----------
+ * plpgsql_dstring_free Dynamic string destruction
+ * ----------
+ */
+void plpgsql_dstring_free(PLpgSQL_dstring *ds)
+{
+ pfree(ds->value);
+}
+
+
+/* ----------
+ * plpgsql_dstring_append Dynamic string extending
+ * ----------
+ */
+void plpgsql_dstring_append(PLpgSQL_dstring *ds, char *str)
+{
+ int len = strlen(str);
+
+ if (ds->used + len + 1 > ds->alloc) {
+ ds->alloc *= 2;
+ ds->value = repalloc(ds->value, ds->alloc);
+ }
+
+ strcpy(&(ds->value[ds->used]), str);
+ ds->used += len;
+}
+
+
+/* ----------
+ * plpgsql_dstring_get Dynamic string get value
+ * ----------
+ */
+char *plpgsql_dstring_get(PLpgSQL_dstring *ds)
+{
+ return ds->value;
+}
+
+
+/* ----------
+ * plpgsql_ns_init Initialize the namestack
+ * ----------
+ */
+void plpgsql_ns_init(void)
+{
+ ns_current = NULL;
+ ns_localmode = false;
+}
+
+
+/* ----------
+ * plpgsql_ns_setlocal Tell plpgsql_ns_lookup to or to
+ * not look into the current level
+ * only.
+ * ----------
+ */
+bool plpgsql_ns_setlocal(bool flag)
+{
+ bool oldstate;
+
+ oldstate = ns_localmode;
+ ns_localmode = flag;
+ return oldstate;
+}
+
+
+/* ----------
+ * plpgsql_ns_push Enter a new namestack level
+ * ----------
+ */
+void plpgsql_ns_push(char *label)
+{
+ PLpgSQL_ns *new;
+
+ new = palloc(sizeof(PLpgSQL_ns));
+ memset(new, 0, sizeof(PLpgSQL_ns));
+ new->upper = ns_current;
+ ns_current = new;
+
+ plpgsql_ns_additem(PLPGSQL_NSTYPE_LABEL, 0, label);
+}
+
+
+/* ----------
+ * plpgsql_ns_pop Return to the previous level
+ * ----------
+ */
+void plpgsql_ns_pop()
+{
+ int i;
+ PLpgSQL_ns *old;
+
+ old = ns_current;
+ ns_current = old->upper;
+
+ for (i = 0; i < old->items_used; i++) {
+ pfree(old->items[i]);
+ }
+ pfree(old->items);
+ pfree(old);
+}
+
+
+/* ----------
+ * plpgsql_ns_additem Add an item to the current
+ * namestack level
+ * ----------
+ */
+void plpgsql_ns_additem(int itemtype, int itemno, char *name)
+{
+ PLpgSQL_ns *ns = ns_current;
+ PLpgSQL_nsitem *nse;
+
+ if (name == NULL)
+ name = "";
+
+ if (ns->items_used == ns->items_alloc) {
+ if (ns->items_alloc == 0) {
+ ns->items_alloc = 32;
+ ns->items = palloc(sizeof(PLpgSQL_nsitem *) * ns->items_alloc);
+ } else {
+ ns->items_alloc *= 2;
+ ns->items = repalloc(ns->items,
+ sizeof(PLpgSQL_nsitem *) * ns->items_alloc);
+ }
+ }
+
+ nse = palloc(sizeof(PLpgSQL_nsitem) + strlen(name));
+ nse->itemtype = itemtype;
+ nse->itemno = itemno;
+ strcpy(nse->name, name);
+ ns->items[ns->items_used++] = nse;
+}
+
+
+/* ----------
+ * plpgsql_ns_lookup Lookup for a word in the namestack
+ * ----------
+ */
+PLpgSQL_nsitem *plpgsql_ns_lookup(char *name, char *label)
+{
+ PLpgSQL_ns *ns;
+ int i;
+
+ /* ----------
+ * If a label is specified, lookup only in that
+ * ----------
+ */
+ if (label != NULL) {
+ for (ns = ns_current; ns != NULL; ns = ns->upper) {
+ if (!strcmp(ns->items[0]->name, label)) {
+ for (i = 1; i < ns->items_used; i++) {
+ if (!strcmp(ns->items[i]->name, name)) {
+ return ns->items[i];
+ }
+ }
+ return NULL; /* name not found in specified label */
+ }
+ }
+ return NULL; /* label not found */
+ }
+
+ /* ----------
+ * No label given, lookup for visible labels ignoring localmode
+ * ----------
+ */
+ for (ns = ns_current; ns != NULL; ns = ns->upper) {
+ if (!strcmp(ns->items[0]->name, name)) {
+ return ns->items[0];
+ }
+ }
+
+ /* ----------
+ * Finally lookup name in the namestack
+ * ----------
+ */
+ for (ns = ns_current; ns != NULL; ns = ns->upper) {
+ for (i = 1; i < ns->items_used; i++) {
+ if (!strcmp(ns->items[i]->name, name))
+ return ns->items[i];
+ }
+ if (ns_localmode) {
+ return NULL; /* name not found in current namespace */
+ }
+ }
+
+ return NULL;
+}
+
+
+/* ----------
+ * plpgsql_ns_rename Rename a namespace entry
+ * ----------
+ */
+void plpgsql_ns_rename(char *oldname, char *newname)
+{
+ PLpgSQL_ns *ns;
+ PLpgSQL_nsitem *newitem;
+ int i;
+
+ /* ----------
+ * Lookup in the current namespace only
+ * ----------
+ */
+ /* ----------
+ * Lookup name in the namestack
+ * ----------
+ */
+ for (ns = ns_current; ns != NULL; ns = ns->upper) {
+ for (i = 1; i < ns->items_used; i++) {
+ if (!strcmp(ns->items[i]->name, oldname)) {
+ newitem = palloc(sizeof(PLpgSQL_nsitem) + strlen(newname));
+ newitem->itemtype = ns->items[i]->itemtype;
+ newitem->itemno = ns->items[i]->itemno;
+ strcpy(newitem->name, newname);
+
+ pfree(oldname);
+ pfree(newname);
+
+ pfree(ns->items[i]);
+ ns->items[i] = newitem;
+ return;
+ }
+ }
+ }
+
+ elog(ERROR, "there is no variable '%s' in the current block", oldname);
+}
+
+
+/* ----------
+ * plpgsql_tolower Translate a string in place to
+ * lower case
+ * ----------
+ */
+char *plpgsql_tolower(char *s)
+{
+ char *cp;
+
+ for (cp = s; *cp; cp++) {
+ if (isupper(*cp)) *cp = tolower(*cp);
+ }
+
+ return s;
+}
+
+
+
+
+
+/**********************************************************************
+ * Debug functions for analyzing the compiled code
+ **********************************************************************/
+static int dump_indent;
+
+static void dump_ind();
+static void dump_stmt(PLpgSQL_stmt *stmt);
+static void dump_block(PLpgSQL_stmt_block *block);
+static void dump_assign(PLpgSQL_stmt_assign *stmt);
+static void dump_if(PLpgSQL_stmt_if *stmt);
+static void dump_loop(PLpgSQL_stmt_loop *stmt);
+static void dump_while(PLpgSQL_stmt_while *stmt);
+static void dump_fori(PLpgSQL_stmt_fori *stmt);
+static void dump_fors(PLpgSQL_stmt_fors *stmt);
+static void dump_select(PLpgSQL_stmt_select *stmt);
+static void dump_exit(PLpgSQL_stmt_exit *stmt);
+static void dump_return(PLpgSQL_stmt_return *stmt);
+static void dump_raise(PLpgSQL_stmt_raise *stmt);
+static void dump_execsql(PLpgSQL_stmt_execsql *stmt);
+static void dump_expr(PLpgSQL_expr *expr);
+
+
+static void dump_ind()
+{
+ int i;
+ for (i = 0; i < dump_indent; i++) {
+ printf(" ");
+ }
+}
+
+static void dump_stmt(PLpgSQL_stmt *stmt)
+{
+ printf("%3d:", stmt->lineno);
+ switch (stmt->cmd_type) {
+ case PLPGSQL_STMT_BLOCK:
+ dump_block((PLpgSQL_stmt_block *)stmt);
+ break;
+ case PLPGSQL_STMT_ASSIGN:
+ dump_assign((PLpgSQL_stmt_assign *)stmt);
+ break;
+ case PLPGSQL_STMT_IF:
+ dump_if((PLpgSQL_stmt_if *)stmt);
+ break;
+ case PLPGSQL_STMT_LOOP:
+ dump_loop((PLpgSQL_stmt_loop *)stmt);
+ break;
+ case PLPGSQL_STMT_WHILE:
+ dump_while((PLpgSQL_stmt_while *)stmt);
+ break;
+ case PLPGSQL_STMT_FORI:
+ dump_fori((PLpgSQL_stmt_fori *)stmt);
+ break;
+ case PLPGSQL_STMT_FORS:
+ dump_fors((PLpgSQL_stmt_fors *)stmt);
+ break;
+ case PLPGSQL_STMT_SELECT:
+ dump_select((PLpgSQL_stmt_select *)stmt);
+ break;
+ case PLPGSQL_STMT_EXIT:
+ dump_exit((PLpgSQL_stmt_exit *)stmt);
+ break;
+ case PLPGSQL_STMT_RETURN:
+ dump_return((PLpgSQL_stmt_return *)stmt);
+ break;
+ case PLPGSQL_STMT_RAISE:
+ dump_raise((PLpgSQL_stmt_raise *)stmt);
+ break;
+ case PLPGSQL_STMT_EXECSQL:
+ dump_execsql((PLpgSQL_stmt_execsql *)stmt);
+ break;
+ default:
+ elog(ERROR, "plpgsql_dump: unknown cmd_type %d\n", stmt->cmd_type);
+ break;
+ }
+}
+
+static void dump_block(PLpgSQL_stmt_block *block)
+{
+ int i;
+ char *name;
+
+ if (block->label == NULL) {
+ name = "*unnamed*";
+ } else {
+ name = block->label;
+ }
+
+ dump_ind();
+ printf("BLOCK <<%s>>\n", name);
+
+ dump_indent += 2;
+ for (i = 0; i < block->body->stmts_used; i++) {
+ dump_stmt((PLpgSQL_stmt *)(block->body->stmts[i]));
+ }
+ dump_indent -= 2;
+
+ dump_ind();
+ printf(" END -- %s\n", name);
+}
+
+static void dump_assign(PLpgSQL_stmt_assign *stmt)
+{
+ dump_ind();
+ printf("ASSIGN var %d := ", stmt->varno);
+ dump_expr(stmt->expr);
+ printf("\n");
+}
+
+static void dump_if(PLpgSQL_stmt_if *stmt)
+{
+ int i;
+
+ dump_ind();
+ printf("IF ");
+ dump_expr(stmt->cond);
+ printf(" THEN\n");
+
+ dump_indent += 2;
+ for (i = 0; i < stmt->true_body->stmts_used; i++) {
+ dump_stmt((PLpgSQL_stmt *)(stmt->true_body->stmts[i]));
+ }
+ dump_indent -= 2;
+
+ dump_ind();
+ printf(" ELSE\n");
+
+ dump_indent += 2;
+ for (i = 0; i < stmt->false_body->stmts_used; i++) {
+ dump_stmt((PLpgSQL_stmt *)(stmt->false_body->stmts[i]));
+ }
+ dump_indent -= 2;
+
+ dump_ind();
+ printf(" ENDIF\n");
+}
+
+static void dump_loop(PLpgSQL_stmt_loop *stmt)
+{
+ int i;
+
+ dump_ind();
+ printf("LOOP\n");
+
+ dump_indent += 2;
+ for (i = 0; i < stmt->body->stmts_used; i++) {
+ dump_stmt((PLpgSQL_stmt *)(stmt->body->stmts[i]));
+ }
+ dump_indent -= 2;
+
+ dump_ind();
+ printf(" ENDLOOP\n");
+}
+
+static void dump_while(PLpgSQL_stmt_while *stmt)
+{
+ int i;
+
+ dump_ind();
+ printf("WHILE ");
+ dump_expr(stmt->cond);
+ printf("\n");
+
+ dump_indent += 2;
+ for (i = 0; i < stmt->body->stmts_used; i++) {
+ dump_stmt((PLpgSQL_stmt *)(stmt->body->stmts[i]));
+ }
+ dump_indent -= 2;
+
+ dump_ind();
+ printf(" ENDWHILE\n");
+}
+
+static void dump_fori(PLpgSQL_stmt_fori *stmt)
+{
+ int i;
+
+ dump_ind();
+ printf("FORI %s %s\n", stmt->var->refname, (stmt->reverse) ? "REVERSE" : "NORMAL");
+
+ dump_indent += 2;
+ dump_ind();
+ printf(" lower = ");
+ dump_expr(stmt->lower);
+ printf("\n");
+ dump_ind();
+ printf(" upper = ");
+ dump_expr(stmt->upper);
+ printf("\n");
+
+ for (i = 0; i < stmt->body->stmts_used; i++) {
+ dump_stmt((PLpgSQL_stmt *)(stmt->body->stmts[i]));
+ }
+ dump_indent -= 2;
+
+ dump_ind();
+ printf(" ENDFORI\n");
+}
+
+static void dump_fors(PLpgSQL_stmt_fors *stmt)
+{
+ int i;
+
+ dump_ind();
+ printf("FORS %s ", (stmt->rec != NULL) ? stmt->rec->refname : stmt->row->refname);
+ dump_expr(stmt->query);
+ printf("\n");
+
+ dump_indent += 2;
+ for (i = 0; i < stmt->body->stmts_used; i++) {
+ dump_stmt((PLpgSQL_stmt *)(stmt->body->stmts[i]));
+ }
+ dump_indent -= 2;
+
+ dump_ind();
+ printf(" ENDFORS\n");
+}
+
+static void dump_select(PLpgSQL_stmt_select *stmt)
+{
+ dump_ind();
+ printf("SELECT ");
+ dump_expr(stmt->query);
+ printf("\n");
+
+ dump_indent += 2;
+ if (stmt->rec != NULL) {
+ dump_ind();
+ printf(" target = %d %s\n", stmt->rec->recno, stmt->rec->refname);
+ }
+ if (stmt->row != NULL) {
+ dump_ind();
+ printf(" target = %d %s\n", stmt->row->rowno, stmt->row->refname);
+ }
+ dump_indent -= 2;
+
+}
+
+static void dump_exit(PLpgSQL_stmt_exit *stmt)
+{
+ dump_ind();
+ printf("EXIT lbl='%s'", stmt->label);
+ if (stmt->cond != NULL) {
+ printf(" WHEN ");
+ dump_expr(stmt->cond);
+ }
+ printf("\n");
+}
+
+static void dump_return(PLpgSQL_stmt_return *stmt)
+{
+ dump_ind();
+ printf("RETURN ");
+ if (stmt->retrecno >= 0) {
+ printf("record %d", stmt->retrecno);
+ } else {
+ if (stmt->expr == NULL) {
+ printf("NULL");
+ } else {
+ dump_expr(stmt->expr);
+ }
+ }
+ printf("\n");
+}
+
+static void dump_raise(PLpgSQL_stmt_raise *stmt)
+{
+ int i;
+
+ dump_ind();
+ printf("RAISE '%s'", stmt->message);
+ for (i = 0; i < stmt->nparams; i++) {
+ printf(" %d", stmt->params[i]);
+ }
+ printf("\n");
+}
+
+static void dump_execsql(PLpgSQL_stmt_execsql *stmt)
+{
+ dump_ind();
+ printf("EXECSQL ");
+ dump_expr(stmt->sqlstmt);
+ printf("\n");
+}
+
+static void dump_expr(PLpgSQL_expr *expr)
+{
+ int i;
+ printf("'%s", expr->query);
+ if (expr->nparams > 0) {
+ printf(" {");
+ for(i = 0; i < expr->nparams; i++) {
+ if (i > 0) printf(", ");
+ printf("$%d=%d", i+1, expr->params[i]);
+ }
+ printf("}");
+ }
+ printf("'");
+}
+
+void plpgsql_dumptree(PLpgSQL_function *func)
+{
+ int i;
+ PLpgSQL_datum *d;
+
+ printf("\nExecution tree of successfully compiled PL/pgSQL function %s:\n",
+ func->fn_name);
+
+ printf("\nFunctions data area:\n");
+ for (i = 0; i < func->ndatums; i++) {
+ d = func->datums[i];
+
+ printf(" entry %d: ", i);
+ switch (d->dtype) {
+ case PLPGSQL_DTYPE_VAR:
+ {
+ PLpgSQL_var *var = (PLpgSQL_var *)d;
+ printf("VAR %-16s type %s (typoid %d) atttypmod %d\n",
+ var->refname, var->datatype->typname,
+ var->datatype->typoid,
+ var->datatype->atttypmod);
+ }
+ break;
+ case PLPGSQL_DTYPE_ROW:
+ {
+ PLpgSQL_row *row = (PLpgSQL_row *)d;
+ int i;
+ printf("ROW %-16s fields", row->refname);
+ for (i = 0; i < row->nfields; i++) {
+ printf(" %s=var %d", row->fieldnames[i],
+ row->varnos[i]);
+ }
+ printf("\n");
+ }
+ break;
+ case PLPGSQL_DTYPE_REC:
+ printf("REC %s\n", ((PLpgSQL_rec *)d)->refname);
+ break;
+ case PLPGSQL_DTYPE_RECFIELD:
+ printf("RECFIELD %-16s of REC %d\n", ((PLpgSQL_recfield *)d)->fieldname, ((PLpgSQL_recfield *)d)->recno);
+ break;
+ case PLPGSQL_DTYPE_TRIGARG:
+ printf("TRIGARG ");
+ dump_expr(((PLpgSQL_trigarg *)d)->argnum);
+ printf("\n");
+ break;
+ default:
+ printf("??? unknown data type %d\n", d->dtype);
+ }
+ }
+ printf("\nFunctions statements:\n");
+
+ dump_indent = 0;
+ printf("%3d:", func->action->lineno);
+ dump_block(func->action);
+ printf("\nEnd of execution tree of function %s\n\n", func->fn_name);
+}
+
+
diff --git a/contrib/plpgsql/src/pl_handler.c b/contrib/plpgsql/src/pl_handler.c
new file mode 100644
index 00000000000..fb01e38b697
--- /dev/null
+++ b/contrib/plpgsql/src/pl_handler.c
@@ -0,0 +1,193 @@
+/**********************************************************************
+ * pl_handler.c - Handler for the PL/pgSQL
+ * procedural language
+ *
+ * IDENTIFICATION
+ * $Header: /cvsroot/pgsql/contrib/plpgsql/src/Attic/pl_handler.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 "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 "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+
+
+static PLpgSQL_function *compiled_functions = NULL;
+
+
+Datum plpgsql_call_handler(FmgrInfo *proinfo,
+ FmgrValues *proargs, bool *isNull);
+
+static Datum plpgsql_func_handler(FmgrInfo *proinfo,
+ FmgrValues *proargs, bool *isNull);
+
+static HeapTuple plpgsql_trigger_handler(FmgrInfo *proinfo);
+
+
+/* ----------
+ * plpgsql_call_handler - This is the only visible function
+ * of the PL interpreter. The PostgreSQL
+ * function manager and trigger manager
+ * call this function for execution of
+ * PL/pgSQL procedures.
+ * ----------
+ */
+Datum
+plpgsql_call_handler(FmgrInfo *proinfo,
+ FmgrValues *proargs,
+ bool *isNull)
+{
+ Datum retval;
+
+ /* ----------
+ * Connect to SPI manager
+ * ----------
+ */
+ if (SPI_connect() != SPI_OK_CONNECT) {
+ elog(ERROR, "plpgsql: cannot connect to SPI manager");
+ }
+
+ /* ----------
+ * Determine if called as function or trigger and
+ * call appropriate subhandler
+ * ----------
+ */
+ if (CurrentTriggerData == NULL) {
+ retval = plpgsql_func_handler(proinfo, proargs, isNull);
+ } else {
+ retval = (Datum)plpgsql_trigger_handler(proinfo);
+ }
+
+ /* ----------
+ * Disconnect from SPI manager
+ * ----------
+ */
+ if (SPI_finish() != SPI_OK_FINISH) {
+ elog(ERROR, "plpgsql: SPI_finish() failed");
+ }
+
+ return retval;
+}
+
+
+/* ----------
+ * plpgsql_func_handler() - Handler for regular function calls
+ * ----------
+ */
+static Datum
+plpgsql_func_handler(FmgrInfo *proinfo,
+ FmgrValues *proargs,
+ bool *isNull)
+{
+ PLpgSQL_function *func;
+
+ /* ----------
+ * Check if we already compiled this function
+ * ----------
+ */
+ for (func = compiled_functions; func != NULL; func = func->next) {
+ if (proinfo->fn_oid == func->fn_oid)
+ break;
+ }
+
+ /* ----------
+ * If not, do so and add it to the compiled ones
+ * ----------
+ */
+ if (func == NULL) {
+ func = plpgsql_compile(proinfo->fn_oid, T_FUNCTION);
+
+ func->next = compiled_functions;
+ compiled_functions = func;
+ }
+
+ return plpgsql_exec_function(func, proargs, isNull);
+}
+
+
+/* ----------
+ * plpgsql_trigger_handler() - Handler for trigger calls
+ * ----------
+ */
+static HeapTuple
+plpgsql_trigger_handler(FmgrInfo *proinfo)
+{
+ TriggerData *trigdata;
+ PLpgSQL_function *func;
+
+ /* ----------
+ * Save the current trigger data local
+ * ----------
+ */
+ trigdata = CurrentTriggerData;
+ CurrentTriggerData = NULL;
+
+ /* ----------
+ * Check if we already compiled this trigger procedure
+ * ----------
+ */
+ for (func = compiled_functions; func != NULL; func = func->next) {
+ if (proinfo->fn_oid == func->fn_oid)
+ break;
+ }
+
+ /* ----------
+ * If not, do so and add it to the compiled ones
+ * ----------
+ */
+ if (func == NULL) {
+ func = plpgsql_compile(proinfo->fn_oid, T_TRIGGER);
+
+ func->next = compiled_functions;
+ compiled_functions = func;
+ }
+
+ return plpgsql_exec_trigger(func, trigdata);
+}
+
+
diff --git a/contrib/plpgsql/src/plpgsql.h b/contrib/plpgsql/src/plpgsql.h
new file mode 100644
index 00000000000..406a6897521
--- /dev/null
+++ b/contrib/plpgsql/src/plpgsql.h
@@ -0,0 +1,475 @@
+/**********************************************************************
+ * plpgsql.h - Definitions for the PL/pgSQL
+ * procedural language
+ *
+ * IDENTIFICATION
+ * $Header: /cvsroot/pgsql/contrib/plpgsql/src/Attic/plpgsql.h,v 1.1 1998/08/22 12:38:33 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.
+ *
+ **********************************************************************/
+#ifndef PLPGSQL_H
+#define PLPGSQL_H
+
+#include <stdio.h>
+#include <stdarg.h>
+#include "postgres.h"
+#include "executor/spi.h"
+#include "commands/trigger.h"
+#include "fmgr.h"
+
+/**********************************************************************
+ * Definitions
+ **********************************************************************/
+
+/* ----------
+ * Compilers namestack item types
+ * ----------
+ */
+enum {
+ PLPGSQL_NSTYPE_LABEL,
+ PLPGSQL_NSTYPE_VAR,
+ PLPGSQL_NSTYPE_ROW,
+ PLPGSQL_NSTYPE_REC,
+ PLPGSQL_NSTYPE_RECFIELD
+};
+
+/* ----------
+ * Datum array node types
+ * ----------
+ */
+enum {
+ PLPGSQL_DTYPE_VAR,
+ PLPGSQL_DTYPE_ROW,
+ PLPGSQL_DTYPE_REC,
+ PLPGSQL_DTYPE_RECFIELD,
+ PLPGSQL_DTYPE_EXPR,
+ PLPGSQL_DTYPE_TRIGARG
+};
+
+/* ----------
+ * Execution tree node types
+ * ----------
+ */
+enum {
+ PLPGSQL_STMT_BLOCK,
+ PLPGSQL_STMT_ASSIGN,
+ PLPGSQL_STMT_IF,
+ PLPGSQL_STMT_LOOP,
+ PLPGSQL_STMT_WHILE,
+ PLPGSQL_STMT_FORI,
+ PLPGSQL_STMT_FORS,
+ PLPGSQL_STMT_SELECT,
+ PLPGSQL_STMT_EXIT,
+ PLPGSQL_STMT_RETURN,
+ PLPGSQL_STMT_RAISE,
+ PLPGSQL_STMT_EXECSQL
+};
+
+
+/* ----------
+ * Execution node return codes
+ * ----------
+ */
+enum {
+ PLPGSQL_RC_OK,
+ PLPGSQL_RC_EXIT,
+ PLPGSQL_RC_RETURN
+};
+
+/**********************************************************************
+ * Node and structure definitions
+ **********************************************************************/
+
+
+typedef struct { /* Dynamic string control structure */
+ int alloc;
+ int used;
+ char *value;
+} PLpgSQL_dstring;
+
+
+typedef struct { /* Postgres base data type */
+ char *typname;
+ Oid typoid;
+ FmgrInfo typinput;
+ bool typbyval;
+ int16 atttypmod;
+} PLpgSQL_type;
+
+
+typedef struct { /* Generic datum array item */
+ int dtype;
+ int dno;
+} PLpgSQL_datum;
+
+
+typedef struct { /* SQL Query to plan and execute */
+ int dtype;
+ int exprno;
+ char *query;
+ void *plan;
+ Oid *plan_argtypes;
+ int nparams;
+ int params[1];
+} PLpgSQL_expr;
+
+
+typedef struct { /* Local variable */
+ int dtype;
+ int varno;
+ char *refname;
+ int lineno;
+
+ PLpgSQL_type *datatype;
+ int isconst;
+ int notnull;
+ PLpgSQL_expr *default_val;
+
+ Datum value;
+ bool isnull;
+ int shouldfree;
+} PLpgSQL_var;
+
+
+typedef struct { /* Rowtype */
+ int dtype;
+ int rowno;
+ char *refname;
+ int lineno;
+ Oid rowtypeclass;
+
+ int nfields;
+ char **fieldnames;
+ int *varnos;
+} PLpgSQL_row;
+
+
+typedef struct { /* Record of undefined structure */
+ int dtype;
+ int recno;
+ char *refname;
+ int lineno;
+
+ HeapTuple tup;
+ TupleDesc tupdesc;
+} PLpgSQL_rec;
+
+
+typedef struct { /* Field in record */
+ int dtype;
+ int rfno;
+ char *fieldname;
+ int recno;
+} PLpgSQL_recfield;
+
+
+typedef struct { /* Positional argument to trigger */
+ int dtype;
+ int dno;
+ PLpgSQL_expr *argnum;
+} PLpgSQL_trigarg;
+
+
+typedef struct { /* Item in the compilers namestack */
+ int itemtype;
+ int itemno;
+ char name[1];
+} PLpgSQL_nsitem;
+
+
+typedef struct PLpgSQL_ns { /* Compiler namestack level */
+ int items_alloc;
+ int items_used;
+ PLpgSQL_nsitem **items;
+ struct PLpgSQL_ns *upper;
+} PLpgSQL_ns;
+
+
+typedef struct { /* List of execution nodes */
+ int stmts_alloc;
+ int stmts_used;
+ struct PLpgSQL_stmt **stmts;
+} PLpgSQL_stmts;
+
+
+typedef struct { /* Generic execution node */
+ int cmd_type;
+ int lineno;
+} PLpgSQL_stmt;
+
+
+typedef struct { /* Block of statements */
+ int cmd_type;
+ int lineno;
+ char *label;
+ PLpgSQL_stmts *body;
+ int n_initvars;
+ int *initvarnos;
+} PLpgSQL_stmt_block;
+
+
+typedef struct { /* Assign statement */
+ int cmd_type;
+ int lineno;
+ int varno;
+ PLpgSQL_expr *expr;
+} PLpgSQL_stmt_assign;
+
+
+typedef struct { /* IF statement */
+ int cmd_type;
+ int lineno;
+ PLpgSQL_expr *cond;
+ PLpgSQL_stmts *true_body;
+ PLpgSQL_stmts *false_body;
+} PLpgSQL_stmt_if;
+
+
+typedef struct { /* Unconditional LOOP statement */
+ int cmd_type;
+ int lineno;
+ char *label;
+ PLpgSQL_stmts *body;
+} PLpgSQL_stmt_loop;
+
+
+typedef struct { /* WHILE cond LOOP statement */
+ int cmd_type;
+ int lineno;
+ char *label;
+ PLpgSQL_expr *cond;
+ PLpgSQL_stmts *body;
+} PLpgSQL_stmt_while;
+
+
+typedef struct { /* FOR statement with integer loopvar */
+ int cmd_type;
+ int lineno;
+ char *label;
+ PLpgSQL_var *var;
+ PLpgSQL_expr *lower;
+ PLpgSQL_expr *upper;
+ int reverse;
+ PLpgSQL_stmts *body;
+} PLpgSQL_stmt_fori;
+
+
+typedef struct { /* FOR statement running over SELECT */
+ int cmd_type;
+ int lineno;
+ char *label;
+ PLpgSQL_rec *rec;
+ PLpgSQL_row *row;
+ PLpgSQL_expr *query;
+ PLpgSQL_stmts *body;
+} PLpgSQL_stmt_fors;
+
+
+typedef struct { /* SELECT ... INTO statement */
+ int cmd_type;
+ int lineno;
+ PLpgSQL_rec *rec;
+ PLpgSQL_row *row;
+ PLpgSQL_expr *query;
+} PLpgSQL_stmt_select;
+
+
+typedef struct { /* EXIT statement */
+ int cmd_type;
+ int lineno;
+ char *label;
+ PLpgSQL_expr *cond;
+} PLpgSQL_stmt_exit;
+
+
+typedef struct { /* RETURN statement */
+ int cmd_type;
+ int lineno;
+ bool retistuple;
+ PLpgSQL_expr *expr;
+ int retrecno;
+} PLpgSQL_stmt_return;
+
+
+typedef struct { /* RAISE statement */
+ int cmd_type;
+ int lineno;
+ int elog_level;
+ char *message;
+ int nparams;
+ int *params;
+} PLpgSQL_stmt_raise;
+
+
+typedef struct { /* Generic SQL statement to execute */
+ int cmd_type;
+ int lineno;
+ PLpgSQL_expr *sqlstmt;
+} PLpgSQL_stmt_execsql;
+
+
+typedef struct PLpgSQL_function { /* Complete compiled function */
+ Oid fn_oid;
+ char *fn_name;
+ int fn_functype;
+ Oid fn_rettype;
+ int fn_rettyplen;
+ bool fn_retbyval;
+ FmgrInfo fn_retinput;
+ bool fn_retistuple;
+ bool fn_retset;
+
+ int fn_nargs;
+ int fn_argvarnos[MAXFMGRARGS];
+ int found_varno;
+ int new_varno;
+ int old_varno;
+ int tg_name_varno;
+ int tg_when_varno;
+ int tg_level_varno;
+ int tg_op_varno;
+ int tg_relid_varno;
+ int tg_relname_varno;
+ int tg_nargs_varno;
+
+ int ndatums;
+ PLpgSQL_datum **datums;
+ PLpgSQL_stmt_block *action;
+ struct PLpgSQL_function *next;
+} PLpgSQL_function;
+
+
+typedef struct { /* Runtime execution data */
+ Datum retval;
+ bool retisnull;
+ Oid rettype;
+ bool retistuple;
+ TupleDesc rettupdesc;
+ bool retisset;
+ char *exitlabel;
+
+ int trig_nargs;
+ Datum *trig_argv;
+
+ int found_varno;
+ int ndatums;
+ PLpgSQL_datum **datums;
+} PLpgSQL_execstate;
+
+
+/**********************************************************************
+ * Global variable declarations
+ **********************************************************************/
+
+extern int plpgsql_DumpExecTree;
+extern int plpgsql_SpaceScanned;
+extern int plpgsql_nDatums;
+extern PLpgSQL_datum **plpgsql_Datums;
+
+extern int plpgsql_error_lineno;
+extern char *plpgsql_error_funcname;
+
+extern PLpgSQL_function *plpgsql_curr_compile;
+
+
+/**********************************************************************
+ * Function declarations
+ **********************************************************************/
+
+
+extern char *pstrdup(char *s);
+
+
+/* ----------
+ * Functions in pl_comp.c
+ * ----------
+ */
+extern PLpgSQL_function *plpgsql_compile(Oid fn_oid, int functype);
+extern int plpgsql_parse_word(char *word);
+extern int plpgsql_parse_dblword(char *string);
+extern int plpgsql_parse_tripword(char *string);
+extern int plpgsql_parse_wordtype(char *string);
+extern int plpgsql_parse_dblwordtype(char *string);
+extern int plpgsql_parse_wordrowtype(char *string);
+extern void plpgsql_adddatum(PLpgSQL_datum *new);
+extern int plpgsql_add_initdatums(int **varnos);
+extern void plpgsql_comperrinfo(void);
+
+
+/* ----------
+ * Functions in pl_exec.c
+ * ----------
+ */
+extern Datum plpgsql_exec_function(PLpgSQL_function *func,
+ FmgrValues *args, bool *isNull);
+extern HeapTuple plpgsql_exec_trigger(PLpgSQL_function *func,
+ TriggerData *trigdata);
+
+
+/* ----------
+ * Functions for the dynamic string handling in pl_funcs.c
+ * ----------
+ */
+extern void plpgsql_dstring_init(PLpgSQL_dstring *ds);
+extern void plpgsql_dstring_free(PLpgSQL_dstring *ds);
+extern void plpgsql_dstring_append(PLpgSQL_dstring *ds, char *str);
+extern char *plpgsql_dstring_get(PLpgSQL_dstring *ds);
+
+/* ----------
+ * Functions for the namestack handling in pl_funcs.c
+ * ----------
+ */
+extern void plpgsql_ns_init(void);
+extern bool plpgsql_ns_setlocal(bool flag);
+extern void plpgsql_ns_push(char *label);
+extern void plpgsql_ns_pop(void);
+extern void plpgsql_ns_additem(int itemtype, int itemno, char *name);
+extern PLpgSQL_nsitem *plpgsql_ns_lookup(char *name, char *nsname);
+extern void plpgsql_ns_rename(char *oldname, char *newname);
+
+/* ----------
+ * Other functions in pl_funcs.c
+ * ----------
+ */
+extern void plpgsql_dumptree(PLpgSQL_function *func);
+extern char *plpgsql_tolower(char *s);
+
+/* ----------
+ * Externs in gram.y and scan.l
+ * ----------
+ */
+extern PLpgSQL_expr *plpgsql_read_expression(int until, char *s);
+extern void plpgsql_yyrestart(FILE *fp);
+extern int plpgsql_yylex();
+extern void plpgsql_setinput(char *s, int functype);
+extern int plpgsql_yyparse();
+
+
+#endif /* PLPGSQL_H */
diff --git a/contrib/plpgsql/src/scan.l b/contrib/plpgsql/src/scan.l
new file mode 100644
index 00000000000..64ddbc17505
--- /dev/null
+++ b/contrib/plpgsql/src/scan.l
@@ -0,0 +1,227 @@
+%{
+/**********************************************************************
+ * scan.l - Scanner for the PL/pgSQL
+ * procedural language
+ *
+ * IDENTIFICATION
+ * $Header: /cvsroot/pgsql/contrib/plpgsql/src/Attic/scan.l,v 1.1 1998/08/22 12:38:33 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.
+ *
+ **********************************************************************/
+
+static char *plpgsql_source;
+static int plpgsql_bytes_left;
+static int scanner_functype;
+static int scanner_typereported;
+int plpgsql_SpaceScanned = 0;
+
+static void plpgsql_input(char *buf, int *result, int max);
+#define YY_INPUT(buf,res,max) plpgsql_input(buf, &res, max)
+%}
+
+WS [[:alpha:]_]
+WC [[:alnum:]_]
+
+%x IN_STRING IN_COMMENT
+
+%%
+ /* ----------
+ * Local variable in scanner to remember where
+ * a string or comment started
+ * ----------
+ */
+ int start_lineno = 0;
+
+ /* ----------
+ * Reset the state when entering the scanner
+ * ----------
+ */
+ BEGIN INITIAL;
+ plpgsql_SpaceScanned = 0;
+
+ /* ----------
+ * On the first call to a new source report the
+ * functions type (T_FUNCTION or T_TRIGGER)
+ * ----------
+ */
+ if (!scanner_typereported) {
+ scanner_typereported = 1;
+ return scanner_functype;
+ }
+
+ /* ----------
+ * The keyword rules
+ * ----------
+ */
+:= { return K_ASSIGN; }
+= { return K_ASSIGN; }
+\.\. { return K_DOTDOT; }
+alias { return K_ALIAS; }
+begin { return K_BEGIN; }
+bpchar { return T_BPCHAR; }
+char { return T_CHAR; }
+constant { return K_CONSTANT; }
+debug { return K_DEBUG; }
+declare { return K_DECLARE; }
+default { return K_DEFAULT; }
+else { return K_ELSE; }
+end { return K_END; }
+exception { return K_EXCEPTION; }
+exit { return K_EXIT; }
+for { return K_FOR; }
+from { return K_FROM; }
+if { return K_IF; }
+in { return K_IN; }
+into { return K_INTO; }
+loop { return K_LOOP; }
+not { return K_NOT; }
+notice { return K_NOTICE; }
+null { return K_NULL; }
+perform { return K_PERFORM; }
+raise { return K_RAISE; }
+record { return K_RECORD; }
+rename { return K_RENAME; }
+return { return K_RETURN; }
+reverse { return K_REVERSE; }
+select { return K_SELECT; }
+then { return K_THEN; }
+to { return K_TO; }
+type { return K_TYPE; }
+varchar { return T_VARCHAR; }
+when { return K_WHEN; }
+while { return K_WHILE; }
+
+^#option { return O_OPTION; }
+dump { return O_DUMP; }
+
+
+ /* ----------
+ * Special word rules
+ * ----------
+ */
+{WS}{WC}* { return plpgsql_parse_word(yytext); }
+{WS}{WC}*\.{WS}{WC}* { return plpgsql_parse_dblword(yytext); }
+{WS}{WC}*\.{WS}{WC}*\.{WS}{WC}* { return plpgsql_parse_tripword(yytext); }
+{WS}{WC}*%TYPE { return plpgsql_parse_wordtype(yytext); }
+{WS}{WC}*\.{WS}{WC}*%TYPE { return plpgsql_parse_dblwordtype(yytext); }
+{WS}{WC}*%ROWTYPE { return plpgsql_parse_wordrowtype(yytext); }
+
+\$[0-9]+ { return plpgsql_parse_word(yytext); }
+[0-9]+ { return T_NUMBER; }
+
+ /* ----------
+ * Ignore whitespaces but remember this happened
+ * ----------
+ */
+[ \t\n]+ { plpgsql_SpaceScanned = 1; }
+
+ /* ----------
+ * Eat up comments
+ * ----------
+ */
+--[^\n]* ;
+\/\* { start_lineno = yylineno;
+ BEGIN IN_COMMENT;
+ }
+<IN_COMMENT>\*\/ { BEGIN INITIAL; }
+<IN_COMMENT>\n ;
+<IN_COMMENT>. ;
+<IN_COMMENT><<EOF>> { plpgsql_comperrinfo();
+ elog(ERROR, "unterminated comment starting on line %d",
+ start_lineno);
+ }
+
+ /* ----------
+ * Collect anything inside of ''s and return one STRING
+ * ----------
+ */
+' { start_lineno = yylineno;
+ BEGIN IN_STRING;
+ yymore();
+ }
+<IN_STRING>\\. |
+<IN_STRING>'' { yymore(); }
+<IN_STRING>' { BEGIN INITIAL;
+ return T_STRING;
+ }
+<IN_STRING><<EOF>> { plpgsql_comperrinfo();
+ elog(ERROR, "unterminated string starting on line %d",
+ start_lineno);
+ }
+<IN_STRING>[^'\\]* { yymore(); }
+
+ /* ----------
+ * Any unmatched character is returned as is
+ * ----------
+ */
+. { return yytext[0]; }
+
+%%
+
+int yywrap()
+{
+ return 1;
+}
+
+
+static void plpgsql_input(char *buf, int *result, int max)
+{
+ int n = max;
+ if (n > plpgsql_bytes_left) {
+ n = plpgsql_bytes_left;
+ }
+
+ if (n == 0) {
+ *result = YY_NULL;
+ return;
+ }
+
+ *result = n;
+ memcpy(buf, plpgsql_source, n);
+ plpgsql_source += n;
+ plpgsql_bytes_left -= n;
+}
+
+
+void plpgsql_setinput(char *source, int functype)
+{
+ yyrestart(NULL);
+ yylineno = 1;
+
+ plpgsql_source = source;
+ if (*plpgsql_source == '\n')
+ plpgsql_source++;
+ plpgsql_bytes_left = strlen(plpgsql_source);
+
+ scanner_functype = functype;
+ scanner_typereported = 0;
+}
+
+