1725 lines
36 KiB
C
1725 lines
36 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* pl_funcs.c - Misc functions for the PL/pgSQL
|
|
* procedural language
|
|
*
|
|
* Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/pl/plpgsql/src/pl_funcs.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "plpgsql.h"
|
|
#include "utils/memutils.h"
|
|
|
|
/* ----------
|
|
* Local variables for namespace handling
|
|
*
|
|
* The namespace structure actually forms a tree, of which only one linear
|
|
* list or "chain" (from the youngest item to the root) is accessible from
|
|
* any one plpgsql statement. During initial parsing of a function, ns_top
|
|
* points to the youngest item accessible from the block currently being
|
|
* parsed. We store the entire tree, however, since at runtime we will need
|
|
* to access the chain that's relevant to any one statement.
|
|
*
|
|
* Block boundaries in the namespace chain are marked by PLPGSQL_NSTYPE_LABEL
|
|
* items.
|
|
* ----------
|
|
*/
|
|
static PLpgSQL_nsitem *ns_top = NULL;
|
|
|
|
|
|
/* ----------
|
|
* plpgsql_ns_init Initialize namespace processing for a new function
|
|
* ----------
|
|
*/
|
|
void
|
|
plpgsql_ns_init(void)
|
|
{
|
|
ns_top = NULL;
|
|
}
|
|
|
|
|
|
/* ----------
|
|
* plpgsql_ns_push Create a new namespace level
|
|
* ----------
|
|
*/
|
|
void
|
|
plpgsql_ns_push(const char *label, PLpgSQL_label_type label_type)
|
|
{
|
|
if (label == NULL)
|
|
label = "";
|
|
plpgsql_ns_additem(PLPGSQL_NSTYPE_LABEL, (int) label_type, label);
|
|
}
|
|
|
|
|
|
/* ----------
|
|
* plpgsql_ns_pop Pop entries back to (and including) the last label
|
|
* ----------
|
|
*/
|
|
void
|
|
plpgsql_ns_pop(void)
|
|
{
|
|
Assert(ns_top != NULL);
|
|
while (ns_top->itemtype != PLPGSQL_NSTYPE_LABEL)
|
|
ns_top = ns_top->prev;
|
|
ns_top = ns_top->prev;
|
|
}
|
|
|
|
|
|
/* ----------
|
|
* plpgsql_ns_top Fetch the current namespace chain end
|
|
* ----------
|
|
*/
|
|
PLpgSQL_nsitem *
|
|
plpgsql_ns_top(void)
|
|
{
|
|
return ns_top;
|
|
}
|
|
|
|
|
|
/* ----------
|
|
* plpgsql_ns_additem Add an item to the current namespace chain
|
|
* ----------
|
|
*/
|
|
void
|
|
plpgsql_ns_additem(PLpgSQL_nsitem_type itemtype, int itemno, const char *name)
|
|
{
|
|
PLpgSQL_nsitem *nse;
|
|
|
|
Assert(name != NULL);
|
|
/* first item added must be a label */
|
|
Assert(ns_top != NULL || itemtype == PLPGSQL_NSTYPE_LABEL);
|
|
|
|
nse = palloc(offsetof(PLpgSQL_nsitem, name) + strlen(name) + 1);
|
|
nse->itemtype = itemtype;
|
|
nse->itemno = itemno;
|
|
nse->prev = ns_top;
|
|
strcpy(nse->name, name);
|
|
ns_top = nse;
|
|
}
|
|
|
|
|
|
/* ----------
|
|
* plpgsql_ns_lookup Lookup an identifier in the given namespace chain
|
|
*
|
|
* Note that this only searches for variables, not labels.
|
|
*
|
|
* If localmode is true, only the topmost block level is searched.
|
|
*
|
|
* name1 must be non-NULL. Pass NULL for name2 and/or name3 if parsing a name
|
|
* with fewer than three components.
|
|
*
|
|
* If names_used isn't NULL, *names_used receives the number of names
|
|
* matched: 0 if no match, 1 if name1 matched an unqualified variable name,
|
|
* 2 if name1 and name2 matched a block label + variable name.
|
|
*
|
|
* Note that name3 is never directly matched to anything. However, if it
|
|
* isn't NULL, we will disregard qualified matches to scalar variables.
|
|
* Similarly, if name2 isn't NULL, we disregard unqualified matches to
|
|
* scalar variables.
|
|
* ----------
|
|
*/
|
|
PLpgSQL_nsitem *
|
|
plpgsql_ns_lookup(PLpgSQL_nsitem *ns_cur, bool localmode,
|
|
const char *name1, const char *name2, const char *name3,
|
|
int *names_used)
|
|
{
|
|
/* Outer loop iterates once per block level in the namespace chain */
|
|
while (ns_cur != NULL)
|
|
{
|
|
PLpgSQL_nsitem *nsitem;
|
|
|
|
/* Check this level for unqualified match to variable name */
|
|
for (nsitem = ns_cur;
|
|
nsitem->itemtype != PLPGSQL_NSTYPE_LABEL;
|
|
nsitem = nsitem->prev)
|
|
{
|
|
if (strcmp(nsitem->name, name1) == 0)
|
|
{
|
|
if (name2 == NULL ||
|
|
nsitem->itemtype != PLPGSQL_NSTYPE_VAR)
|
|
{
|
|
if (names_used)
|
|
*names_used = 1;
|
|
return nsitem;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Check this level for qualified match to variable name */
|
|
if (name2 != NULL &&
|
|
strcmp(nsitem->name, name1) == 0)
|
|
{
|
|
for (nsitem = ns_cur;
|
|
nsitem->itemtype != PLPGSQL_NSTYPE_LABEL;
|
|
nsitem = nsitem->prev)
|
|
{
|
|
if (strcmp(nsitem->name, name2) == 0)
|
|
{
|
|
if (name3 == NULL ||
|
|
nsitem->itemtype != PLPGSQL_NSTYPE_VAR)
|
|
{
|
|
if (names_used)
|
|
*names_used = 2;
|
|
return nsitem;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (localmode)
|
|
break; /* do not look into upper levels */
|
|
|
|
ns_cur = nsitem->prev;
|
|
}
|
|
|
|
/* This is just to suppress possibly-uninitialized-variable warnings */
|
|
if (names_used)
|
|
*names_used = 0;
|
|
return NULL; /* No match found */
|
|
}
|
|
|
|
|
|
/* ----------
|
|
* plpgsql_ns_lookup_label Lookup a label in the given namespace chain
|
|
* ----------
|
|
*/
|
|
PLpgSQL_nsitem *
|
|
plpgsql_ns_lookup_label(PLpgSQL_nsitem *ns_cur, const char *name)
|
|
{
|
|
while (ns_cur != NULL)
|
|
{
|
|
if (ns_cur->itemtype == PLPGSQL_NSTYPE_LABEL &&
|
|
strcmp(ns_cur->name, name) == 0)
|
|
return ns_cur;
|
|
ns_cur = ns_cur->prev;
|
|
}
|
|
|
|
return NULL; /* label not found */
|
|
}
|
|
|
|
|
|
/* ----------
|
|
* plpgsql_ns_find_nearest_loop Find innermost loop label in namespace chain
|
|
* ----------
|
|
*/
|
|
PLpgSQL_nsitem *
|
|
plpgsql_ns_find_nearest_loop(PLpgSQL_nsitem *ns_cur)
|
|
{
|
|
while (ns_cur != NULL)
|
|
{
|
|
if (ns_cur->itemtype == PLPGSQL_NSTYPE_LABEL &&
|
|
ns_cur->itemno == PLPGSQL_LABEL_LOOP)
|
|
return ns_cur;
|
|
ns_cur = ns_cur->prev;
|
|
}
|
|
|
|
return NULL; /* no loop found */
|
|
}
|
|
|
|
|
|
/*
|
|
* Statement type as a string, for use in error messages etc.
|
|
*/
|
|
const char *
|
|
plpgsql_stmt_typename(PLpgSQL_stmt *stmt)
|
|
{
|
|
switch (stmt->cmd_type)
|
|
{
|
|
case PLPGSQL_STMT_BLOCK:
|
|
return _("statement block");
|
|
case PLPGSQL_STMT_ASSIGN:
|
|
return _("assignment");
|
|
case PLPGSQL_STMT_IF:
|
|
return "IF";
|
|
case PLPGSQL_STMT_CASE:
|
|
return "CASE";
|
|
case PLPGSQL_STMT_LOOP:
|
|
return "LOOP";
|
|
case PLPGSQL_STMT_WHILE:
|
|
return "WHILE";
|
|
case PLPGSQL_STMT_FORI:
|
|
return _("FOR with integer loop variable");
|
|
case PLPGSQL_STMT_FORS:
|
|
return _("FOR over SELECT rows");
|
|
case PLPGSQL_STMT_FORC:
|
|
return _("FOR over cursor");
|
|
case PLPGSQL_STMT_FOREACH_A:
|
|
return _("FOREACH over array");
|
|
case PLPGSQL_STMT_EXIT:
|
|
return ((PLpgSQL_stmt_exit *) stmt)->is_exit ? "EXIT" : "CONTINUE";
|
|
case PLPGSQL_STMT_RETURN:
|
|
return "RETURN";
|
|
case PLPGSQL_STMT_RETURN_NEXT:
|
|
return "RETURN NEXT";
|
|
case PLPGSQL_STMT_RETURN_QUERY:
|
|
return "RETURN QUERY";
|
|
case PLPGSQL_STMT_RAISE:
|
|
return "RAISE";
|
|
case PLPGSQL_STMT_ASSERT:
|
|
return "ASSERT";
|
|
case PLPGSQL_STMT_EXECSQL:
|
|
return _("SQL statement");
|
|
case PLPGSQL_STMT_DYNEXECUTE:
|
|
return "EXECUTE";
|
|
case PLPGSQL_STMT_DYNFORS:
|
|
return _("FOR over EXECUTE statement");
|
|
case PLPGSQL_STMT_GETDIAG:
|
|
return ((PLpgSQL_stmt_getdiag *) stmt)->is_stacked ?
|
|
"GET STACKED DIAGNOSTICS" : "GET DIAGNOSTICS";
|
|
case PLPGSQL_STMT_OPEN:
|
|
return "OPEN";
|
|
case PLPGSQL_STMT_FETCH:
|
|
return ((PLpgSQL_stmt_fetch *) stmt)->is_move ? "MOVE" : "FETCH";
|
|
case PLPGSQL_STMT_CLOSE:
|
|
return "CLOSE";
|
|
case PLPGSQL_STMT_PERFORM:
|
|
return "PERFORM";
|
|
case PLPGSQL_STMT_CALL:
|
|
return ((PLpgSQL_stmt_call *) stmt)->is_call ? "CALL" : "DO";
|
|
case PLPGSQL_STMT_COMMIT:
|
|
return "COMMIT";
|
|
case PLPGSQL_STMT_ROLLBACK:
|
|
return "ROLLBACK";
|
|
case PLPGSQL_STMT_SET:
|
|
return "SET";
|
|
}
|
|
|
|
return "unknown";
|
|
}
|
|
|
|
/*
|
|
* GET DIAGNOSTICS item name as a string, for use in error messages etc.
|
|
*/
|
|
const char *
|
|
plpgsql_getdiag_kindname(PLpgSQL_getdiag_kind kind)
|
|
{
|
|
switch (kind)
|
|
{
|
|
case PLPGSQL_GETDIAG_ROW_COUNT:
|
|
return "ROW_COUNT";
|
|
case PLPGSQL_GETDIAG_CONTEXT:
|
|
return "PG_CONTEXT";
|
|
case PLPGSQL_GETDIAG_ERROR_CONTEXT:
|
|
return "PG_EXCEPTION_CONTEXT";
|
|
case PLPGSQL_GETDIAG_ERROR_DETAIL:
|
|
return "PG_EXCEPTION_DETAIL";
|
|
case PLPGSQL_GETDIAG_ERROR_HINT:
|
|
return "PG_EXCEPTION_HINT";
|
|
case PLPGSQL_GETDIAG_RETURNED_SQLSTATE:
|
|
return "RETURNED_SQLSTATE";
|
|
case PLPGSQL_GETDIAG_COLUMN_NAME:
|
|
return "COLUMN_NAME";
|
|
case PLPGSQL_GETDIAG_CONSTRAINT_NAME:
|
|
return "CONSTRAINT_NAME";
|
|
case PLPGSQL_GETDIAG_DATATYPE_NAME:
|
|
return "PG_DATATYPE_NAME";
|
|
case PLPGSQL_GETDIAG_MESSAGE_TEXT:
|
|
return "MESSAGE_TEXT";
|
|
case PLPGSQL_GETDIAG_TABLE_NAME:
|
|
return "TABLE_NAME";
|
|
case PLPGSQL_GETDIAG_SCHEMA_NAME:
|
|
return "SCHEMA_NAME";
|
|
}
|
|
|
|
return "unknown";
|
|
}
|
|
|
|
|
|
/**********************************************************************
|
|
* Release memory when a PL/pgSQL function is no longer needed
|
|
*
|
|
* The code for recursing through the function tree is really only
|
|
* needed to locate PLpgSQL_expr nodes, which may contain references
|
|
* to saved SPI Plans that must be freed. The function tree itself,
|
|
* along with subsidiary data, is freed in one swoop by freeing the
|
|
* function's permanent memory context.
|
|
**********************************************************************/
|
|
static void free_stmt(PLpgSQL_stmt *stmt);
|
|
static void free_block(PLpgSQL_stmt_block *block);
|
|
static void free_assign(PLpgSQL_stmt_assign *stmt);
|
|
static void free_if(PLpgSQL_stmt_if *stmt);
|
|
static void free_case(PLpgSQL_stmt_case *stmt);
|
|
static void free_loop(PLpgSQL_stmt_loop *stmt);
|
|
static void free_while(PLpgSQL_stmt_while *stmt);
|
|
static void free_fori(PLpgSQL_stmt_fori *stmt);
|
|
static void free_fors(PLpgSQL_stmt_fors *stmt);
|
|
static void free_forc(PLpgSQL_stmt_forc *stmt);
|
|
static void free_foreach_a(PLpgSQL_stmt_foreach_a *stmt);
|
|
static void free_exit(PLpgSQL_stmt_exit *stmt);
|
|
static void free_return(PLpgSQL_stmt_return *stmt);
|
|
static void free_return_next(PLpgSQL_stmt_return_next *stmt);
|
|
static void free_return_query(PLpgSQL_stmt_return_query *stmt);
|
|
static void free_raise(PLpgSQL_stmt_raise *stmt);
|
|
static void free_assert(PLpgSQL_stmt_assert *stmt);
|
|
static void free_execsql(PLpgSQL_stmt_execsql *stmt);
|
|
static void free_dynexecute(PLpgSQL_stmt_dynexecute *stmt);
|
|
static void free_dynfors(PLpgSQL_stmt_dynfors *stmt);
|
|
static void free_getdiag(PLpgSQL_stmt_getdiag *stmt);
|
|
static void free_open(PLpgSQL_stmt_open *stmt);
|
|
static void free_fetch(PLpgSQL_stmt_fetch *stmt);
|
|
static void free_close(PLpgSQL_stmt_close *stmt);
|
|
static void free_perform(PLpgSQL_stmt_perform *stmt);
|
|
static void free_call(PLpgSQL_stmt_call *stmt);
|
|
static void free_commit(PLpgSQL_stmt_commit *stmt);
|
|
static void free_rollback(PLpgSQL_stmt_rollback *stmt);
|
|
static void free_set(PLpgSQL_stmt_set *stmt);
|
|
static void free_expr(PLpgSQL_expr *expr);
|
|
|
|
|
|
static void
|
|
free_stmt(PLpgSQL_stmt *stmt)
|
|
{
|
|
switch (stmt->cmd_type)
|
|
{
|
|
case PLPGSQL_STMT_BLOCK:
|
|
free_block((PLpgSQL_stmt_block *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_ASSIGN:
|
|
free_assign((PLpgSQL_stmt_assign *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_IF:
|
|
free_if((PLpgSQL_stmt_if *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_CASE:
|
|
free_case((PLpgSQL_stmt_case *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_LOOP:
|
|
free_loop((PLpgSQL_stmt_loop *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_WHILE:
|
|
free_while((PLpgSQL_stmt_while *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_FORI:
|
|
free_fori((PLpgSQL_stmt_fori *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_FORS:
|
|
free_fors((PLpgSQL_stmt_fors *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_FORC:
|
|
free_forc((PLpgSQL_stmt_forc *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_FOREACH_A:
|
|
free_foreach_a((PLpgSQL_stmt_foreach_a *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_EXIT:
|
|
free_exit((PLpgSQL_stmt_exit *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_RETURN:
|
|
free_return((PLpgSQL_stmt_return *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_RETURN_NEXT:
|
|
free_return_next((PLpgSQL_stmt_return_next *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_RETURN_QUERY:
|
|
free_return_query((PLpgSQL_stmt_return_query *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_RAISE:
|
|
free_raise((PLpgSQL_stmt_raise *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_ASSERT:
|
|
free_assert((PLpgSQL_stmt_assert *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_EXECSQL:
|
|
free_execsql((PLpgSQL_stmt_execsql *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_DYNEXECUTE:
|
|
free_dynexecute((PLpgSQL_stmt_dynexecute *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_DYNFORS:
|
|
free_dynfors((PLpgSQL_stmt_dynfors *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_GETDIAG:
|
|
free_getdiag((PLpgSQL_stmt_getdiag *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_OPEN:
|
|
free_open((PLpgSQL_stmt_open *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_FETCH:
|
|
free_fetch((PLpgSQL_stmt_fetch *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_CLOSE:
|
|
free_close((PLpgSQL_stmt_close *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_PERFORM:
|
|
free_perform((PLpgSQL_stmt_perform *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_CALL:
|
|
free_call((PLpgSQL_stmt_call *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_COMMIT:
|
|
free_commit((PLpgSQL_stmt_commit *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_ROLLBACK:
|
|
free_rollback((PLpgSQL_stmt_rollback *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_SET:
|
|
free_set((PLpgSQL_stmt_set *) stmt);
|
|
break;
|
|
default:
|
|
elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
free_stmts(List *stmts)
|
|
{
|
|
ListCell *s;
|
|
|
|
foreach(s, stmts)
|
|
{
|
|
free_stmt((PLpgSQL_stmt *) lfirst(s));
|
|
}
|
|
}
|
|
|
|
static void
|
|
free_block(PLpgSQL_stmt_block *block)
|
|
{
|
|
free_stmts(block->body);
|
|
if (block->exceptions)
|
|
{
|
|
ListCell *e;
|
|
|
|
foreach(e, block->exceptions->exc_list)
|
|
{
|
|
PLpgSQL_exception *exc = (PLpgSQL_exception *) lfirst(e);
|
|
|
|
free_stmts(exc->action);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
free_assign(PLpgSQL_stmt_assign *stmt)
|
|
{
|
|
free_expr(stmt->expr);
|
|
}
|
|
|
|
static void
|
|
free_if(PLpgSQL_stmt_if *stmt)
|
|
{
|
|
ListCell *l;
|
|
|
|
free_expr(stmt->cond);
|
|
free_stmts(stmt->then_body);
|
|
foreach(l, stmt->elsif_list)
|
|
{
|
|
PLpgSQL_if_elsif *elif = (PLpgSQL_if_elsif *) lfirst(l);
|
|
|
|
free_expr(elif->cond);
|
|
free_stmts(elif->stmts);
|
|
}
|
|
free_stmts(stmt->else_body);
|
|
}
|
|
|
|
static void
|
|
free_case(PLpgSQL_stmt_case *stmt)
|
|
{
|
|
ListCell *l;
|
|
|
|
free_expr(stmt->t_expr);
|
|
foreach(l, stmt->case_when_list)
|
|
{
|
|
PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l);
|
|
|
|
free_expr(cwt->expr);
|
|
free_stmts(cwt->stmts);
|
|
}
|
|
free_stmts(stmt->else_stmts);
|
|
}
|
|
|
|
static void
|
|
free_loop(PLpgSQL_stmt_loop *stmt)
|
|
{
|
|
free_stmts(stmt->body);
|
|
}
|
|
|
|
static void
|
|
free_while(PLpgSQL_stmt_while *stmt)
|
|
{
|
|
free_expr(stmt->cond);
|
|
free_stmts(stmt->body);
|
|
}
|
|
|
|
static void
|
|
free_fori(PLpgSQL_stmt_fori *stmt)
|
|
{
|
|
free_expr(stmt->lower);
|
|
free_expr(stmt->upper);
|
|
free_expr(stmt->step);
|
|
free_stmts(stmt->body);
|
|
}
|
|
|
|
static void
|
|
free_fors(PLpgSQL_stmt_fors *stmt)
|
|
{
|
|
free_stmts(stmt->body);
|
|
free_expr(stmt->query);
|
|
}
|
|
|
|
static void
|
|
free_forc(PLpgSQL_stmt_forc *stmt)
|
|
{
|
|
free_stmts(stmt->body);
|
|
free_expr(stmt->argquery);
|
|
}
|
|
|
|
static void
|
|
free_foreach_a(PLpgSQL_stmt_foreach_a *stmt)
|
|
{
|
|
free_expr(stmt->expr);
|
|
free_stmts(stmt->body);
|
|
}
|
|
|
|
static void
|
|
free_open(PLpgSQL_stmt_open *stmt)
|
|
{
|
|
ListCell *lc;
|
|
|
|
free_expr(stmt->argquery);
|
|
free_expr(stmt->query);
|
|
free_expr(stmt->dynquery);
|
|
foreach(lc, stmt->params)
|
|
{
|
|
free_expr((PLpgSQL_expr *) lfirst(lc));
|
|
}
|
|
}
|
|
|
|
static void
|
|
free_fetch(PLpgSQL_stmt_fetch *stmt)
|
|
{
|
|
free_expr(stmt->expr);
|
|
}
|
|
|
|
static void
|
|
free_close(PLpgSQL_stmt_close *stmt)
|
|
{
|
|
}
|
|
|
|
static void
|
|
free_perform(PLpgSQL_stmt_perform *stmt)
|
|
{
|
|
free_expr(stmt->expr);
|
|
}
|
|
|
|
static void
|
|
free_call(PLpgSQL_stmt_call *stmt)
|
|
{
|
|
free_expr(stmt->expr);
|
|
}
|
|
|
|
static void
|
|
free_commit(PLpgSQL_stmt_commit *stmt)
|
|
{
|
|
}
|
|
|
|
static void
|
|
free_rollback(PLpgSQL_stmt_rollback *stmt)
|
|
{
|
|
}
|
|
|
|
static void
|
|
free_set(PLpgSQL_stmt_set *stmt)
|
|
{
|
|
free_expr(stmt->expr);
|
|
}
|
|
|
|
static void
|
|
free_exit(PLpgSQL_stmt_exit *stmt)
|
|
{
|
|
free_expr(stmt->cond);
|
|
}
|
|
|
|
static void
|
|
free_return(PLpgSQL_stmt_return *stmt)
|
|
{
|
|
free_expr(stmt->expr);
|
|
}
|
|
|
|
static void
|
|
free_return_next(PLpgSQL_stmt_return_next *stmt)
|
|
{
|
|
free_expr(stmt->expr);
|
|
}
|
|
|
|
static void
|
|
free_return_query(PLpgSQL_stmt_return_query *stmt)
|
|
{
|
|
ListCell *lc;
|
|
|
|
free_expr(stmt->query);
|
|
free_expr(stmt->dynquery);
|
|
foreach(lc, stmt->params)
|
|
{
|
|
free_expr((PLpgSQL_expr *) lfirst(lc));
|
|
}
|
|
}
|
|
|
|
static void
|
|
free_raise(PLpgSQL_stmt_raise *stmt)
|
|
{
|
|
ListCell *lc;
|
|
|
|
foreach(lc, stmt->params)
|
|
{
|
|
free_expr((PLpgSQL_expr *) lfirst(lc));
|
|
}
|
|
foreach(lc, stmt->options)
|
|
{
|
|
PLpgSQL_raise_option *opt = (PLpgSQL_raise_option *) lfirst(lc);
|
|
|
|
free_expr(opt->expr);
|
|
}
|
|
}
|
|
|
|
static void
|
|
free_assert(PLpgSQL_stmt_assert *stmt)
|
|
{
|
|
free_expr(stmt->cond);
|
|
free_expr(stmt->message);
|
|
}
|
|
|
|
static void
|
|
free_execsql(PLpgSQL_stmt_execsql *stmt)
|
|
{
|
|
free_expr(stmt->sqlstmt);
|
|
}
|
|
|
|
static void
|
|
free_dynexecute(PLpgSQL_stmt_dynexecute *stmt)
|
|
{
|
|
ListCell *lc;
|
|
|
|
free_expr(stmt->query);
|
|
foreach(lc, stmt->params)
|
|
{
|
|
free_expr((PLpgSQL_expr *) lfirst(lc));
|
|
}
|
|
}
|
|
|
|
static void
|
|
free_dynfors(PLpgSQL_stmt_dynfors *stmt)
|
|
{
|
|
ListCell *lc;
|
|
|
|
free_stmts(stmt->body);
|
|
free_expr(stmt->query);
|
|
foreach(lc, stmt->params)
|
|
{
|
|
free_expr((PLpgSQL_expr *) lfirst(lc));
|
|
}
|
|
}
|
|
|
|
static void
|
|
free_getdiag(PLpgSQL_stmt_getdiag *stmt)
|
|
{
|
|
}
|
|
|
|
static void
|
|
free_expr(PLpgSQL_expr *expr)
|
|
{
|
|
if (expr && expr->plan)
|
|
{
|
|
SPI_freeplan(expr->plan);
|
|
expr->plan = NULL;
|
|
}
|
|
}
|
|
|
|
void
|
|
plpgsql_free_function_memory(PLpgSQL_function *func)
|
|
{
|
|
int i;
|
|
|
|
/* Better not call this on an in-use function */
|
|
Assert(func->use_count == 0);
|
|
|
|
/* Release plans associated with variable declarations */
|
|
for (i = 0; i < func->ndatums; i++)
|
|
{
|
|
PLpgSQL_datum *d = func->datums[i];
|
|
|
|
switch (d->dtype)
|
|
{
|
|
case PLPGSQL_DTYPE_VAR:
|
|
case PLPGSQL_DTYPE_PROMISE:
|
|
{
|
|
PLpgSQL_var *var = (PLpgSQL_var *) d;
|
|
|
|
free_expr(var->default_val);
|
|
free_expr(var->cursor_explicit_expr);
|
|
}
|
|
break;
|
|
case PLPGSQL_DTYPE_ROW:
|
|
break;
|
|
case PLPGSQL_DTYPE_REC:
|
|
{
|
|
PLpgSQL_rec *rec = (PLpgSQL_rec *) d;
|
|
|
|
free_expr(rec->default_val);
|
|
}
|
|
break;
|
|
case PLPGSQL_DTYPE_RECFIELD:
|
|
break;
|
|
case PLPGSQL_DTYPE_ARRAYELEM:
|
|
free_expr(((PLpgSQL_arrayelem *) d)->subscript);
|
|
break;
|
|
default:
|
|
elog(ERROR, "unrecognized data type: %d", d->dtype);
|
|
}
|
|
}
|
|
func->ndatums = 0;
|
|
|
|
/* Release plans in statement tree */
|
|
if (func->action)
|
|
free_block(func->action);
|
|
func->action = NULL;
|
|
|
|
/*
|
|
* And finally, release all memory except the PLpgSQL_function struct
|
|
* itself (which has to be kept around because there may be multiple
|
|
* fn_extra pointers to it).
|
|
*/
|
|
if (func->fn_cxt)
|
|
MemoryContextDelete(func->fn_cxt);
|
|
func->fn_cxt = NULL;
|
|
}
|
|
|
|
|
|
/**********************************************************************
|
|
* Debug functions for analyzing the compiled code
|
|
**********************************************************************/
|
|
static int dump_indent;
|
|
|
|
static void dump_ind(void);
|
|
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_case(PLpgSQL_stmt_case *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_forc(PLpgSQL_stmt_forc *stmt);
|
|
static void dump_foreach_a(PLpgSQL_stmt_foreach_a *stmt);
|
|
static void dump_exit(PLpgSQL_stmt_exit *stmt);
|
|
static void dump_return(PLpgSQL_stmt_return *stmt);
|
|
static void dump_return_next(PLpgSQL_stmt_return_next *stmt);
|
|
static void dump_return_query(PLpgSQL_stmt_return_query *stmt);
|
|
static void dump_raise(PLpgSQL_stmt_raise *stmt);
|
|
static void dump_assert(PLpgSQL_stmt_assert *stmt);
|
|
static void dump_execsql(PLpgSQL_stmt_execsql *stmt);
|
|
static void dump_dynexecute(PLpgSQL_stmt_dynexecute *stmt);
|
|
static void dump_dynfors(PLpgSQL_stmt_dynfors *stmt);
|
|
static void dump_getdiag(PLpgSQL_stmt_getdiag *stmt);
|
|
static void dump_open(PLpgSQL_stmt_open *stmt);
|
|
static void dump_fetch(PLpgSQL_stmt_fetch *stmt);
|
|
static void dump_cursor_direction(PLpgSQL_stmt_fetch *stmt);
|
|
static void dump_close(PLpgSQL_stmt_close *stmt);
|
|
static void dump_perform(PLpgSQL_stmt_perform *stmt);
|
|
static void dump_call(PLpgSQL_stmt_call *stmt);
|
|
static void dump_commit(PLpgSQL_stmt_commit *stmt);
|
|
static void dump_rollback(PLpgSQL_stmt_rollback *stmt);
|
|
static void dump_set(PLpgSQL_stmt_set *stmt);
|
|
static void dump_expr(PLpgSQL_expr *expr);
|
|
|
|
|
|
static void
|
|
dump_ind(void)
|
|
{
|
|
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_CASE:
|
|
dump_case((PLpgSQL_stmt_case *) 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_FORC:
|
|
dump_forc((PLpgSQL_stmt_forc *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_FOREACH_A:
|
|
dump_foreach_a((PLpgSQL_stmt_foreach_a *) 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_RETURN_NEXT:
|
|
dump_return_next((PLpgSQL_stmt_return_next *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_RETURN_QUERY:
|
|
dump_return_query((PLpgSQL_stmt_return_query *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_RAISE:
|
|
dump_raise((PLpgSQL_stmt_raise *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_ASSERT:
|
|
dump_assert((PLpgSQL_stmt_assert *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_EXECSQL:
|
|
dump_execsql((PLpgSQL_stmt_execsql *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_DYNEXECUTE:
|
|
dump_dynexecute((PLpgSQL_stmt_dynexecute *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_DYNFORS:
|
|
dump_dynfors((PLpgSQL_stmt_dynfors *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_GETDIAG:
|
|
dump_getdiag((PLpgSQL_stmt_getdiag *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_OPEN:
|
|
dump_open((PLpgSQL_stmt_open *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_FETCH:
|
|
dump_fetch((PLpgSQL_stmt_fetch *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_CLOSE:
|
|
dump_close((PLpgSQL_stmt_close *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_PERFORM:
|
|
dump_perform((PLpgSQL_stmt_perform *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_CALL:
|
|
dump_call((PLpgSQL_stmt_call *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_COMMIT:
|
|
dump_commit((PLpgSQL_stmt_commit *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_ROLLBACK:
|
|
dump_rollback((PLpgSQL_stmt_rollback *) stmt);
|
|
break;
|
|
case PLPGSQL_STMT_SET:
|
|
dump_set((PLpgSQL_stmt_set *) stmt);
|
|
break;
|
|
default:
|
|
elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
dump_stmts(List *stmts)
|
|
{
|
|
ListCell *s;
|
|
|
|
dump_indent += 2;
|
|
foreach(s, stmts)
|
|
dump_stmt((PLpgSQL_stmt *) lfirst(s));
|
|
dump_indent -= 2;
|
|
}
|
|
|
|
static void
|
|
dump_block(PLpgSQL_stmt_block *block)
|
|
{
|
|
char *name;
|
|
|
|
if (block->label == NULL)
|
|
name = "*unnamed*";
|
|
else
|
|
name = block->label;
|
|
|
|
dump_ind();
|
|
printf("BLOCK <<%s>>\n", name);
|
|
|
|
dump_stmts(block->body);
|
|
|
|
if (block->exceptions)
|
|
{
|
|
ListCell *e;
|
|
|
|
foreach(e, block->exceptions->exc_list)
|
|
{
|
|
PLpgSQL_exception *exc = (PLpgSQL_exception *) lfirst(e);
|
|
PLpgSQL_condition *cond;
|
|
|
|
dump_ind();
|
|
printf(" EXCEPTION WHEN ");
|
|
for (cond = exc->conditions; cond; cond = cond->next)
|
|
{
|
|
if (cond != exc->conditions)
|
|
printf(" OR ");
|
|
printf("%s", cond->condname);
|
|
}
|
|
printf(" THEN\n");
|
|
dump_stmts(exc->action);
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
ListCell *l;
|
|
|
|
dump_ind();
|
|
printf("IF ");
|
|
dump_expr(stmt->cond);
|
|
printf(" THEN\n");
|
|
dump_stmts(stmt->then_body);
|
|
foreach(l, stmt->elsif_list)
|
|
{
|
|
PLpgSQL_if_elsif *elif = (PLpgSQL_if_elsif *) lfirst(l);
|
|
|
|
dump_ind();
|
|
printf(" ELSIF ");
|
|
dump_expr(elif->cond);
|
|
printf(" THEN\n");
|
|
dump_stmts(elif->stmts);
|
|
}
|
|
if (stmt->else_body != NIL)
|
|
{
|
|
dump_ind();
|
|
printf(" ELSE\n");
|
|
dump_stmts(stmt->else_body);
|
|
}
|
|
dump_ind();
|
|
printf(" ENDIF\n");
|
|
}
|
|
|
|
static void
|
|
dump_case(PLpgSQL_stmt_case *stmt)
|
|
{
|
|
ListCell *l;
|
|
|
|
dump_ind();
|
|
printf("CASE %d ", stmt->t_varno);
|
|
if (stmt->t_expr)
|
|
dump_expr(stmt->t_expr);
|
|
printf("\n");
|
|
dump_indent += 6;
|
|
foreach(l, stmt->case_when_list)
|
|
{
|
|
PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l);
|
|
|
|
dump_ind();
|
|
printf("WHEN ");
|
|
dump_expr(cwt->expr);
|
|
printf("\n");
|
|
dump_ind();
|
|
printf("THEN\n");
|
|
dump_indent += 2;
|
|
dump_stmts(cwt->stmts);
|
|
dump_indent -= 2;
|
|
}
|
|
if (stmt->have_else)
|
|
{
|
|
dump_ind();
|
|
printf("ELSE\n");
|
|
dump_indent += 2;
|
|
dump_stmts(stmt->else_stmts);
|
|
dump_indent -= 2;
|
|
}
|
|
dump_indent -= 6;
|
|
dump_ind();
|
|
printf(" ENDCASE\n");
|
|
}
|
|
|
|
static void
|
|
dump_loop(PLpgSQL_stmt_loop *stmt)
|
|
{
|
|
dump_ind();
|
|
printf("LOOP\n");
|
|
|
|
dump_stmts(stmt->body);
|
|
|
|
dump_ind();
|
|
printf(" ENDLOOP\n");
|
|
}
|
|
|
|
static void
|
|
dump_while(PLpgSQL_stmt_while *stmt)
|
|
{
|
|
dump_ind();
|
|
printf("WHILE ");
|
|
dump_expr(stmt->cond);
|
|
printf("\n");
|
|
|
|
dump_stmts(stmt->body);
|
|
|
|
dump_ind();
|
|
printf(" ENDWHILE\n");
|
|
}
|
|
|
|
static void
|
|
dump_fori(PLpgSQL_stmt_fori *stmt)
|
|
{
|
|
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");
|
|
if (stmt->step)
|
|
{
|
|
dump_ind();
|
|
printf(" step = ");
|
|
dump_expr(stmt->step);
|
|
printf("\n");
|
|
}
|
|
dump_indent -= 2;
|
|
|
|
dump_stmts(stmt->body);
|
|
|
|
dump_ind();
|
|
printf(" ENDFORI\n");
|
|
}
|
|
|
|
static void
|
|
dump_fors(PLpgSQL_stmt_fors *stmt)
|
|
{
|
|
dump_ind();
|
|
printf("FORS %s ", stmt->var->refname);
|
|
dump_expr(stmt->query);
|
|
printf("\n");
|
|
|
|
dump_stmts(stmt->body);
|
|
|
|
dump_ind();
|
|
printf(" ENDFORS\n");
|
|
}
|
|
|
|
static void
|
|
dump_forc(PLpgSQL_stmt_forc *stmt)
|
|
{
|
|
dump_ind();
|
|
printf("FORC %s ", stmt->var->refname);
|
|
printf("curvar=%d\n", stmt->curvar);
|
|
|
|
dump_indent += 2;
|
|
if (stmt->argquery != NULL)
|
|
{
|
|
dump_ind();
|
|
printf(" arguments = ");
|
|
dump_expr(stmt->argquery);
|
|
printf("\n");
|
|
}
|
|
dump_indent -= 2;
|
|
|
|
dump_stmts(stmt->body);
|
|
|
|
dump_ind();
|
|
printf(" ENDFORC\n");
|
|
}
|
|
|
|
static void
|
|
dump_foreach_a(PLpgSQL_stmt_foreach_a *stmt)
|
|
{
|
|
dump_ind();
|
|
printf("FOREACHA var %d ", stmt->varno);
|
|
if (stmt->slice != 0)
|
|
printf("SLICE %d ", stmt->slice);
|
|
printf("IN ");
|
|
dump_expr(stmt->expr);
|
|
printf("\n");
|
|
|
|
dump_stmts(stmt->body);
|
|
|
|
dump_ind();
|
|
printf(" ENDFOREACHA");
|
|
}
|
|
|
|
static void
|
|
dump_open(PLpgSQL_stmt_open *stmt)
|
|
{
|
|
dump_ind();
|
|
printf("OPEN curvar=%d\n", stmt->curvar);
|
|
|
|
dump_indent += 2;
|
|
if (stmt->argquery != NULL)
|
|
{
|
|
dump_ind();
|
|
printf(" arguments = '");
|
|
dump_expr(stmt->argquery);
|
|
printf("'\n");
|
|
}
|
|
if (stmt->query != NULL)
|
|
{
|
|
dump_ind();
|
|
printf(" query = '");
|
|
dump_expr(stmt->query);
|
|
printf("'\n");
|
|
}
|
|
if (stmt->dynquery != NULL)
|
|
{
|
|
dump_ind();
|
|
printf(" execute = '");
|
|
dump_expr(stmt->dynquery);
|
|
printf("'\n");
|
|
|
|
if (stmt->params != NIL)
|
|
{
|
|
ListCell *lc;
|
|
int i;
|
|
|
|
dump_indent += 2;
|
|
dump_ind();
|
|
printf(" USING\n");
|
|
dump_indent += 2;
|
|
i = 1;
|
|
foreach(lc, stmt->params)
|
|
{
|
|
dump_ind();
|
|
printf(" parameter $%d: ", i++);
|
|
dump_expr((PLpgSQL_expr *) lfirst(lc));
|
|
printf("\n");
|
|
}
|
|
dump_indent -= 4;
|
|
}
|
|
}
|
|
dump_indent -= 2;
|
|
}
|
|
|
|
static void
|
|
dump_fetch(PLpgSQL_stmt_fetch *stmt)
|
|
{
|
|
dump_ind();
|
|
|
|
if (!stmt->is_move)
|
|
{
|
|
printf("FETCH curvar=%d\n", stmt->curvar);
|
|
dump_cursor_direction(stmt);
|
|
|
|
dump_indent += 2;
|
|
if (stmt->target != NULL)
|
|
{
|
|
dump_ind();
|
|
printf(" target = %d %s\n",
|
|
stmt->target->dno, stmt->target->refname);
|
|
}
|
|
dump_indent -= 2;
|
|
}
|
|
else
|
|
{
|
|
printf("MOVE curvar=%d\n", stmt->curvar);
|
|
dump_cursor_direction(stmt);
|
|
}
|
|
}
|
|
|
|
static void
|
|
dump_cursor_direction(PLpgSQL_stmt_fetch *stmt)
|
|
{
|
|
dump_indent += 2;
|
|
dump_ind();
|
|
switch (stmt->direction)
|
|
{
|
|
case FETCH_FORWARD:
|
|
printf(" FORWARD ");
|
|
break;
|
|
case FETCH_BACKWARD:
|
|
printf(" BACKWARD ");
|
|
break;
|
|
case FETCH_ABSOLUTE:
|
|
printf(" ABSOLUTE ");
|
|
break;
|
|
case FETCH_RELATIVE:
|
|
printf(" RELATIVE ");
|
|
break;
|
|
default:
|
|
printf("??? unknown cursor direction %d", stmt->direction);
|
|
}
|
|
|
|
if (stmt->expr)
|
|
{
|
|
dump_expr(stmt->expr);
|
|
printf("\n");
|
|
}
|
|
else
|
|
printf("%ld\n", stmt->how_many);
|
|
|
|
dump_indent -= 2;
|
|
}
|
|
|
|
static void
|
|
dump_close(PLpgSQL_stmt_close *stmt)
|
|
{
|
|
dump_ind();
|
|
printf("CLOSE curvar=%d\n", stmt->curvar);
|
|
}
|
|
|
|
static void
|
|
dump_perform(PLpgSQL_stmt_perform *stmt)
|
|
{
|
|
dump_ind();
|
|
printf("PERFORM expr = ");
|
|
dump_expr(stmt->expr);
|
|
printf("\n");
|
|
}
|
|
|
|
static void
|
|
dump_call(PLpgSQL_stmt_call *stmt)
|
|
{
|
|
dump_ind();
|
|
printf("%s expr = ", stmt->is_call ? "CALL" : "DO");
|
|
dump_expr(stmt->expr);
|
|
printf("\n");
|
|
}
|
|
|
|
static void
|
|
dump_commit(PLpgSQL_stmt_commit *stmt)
|
|
{
|
|
dump_ind();
|
|
if (stmt->chain)
|
|
printf("COMMIT AND CHAIN\n");
|
|
else
|
|
printf("COMMIT\n");
|
|
}
|
|
|
|
static void
|
|
dump_rollback(PLpgSQL_stmt_rollback *stmt)
|
|
{
|
|
dump_ind();
|
|
if (stmt->chain)
|
|
printf("ROLLBACK AND CHAIN\n");
|
|
else
|
|
printf("ROLLBACK\n");
|
|
}
|
|
|
|
static void
|
|
dump_set(PLpgSQL_stmt_set *stmt)
|
|
{
|
|
dump_ind();
|
|
printf("%s\n", stmt->expr->query);
|
|
}
|
|
|
|
static void
|
|
dump_exit(PLpgSQL_stmt_exit *stmt)
|
|
{
|
|
dump_ind();
|
|
printf("%s", stmt->is_exit ? "EXIT" : "CONTINUE");
|
|
if (stmt->label != NULL)
|
|
printf(" label='%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->retvarno >= 0)
|
|
printf("variable %d", stmt->retvarno);
|
|
else if (stmt->expr != NULL)
|
|
dump_expr(stmt->expr);
|
|
else
|
|
printf("NULL");
|
|
printf("\n");
|
|
}
|
|
|
|
static void
|
|
dump_return_next(PLpgSQL_stmt_return_next *stmt)
|
|
{
|
|
dump_ind();
|
|
printf("RETURN NEXT ");
|
|
if (stmt->retvarno >= 0)
|
|
printf("variable %d", stmt->retvarno);
|
|
else if (stmt->expr != NULL)
|
|
dump_expr(stmt->expr);
|
|
else
|
|
printf("NULL");
|
|
printf("\n");
|
|
}
|
|
|
|
static void
|
|
dump_return_query(PLpgSQL_stmt_return_query *stmt)
|
|
{
|
|
dump_ind();
|
|
if (stmt->query)
|
|
{
|
|
printf("RETURN QUERY ");
|
|
dump_expr(stmt->query);
|
|
printf("\n");
|
|
}
|
|
else
|
|
{
|
|
printf("RETURN QUERY EXECUTE ");
|
|
dump_expr(stmt->dynquery);
|
|
printf("\n");
|
|
if (stmt->params != NIL)
|
|
{
|
|
ListCell *lc;
|
|
int i;
|
|
|
|
dump_indent += 2;
|
|
dump_ind();
|
|
printf(" USING\n");
|
|
dump_indent += 2;
|
|
i = 1;
|
|
foreach(lc, stmt->params)
|
|
{
|
|
dump_ind();
|
|
printf(" parameter $%d: ", i++);
|
|
dump_expr((PLpgSQL_expr *) lfirst(lc));
|
|
printf("\n");
|
|
}
|
|
dump_indent -= 4;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
dump_raise(PLpgSQL_stmt_raise *stmt)
|
|
{
|
|
ListCell *lc;
|
|
int i = 0;
|
|
|
|
dump_ind();
|
|
printf("RAISE level=%d", stmt->elog_level);
|
|
if (stmt->condname)
|
|
printf(" condname='%s'", stmt->condname);
|
|
if (stmt->message)
|
|
printf(" message='%s'", stmt->message);
|
|
printf("\n");
|
|
dump_indent += 2;
|
|
foreach(lc, stmt->params)
|
|
{
|
|
dump_ind();
|
|
printf(" parameter %d: ", i++);
|
|
dump_expr((PLpgSQL_expr *) lfirst(lc));
|
|
printf("\n");
|
|
}
|
|
if (stmt->options)
|
|
{
|
|
dump_ind();
|
|
printf(" USING\n");
|
|
dump_indent += 2;
|
|
foreach(lc, stmt->options)
|
|
{
|
|
PLpgSQL_raise_option *opt = (PLpgSQL_raise_option *) lfirst(lc);
|
|
|
|
dump_ind();
|
|
switch (opt->opt_type)
|
|
{
|
|
case PLPGSQL_RAISEOPTION_ERRCODE:
|
|
printf(" ERRCODE = ");
|
|
break;
|
|
case PLPGSQL_RAISEOPTION_MESSAGE:
|
|
printf(" MESSAGE = ");
|
|
break;
|
|
case PLPGSQL_RAISEOPTION_DETAIL:
|
|
printf(" DETAIL = ");
|
|
break;
|
|
case PLPGSQL_RAISEOPTION_HINT:
|
|
printf(" HINT = ");
|
|
break;
|
|
case PLPGSQL_RAISEOPTION_COLUMN:
|
|
printf(" COLUMN = ");
|
|
break;
|
|
case PLPGSQL_RAISEOPTION_CONSTRAINT:
|
|
printf(" CONSTRAINT = ");
|
|
break;
|
|
case PLPGSQL_RAISEOPTION_DATATYPE:
|
|
printf(" DATATYPE = ");
|
|
break;
|
|
case PLPGSQL_RAISEOPTION_TABLE:
|
|
printf(" TABLE = ");
|
|
break;
|
|
case PLPGSQL_RAISEOPTION_SCHEMA:
|
|
printf(" SCHEMA = ");
|
|
break;
|
|
}
|
|
dump_expr(opt->expr);
|
|
printf("\n");
|
|
}
|
|
dump_indent -= 2;
|
|
}
|
|
dump_indent -= 2;
|
|
}
|
|
|
|
static void
|
|
dump_assert(PLpgSQL_stmt_assert *stmt)
|
|
{
|
|
dump_ind();
|
|
printf("ASSERT ");
|
|
dump_expr(stmt->cond);
|
|
printf("\n");
|
|
|
|
dump_indent += 2;
|
|
if (stmt->message != NULL)
|
|
{
|
|
dump_ind();
|
|
printf(" MESSAGE = ");
|
|
dump_expr(stmt->message);
|
|
printf("\n");
|
|
}
|
|
dump_indent -= 2;
|
|
}
|
|
|
|
static void
|
|
dump_execsql(PLpgSQL_stmt_execsql *stmt)
|
|
{
|
|
dump_ind();
|
|
printf("EXECSQL ");
|
|
dump_expr(stmt->sqlstmt);
|
|
printf("\n");
|
|
|
|
dump_indent += 2;
|
|
if (stmt->target != NULL)
|
|
{
|
|
dump_ind();
|
|
printf(" INTO%s target = %d %s\n",
|
|
stmt->strict ? " STRICT" : "",
|
|
stmt->target->dno, stmt->target->refname);
|
|
}
|
|
dump_indent -= 2;
|
|
}
|
|
|
|
static void
|
|
dump_dynexecute(PLpgSQL_stmt_dynexecute *stmt)
|
|
{
|
|
dump_ind();
|
|
printf("EXECUTE ");
|
|
dump_expr(stmt->query);
|
|
printf("\n");
|
|
|
|
dump_indent += 2;
|
|
if (stmt->target != NULL)
|
|
{
|
|
dump_ind();
|
|
printf(" INTO%s target = %d %s\n",
|
|
stmt->strict ? " STRICT" : "",
|
|
stmt->target->dno, stmt->target->refname);
|
|
}
|
|
if (stmt->params != NIL)
|
|
{
|
|
ListCell *lc;
|
|
int i;
|
|
|
|
dump_ind();
|
|
printf(" USING\n");
|
|
dump_indent += 2;
|
|
i = 1;
|
|
foreach(lc, stmt->params)
|
|
{
|
|
dump_ind();
|
|
printf(" parameter %d: ", i++);
|
|
dump_expr((PLpgSQL_expr *) lfirst(lc));
|
|
printf("\n");
|
|
}
|
|
dump_indent -= 2;
|
|
}
|
|
dump_indent -= 2;
|
|
}
|
|
|
|
static void
|
|
dump_dynfors(PLpgSQL_stmt_dynfors *stmt)
|
|
{
|
|
dump_ind();
|
|
printf("FORS %s EXECUTE ", stmt->var->refname);
|
|
dump_expr(stmt->query);
|
|
printf("\n");
|
|
if (stmt->params != NIL)
|
|
{
|
|
ListCell *lc;
|
|
int i;
|
|
|
|
dump_indent += 2;
|
|
dump_ind();
|
|
printf(" USING\n");
|
|
dump_indent += 2;
|
|
i = 1;
|
|
foreach(lc, stmt->params)
|
|
{
|
|
dump_ind();
|
|
printf(" parameter $%d: ", i++);
|
|
dump_expr((PLpgSQL_expr *) lfirst(lc));
|
|
printf("\n");
|
|
}
|
|
dump_indent -= 4;
|
|
}
|
|
dump_stmts(stmt->body);
|
|
dump_ind();
|
|
printf(" ENDFORS\n");
|
|
}
|
|
|
|
static void
|
|
dump_getdiag(PLpgSQL_stmt_getdiag *stmt)
|
|
{
|
|
ListCell *lc;
|
|
|
|
dump_ind();
|
|
printf("GET %s DIAGNOSTICS ", stmt->is_stacked ? "STACKED" : "CURRENT");
|
|
foreach(lc, stmt->diag_items)
|
|
{
|
|
PLpgSQL_diag_item *diag_item = (PLpgSQL_diag_item *) lfirst(lc);
|
|
|
|
if (lc != list_head(stmt->diag_items))
|
|
printf(", ");
|
|
|
|
printf("{var %d} = %s", diag_item->target,
|
|
plpgsql_getdiag_kindname(diag_item->kind));
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
static void
|
|
dump_expr(PLpgSQL_expr *expr)
|
|
{
|
|
printf("'%s'", expr->query);
|
|
}
|
|
|
|
void
|
|
plpgsql_dumptree(PLpgSQL_function *func)
|
|
{
|
|
int i;
|
|
PLpgSQL_datum *d;
|
|
|
|
printf("\nExecution tree of successfully compiled PL/pgSQL function %s:\n",
|
|
func->fn_signature);
|
|
|
|
printf("\nFunction's 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:
|
|
case PLPGSQL_DTYPE_PROMISE:
|
|
{
|
|
PLpgSQL_var *var = (PLpgSQL_var *) d;
|
|
|
|
printf("VAR %-16s type %s (typoid %u) atttypmod %d\n",
|
|
var->refname, var->datatype->typname,
|
|
var->datatype->typoid,
|
|
var->datatype->atttypmod);
|
|
if (var->isconst)
|
|
printf(" CONSTANT\n");
|
|
if (var->notnull)
|
|
printf(" NOT NULL\n");
|
|
if (var->default_val != NULL)
|
|
{
|
|
printf(" DEFAULT ");
|
|
dump_expr(var->default_val);
|
|
printf("\n");
|
|
}
|
|
if (var->cursor_explicit_expr != NULL)
|
|
{
|
|
if (var->cursor_explicit_argrow >= 0)
|
|
printf(" CURSOR argument row %d\n", var->cursor_explicit_argrow);
|
|
|
|
printf(" CURSOR IS ");
|
|
dump_expr(var->cursor_explicit_expr);
|
|
printf("\n");
|
|
}
|
|
if (var->promise != PLPGSQL_PROMISE_NONE)
|
|
printf(" PROMISE %d\n",
|
|
(int) var->promise);
|
|
}
|
|
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 %-16s typoid %u\n",
|
|
((PLpgSQL_rec *) d)->refname,
|
|
((PLpgSQL_rec *) d)->rectypeid);
|
|
if (((PLpgSQL_rec *) d)->isconst)
|
|
printf(" CONSTANT\n");
|
|
if (((PLpgSQL_rec *) d)->notnull)
|
|
printf(" NOT NULL\n");
|
|
if (((PLpgSQL_rec *) d)->default_val != NULL)
|
|
{
|
|
printf(" DEFAULT ");
|
|
dump_expr(((PLpgSQL_rec *) d)->default_val);
|
|
printf("\n");
|
|
}
|
|
break;
|
|
case PLPGSQL_DTYPE_RECFIELD:
|
|
printf("RECFIELD %-16s of REC %d\n",
|
|
((PLpgSQL_recfield *) d)->fieldname,
|
|
((PLpgSQL_recfield *) d)->recparentno);
|
|
break;
|
|
case PLPGSQL_DTYPE_ARRAYELEM:
|
|
printf("ARRAYELEM of VAR %d subscript ",
|
|
((PLpgSQL_arrayelem *) d)->arrayparentno);
|
|
dump_expr(((PLpgSQL_arrayelem *) d)->subscript);
|
|
printf("\n");
|
|
break;
|
|
default:
|
|
printf("??? unknown data type %d\n", d->dtype);
|
|
}
|
|
}
|
|
printf("\nFunction's 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_signature);
|
|
fflush(stdout);
|
|
}
|