#include "postgres.h"
#include <ctype.h>
#include "access/htup_details.h"
#include "catalog/namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "nodes/makefuncs.h"
#include "parser/parse_node.h"
#include "plpgsql.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/regproc.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
static __thread int datums_alloc;
__thread int plpgsql_nDatums;
__thread PLpgSQL_datum **plpgsql_Datums;
static __thread int datums_last;
__thread char *plpgsql_error_funcname;
__thread bool plpgsql_DumpExecTree = false;
__thread bool plpgsql_check_syntax = false;
__thread PLpgSQL_function *plpgsql_curr_compile;
__thread MemoryContext plpgsql_compile_tmp_cxt;
typedef struct
{
const char *label;
int sqlerrstate;
} ExceptionLabelMap;
static const ExceptionLabelMap exception_label_map[] = {
#include "plerrcodes.h"
{NULL, 0}
};
static void plpgsql_compile_error_callback(void *arg);
static void add_parameter_name(PLpgSQL_nsitem_type itemtype, int itemno, const char *name);
static void add_dummy_return(PLpgSQL_function *function);
static Node *plpgsql_pre_column_ref(ParseState *pstate, ColumnRef *cref);
static Node *plpgsql_post_column_ref(ParseState *pstate, ColumnRef *cref, Node *var);
static Node *plpgsql_param_ref(ParseState *pstate, ParamRef *pref);
static Node *resolve_column_ref(ParseState *pstate, PLpgSQL_expr *expr,
ColumnRef *cref, bool error_if_no_field);
static Node *make_datum_param(PLpgSQL_expr *expr, int dno, int location);
static PLpgSQL_row *build_row_from_vars(PLpgSQL_variable **vars, int numvars);
static PLpgSQL_type *build_datatype(HeapTuple typeTup, int32 typmod,
Oid collation, TypeName *origtypname);
struct compile_error_callback_arg
{
const char *proc_source;
yyscan_t yyscanner;
};
void
plpgsql_compile_callback(FunctionCallInfo fcinfo,
HeapTuple procTup,
const CachedFunctionHashKey *hashkey,
CachedFunction *cfunc,
bool forValidator)
{
PLpgSQL_function *function = (PLpgSQL_function *) cfunc;
Form_pg_proc procStruct = (Form_pg_proc) GETSTRUCT(procTup);
bool is_dml_trigger = CALLED_AS_TRIGGER(fcinfo);
bool is_event_trigger = CALLED_AS_EVENT_TRIGGER(fcinfo);
yyscan_t scanner;
Datum prosrcdatum;
char *proc_source;
HeapTuple typeTup;
Form_pg_type typeStruct;
PLpgSQL_variable *var;
PLpgSQL_rec *rec;
int i;
struct compile_error_callback_arg cbarg;
ErrorContextCallback plerrcontext;
int parse_rc;
Oid rettypeid;
int numargs;
int num_in_args = 0;
int num_out_args = 0;
Oid *argtypes;
char **argnames;
char *argmodes;
int *in_arg_varnos = NULL;
PLpgSQL_variable **out_arg_variables;
MemoryContext func_cxt;
prosrcdatum = SysCacheGetAttrNotNull(PROCOID, procTup, Anum_pg_proc_prosrc);
proc_source = TextDatumGetCString(prosrcdatum);
scanner = plpgsql_scanner_init(proc_source);
plpgsql_error_funcname = pstrdup(NameStr(procStruct->proname));
cbarg.proc_source = forValidator ? proc_source : NULL;
cbarg.yyscanner = scanner;
plerrcontext.callback = plpgsql_compile_error_callback;
plerrcontext.arg = &cbarg;
plerrcontext.previous = error_context_stack;
error_context_stack = &plerrcontext;
plpgsql_check_syntax = forValidator;
plpgsql_curr_compile = function;
func_cxt = AllocSetContextCreate(CurrentMemoryContext,
"PL/pgSQL function",
ALLOCSET_DEFAULT_SIZES);
plpgsql_compile_tmp_cxt = MemoryContextSwitchTo(func_cxt);
function->fn_signature = format_procedure(fcinfo->flinfo->fn_oid);
MemoryContextSetIdentifier(func_cxt, function->fn_signature);
function->fn_oid = fcinfo->flinfo->fn_oid;
function->fn_input_collation = fcinfo->fncollation;
function->fn_cxt = func_cxt;
function->out_param_varno = -1;
function->resolve_option = plpgsql_variable_conflict;
function->print_strict_params = plpgsql_print_strict_params;
function->extra_warnings = forValidator ? plpgsql_extra_warnings : 0;
function->extra_errors = forValidator ? plpgsql_extra_errors : 0;
if (is_dml_trigger)
function->fn_is_trigger = PLPGSQL_DML_TRIGGER;
else if (is_event_trigger)
function->fn_is_trigger = PLPGSQL_EVENT_TRIGGER;
else
function->fn_is_trigger = PLPGSQL_NOT_TRIGGER;
function->fn_prokind = procStruct->prokind;
function->nstatements = 0;
function->requires_procedure_resowner = false;
function->has_exception_block = false;
plpgsql_ns_init();
plpgsql_ns_push(NameStr(procStruct->proname), PLPGSQL_LABEL_BLOCK);
plpgsql_DumpExecTree = false;
plpgsql_start_datums();
switch (function->fn_is_trigger)
{
case PLPGSQL_NOT_TRIGGER:
MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
numargs = get_func_arg_info(procTup,
&argtypes, &argnames, &argmodes);
cfunc_resolve_polymorphic_argtypes(numargs, argtypes, argmodes,
fcinfo->flinfo->fn_expr,
forValidator,
plpgsql_error_funcname);
in_arg_varnos = (int *) palloc(numargs * sizeof(int));
out_arg_variables = (PLpgSQL_variable **) palloc(numargs * sizeof(PLpgSQL_variable *));
MemoryContextSwitchTo(func_cxt);
for (i = 0; i < numargs; i++)
{
char buf[32];
Oid argtypeid = argtypes[i];
char argmode = argmodes ? argmodes[i] : PROARGMODE_IN;
PLpgSQL_type *argdtype;
PLpgSQL_variable *argvariable;
PLpgSQL_nsitem_type argitemtype;
snprintf(buf, sizeof(buf), "$%d", i + 1);
argdtype = plpgsql_build_datatype(argtypeid,
-1,
function->fn_input_collation,
NULL);
if (argdtype->ttype == PLPGSQL_TTYPE_PSEUDO)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("PL/pgSQL functions cannot accept type %s",
format_type_be(argtypeid))));
argvariable = plpgsql_build_variable((argnames &&
argnames[i][0] != '\0') ?
argnames[i] : buf,
0, argdtype, false);
if (argvariable->dtype == PLPGSQL_DTYPE_VAR)
{
argitemtype = PLPGSQL_NSTYPE_VAR;
}
else
{
Assert(argvariable->dtype == PLPGSQL_DTYPE_REC);
argitemtype = PLPGSQL_NSTYPE_REC;
}
if (argmode == PROARGMODE_IN ||
argmode == PROARGMODE_INOUT ||
argmode == PROARGMODE_VARIADIC)
in_arg_varnos[num_in_args++] = argvariable->dno;
if (argmode == PROARGMODE_OUT ||
argmode == PROARGMODE_INOUT ||
argmode == PROARGMODE_TABLE)
out_arg_variables[num_out_args++] = argvariable;
add_parameter_name(argitemtype, argvariable->dno, buf);
if (argnames && argnames[i][0] != '\0')
add_parameter_name(argitemtype, argvariable->dno,
argnames[i]);
}
if (num_out_args > 1 ||
(num_out_args == 1 && function->fn_prokind == PROKIND_PROCEDURE))
{
PLpgSQL_row *row = build_row_from_vars(out_arg_variables,
num_out_args);
plpgsql_adddatum((PLpgSQL_datum *) row);
function->out_param_varno = row->dno;
}
else if (num_out_args == 1)
function->out_param_varno = out_arg_variables[0]->dno;
rettypeid = procStruct->prorettype;
if (IsPolymorphicType(rettypeid))
{
if (forValidator)
{
if (rettypeid == ANYARRAYOID ||
rettypeid == ANYCOMPATIBLEARRAYOID)
rettypeid = INT4ARRAYOID;
else if (rettypeid == ANYRANGEOID ||
rettypeid == ANYCOMPATIBLERANGEOID)
rettypeid = INT4RANGEOID;
else if (rettypeid == ANYMULTIRANGEOID)
rettypeid = INT4MULTIRANGEOID;
else
rettypeid = INT4OID;
}
else
{
rettypeid = get_fn_expr_rettype(fcinfo->flinfo);
if (!OidIsValid(rettypeid))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("could not determine actual return type "
"for polymorphic function \"%s\"",
plpgsql_error_funcname)));
}
}
function->fn_rettype = rettypeid;
function->fn_retset = procStruct->proretset;
typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(rettypeid));
if (!HeapTupleIsValid(typeTup))
elog(ERROR, "cache lookup failed for type %u", rettypeid);
typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
if (typeStruct->typtype == TYPTYPE_PSEUDO)
{
if (rettypeid == VOIDOID ||
rettypeid == RECORDOID)
;
else if (rettypeid == TRIGGEROID || rettypeid == EVENT_TRIGGEROID)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("trigger functions can only be called as triggers")));
else
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("PL/pgSQL functions cannot return type %s",
format_type_be(rettypeid))));
}
function->fn_retistuple = type_is_rowtype(rettypeid);
function->fn_retisdomain = (typeStruct->typtype == TYPTYPE_DOMAIN);
function->fn_retbyval = typeStruct->typbyval;
function->fn_rettyplen = typeStruct->typlen;
if (IsPolymorphicType(procStruct->prorettype) &&
num_out_args == 0)
{
(void) plpgsql_build_variable("$0", 0,
build_datatype(typeTup,
-1,
function->fn_input_collation,
NULL),
true);
}
ReleaseSysCache(typeTup);
break;
case PLPGSQL_DML_TRIGGER:
function->fn_rettype = InvalidOid;
function->fn_retbyval = false;
function->fn_retistuple = true;
function->fn_retisdomain = false;
function->fn_retset = false;
if (procStruct->pronargs != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("trigger functions cannot have declared arguments"),
errhint("The arguments of the trigger can be accessed through TG_NARGS and TG_ARGV instead.")));
rec = plpgsql_build_record("new", 0, NULL, RECORDOID, true);
function->new_varno = rec->dno;
rec = plpgsql_build_record("old", 0, NULL, RECORDOID, true);
function->old_varno = rec->dno;
var = plpgsql_build_variable("tg_name", 0,
plpgsql_build_datatype(NAMEOID,
-1,
function->fn_input_collation,
NULL),
true);
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
var->dtype = PLPGSQL_DTYPE_PROMISE;
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_NAME;
var = plpgsql_build_variable("tg_when", 0,
plpgsql_build_datatype(TEXTOID,
-1,
function->fn_input_collation,
NULL),
true);
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
var->dtype = PLPGSQL_DTYPE_PROMISE;
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_WHEN;
var = plpgsql_build_variable("tg_level", 0,
plpgsql_build_datatype(TEXTOID,
-1,
function->fn_input_collation,
NULL),
true);
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
var->dtype = PLPGSQL_DTYPE_PROMISE;
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_LEVEL;
var = plpgsql_build_variable("tg_op", 0,
plpgsql_build_datatype(TEXTOID,
-1,
function->fn_input_collation,
NULL),
true);
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
var->dtype = PLPGSQL_DTYPE_PROMISE;
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_OP;
var = plpgsql_build_variable("tg_relid", 0,
plpgsql_build_datatype(OIDOID,
-1,
InvalidOid,
NULL),
true);
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
var->dtype = PLPGSQL_DTYPE_PROMISE;
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_RELID;
var = plpgsql_build_variable("tg_relname", 0,
plpgsql_build_datatype(NAMEOID,
-1,
function->fn_input_collation,
NULL),
true);
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
var->dtype = PLPGSQL_DTYPE_PROMISE;
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_TABLE_NAME;
var = plpgsql_build_variable("tg_table_name", 0,
plpgsql_build_datatype(NAMEOID,
-1,
function->fn_input_collation,
NULL),
true);
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
var->dtype = PLPGSQL_DTYPE_PROMISE;
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_TABLE_NAME;
var = plpgsql_build_variable("tg_table_schema", 0,
plpgsql_build_datatype(NAMEOID,
-1,
function->fn_input_collation,
NULL),
true);
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
var->dtype = PLPGSQL_DTYPE_PROMISE;
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_TABLE_SCHEMA;
var = plpgsql_build_variable("tg_nargs", 0,
plpgsql_build_datatype(INT4OID,
-1,
InvalidOid,
NULL),
true);
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
var->dtype = PLPGSQL_DTYPE_PROMISE;
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_NARGS;
var = plpgsql_build_variable("tg_argv", 0,
plpgsql_build_datatype(TEXTARRAYOID,
-1,
function->fn_input_collation,
NULL),
true);
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
var->dtype = PLPGSQL_DTYPE_PROMISE;
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_ARGV;
break;
case PLPGSQL_EVENT_TRIGGER:
function->fn_rettype = VOIDOID;
function->fn_retbyval = false;
function->fn_retistuple = true;
function->fn_retisdomain = false;
function->fn_retset = false;
if (procStruct->pronargs != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("event trigger functions cannot have declared arguments")));
var = plpgsql_build_variable("tg_event", 0,
plpgsql_build_datatype(TEXTOID,
-1,
function->fn_input_collation,
NULL),
true);
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
var->dtype = PLPGSQL_DTYPE_PROMISE;
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_EVENT;
var = plpgsql_build_variable("tg_tag", 0,
plpgsql_build_datatype(TEXTOID,
-1,
function->fn_input_collation,
NULL),
true);
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
var->dtype = PLPGSQL_DTYPE_PROMISE;
((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_TAG;
break;
default:
elog(ERROR, "unrecognized function typecode: %d",
(int) function->fn_is_trigger);
break;
}
function->fn_readonly = (procStruct->provolatile != PROVOLATILE_VOLATILE);
var = plpgsql_build_variable("found", 0,
plpgsql_build_datatype(BOOLOID,
-1,
InvalidOid,
NULL),
true);
function->found_varno = var->dno;
parse_rc = plpgsql_yyparse(&function->action, scanner);
if (parse_rc != 0)
elog(ERROR, "plpgsql parser returned %d", parse_rc);
plpgsql_scanner_finish(scanner);
pfree(proc_source);
if (num_out_args > 0 || function->fn_rettype == VOIDOID ||
function->fn_retset)
add_dummy_return(function);
function->fn_nargs = procStruct->pronargs;
for (i = 0; i < function->fn_nargs; i++)
function->fn_argvarnos[i] = in_arg_varnos[i];
plpgsql_finish_datums(function);
if (function->has_exception_block)
plpgsql_mark_local_assignment_targets(function);
if (plpgsql_DumpExecTree)
plpgsql_dumptree(function);
MemoryContextSetParent(func_cxt, CacheMemoryContext);
error_context_stack = plerrcontext.previous;
plpgsql_error_funcname = NULL;
plpgsql_check_syntax = false;
MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
plpgsql_compile_tmp_cxt = NULL;
}
PLpgSQL_function *
plpgsql_compile_inline(char *proc_source)
{
yyscan_t scanner;
char *func_name = "inline_code_block";
PLpgSQL_function *function;
struct compile_error_callback_arg cbarg;
ErrorContextCallback plerrcontext;
PLpgSQL_variable *var;
int parse_rc;
MemoryContext func_cxt;
scanner = plpgsql_scanner_init(proc_source);
plpgsql_error_funcname = func_name;
cbarg.proc_source = proc_source;
cbarg.yyscanner = scanner;
plerrcontext.callback = plpgsql_compile_error_callback;
plerrcontext.arg = &cbarg;
plerrcontext.previous = error_context_stack;
error_context_stack = &plerrcontext;
plpgsql_check_syntax = check_function_bodies;
function = (PLpgSQL_function *) palloc0(sizeof(PLpgSQL_function));
plpgsql_curr_compile = function;
func_cxt = AllocSetContextCreate(CurrentMemoryContext,
"PL/pgSQL inline code context",
ALLOCSET_DEFAULT_SIZES);
plpgsql_compile_tmp_cxt = MemoryContextSwitchTo(func_cxt);
function->fn_signature = pstrdup(func_name);
function->fn_is_trigger = PLPGSQL_NOT_TRIGGER;
function->fn_input_collation = InvalidOid;
function->fn_cxt = func_cxt;
function->out_param_varno = -1;
function->resolve_option = plpgsql_variable_conflict;
function->print_strict_params = plpgsql_print_strict_params;
function->extra_warnings = 0;
function->extra_errors = 0;
function->nstatements = 0;
function->requires_procedure_resowner = false;
function->has_exception_block = false;
plpgsql_ns_init();
plpgsql_ns_push(func_name, PLPGSQL_LABEL_BLOCK);
plpgsql_DumpExecTree = false;
plpgsql_start_datums();
function->fn_rettype = VOIDOID;
function->fn_retset = false;
function->fn_retistuple = false;
function->fn_retisdomain = false;
function->fn_prokind = PROKIND_FUNCTION;
function->fn_retbyval = true;
function->fn_rettyplen = sizeof(int32);
function->fn_readonly = false;
var = plpgsql_build_variable("found", 0,
plpgsql_build_datatype(BOOLOID,
-1,
InvalidOid,
NULL),
true);
function->found_varno = var->dno;
parse_rc = plpgsql_yyparse(&function->action, scanner);
if (parse_rc != 0)
elog(ERROR, "plpgsql parser returned %d", parse_rc);
plpgsql_scanner_finish(scanner);
if (function->fn_rettype == VOIDOID)
add_dummy_return(function);
function->fn_nargs = 0;
plpgsql_finish_datums(function);
if (function->has_exception_block)
plpgsql_mark_local_assignment_targets(function);
if (plpgsql_DumpExecTree)
plpgsql_dumptree(function);
error_context_stack = plerrcontext.previous;
plpgsql_error_funcname = NULL;
plpgsql_check_syntax = false;
MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
plpgsql_compile_tmp_cxt = NULL;
return function;
}
static void
plpgsql_compile_error_callback(void *arg)
{
struct compile_error_callback_arg *cbarg = (struct compile_error_callback_arg *) arg;
yyscan_t yyscanner = cbarg->yyscanner;
if (cbarg->proc_source)
{
if (function_parse_error_transpose(cbarg->proc_source))
return;
}
if (plpgsql_error_funcname)
errcontext("compilation of PL/pgSQL function \"%s\" near line %d",
plpgsql_error_funcname, plpgsql_latest_lineno(yyscanner));
}
static void
add_parameter_name(PLpgSQL_nsitem_type itemtype, int itemno, const char *name)
{
if (plpgsql_ns_lookup(plpgsql_ns_top(), true,
name, NULL, NULL,
NULL) != NULL)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("parameter name \"%s\" used more than once",
name)));
plpgsql_ns_additem(itemtype, itemno, name);
}
static void
add_dummy_return(PLpgSQL_function *function)
{
if (function->action->exceptions != NULL ||
function->action->label != NULL)
{
PLpgSQL_stmt_block *new;
new = palloc0(sizeof(PLpgSQL_stmt_block));
new->cmd_type = PLPGSQL_STMT_BLOCK;
new->stmtid = ++function->nstatements;
new->body = list_make1(function->action);
function->action = new;
}
if (function->action->body == NIL ||
((PLpgSQL_stmt *) llast(function->action->body))->cmd_type != PLPGSQL_STMT_RETURN)
{
PLpgSQL_stmt_return *new;
new = palloc0(sizeof(PLpgSQL_stmt_return));
new->cmd_type = PLPGSQL_STMT_RETURN;
new->stmtid = ++function->nstatements;
new->expr = NULL;
new->retvarno = function->out_param_varno;
function->action->body = lappend(function->action->body, new);
}
}
bool
plpgsql_parse_word(char *word1, const char *yytxt, bool lookup,
PLwdatum *wdatum, PLword *word)
{
PLpgSQL_nsitem *ns;
if (lookup && plpgsql_IdentifierLookup == IDENTIFIER_LOOKUP_NORMAL)
{
ns = plpgsql_ns_lookup(plpgsql_ns_top(), false,
word1, NULL, NULL,
NULL);
if (ns != NULL)
{
switch (ns->itemtype)
{
case PLPGSQL_NSTYPE_VAR:
case PLPGSQL_NSTYPE_REC:
wdatum->datum = plpgsql_Datums[ns->itemno];
wdatum->ident = word1;
wdatum->quoted = (yytxt[0] == '"');
wdatum->idents = NIL;
return true;
default:
elog(ERROR, "unrecognized plpgsql itemtype: %d",
ns->itemtype);
}
}
}
word->ident = word1;
word->quoted = (yytxt[0] == '"');
return false;
}
bool
plpgsql_parse_dblword(char *word1, char *word2,
PLwdatum *wdatum, PLcword *cword)
{
PLpgSQL_nsitem *ns;
List *idents;
int nnames;
idents = list_make2(makeString(word1),
makeString(word2));
if (plpgsql_IdentifierLookup != IDENTIFIER_LOOKUP_DECLARE)
{
ns = plpgsql_ns_lookup(plpgsql_ns_top(), false,
word1, word2, NULL,
&nnames);
if (ns != NULL)
{
switch (ns->itemtype)
{
case PLPGSQL_NSTYPE_VAR:
wdatum->datum = plpgsql_Datums[ns->itemno];
wdatum->ident = NULL;
wdatum->quoted = false;
wdatum->idents = idents;
return true;
case PLPGSQL_NSTYPE_REC:
if (nnames == 1)
{
PLpgSQL_rec *rec;
PLpgSQL_recfield *new;
rec = (PLpgSQL_rec *) (plpgsql_Datums[ns->itemno]);
new = plpgsql_build_recfield(rec, word2);
wdatum->datum = (PLpgSQL_datum *) new;
}
else
{
wdatum->datum = plpgsql_Datums[ns->itemno];
}
wdatum->ident = NULL;
wdatum->quoted = false;
wdatum->idents = idents;
return true;
default:
break;
}
}
}
cword->idents = idents;
return false;
}
bool
plpgsql_parse_tripword(char *word1, char *word2, char *word3,
PLwdatum *wdatum, PLcword *cword)
{
PLpgSQL_nsitem *ns;
List *idents;
int nnames;
if (plpgsql_IdentifierLookup != IDENTIFIER_LOOKUP_DECLARE)
{
ns = plpgsql_ns_lookup(plpgsql_ns_top(), false,
word1, word2, word3,
&nnames);
if (ns != NULL)
{
switch (ns->itemtype)
{
case PLPGSQL_NSTYPE_REC:
{
PLpgSQL_rec *rec;
PLpgSQL_recfield *new;
rec = (PLpgSQL_rec *) (plpgsql_Datums[ns->itemno]);
if (nnames == 1)
{
new = plpgsql_build_recfield(rec, word2);
idents = list_make2(makeString(word1),
makeString(word2));
}
else
{
new = plpgsql_build_recfield(rec, word3);
idents = list_make3(makeString(word1),
makeString(word2),
makeString(word3));
}
wdatum->datum = (PLpgSQL_datum *) new;
wdatum->ident = NULL;
wdatum->quoted = false;
wdatum->idents = idents;
return true;
}
default:
break;
}
}
}
idents = list_make3(makeString(word1),
makeString(word2),
makeString(word3));
cword->idents = idents;
return false;
}
PLpgSQL_type *
plpgsql_parse_wordtype(char *ident)
{
PLpgSQL_type *typ;
typ = (PLpgSQL_type *) palloc0(sizeof(PLpgSQL_type));
typ->typname = psprintf("%s%%TYPE", quote_identifier(ident));
typ->origtypname = makeTypeName(ident);
typ->origtypname->pct_type = true;
typ->ttype = PLPGSQL_TTYPE_SCALAR;
return typ;
}
PLpgSQL_type *
plpgsql_parse_cwordtype(List *idents)
{
PLpgSQL_type *typ;
typ = (PLpgSQL_type *) palloc0(sizeof(PLpgSQL_type));
typ->typname = psprintf("%s%%TYPE", NameListToQuotedString(idents));
typ->origtypname = makeTypeNameFromNameList(idents);
typ->origtypname->pct_type = true;
typ->ttype = PLPGSQL_TTYPE_SCALAR;
return typ;
}
PLpgSQL_type *
plpgsql_parse_wordrowtype(char *ident)
{
PLpgSQL_type *typ;
typ = (PLpgSQL_type *) palloc0(sizeof(PLpgSQL_type));
typ->typname = psprintf("%s%%rowtype", quote_identifier(ident));
typ->origtypname = makeTypeName(ident);
typ->ttype = PLPGSQL_TTYPE_SCALAR;
return typ;
}
PLpgSQL_type *
plpgsql_parse_cwordrowtype(List *idents)
{
PLpgSQL_type *typ;
typ = (PLpgSQL_type *) palloc0(sizeof(PLpgSQL_type));
typ->typname = psprintf("%s%%rowtype", NameListToQuotedString(idents));
typ->origtypname = makeTypeNameFromNameList(idents);
typ->ttype = PLPGSQL_TTYPE_SCALAR;
return typ;
}
PLpgSQL_variable *
plpgsql_build_variable(const char *refname, int lineno, PLpgSQL_type *dtype,
bool add2namespace)
{
PLpgSQL_variable *result;
switch (dtype->ttype)
{
case PLPGSQL_TTYPE_SCALAR:
{
PLpgSQL_var *var;
var = palloc0(sizeof(PLpgSQL_var));
var->dtype = PLPGSQL_DTYPE_VAR;
var->refname = pstrdup(refname);
var->lineno = lineno;
var->datatype = dtype;
var->value = 0;
var->isnull = true;
var->freeval = false;
plpgsql_adddatum((PLpgSQL_datum *) var);
if (add2namespace)
plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR,
var->dno,
refname);
result = (PLpgSQL_variable *) var;
break;
}
case PLPGSQL_TTYPE_REC:
{
PLpgSQL_rec *rec;
rec = plpgsql_build_record(refname, lineno,
dtype, dtype->typoid,
add2namespace);
result = (PLpgSQL_variable *) rec;
break;
}
case PLPGSQL_TTYPE_PSEUDO:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("variable \"%s\" has pseudo-type %s",
refname, format_type_be(dtype->typoid))));
result = NULL;
break;
default:
elog(ERROR, "unrecognized ttype: %d", dtype->ttype);
result = NULL;
break;
}
return result;
}
PLpgSQL_rec *
plpgsql_build_record(const char *refname, int lineno,
PLpgSQL_type *dtype, Oid rectypeid,
bool add2namespace)
{
PLpgSQL_rec *rec;
rec = palloc0(sizeof(PLpgSQL_rec));
rec->dtype = PLPGSQL_DTYPE_REC;
rec->refname = pstrdup(refname);
rec->lineno = lineno;
rec->datatype = dtype;
rec->rectypeid = rectypeid;
rec->firstfield = -1;
rec->erh = NULL;
plpgsql_adddatum((PLpgSQL_datum *) rec);
if (add2namespace)
plpgsql_ns_additem(PLPGSQL_NSTYPE_REC, rec->dno, rec->refname);
return rec;
}
static PLpgSQL_row *
build_row_from_vars(PLpgSQL_variable **vars, int numvars)
{
PLpgSQL_row *row;
int i;
row = palloc0(sizeof(PLpgSQL_row));
row->dtype = PLPGSQL_DTYPE_ROW;
row->refname = "(unnamed row)";
row->lineno = -1;
row->rowtupdesc = CreateTemplateTupleDesc(numvars);
row->nfields = numvars;
row->fieldnames = palloc(numvars * sizeof(char *));
row->varnos = palloc(numvars * sizeof(int));
for (i = 0; i < numvars; i++)
{
PLpgSQL_variable *var = vars[i];
Oid typoid;
int32 typmod;
Oid typcoll;
Assert(!var->isconst);
switch (var->dtype)
{
case PLPGSQL_DTYPE_VAR:
case PLPGSQL_DTYPE_PROMISE:
typoid = ((PLpgSQL_var *) var)->datatype->typoid;
typmod = ((PLpgSQL_var *) var)->datatype->atttypmod;
typcoll = ((PLpgSQL_var *) var)->datatype->collation;
break;
case PLPGSQL_DTYPE_REC:
typoid = ((PLpgSQL_rec *) var)->rectypeid;
typmod = -1;
typcoll = InvalidOid;
break;
default:
elog(ERROR, "unrecognized dtype: %d", var->dtype);
typoid = InvalidOid;
typmod = 0;
typcoll = InvalidOid;
break;
}
row->fieldnames[i] = var->refname;
row->varnos[i] = var->dno;
TupleDescInitEntry(row->rowtupdesc, i + 1,
var->refname,
typoid, typmod,
0);
TupleDescInitEntryCollation(row->rowtupdesc, i + 1, typcoll);
}
return row;
}
PLpgSQL_recfield *
plpgsql_build_recfield(PLpgSQL_rec *rec, const char *fldname)
{
PLpgSQL_recfield *recfield;
int i;
i = rec->firstfield;
while (i >= 0)
{
PLpgSQL_recfield *fld = (PLpgSQL_recfield *) plpgsql_Datums[i];
Assert(fld->dtype == PLPGSQL_DTYPE_RECFIELD &&
fld->recparentno == rec->dno);
if (strcmp(fld->fieldname, fldname) == 0)
return fld;
i = fld->nextfield;
}
recfield = palloc0(sizeof(PLpgSQL_recfield));
recfield->dtype = PLPGSQL_DTYPE_RECFIELD;
recfield->fieldname = pstrdup(fldname);
recfield->recparentno = rec->dno;
recfield->rectupledescid = INVALID_TUPLEDESC_IDENTIFIER;
plpgsql_adddatum((PLpgSQL_datum *) recfield);
recfield->nextfield = rec->firstfield;
rec->firstfield = recfield->dno;
return recfield;
}
PLpgSQL_type *
plpgsql_build_datatype(Oid typeOid, int32 typmod,
Oid collation, TypeName *origtypname)
{
HeapTuple typeTup;
PLpgSQL_type *typ;
typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typeOid));
if (!HeapTupleIsValid(typeTup))
elog(ERROR, "cache lookup failed for type %u", typeOid);
typ = build_datatype(typeTup, typmod, collation, origtypname);
ReleaseSysCache(typeTup);
return typ;
}
static PLpgSQL_type *
build_datatype(HeapTuple typeTup, int32 typmod,
Oid collation, TypeName *origtypname)
{
Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
PLpgSQL_type *typ;
if (!typeStruct->typisdefined)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("type \"%s\" is only a shell",
NameStr(typeStruct->typname))));
typ = (PLpgSQL_type *) palloc(sizeof(PLpgSQL_type));
typ->typname = pstrdup(NameStr(typeStruct->typname));
typ->typoid = typeStruct->oid;
switch (typeStruct->typtype)
{
case TYPTYPE_BASE:
case TYPTYPE_ENUM:
case TYPTYPE_RANGE:
case TYPTYPE_MULTIRANGE:
typ->ttype = PLPGSQL_TTYPE_SCALAR;
break;
case TYPTYPE_COMPOSITE:
typ->ttype = PLPGSQL_TTYPE_REC;
break;
case TYPTYPE_DOMAIN:
if (type_is_rowtype(typeStruct->typbasetype))
typ->ttype = PLPGSQL_TTYPE_REC;
else
typ->ttype = PLPGSQL_TTYPE_SCALAR;
break;
case TYPTYPE_PSEUDO:
if (typ->typoid == RECORDOID)
typ->ttype = PLPGSQL_TTYPE_REC;
else
typ->ttype = PLPGSQL_TTYPE_PSEUDO;
break;
default:
elog(ERROR, "unrecognized typtype: %d",
(int) typeStruct->typtype);
break;
}
typ->typlen = typeStruct->typlen;
typ->typbyval = typeStruct->typbyval;
typ->typtype = typeStruct->typtype;
typ->collation = typeStruct->typcollation;
if (OidIsValid(collation) && OidIsValid(typ->collation))
typ->collation = collation;
if (typeStruct->typtype == TYPTYPE_BASE)
{
typ->typisarray = (IsTrueArrayType(typeStruct) &&
typeStruct->typstorage != TYPSTORAGE_PLAIN);
}
else if (typeStruct->typtype == TYPTYPE_DOMAIN)
{
typ->typisarray = (typeStruct->typlen == -1 &&
typeStruct->typstorage != TYPSTORAGE_PLAIN &&
OidIsValid(get_base_element_type(typeStruct->typbasetype)));
}
else
typ->typisarray = false;
typ->atttypmod = typmod;
{
typ->origtypname = NULL;
typ->tcache = NULL;
typ->tupdesc_id = 0;
}
return typ;
}
PLpgSQL_type *
plpgsql_build_datatype_arrayof(PLpgSQL_type *dtype)
{
Oid array_typeid;
if (dtype->typisarray)
return dtype;
array_typeid = get_array_type(dtype->typoid);
if (!OidIsValid(array_typeid))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("could not find array type for data type %s",
format_type_be(dtype->typoid))));
return plpgsql_build_datatype(array_typeid, dtype->atttypmod,
dtype->collation, NULL);
}
int
plpgsql_recognize_err_condition(const char *condname, bool allow_sqlstate)
{
int i;
if (allow_sqlstate)
{
if (strlen(condname) == 5 &&
strspn(condname, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") == 5)
return MAKE_SQLSTATE(condname[0],
condname[1],
condname[2],
condname[3],
condname[4]);
}
for (i = 0; exception_label_map[i].label != NULL; i++)
{
if (strcmp(condname, exception_label_map[i].label) == 0)
return exception_label_map[i].sqlerrstate;
}
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("unrecognized exception condition \"%s\"",
condname)));
return 0;
}
PLpgSQL_condition *
plpgsql_parse_err_condition(char *condname)
{
int i;
PLpgSQL_condition *new;
PLpgSQL_condition *prev;
if (strcmp(condname, "others") == 0)
{
new = palloc(sizeof(PLpgSQL_condition));
new->sqlerrstate = PLPGSQL_OTHERS;
new->condname = condname;
new->next = NULL;
return new;
}
prev = NULL;
for (i = 0; exception_label_map[i].label != NULL; i++)
{
if (strcmp(condname, exception_label_map[i].label) == 0)
{
new = palloc(sizeof(PLpgSQL_condition));
new->sqlerrstate = exception_label_map[i].sqlerrstate;
new->condname = condname;
new->next = prev;
prev = new;
}
}
if (!prev)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("unrecognized exception condition \"%s\"",
condname)));
return prev;
}
void
plpgsql_start_datums(void)
{
datums_alloc = 128;
plpgsql_nDatums = 0;
plpgsql_Datums = MemoryContextAlloc(plpgsql_compile_tmp_cxt,
sizeof(PLpgSQL_datum *) * datums_alloc);
datums_last = 0;
}
void
plpgsql_adddatum(PLpgSQL_datum *newdatum)
{
if (plpgsql_nDatums == datums_alloc)
{
datums_alloc *= 2;
plpgsql_Datums = repalloc(plpgsql_Datums, sizeof(PLpgSQL_datum *) * datums_alloc);
}
newdatum->dno = plpgsql_nDatums;
plpgsql_Datums[plpgsql_nDatums++] = newdatum;
}
void
plpgsql_finish_datums(PLpgSQL_function *function)
{
Size copiable_size = 0;
int i;
function->ndatums = plpgsql_nDatums;
function->datums = palloc(sizeof(PLpgSQL_datum *) * plpgsql_nDatums);
for (i = 0; i < plpgsql_nDatums; i++)
{
function->datums[i] = plpgsql_Datums[i];
switch (function->datums[i]->dtype)
{
case PLPGSQL_DTYPE_VAR:
case PLPGSQL_DTYPE_PROMISE:
copiable_size += MAXALIGN(sizeof(PLpgSQL_var));
break;
case PLPGSQL_DTYPE_REC:
copiable_size += MAXALIGN(sizeof(PLpgSQL_rec));
break;
default:
break;
}
}
function->copiable_size = copiable_size;
}
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:
case PLPGSQL_DTYPE_REC:
n++;
break;
default:
break;
}
}
if (varnos != NULL)
{
if (n > 0)
{
*varnos = (int *) palloc(sizeof(int) * n);
n = 0;
for (i = datums_last; i < plpgsql_nDatums; i++)
{
switch (plpgsql_Datums[i]->dtype)
{
case PLPGSQL_DTYPE_VAR:
case PLPGSQL_DTYPE_REC:
(*varnos)[n++] = plpgsql_Datums[i]->dno;
default:
break;
}
}
}
else
*varnos = NULL;
}
datums_last = plpgsql_nDatums;
return n;
}