Hi st 28. 7. 2021 v 11:01 odesílatel Pavel Stehule <pavel.steh...@gmail.com> napsal:
> > > pá 23. 7. 2021 v 10:47 odesílatel Pavel Stehule <pavel.steh...@gmail.com> > napsal: > >> >> >> pá 23. 7. 2021 v 10:30 odesílatel Aleksander Alekseev < >> aleksan...@timescale.com> napsal: >> >>> Hi Pavel, >>> >>> > I know it. Attached patch try to fix this issue >>> > >>> > I merged you patch (thank you) >>> >>> Thanks! I did some more minor changes, mostly in the comments. See the >>> attached patch. Other than that it looks OK. I think it's Ready for >>> Committer now. >>> >> >> looks well, >> >> thank you very much >> >> Pavel >> > > rebase > unfortunately, previous patch that I sent was broken, so I am sending fixed patch and fresh rebase Regards Pavel > Pavel > > >> >>> -- >>> Best regards, >>> Aleksander Alekseev >>> >>
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 7c5bc63778..77bceec7d9 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -4072,6 +4072,8 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate, { (*plpgsql_plugin_ptr)->error_callback = plpgsql_exec_error_callback; (*plpgsql_plugin_ptr)->assign_expr = exec_assign_expr; + (*plpgsql_plugin_ptr)->eval_datum = exec_eval_datum; + (*plpgsql_plugin_ptr)->cast_value = do_cast_value; if ((*plpgsql_plugin_ptr)->func_setup) ((*plpgsql_plugin_ptr)->func_setup) (estate, func); diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y index 0f6a5b30b1..abe7d63f78 100644 --- a/src/pl/plpgsql/src/pl_gram.y +++ b/src/pl/plpgsql/src/pl_gram.y @@ -415,6 +415,7 @@ pl_block : decl_sect K_BEGIN proc_sect exception_sect K_END opt_label new->cmd_type = PLPGSQL_STMT_BLOCK; new->lineno = plpgsql_location_to_lineno(@2); new->stmtid = ++plpgsql_curr_compile->nstatements; + new->ns = plpgsql_ns_top(); new->label = $1.label; new->n_initvars = $1.n_initvars; new->initvarnos = $1.initvarnos; @@ -907,7 +908,8 @@ stmt_perform : K_PERFORM new = palloc0(sizeof(PLpgSQL_stmt_perform)); new->cmd_type = PLPGSQL_STMT_PERFORM; new->lineno = plpgsql_location_to_lineno(@1); - new->stmtid = ++plpgsql_curr_compile->nstatements; + new->stmtid = ++plpgsql_curr_compile->nstatements; + new->ns = plpgsql_ns_top(); plpgsql_push_back_token(K_PERFORM); /* @@ -943,6 +945,7 @@ stmt_call : K_CALL new->cmd_type = PLPGSQL_STMT_CALL; new->lineno = plpgsql_location_to_lineno(@1); new->stmtid = ++plpgsql_curr_compile->nstatements; + new->ns = plpgsql_ns_top(); plpgsql_push_back_token(K_CALL); new->expr = read_sql_stmt(); new->is_call = true; @@ -962,6 +965,7 @@ stmt_call : K_CALL new->cmd_type = PLPGSQL_STMT_CALL; new->lineno = plpgsql_location_to_lineno(@1); new->stmtid = ++plpgsql_curr_compile->nstatements; + new->ns = plpgsql_ns_top(); plpgsql_push_back_token(K_DO); new->expr = read_sql_stmt(); new->is_call = false; @@ -1000,7 +1004,8 @@ stmt_assign : T_DATUM new = palloc0(sizeof(PLpgSQL_stmt_assign)); new->cmd_type = PLPGSQL_STMT_ASSIGN; new->lineno = plpgsql_location_to_lineno(@1); - new->stmtid = ++plpgsql_curr_compile->nstatements; + new->stmtid = ++plpgsql_curr_compile->nstatements; + new->ns = plpgsql_ns_top(); new->varno = $1.datum->dno; /* Push back the head name to include it in the stmt */ plpgsql_push_back_token(T_DATUM); @@ -1022,6 +1027,7 @@ stmt_getdiag : K_GET getdiag_area_opt K_DIAGNOSTICS getdiag_list ';' new->cmd_type = PLPGSQL_STMT_GETDIAG; new->lineno = plpgsql_location_to_lineno(@1); new->stmtid = ++plpgsql_curr_compile->nstatements; + new->ns = plpgsql_ns_top(); new->is_stacked = $2; new->diag_items = $4; @@ -1194,6 +1200,7 @@ stmt_if : K_IF expr_until_then proc_sect stmt_elsifs stmt_else K_END K_IF ';' new->cmd_type = PLPGSQL_STMT_IF; new->lineno = plpgsql_location_to_lineno(@1); new->stmtid = ++plpgsql_curr_compile->nstatements; + new->ns = plpgsql_ns_top(); new->cond = $2; new->then_body = $3; new->elsif_list = $4; @@ -1299,6 +1306,7 @@ stmt_loop : opt_loop_label K_LOOP loop_body new->cmd_type = PLPGSQL_STMT_LOOP; new->lineno = plpgsql_location_to_lineno(@2); new->stmtid = ++plpgsql_curr_compile->nstatements; + new->ns = plpgsql_ns_top(); new->label = $1; new->body = $3.stmts; @@ -1317,6 +1325,7 @@ stmt_while : opt_loop_label K_WHILE expr_until_loop loop_body new->cmd_type = PLPGSQL_STMT_WHILE; new->lineno = plpgsql_location_to_lineno(@2); new->stmtid = ++plpgsql_curr_compile->nstatements; + new->ns = plpgsql_ns_top(); new->label = $1; new->cond = $3; new->body = $4.stmts; @@ -1381,6 +1390,7 @@ for_control : for_variable K_IN new = palloc0(sizeof(PLpgSQL_stmt_dynfors)); new->cmd_type = PLPGSQL_STMT_DYNFORS; new->stmtid = ++plpgsql_curr_compile->nstatements; + new->ns = plpgsql_ns_top(); if ($1.row) { new->var = (PLpgSQL_variable *) $1.row; @@ -1427,6 +1437,7 @@ for_control : for_variable K_IN new = (PLpgSQL_stmt_forc *) palloc0(sizeof(PLpgSQL_stmt_forc)); new->cmd_type = PLPGSQL_STMT_FORC; new->stmtid = ++plpgsql_curr_compile->nstatements; + new->ns = plpgsql_ns_top(); new->curvar = cursor->dno; /* Should have had a single variable name */ @@ -1547,6 +1558,7 @@ for_control : for_variable K_IN new = palloc0(sizeof(PLpgSQL_stmt_fori)); new->cmd_type = PLPGSQL_STMT_FORI; new->stmtid = ++plpgsql_curr_compile->nstatements; + new->ns = plpgsql_ns_top(); new->var = fvar; new->reverse = reverse; new->lower = expr1; @@ -1575,6 +1587,7 @@ for_control : for_variable K_IN new = palloc0(sizeof(PLpgSQL_stmt_fors)); new->cmd_type = PLPGSQL_STMT_FORS; new->stmtid = ++plpgsql_curr_compile->nstatements; + new->ns = plpgsql_ns_top(); if ($1.row) { new->var = (PLpgSQL_variable *) $1.row; @@ -1676,6 +1689,7 @@ stmt_foreach_a : opt_loop_label K_FOREACH for_variable foreach_slice K_IN K_ARRA new->cmd_type = PLPGSQL_STMT_FOREACH_A; new->lineno = plpgsql_location_to_lineno(@2); new->stmtid = ++plpgsql_curr_compile->nstatements; + new->ns = plpgsql_ns_top(); new->label = $1; new->slice = $4; new->expr = $7; @@ -1723,6 +1737,7 @@ stmt_exit : exit_type opt_label opt_exitcond new = palloc0(sizeof(PLpgSQL_stmt_exit)); new->cmd_type = PLPGSQL_STMT_EXIT; new->stmtid = ++plpgsql_curr_compile->nstatements; + new->ns = plpgsql_ns_top(); new->is_exit = $1; new->lineno = plpgsql_location_to_lineno(@1); new->label = $2; @@ -1815,6 +1830,7 @@ stmt_raise : K_RAISE new->cmd_type = PLPGSQL_STMT_RAISE; new->lineno = plpgsql_location_to_lineno(@1); new->stmtid = ++plpgsql_curr_compile->nstatements; + new->ns = plpgsql_ns_top(); new->elog_level = ERROR; /* default */ new->condname = NULL; new->message = NULL; @@ -1960,6 +1976,7 @@ stmt_assert : K_ASSERT new->cmd_type = PLPGSQL_STMT_ASSERT; new->lineno = plpgsql_location_to_lineno(@1); new->stmtid = ++plpgsql_curr_compile->nstatements; + new->ns = plpgsql_ns_top(); new->cond = read_sql_expression2(',', ';', ", or ;", @@ -2040,6 +2057,7 @@ stmt_dynexecute : K_EXECUTE new->cmd_type = PLPGSQL_STMT_DYNEXECUTE; new->lineno = plpgsql_location_to_lineno(@1); new->stmtid = ++plpgsql_curr_compile->nstatements; + new->ns = plpgsql_ns_top(); new->query = expr; new->into = false; new->strict = false; @@ -2097,6 +2115,7 @@ stmt_open : K_OPEN cursor_variable new->cmd_type = PLPGSQL_STMT_OPEN; new->lineno = plpgsql_location_to_lineno(@1); new->stmtid = ++plpgsql_curr_compile->nstatements; + new->ns = plpgsql_ns_top(); new->curvar = $2->dno; new->cursor_options = CURSOR_OPT_FAST_PLAN; @@ -2222,6 +2241,7 @@ stmt_close : K_CLOSE cursor_variable ';' new->cmd_type = PLPGSQL_STMT_CLOSE; new->lineno = plpgsql_location_to_lineno(@1); new->stmtid = ++plpgsql_curr_compile->nstatements; + new->ns = plpgsql_ns_top(); new->curvar = $2->dno; $$ = (PLpgSQL_stmt *)new; @@ -2243,6 +2263,7 @@ stmt_commit : K_COMMIT opt_transaction_chain ';' new->cmd_type = PLPGSQL_STMT_COMMIT; new->lineno = plpgsql_location_to_lineno(@1); new->stmtid = ++plpgsql_curr_compile->nstatements; + new->ns = plpgsql_ns_top(); new->chain = $2; $$ = (PLpgSQL_stmt *)new; @@ -2257,6 +2278,7 @@ stmt_rollback : K_ROLLBACK opt_transaction_chain ';' new->cmd_type = PLPGSQL_STMT_ROLLBACK; new->lineno = plpgsql_location_to_lineno(@1); new->stmtid = ++plpgsql_curr_compile->nstatements; + new->ns = plpgsql_ns_top(); new->chain = $2; $$ = (PLpgSQL_stmt *)new; @@ -2269,7 +2291,6 @@ opt_transaction_chain: | /* EMPTY */ { $$ = false; } ; - cursor_variable : T_DATUM { /* @@ -3047,6 +3068,7 @@ make_execsql_stmt(int firsttoken, int location) execsql->cmd_type = PLPGSQL_STMT_EXECSQL; execsql->lineno = plpgsql_location_to_lineno(location); execsql->stmtid = ++plpgsql_curr_compile->nstatements; + execsql->ns = plpgsql_ns_top(); execsql->sqlstmt = expr; execsql->into = have_into; execsql->strict = have_strict; @@ -3073,6 +3095,7 @@ read_fetch_direction(void) fetch = (PLpgSQL_stmt_fetch *) palloc0(sizeof(PLpgSQL_stmt_fetch)); fetch->cmd_type = PLPGSQL_STMT_FETCH; fetch->stmtid = ++plpgsql_curr_compile->nstatements; + fetch->ns = plpgsql_ns_top(); /* set direction defaults: */ fetch->direction = FETCH_FORWARD; fetch->how_many = 1; @@ -3226,6 +3249,7 @@ make_return_stmt(int location) new->cmd_type = PLPGSQL_STMT_RETURN; new->lineno = plpgsql_location_to_lineno(location); new->stmtid = ++plpgsql_curr_compile->nstatements; + new->ns = plpgsql_ns_top(); new->expr = NULL; new->retvarno = -1; @@ -3314,6 +3338,7 @@ make_return_next_stmt(int location) new->cmd_type = PLPGSQL_STMT_RETURN_NEXT; new->lineno = plpgsql_location_to_lineno(location); new->stmtid = ++plpgsql_curr_compile->nstatements; + new->ns = plpgsql_ns_top(); new->expr = NULL; new->retvarno = -1; @@ -3378,6 +3403,7 @@ make_return_query_stmt(int location) new->cmd_type = PLPGSQL_STMT_RETURN_QUERY; new->lineno = plpgsql_location_to_lineno(location); new->stmtid = ++plpgsql_curr_compile->nstatements; + new->ns = plpgsql_ns_top(); /* check for RETURN QUERY EXECUTE */ if ((tok = yylex()) != K_EXECUTE) @@ -4042,6 +4068,7 @@ make_case(int location, PLpgSQL_expr *t_expr, new->cmd_type = PLPGSQL_STMT_CASE; new->lineno = plpgsql_location_to_lineno(location); new->stmtid = ++plpgsql_curr_compile->nstatements; + new->ns = plpgsql_ns_top(); new->t_expr = t_expr; new->t_varno = 0; new->case_when_list = case_when_list; diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index ebd3a5d3c8..d1db206a58 100644 --- a/src/pl/plpgsql/src/plpgsql.h +++ b/src/pl/plpgsql/src/plpgsql.h @@ -460,6 +460,9 @@ typedef struct PLpgSQL_stmt * per-statement metrics. */ unsigned int stmtid; + + /* namespace chain visible to this statement */ + PLpgSQL_nsitem *ns; } PLpgSQL_stmt; /* @@ -500,6 +503,7 @@ typedef struct PLpgSQL_stmt_block PLpgSQL_stmt_type cmd_type; int lineno; unsigned int stmtid; + PLpgSQL_nsitem *ns; char *label; List *body; /* List of statements */ int n_initvars; /* Length of initvarnos[] */ @@ -515,6 +519,7 @@ typedef struct PLpgSQL_stmt_assign PLpgSQL_stmt_type cmd_type; int lineno; unsigned int stmtid; + PLpgSQL_nsitem *ns; int varno; PLpgSQL_expr *expr; } PLpgSQL_stmt_assign; @@ -527,6 +532,7 @@ typedef struct PLpgSQL_stmt_perform PLpgSQL_stmt_type cmd_type; int lineno; unsigned int stmtid; + PLpgSQL_nsitem *ns; PLpgSQL_expr *expr; } PLpgSQL_stmt_perform; @@ -538,6 +544,7 @@ typedef struct PLpgSQL_stmt_call PLpgSQL_stmt_type cmd_type; int lineno; unsigned int stmtid; + PLpgSQL_nsitem *ns; PLpgSQL_expr *expr; bool is_call; PLpgSQL_variable *target; @@ -551,6 +558,7 @@ typedef struct PLpgSQL_stmt_commit PLpgSQL_stmt_type cmd_type; int lineno; unsigned int stmtid; + PLpgSQL_nsitem *ns; bool chain; } PLpgSQL_stmt_commit; @@ -562,6 +570,7 @@ typedef struct PLpgSQL_stmt_rollback PLpgSQL_stmt_type cmd_type; int lineno; unsigned int stmtid; + PLpgSQL_nsitem *ns; bool chain; } PLpgSQL_stmt_rollback; @@ -582,6 +591,7 @@ typedef struct PLpgSQL_stmt_getdiag PLpgSQL_stmt_type cmd_type; int lineno; unsigned int stmtid; + PLpgSQL_nsitem *ns; bool is_stacked; /* STACKED or CURRENT diagnostics area? */ List *diag_items; /* List of PLpgSQL_diag_item */ } PLpgSQL_stmt_getdiag; @@ -594,6 +604,7 @@ typedef struct PLpgSQL_stmt_if PLpgSQL_stmt_type cmd_type; int lineno; unsigned int stmtid; + PLpgSQL_nsitem *ns; PLpgSQL_expr *cond; /* boolean expression for THEN */ List *then_body; /* List of statements */ List *elsif_list; /* List of PLpgSQL_if_elsif structs */ @@ -618,6 +629,7 @@ typedef struct PLpgSQL_stmt_case PLpgSQL_stmt_type cmd_type; int lineno; unsigned int stmtid; + PLpgSQL_nsitem *ns; PLpgSQL_expr *t_expr; /* test expression, or NULL if none */ int t_varno; /* var to store test expression value into */ List *case_when_list; /* List of PLpgSQL_case_when structs */ @@ -643,6 +655,7 @@ typedef struct PLpgSQL_stmt_loop PLpgSQL_stmt_type cmd_type; int lineno; unsigned int stmtid; + PLpgSQL_nsitem *ns; char *label; List *body; /* List of statements */ } PLpgSQL_stmt_loop; @@ -655,6 +668,7 @@ typedef struct PLpgSQL_stmt_while PLpgSQL_stmt_type cmd_type; int lineno; unsigned int stmtid; + PLpgSQL_nsitem *ns; char *label; PLpgSQL_expr *cond; List *body; /* List of statements */ @@ -668,6 +682,7 @@ typedef struct PLpgSQL_stmt_fori PLpgSQL_stmt_type cmd_type; int lineno; unsigned int stmtid; + PLpgSQL_nsitem *ns; char *label; PLpgSQL_var *var; PLpgSQL_expr *lower; @@ -687,6 +702,7 @@ typedef struct PLpgSQL_stmt_forq PLpgSQL_stmt_type cmd_type; int lineno; unsigned int stmtid; + PLpgSQL_nsitem *ns; char *label; PLpgSQL_variable *var; /* Loop variable (record or row) */ List *body; /* List of statements */ @@ -700,6 +716,7 @@ typedef struct PLpgSQL_stmt_fors PLpgSQL_stmt_type cmd_type; int lineno; unsigned int stmtid; + PLpgSQL_nsitem *ns; char *label; PLpgSQL_variable *var; /* Loop variable (record or row) */ List *body; /* List of statements */ @@ -715,6 +732,7 @@ typedef struct PLpgSQL_stmt_forc PLpgSQL_stmt_type cmd_type; int lineno; unsigned int stmtid; + PLpgSQL_nsitem *ns; char *label; PLpgSQL_variable *var; /* Loop variable (record or row) */ List *body; /* List of statements */ @@ -731,6 +749,7 @@ typedef struct PLpgSQL_stmt_dynfors PLpgSQL_stmt_type cmd_type; int lineno; unsigned int stmtid; + PLpgSQL_nsitem *ns; char *label; PLpgSQL_variable *var; /* Loop variable (record or row) */ List *body; /* List of statements */ @@ -747,6 +766,7 @@ typedef struct PLpgSQL_stmt_foreach_a PLpgSQL_stmt_type cmd_type; int lineno; unsigned int stmtid; + PLpgSQL_nsitem *ns; char *label; int varno; /* loop target variable */ int slice; /* slice dimension, or 0 */ @@ -762,6 +782,7 @@ typedef struct PLpgSQL_stmt_open PLpgSQL_stmt_type cmd_type; int lineno; unsigned int stmtid; + PLpgSQL_nsitem *ns; int curvar; int cursor_options; PLpgSQL_expr *argquery; @@ -778,6 +799,7 @@ typedef struct PLpgSQL_stmt_fetch PLpgSQL_stmt_type cmd_type; int lineno; unsigned int stmtid; + PLpgSQL_nsitem *ns; PLpgSQL_variable *target; /* target (record or row) */ int curvar; /* cursor variable to fetch from */ FetchDirection direction; /* fetch direction */ @@ -795,6 +817,7 @@ typedef struct PLpgSQL_stmt_close PLpgSQL_stmt_type cmd_type; int lineno; unsigned int stmtid; + PLpgSQL_nsitem *ns; int curvar; } PLpgSQL_stmt_close; @@ -806,6 +829,7 @@ typedef struct PLpgSQL_stmt_exit PLpgSQL_stmt_type cmd_type; int lineno; unsigned int stmtid; + PLpgSQL_nsitem *ns; bool is_exit; /* Is this an exit or a continue? */ char *label; /* NULL if it's an unlabeled EXIT/CONTINUE */ PLpgSQL_expr *cond; @@ -819,6 +843,7 @@ typedef struct PLpgSQL_stmt_return PLpgSQL_stmt_type cmd_type; int lineno; unsigned int stmtid; + PLpgSQL_nsitem *ns; PLpgSQL_expr *expr; int retvarno; } PLpgSQL_stmt_return; @@ -831,6 +856,7 @@ typedef struct PLpgSQL_stmt_return_next PLpgSQL_stmt_type cmd_type; int lineno; unsigned int stmtid; + PLpgSQL_nsitem *ns; PLpgSQL_expr *expr; int retvarno; } PLpgSQL_stmt_return_next; @@ -843,6 +869,7 @@ typedef struct PLpgSQL_stmt_return_query PLpgSQL_stmt_type cmd_type; int lineno; unsigned int stmtid; + PLpgSQL_nsitem *ns; PLpgSQL_expr *query; /* if static query */ PLpgSQL_expr *dynquery; /* if dynamic query (RETURN QUERY EXECUTE) */ List *params; /* USING arguments for dynamic query */ @@ -856,6 +883,7 @@ typedef struct PLpgSQL_stmt_raise PLpgSQL_stmt_type cmd_type; int lineno; unsigned int stmtid; + PLpgSQL_nsitem *ns; int elog_level; char *condname; /* condition name, SQLSTATE, or NULL */ char *message; /* old-style message format literal, or NULL */ @@ -880,6 +908,7 @@ typedef struct PLpgSQL_stmt_assert PLpgSQL_stmt_type cmd_type; int lineno; unsigned int stmtid; + PLpgSQL_nsitem *ns; PLpgSQL_expr *cond; PLpgSQL_expr *message; } PLpgSQL_stmt_assert; @@ -892,6 +921,7 @@ typedef struct PLpgSQL_stmt_execsql PLpgSQL_stmt_type cmd_type; int lineno; unsigned int stmtid; + PLpgSQL_nsitem *ns; PLpgSQL_expr *sqlstmt; bool mod_stmt; /* is the stmt INSERT/UPDATE/DELETE? */ bool mod_stmt_set; /* is mod_stmt valid yet? */ @@ -908,6 +938,7 @@ typedef struct PLpgSQL_stmt_dynexecute PLpgSQL_stmt_type cmd_type; int lineno; unsigned int stmtid; + PLpgSQL_nsitem *ns; PLpgSQL_expr *query; /* string expression */ bool into; /* INTO supplied? */ bool strict; /* INTO STRICT flag */ @@ -1120,8 +1151,11 @@ typedef struct PLpgSQL_execstate * * Also, immediately before any call to func_setup, PL/pgSQL fills in the * error_callback and assign_expr fields with pointers to its own - * plpgsql_exec_error_callback and exec_assign_expr functions. This is - * a somewhat ad-hoc expedient to simplify life for debugger plugins. + * plpgsql_exec_error_callback and exec_assign_expr functions. eval_datum + * is assigned to function exec_eval_datum, and cast_value to function + * do_cast_value. With last two functions is easy to get content of + * any PLpgSQL_datum in any expected type. This is a somewhat ad-hoc + * expedient to simplify life for debugger plugins. */ typedef struct PLpgSQL_plugin { @@ -1136,6 +1170,13 @@ typedef struct PLpgSQL_plugin void (*error_callback) (void *arg); void (*assign_expr) (PLpgSQL_execstate *estate, PLpgSQL_datum *target, PLpgSQL_expr *expr); + void (*eval_datum) (PLpgSQL_execstate *estate, PLpgSQL_datum *datum, + Oid *typeid, int32 *typetypmod, Datum *value, + bool *isnull); + Datum (*cast_value) (PLpgSQL_execstate *estate, + Datum value, bool *isnull, + Oid valtype, int32 valtypmod, + Oid reqtype, int32 reqtypmod); } PLpgSQL_plugin; /* diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile index dffc79b2d9..fa4f643599 100644 --- a/src/test/modules/Makefile +++ b/src/test/modules/Makefile @@ -15,6 +15,7 @@ SUBDIRS = \ snapshot_too_old \ spgist_name_ops \ test_bloomfilter \ + test_dbgapi \ test_ddl_deparse \ test_extensions \ test_ginpostinglist \ diff --git a/src/test/modules/test_dbgapi/.gitignore b/src/test/modules/test_dbgapi/.gitignore new file mode 100644 index 0000000000..44d119cfcc --- /dev/null +++ b/src/test/modules/test_dbgapi/.gitignore @@ -0,0 +1,3 @@ +# Generated subdirectories +/log/ +/results/ diff --git a/src/test/modules/test_dbgapi/Makefile b/src/test/modules/test_dbgapi/Makefile new file mode 100644 index 0000000000..df5e9e9bbd --- /dev/null +++ b/src/test/modules/test_dbgapi/Makefile @@ -0,0 +1,22 @@ +# src/test/modules/test_dbgapi/Makefile + +MODULES = test_dbgapi + +EXTENSION = test_dbgapi +DATA = test_dbgapi--1.0.sql +PGFILEDESC = "test_dbgapi - test of PL/pgSQL debug API" + +REGRESS = test_dbgapi + +PG_CPPFLAGS = -I$(top_srcdir)/src/pl/plpgsql/src + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = src/test/modules/test_dbgapi +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/src/test/modules/test_dbgapi/README b/src/test/modules/test_dbgapi/README new file mode 100644 index 0000000000..cb6c1ee30f --- /dev/null +++ b/src/test/modules/test_dbgapi/README @@ -0,0 +1,7 @@ +test_dbgapi +=========== + +test_dbgapi is an example of usage of PLpgSQL debug API. This extension +implements some very basic (and simple) code execution tracing. The +implemented functionality is very limited, because main purpose is +allow to run regress test of debug API. diff --git a/src/test/modules/test_dbgapi/expected/test_dbgapi.out b/src/test/modules/test_dbgapi/expected/test_dbgapi.out new file mode 100644 index 0000000000..1011d6231e --- /dev/null +++ b/src/test/modules/test_dbgapi/expected/test_dbgapi.out @@ -0,0 +1,90 @@ +CREATE EXTENSION test_dbgapi; +/* + * There is lot of shadowed variables "v". The test should to + * choose the variable "v" from namespace that is assigned to + * current statement, and show the value before and after an + * execution of any statement. + */ +CREATE OR REPLACE FUNCTION trace_variable_test_func(v int) +RETURNS void AS $$ +BEGIN + v := v + 1; + +<<b1>> + DECLARE + v int DEFAULT trace_variable_test_func.v + 100; + BEGIN + v := v + 1; + FOR v IN v + 10 .. v + 13 + LOOP + RAISE NOTICE 'v = %', v; + END LOOP; + END; +END; +$$ LANGUAGE plpgsql; +/* + * Prepare simple tracer + */ +SELECT trace_plpgsql(true); + trace_plpgsql +--------------- + +(1 row) + +SET test_dbgapi.trace_variable = 'v'; +SET test_dbgapi.trace_forloop_variable = ON; +SELECT trace_variable_test_func(10); +NOTICE: start of function: "trace_variable_test_func(integer)" +NOTICE: start of statement: "statement block", no: 6 on line 2 +NOTICE: value of variable "v" is "10" (before) +NOTICE: start of statement: "assignment", no: 1 on line 3 +NOTICE: value of variable "v" is "10" (before) +NOTICE: end of statement: "assignment", no: 1 on line 3 +NOTICE: value of variable "v" is "11" (after) +NOTICE: start of statement: "statement block", no: 5 on line 8 +NOTICE: value of variable "v" is "11" (before) +NOTICE: start of statement: "assignment", no: 2 on line 9 +NOTICE: value of variable "v" is "111" (before) +NOTICE: end of statement: "assignment", no: 2 on line 9 +NOTICE: value of variable "v" is "112" (after) +NOTICE: start of statement: "FOR with integer loop variable", no: 3 on line 10 +NOTICE: value of variable "v" is "112" (before) +NOTICE: start of statement: "RAISE", no: 4 on line 12 +NOTICE: most inner fori loop control variable "v" is 122 +NOTICE: value of variable "v" is "122" (before) +NOTICE: v = 122 +NOTICE: end of statement: "RAISE", no: 4 on line 12 +NOTICE: value of variable "v" is "122" (after) +NOTICE: start of statement: "RAISE", no: 4 on line 12 +NOTICE: most inner fori loop control variable "v" is 123 +NOTICE: value of variable "v" is "123" (before) +NOTICE: v = 123 +NOTICE: end of statement: "RAISE", no: 4 on line 12 +NOTICE: value of variable "v" is "123" (after) +NOTICE: start of statement: "RAISE", no: 4 on line 12 +NOTICE: most inner fori loop control variable "v" is 124 +NOTICE: value of variable "v" is "124" (before) +NOTICE: v = 124 +NOTICE: end of statement: "RAISE", no: 4 on line 12 +NOTICE: value of variable "v" is "124" (after) +NOTICE: start of statement: "RAISE", no: 4 on line 12 +NOTICE: most inner fori loop control variable "v" is 125 +NOTICE: value of variable "v" is "125" (before) +NOTICE: v = 125 +NOTICE: end of statement: "RAISE", no: 4 on line 12 +NOTICE: value of variable "v" is "125" (after) +NOTICE: end of statement: "FOR with integer loop variable", no: 3 on line 10 +NOTICE: value of variable "v" is "125" (after) +NOTICE: end of statement: "statement block", no: 5 on line 8 +NOTICE: value of variable "v" is "112" (after) +NOTICE: start of statement: "RETURN", no: 7 on line 0 +NOTICE: now, there is not any outer fori cycle +NOTICE: end of statement: "RETURN", no: 7 on line 0 +NOTICE: end of statement: "statement block", no: 6 on line 2 +NOTICE: value of variable "v" is "11" (after) +NOTICE: end of function: "trace_variable_test_func(integer)" + trace_variable_test_func +-------------------------- + +(1 row) + diff --git a/src/test/modules/test_dbgapi/sql/test_dbgapi.sql b/src/test/modules/test_dbgapi/sql/test_dbgapi.sql new file mode 100644 index 0000000000..bcede18a7c --- /dev/null +++ b/src/test/modules/test_dbgapi/sql/test_dbgapi.sql @@ -0,0 +1,36 @@ +CREATE EXTENSION test_dbgapi; + +/* + * There is lot of shadowed variables "v". The test should to + * choose the variable "v" from namespace that is assigned to + * current statement, and show the value before and after an + * execution of any statement. + */ +CREATE OR REPLACE FUNCTION trace_variable_test_func(v int) +RETURNS void AS $$ +BEGIN + v := v + 1; + +<<b1>> + DECLARE + v int DEFAULT trace_variable_test_func.v + 100; + BEGIN + v := v + 1; + FOR v IN v + 10 .. v + 13 + LOOP + RAISE NOTICE 'v = %', v; + END LOOP; + END; +END; +$$ LANGUAGE plpgsql; + +/* + * Prepare simple tracer + */ +SELECT trace_plpgsql(true); + +SET test_dbgapi.trace_variable = 'v'; +SET test_dbgapi.trace_forloop_variable = ON; + +SELECT trace_variable_test_func(10); + diff --git a/src/test/modules/test_dbgapi/test_dbgapi--1.0.sql b/src/test/modules/test_dbgapi/test_dbgapi--1.0.sql new file mode 100644 index 0000000000..87834e7ea5 --- /dev/null +++ b/src/test/modules/test_dbgapi/test_dbgapi--1.0.sql @@ -0,0 +1,7 @@ +/* src/test/modules/test_dbgapi/test_dbgapi--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION test_dbgapi" to load this file. \quit + +CREATE FUNCTION trace_plpgsql(boolean) RETURNS void + AS 'MODULE_PATHNAME' LANGUAGE C; diff --git a/src/test/modules/test_dbgapi/test_dbgapi.c b/src/test/modules/test_dbgapi/test_dbgapi.c new file mode 100644 index 0000000000..35966016d5 --- /dev/null +++ b/src/test/modules/test_dbgapi/test_dbgapi.c @@ -0,0 +1,476 @@ +/*------------------------------------------------------------------------- + * + * test_dbgapi.c + * Simple PLpgSQL tracer designed to test PLpgSQL's debug API + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/test/modules/test_dbgapi/test_dbgapi.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" +#include "plpgsql.h" +#include "utils/guc.h" +#include "utils/builtins.h" + +PG_MODULE_MAGIC; + +/* Module load/unload functions */ +void _PG_init(void); +void _PG_fini(void); + +static void func_beg(PLpgSQL_execstate *estate, PLpgSQL_function *func); +static void func_end(PLpgSQL_execstate *estate, PLpgSQL_function *func); +static void stmt_beg(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt); +static void stmt_end(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt); + +static bool is_nested_of(PLpgSQL_stmt *stmt, PLpgSQL_stmt *outer_stmt); +static PLpgSQL_nsitem *outer_ns(PLpgSQL_stmt *stmt); +static char *get_string_value(PLpgSQL_execstate *estate, PLpgSQL_datum *datum); +static char *get_string_expr(PLpgSQL_execstate *estate, PLpgSQL_nsitem *ns, char *query); + +static PLpgSQL_plugin plugin_funcs = {NULL, + func_beg, + func_end, + stmt_beg, + stmt_end, + NULL, +NULL}; + +typedef const char *(*test_dbgapi_stmt_typename_t) (PLpgSQL_stmt *stmt); +typedef PLpgSQL_nsitem *(*test_dbgapi_ns_lookup_t) (PLpgSQL_nsitem *ns_cur, + bool localmode, const char *name1, const char *name2, + const char *name3, int *names_used); + +static test_dbgapi_stmt_typename_t stmt_typename_p; +static test_dbgapi_ns_lookup_t ns_lookup_p; + +static PLpgSQL_plugin **test_dbgapi_plugin_var = NULL; + +static bool trace_forloop_variable = false; +static char *trace_variable = NULL; + +#define LOAD_EXTERNAL_FUNCTION(file, funcname) \ + ((void *) (load_external_function(file, funcname, true, NULL))) + +#define get_eval_mcontext(estate) \ + ((estate)->eval_econtext->ecxt_per_tuple_memory) + +PG_FUNCTION_INFO_V1(trace_plpgsql); + +typedef struct +{ + List *fori_stmt_stack; +} test_dbgapi_info; + + +static void +func_beg(PLpgSQL_execstate *estate, PLpgSQL_function *func) +{ + MemoryContext oldcxt; + + if (func->fn_signature) + ereport(NOTICE, + (errmsg("start of function: \"%s\"", func->fn_signature))); + else + ereport(NOTICE, + (errmsg("start of anonymous block"))); + + oldcxt = MemoryContextSwitchTo(estate->datum_context); + + estate->plugin_info = palloc0(sizeof(test_dbgapi_info)); + + MemoryContextSwitchTo(oldcxt); +} + +static void +func_end(PLpgSQL_execstate *estate, PLpgSQL_function *func) +{ + if (func->fn_signature) + ereport(NOTICE, + (errmsg("end of function: \"%s\"", func->fn_signature))); + else + ereport(NOTICE, + (errmsg("end of anonymous block"))); +} + +static void +stmt_beg(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt) +{ + test_dbgapi_info *pinfo = estate->plugin_info; + + ereport(NOTICE, + (errmsg("start of statement: \"%s\", no: %d on line %d", + (char *) stmt_typename_p(stmt), + stmt->stmtid, stmt->lineno))); + + /* + * We have to recheck accuracy of saved statement stack, because it can be + * broken by any handled exception. In this case, the *end handlers are + * not executed (so we cannot to maintain this stack by appending in + * stmt_beg and trimming in stmt_end). + * + * This check can be simple - the namespace of current statement should be + * subset of namespace of last fori statement from stack. + */ + if (pinfo && (pinfo->fori_stmt_stack != NIL)) + { + int i; + bool found = false; + + /* + * Try to search top most accurate fori statement. In usual cases the + * most top fori statement is accurate, and this cycle is quickly + * leaved. This is NxM algorithm, but a) this is just test + * application, b) in reality the N and M are low, so it is not + * necessary to implement faster but more complex algorithm. + */ + for (i = list_length(pinfo->fori_stmt_stack); i > 0; i--) + { + if (is_nested_of(stmt, (PLpgSQL_stmt *) + list_nth(pinfo->fori_stmt_stack, i - 1))) + { + if (i < list_length(pinfo->fori_stmt_stack)) + { + /* We need to trim stack of fori statements */ + pinfo->fori_stmt_stack = list_truncate(pinfo->fori_stmt_stack, i); + + ereport(NOTICE, + (errmsg("the stack of fori statements is truncated to %d", + i))); + } + + found = true; + break; + } + } + + if (!found) + { + list_free(pinfo->fori_stmt_stack); + pinfo->fori_stmt_stack = NIL; + + ereport(NOTICE, (errmsg("now, there is not any outer fori cycle"))); + } + } + + if (pinfo && + pinfo->fori_stmt_stack && + (list_length(pinfo->fori_stmt_stack) > 0)) + { + PLpgSQL_stmt_fori *top_fori_stmt; + PLpgSQL_var *var; + + top_fori_stmt = (PLpgSQL_stmt_fori *) llast(pinfo->fori_stmt_stack); + var = top_fori_stmt->var; + + ereport(NOTICE, + (errmsg("most inner fori loop control variable \"%s\" is %s", + var->refname, + get_string_value(estate, estate->datums[var->dno])))); + } + + if (trace_forloop_variable && + (stmt->cmd_type == PLPGSQL_STMT_FORI)) + pinfo->fori_stmt_stack = lappend(pinfo->fori_stmt_stack, stmt); + + /* + * Another case - print content of variable specified by name. The + * variable can be shadowed, and we want to chose variable that is visible + * from current namespace. + * + * Note: there are two ways to do it. Since this code is used as a + * regression test, both are shown. + */ + if (trace_variable && (*trace_variable != '\0')) + { + PLpgSQL_nsitem *nsi; + + nsi = ns_lookup_p(outer_ns(stmt), false, "v", NULL, NULL, NULL); + if (nsi && + ((nsi->itemtype == PLPGSQL_NSTYPE_VAR) || + (nsi->itemtype == PLPGSQL_NSTYPE_REC))) + { + ereport(NOTICE, + (errmsg("value of variable \"%s\" is \"%s\" (before)", + nsi->name, + get_string_value(estate, + estate->datums[nsi->itemno])))); + } + } +} + +static void +stmt_end(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt) +{ + ereport(NOTICE, + (errmsg("end of statement: \"%s\", no: %d on line %d", + (char *) stmt_typename_p(stmt), + stmt->stmtid, stmt->lineno))); + + if (trace_variable && (*trace_variable != '\0')) + { + char *str = get_string_expr(estate, stmt->ns, trace_variable); + + if (str) + ereport(NOTICE, + (errmsg("value of variable \"%s\" is \"%s\" (after)", + trace_variable, str))); + } +} + +/* + * Module initialization + */ +void +_PG_init(void) +{ + static bool inited = false; + + if (inited) + return; + + /* + * Currently these asserts don't ensure any safety, but it can be changed + * if referenced functions will be declared in a header file. + */ + AssertVariableIsOfType(stmt_typename_p, test_dbgapi_stmt_typename_t); + stmt_typename_p = (test_dbgapi_stmt_typename_t) + LOAD_EXTERNAL_FUNCTION("$libdir/plpgsql", "plpgsql_stmt_typename"); + + AssertVariableIsOfType(ns_lookup_p, test_dbgapi_ns_lookup_t); + ns_lookup_p = (test_dbgapi_ns_lookup_t) + LOAD_EXTERNAL_FUNCTION("$libdir/plpgsql", "plpgsql_ns_lookup"); + + DefineCustomBoolVariable("test_dbgapi.trace_forloop_variable", + "when it is true, then control variable of for loop cycles will be printed", + NULL, + &trace_forloop_variable, + false, + PGC_USERSET, 0, + NULL, NULL, NULL); + + DefineCustomStringVariable("test_dbgapi.trace_variable", + "the content of specified variable is printed before and after any executed statement", + NULL, + &trace_variable, + "", + PGC_USERSET, + 0, + NULL, NULL, NULL); + + EmitWarningsOnPlaceholders("test_dbgapi"); + + inited = true; +} + +/* + * Module unload callback + */ +void +_PG_fini(void) +{ + *test_dbgapi_plugin_var = NULL; +} + +Datum +trace_plpgsql(PG_FUNCTION_ARGS) +{ + bool enable_tracer = PG_GETARG_BOOL(0); + + if (enable_tracer) + { + test_dbgapi_plugin_var = (PLpgSQL_plugin **) find_rendezvous_variable("PLpgSQL_plugin"); + *test_dbgapi_plugin_var = &plugin_funcs; + } + else + *test_dbgapi_plugin_var = NULL; + + PG_RETURN_VOID(); +} + +/* + * Returns true, if statement stmt is any inner statement of + * outer (surely some block statement e.g. loop, case, block). + */ +static bool +is_nested_of(PLpgSQL_stmt *stmt, PLpgSQL_stmt *outer_stmt) +{ + PLpgSQL_nsitem *ns = stmt->ns; + PLpgSQL_nsitem *outer_stmt_ns = outer_stmt->ns; + + /* + * There are two possibilities how to assign namespace to some block + * statement. First (that was not implemented) is using real outer + * namespace. The advantage of this method is consistency for all plpgsql + * statements. Unfortunately, in this case we lose one level of + * namespaces. Instead we assign a namespace created for block statement + * (this is namespace for all body's statements). There is inconsistency + * in semantic of assigned namespaces between block and non block + * statements, but we can easy detect if some statement is nested inside + * another statement, and we can detect implicit variables. + */ + while (ns) + { + if (ns == outer_stmt_ns) + return true; + + ns = ns->prev; + } + + return false; +} + +/* + * Returns outer statement's namespace. For block's statements + * we have to go one level up, to skip statement's namespace. + */ +static PLpgSQL_nsitem * +outer_ns(PLpgSQL_stmt *stmt) +{ + switch (stmt->cmd_type) + { + case PLPGSQL_STMT_BLOCK: + case PLPGSQL_STMT_LOOP: + case PLPGSQL_STMT_WHILE: + case PLPGSQL_STMT_FORI: + case PLPGSQL_STMT_FORS: + case PLPGSQL_STMT_FORC: + case PLPGSQL_STMT_FOREACH_A: + case PLPGSQL_STMT_DYNFORS: + return stmt->ns->prev; + + default: + return stmt->ns; + } +} + +/* + * Don't free the returned string, since it can be constant! Returned string, + * when it is not a const string, will be released when eval memory + * context will be reset. + */ +static char * +get_string_value(PLpgSQL_execstate *estate, PLpgSQL_datum *datum) +{ + Oid typeid; + int32 typmod; + Datum value, + text_value; + bool isnull; + char *result; + MemoryContext oldcxt; + + oldcxt = MemoryContextSwitchTo(get_eval_mcontext(estate)); + + /* Example of usage debug API eval_datum and cast_value functions */ + (*test_dbgapi_plugin_var)->eval_datum(estate, datum, + &typeid, &typmod, &value, &isnull); + + /* cast value to text */ + if (!isnull) + { + /* + * Generic cast is more usable than just cast to string. There can be + * casts from any to boolean or from numeric to double. + */ + text_value = (*test_dbgapi_plugin_var)->cast_value(estate, + value, &isnull, + typeid, typmod, + TEXTOID, -1); + + result = TextDatumGetCString(text_value); + } + else + result = "NULL"; + + MemoryContextSwitchTo(oldcxt); + + return result; +} + +/* + * The value of variable can be taken by evaluation of + * an expression. This is older technique based on assign_expr + * method. + */ +static char * +get_string_expr(PLpgSQL_execstate *estate, PLpgSQL_nsitem *ns, char *query) +{ + PLpgSQL_var result; + PLpgSQL_type typ; + PLpgSQL_expr expr; + bool iserror = false; + char *result_str = NULL; + MemoryContext oldcxt; + ResourceOwner oldowner; + + memset(&result, 0, sizeof(result)); + memset(&typ, 0, sizeof(typ)); + memset(&expr, 0, sizeof(expr)); + + result.dtype = PLPGSQL_DTYPE_VAR; + result.dno = -1; + result.refname = "*auxstorage*"; + result.datatype = &typ; + + typ.typoid = TEXTOID; + typ.ttype = PLPGSQL_TTYPE_SCALAR; + typ.typlen = -1; + typ.typbyval = false; + typ.typtype = 'b'; + + expr.query = query; + expr.ns = ns; + expr.parseMode = RAW_PARSE_PLPGSQL_EXPR; + + oldowner = CurrentResourceOwner; + oldcxt = MemoryContextSwitchTo(get_eval_mcontext(estate)); + + BeginInternalSubTransaction(NULL); + MemoryContextSwitchTo(get_eval_mcontext(estate)); + + PG_TRY(); + { + /* It can fail when the variable doesn't exist */ + (*test_dbgapi_plugin_var)->assign_expr(estate, + (PLpgSQL_datum *) &result, + &expr); + + ReleaseCurrentSubTransaction(); + MemoryContextSwitchTo(get_eval_mcontext(estate)); + CurrentResourceOwner = oldowner; + + SPI_restore_connection(); + } + PG_CATCH(); + { + MemoryContextSwitchTo(get_eval_mcontext(estate)); + FlushErrorState(); + RollbackAndReleaseCurrentSubTransaction(); + MemoryContextSwitchTo(get_eval_mcontext(estate)); + CurrentResourceOwner = oldowner; + + iserror = true; + + SPI_restore_connection(); + } + PG_END_TRY(); + + if (!iserror) + { + if (!result.isnull) + result_str = text_to_cstring(DatumGetTextP(result.value)); + else + result_str = "NULL"; + } + + MemoryContextSwitchTo(oldcxt); + + return result_str; +} diff --git a/src/test/modules/test_dbgapi/test_dbgapi.control b/src/test/modules/test_dbgapi/test_dbgapi.control new file mode 100644 index 0000000000..977ff193cb --- /dev/null +++ b/src/test/modules/test_dbgapi/test_dbgapi.control @@ -0,0 +1,7 @@ +# test_dbgapi extension +comment = 'test_dbgapi' +default_version = '1.0' +module_pathname = '$libdir/test_dbgapi' +relocatable = false +schema = pg_catalog +trusted = true