/*------------------------------------------------------------------------- * * 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 "utils/memutils.h" #include "plpgsql.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); }