#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_type.h"
#include "plpgsql.h"
#include "utils/builtins.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/regproc.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
__thread PLpgSQL_stmt_block *plpgsql_parse_result;
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 plpgsql_hashent
{
PLpgSQL_func_hashkey key;
PLpgSQL_function *function;
} plpgsql_HashEnt;
#define FUNCS_PER_USER 128
typedef struct
{
const char *label;
int sqlerrstate;
} ExceptionLabelMap;
static const ExceptionLabelMap exception_label_map[] = {
#include "plerrcodes.h"
{NULL, 0}
};
static PLpgSQL_function *do_compile(FunctionCallInfo fcinfo,
HeapTuple procTup,
PLpgSQL_function *function,
PLpgSQL_func_hashkey *hashkey,
bool forValidator);
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);
static void compute_function_hashkey(FunctionCallInfo fcinfo,
Form_pg_proc procStruct,
PLpgSQL_func_hashkey *hashkey,
bool forValidator);
static void plpgsql_resolve_polymorphic_argtypes(int numargs,
Oid *argtypes, char *argmodes,
Node *call_expr, bool forValidator,
const char *proname);
static PLpgSQL_function *plpgsql_HashTableLookup(PLpgSQL_func_hashkey *func_key);
static void plpgsql_HashTableInsert(PLpgSQL_function *function,
PLpgSQL_func_hashkey *func_key);
static void plpgsql_HashTableDelete(PLpgSQL_function *function);
static void delete_function(PLpgSQL_function *func);
PLpgSQL_function *
plpgsql_compile_inline(char *proc_source)
{
char *func_name = "inline_code_block";
PLpgSQL_function *function;
ErrorContextCallback plerrcontext;
PLpgSQL_variable *var;
int parse_rc;
MemoryContext func_cxt;
plpgsql_scanner_init(proc_source);
plpgsql_error_funcname = func_name;
plerrcontext.callback = plpgsql_compile_error_callback;
plerrcontext.arg = proc_source;
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;
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();
if (parse_rc != 0)
elog(ERROR, "plpgsql parser returned %d", parse_rc);
function->action = plpgsql_parse_result;
plpgsql_scanner_finish();
if (function->fn_rettype == VOIDOID)
add_dummy_return(function);
function->fn_nargs = 0;
plpgsql_finish_datums(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)
{
if (arg)
{
if (function_parse_error_transpose((const char *) arg))
return;
}
if (plpgsql_error_funcname)
errcontext("compilation of PL/pgSQL function \"%s\" near line %d",
plpgsql_error_funcname, plpgsql_latest_lineno());
}
static void
add_dummy_return(PLpgSQL_function *function)
{
if (function->action->exceptions != 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;
idents = list_make3(makeString(word1),
makeString(word2),
makeString(word3));
if (plpgsql_IdentifierLookup != IDENTIFIER_LOOKUP_DECLARE)
{
ns = plpgsql_ns_lookup(plpgsql_ns_top(), false,
word1, word2, word3,
&nnames);
if (ns != NULL && nnames == 2)
{
switch (ns->itemtype)
{
case PLPGSQL_NSTYPE_REC:
{
PLpgSQL_rec *rec;
PLpgSQL_recfield *new;
rec = (PLpgSQL_rec *) (plpgsql_Datums[ns->itemno]);
new = plpgsql_build_recfield(rec, word3);
wdatum->datum = (PLpgSQL_datum *) new;
wdatum->ident = NULL;
wdatum->quoted = false;
wdatum->idents = idents;
return true;
}
default:
break;
}
}
}
cword->idents = idents;
return false;
}
PLpgSQL_type * plpgsql_parse_wordtype(char *ident) { return NULL; }
PLpgSQL_type * plpgsql_parse_cwordtype(List *idents) { return NULL; }
PLpgSQL_type * plpgsql_parse_wordrowtype(char *ident) { return NULL; }
PLpgSQL_type * plpgsql_parse_cwordrowtype(List *idents) { return NULL; }
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;
}
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) { PLpgSQL_type *typ; typ = (PLpgSQL_type *) palloc0(sizeof(PLpgSQL_type)); typ->typname = pstrdup("UNKNOWN"); typ->ttype = PLPGSQL_TTYPE_SCALAR; return typ; }
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 = 0;
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;
}