/********************************************************************** * pl_comp.c - Compiler part of the PL/pgSQL * procedural language * * IDENTIFICATION * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.14 1999/12/20 01:41:32 tgl 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 #include #include #include #include #include #include #include "plpgsql.h" #include "pl.tab.h" #include "executor/spi.h" #include "commands/trigger.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 char plpgsql_yytext[]; extern int plpgsql_yylineno; /* ---------- * 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 * ---------- */ extern void plpgsql_yyerror(const char *s); 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; Form_pg_type 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(PROCOID, 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(TYPEOID, ObjectIdGetDatum(procStruct->prorettype), 0, 0, 0); if (!HeapTupleIsValid(typeTup)) { plpgsql_comperrinfo(); elog(ERROR, "cache lookup for return type %u failed", procStruct->prorettype); } typeStruct = (Form_pg_type) 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(TYPEOID, ObjectIdGetDatum(procStruct->proargtypes[i]), 0, 0, 0); if (!HeapTupleIsValid(typeTup)) { plpgsql_comperrinfo(); elog(ERROR, "cache lookup for argument type %u failed", procStruct->proargtypes[i]); } typeStruct = (Form_pg_type) 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 %u 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; Form_pg_type typeStruct; char *typeXlated; /* ---------- * We do our lookups case insensitive * ---------- */ cp = plpgsql_tolower(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(TYPENAME, PointerGetDatum(typeXlated), 0, 0, 0); if (HeapTupleIsValid(typeTup)) { PLpgSQL_type *typ; typeStruct = (Form_pg_type) 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_data->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(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(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; Form_pg_type typeStruct; char *typeXlated; bool old_nsstate; /* ---------- * We do our lookups case insensitive * ---------- */ cp = plpgsql_tolower(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(TYPENAME, PointerGetDatum(typeXlated), 0, 0, 0); if (HeapTupleIsValid(typeTup)) { PLpgSQL_type *typ; typeStruct = (Form_pg_type) 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_data->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; Form_pg_attribute attrStruct; HeapTuple typetup; Form_pg_type typeStruct; PLpgSQL_type *typ; /* ---------- * Convert to lower case and separate the words * ---------- */ word1 = plpgsql_tolower(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_data->t_oid), PointerGetDatum(word2), 0, 0); if (!HeapTupleIsValid(attrtup)) { pfree(word1); return T_ERROR; } attrStruct = (Form_pg_attribute) GETSTRUCT(attrtup); typetup = SearchSysCacheTuple(TYPEOID, ObjectIdGetDatum(attrStruct->atttypid), 0, 0, 0); if (!HeapTupleIsValid(typetup)) { plpgsql_comperrinfo(); elog(ERROR, "cache lookup for type %u of %s.%s failed", attrStruct->atttypid, word1, word2); } /* ---------- * Found that - build a compiler type struct and return it * ---------- */ typeStruct = (Form_pg_type) GETSTRUCT(typetup); typ = (PLpgSQL_type *) malloc(sizeof(PLpgSQL_type)); typ->typname = strdup(nameout(&(typeStruct->typname))); typ->typoid = typetup->t_data->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; Form_pg_type typeStruct; HeapTuple attrtup; Form_pg_attribute 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(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(TYPENAME, 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_data->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_data->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 = (Form_pg_attribute) GETSTRUCT(attrtup); typetup = SearchSysCacheTuple(TYPEOID, ObjectIdGetDatum(attrStruct->atttypid), 0, 0, 0); if (!HeapTupleIsValid(typetup)) { plpgsql_comperrinfo(); elog(ERROR, "cache lookup for type %u of %s.%s failed", attrStruct->atttypid, word1, nameout(&(attrStruct->attname))); } typeStruct = (Form_pg_type) 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(NameStr(typeStruct->typname)); var->datatype->typoid = typetup->t_data->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() */