Hi
Ășt 25. 4. 2023 v 10:27 odesĂlatel Pavel Stehule <[email protected]>
napsal:
> Hi
>
> When I implemented profiler and coverage check to plpgsql_check I had to
> write a lot of hard maintaining code related to corect finishing some
> operations (counter incrementing) usually executed by stmt_end and func_end
> hooks. It is based on the fmgr hook and its own statement call stack. Can
> be nice if I can throw this code and use some nice buildin API.
>
> Can we enhance dbg API with two hooks stmt_end_err func_end_err ?
>
> These hooks can be called from exception handlers before re raising.
>
> Or we can define new hooks like executor hooks - stmt_exec and func_exec.
> In custom hooks the exception can be catched.
>
> What do you think about this proposal?
>
>
I did quick and ugly benchmark on worst case
CREATE OR REPLACE FUNCTION public.speedtest(i integer)
RETURNS void
LANGUAGE plpgsql
IMMUTABLE
AS $function$
declare c int = 0;
begin
while c < i
loop
c := c + 1;
end loop;
raise notice '%', c;
end;
$function$
and is possible to write some code (see ugly patch) without any performance
impacts if the hooks are not used. When hooks are active, then there is 7%
performance lost. It is not nice - but this is the worst case. The impact
on real code should be significantly lower
Regards
Pavel
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 4b76f7699a..dc21abd54d 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -1974,157 +1974,180 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
}
-/* ----------
- * exec_stmts Iterate over a list of statements
- * as long as their return code is OK
- * ----------
- */
static int
-exec_stmts(PLpgSQL_execstate *estate, List *stmts)
+exec_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
{
- PLpgSQL_stmt *save_estmt = estate->err_stmt;
- ListCell *s;
+ int rc;
- if (stmts == NIL)
+ switch (stmt->cmd_type)
{
- /*
- * Ensure we do a CHECK_FOR_INTERRUPTS() even though there is no
- * statement. This prevents hangup in a tight loop if, for instance,
- * there is a LOOP construct with an empty body.
- */
- CHECK_FOR_INTERRUPTS();
- return PLPGSQL_RC_OK;
- }
+ case PLPGSQL_STMT_BLOCK:
+ rc = exec_stmt_block(estate, (PLpgSQL_stmt_block *) stmt);
+ break;
- foreach(s, stmts)
- {
- PLpgSQL_stmt *stmt = (PLpgSQL_stmt *) lfirst(s);
- int rc;
+ case PLPGSQL_STMT_ASSIGN:
+ rc = exec_stmt_assign(estate, (PLpgSQL_stmt_assign *) stmt);
+ break;
- estate->err_stmt = stmt;
+ case PLPGSQL_STMT_PERFORM:
+ rc = exec_stmt_perform(estate, (PLpgSQL_stmt_perform *) stmt);
+ break;
- /* Let the plugin know that we are about to execute this statement */
- if (*plpgsql_plugin_ptr && (*plpgsql_plugin_ptr)->stmt_beg)
- ((*plpgsql_plugin_ptr)->stmt_beg) (estate, stmt);
+ case PLPGSQL_STMT_CALL:
+ rc = exec_stmt_call(estate, (PLpgSQL_stmt_call *) stmt);
+ break;
- CHECK_FOR_INTERRUPTS();
+ case PLPGSQL_STMT_GETDIAG:
+ rc = exec_stmt_getdiag(estate, (PLpgSQL_stmt_getdiag *) stmt);
+ break;
- switch (stmt->cmd_type)
- {
- case PLPGSQL_STMT_BLOCK:
- rc = exec_stmt_block(estate, (PLpgSQL_stmt_block *) stmt);
- break;
+ case PLPGSQL_STMT_IF:
+ rc = exec_stmt_if(estate, (PLpgSQL_stmt_if *) stmt);
+ break;
- case PLPGSQL_STMT_ASSIGN:
- rc = exec_stmt_assign(estate, (PLpgSQL_stmt_assign *) stmt);
- break;
+ case PLPGSQL_STMT_CASE:
+ rc = exec_stmt_case(estate, (PLpgSQL_stmt_case *) stmt);
+ break;
- case PLPGSQL_STMT_PERFORM:
- rc = exec_stmt_perform(estate, (PLpgSQL_stmt_perform *) stmt);
- break;
+ case PLPGSQL_STMT_LOOP:
+ rc = exec_stmt_loop(estate, (PLpgSQL_stmt_loop *) stmt);
+ break;
- case PLPGSQL_STMT_CALL:
- rc = exec_stmt_call(estate, (PLpgSQL_stmt_call *) stmt);
- break;
+ case PLPGSQL_STMT_WHILE:
+ rc = exec_stmt_while(estate, (PLpgSQL_stmt_while *) stmt);
+ break;
- case PLPGSQL_STMT_GETDIAG:
- rc = exec_stmt_getdiag(estate, (PLpgSQL_stmt_getdiag *) stmt);
- break;
+ case PLPGSQL_STMT_FORI:
+ rc = exec_stmt_fori(estate, (PLpgSQL_stmt_fori *) stmt);
+ break;
- case PLPGSQL_STMT_IF:
- rc = exec_stmt_if(estate, (PLpgSQL_stmt_if *) stmt);
- break;
+ case PLPGSQL_STMT_FORS:
+ rc = exec_stmt_fors(estate, (PLpgSQL_stmt_fors *) stmt);
+ break;
- case PLPGSQL_STMT_CASE:
- rc = exec_stmt_case(estate, (PLpgSQL_stmt_case *) stmt);
- break;
+ case PLPGSQL_STMT_FORC:
+ rc = exec_stmt_forc(estate, (PLpgSQL_stmt_forc *) stmt);
+ break;
- case PLPGSQL_STMT_LOOP:
- rc = exec_stmt_loop(estate, (PLpgSQL_stmt_loop *) stmt);
- break;
+ case PLPGSQL_STMT_FOREACH_A:
+ rc = exec_stmt_foreach_a(estate, (PLpgSQL_stmt_foreach_a *) stmt);
+ break;
- case PLPGSQL_STMT_WHILE:
- rc = exec_stmt_while(estate, (PLpgSQL_stmt_while *) stmt);
- break;
+ case PLPGSQL_STMT_EXIT:
+ rc = exec_stmt_exit(estate, (PLpgSQL_stmt_exit *) stmt);
+ break;
- case PLPGSQL_STMT_FORI:
- rc = exec_stmt_fori(estate, (PLpgSQL_stmt_fori *) stmt);
- break;
+ case PLPGSQL_STMT_RETURN:
+ rc = exec_stmt_return(estate, (PLpgSQL_stmt_return *) stmt);
+ break;
- case PLPGSQL_STMT_FORS:
- rc = exec_stmt_fors(estate, (PLpgSQL_stmt_fors *) stmt);
- break;
+ case PLPGSQL_STMT_RETURN_NEXT:
+ rc = exec_stmt_return_next(estate, (PLpgSQL_stmt_return_next *) stmt);
+ break;
- case PLPGSQL_STMT_FORC:
- rc = exec_stmt_forc(estate, (PLpgSQL_stmt_forc *) stmt);
- break;
+ case PLPGSQL_STMT_RETURN_QUERY:
+ rc = exec_stmt_return_query(estate, (PLpgSQL_stmt_return_query *) stmt);
+ break;
- case PLPGSQL_STMT_FOREACH_A:
- rc = exec_stmt_foreach_a(estate, (PLpgSQL_stmt_foreach_a *) stmt);
- break;
+ case PLPGSQL_STMT_RAISE:
+ rc = exec_stmt_raise(estate, (PLpgSQL_stmt_raise *) stmt);
+ break;
- case PLPGSQL_STMT_EXIT:
- rc = exec_stmt_exit(estate, (PLpgSQL_stmt_exit *) stmt);
- break;
+ case PLPGSQL_STMT_ASSERT:
+ rc = exec_stmt_assert(estate, (PLpgSQL_stmt_assert *) stmt);
+ break;
- case PLPGSQL_STMT_RETURN:
- rc = exec_stmt_return(estate, (PLpgSQL_stmt_return *) stmt);
- break;
+ case PLPGSQL_STMT_EXECSQL:
+ rc = exec_stmt_execsql(estate, (PLpgSQL_stmt_execsql *) stmt);
+ break;
- case PLPGSQL_STMT_RETURN_NEXT:
- rc = exec_stmt_return_next(estate, (PLpgSQL_stmt_return_next *) stmt);
- break;
+ case PLPGSQL_STMT_DYNEXECUTE:
+ rc = exec_stmt_dynexecute(estate, (PLpgSQL_stmt_dynexecute *) stmt);
+ break;
- case PLPGSQL_STMT_RETURN_QUERY:
- rc = exec_stmt_return_query(estate, (PLpgSQL_stmt_return_query *) stmt);
- break;
+ case PLPGSQL_STMT_DYNFORS:
+ rc = exec_stmt_dynfors(estate, (PLpgSQL_stmt_dynfors *) stmt);
+ break;
- case PLPGSQL_STMT_RAISE:
- rc = exec_stmt_raise(estate, (PLpgSQL_stmt_raise *) stmt);
- break;
+ case PLPGSQL_STMT_OPEN:
+ rc = exec_stmt_open(estate, (PLpgSQL_stmt_open *) stmt);
+ break;
- case PLPGSQL_STMT_ASSERT:
- rc = exec_stmt_assert(estate, (PLpgSQL_stmt_assert *) stmt);
- break;
+ case PLPGSQL_STMT_FETCH:
+ rc = exec_stmt_fetch(estate, (PLpgSQL_stmt_fetch *) stmt);
+ break;
- case PLPGSQL_STMT_EXECSQL:
- rc = exec_stmt_execsql(estate, (PLpgSQL_stmt_execsql *) stmt);
- break;
+ case PLPGSQL_STMT_CLOSE:
+ rc = exec_stmt_close(estate, (PLpgSQL_stmt_close *) stmt);
+ break;
- case PLPGSQL_STMT_DYNEXECUTE:
- rc = exec_stmt_dynexecute(estate, (PLpgSQL_stmt_dynexecute *) stmt);
- break;
+ case PLPGSQL_STMT_COMMIT:
+ rc = exec_stmt_commit(estate, (PLpgSQL_stmt_commit *) stmt);
+ break;
- case PLPGSQL_STMT_DYNFORS:
- rc = exec_stmt_dynfors(estate, (PLpgSQL_stmt_dynfors *) stmt);
- break;
+ case PLPGSQL_STMT_ROLLBACK:
+ rc = exec_stmt_rollback(estate, (PLpgSQL_stmt_rollback *) stmt);
+ break;
- case PLPGSQL_STMT_OPEN:
- rc = exec_stmt_open(estate, (PLpgSQL_stmt_open *) stmt);
- break;
+ default:
+ elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
+ rc = -1; /* keep compiler quiet */
+ }
- case PLPGSQL_STMT_FETCH:
- rc = exec_stmt_fetch(estate, (PLpgSQL_stmt_fetch *) stmt);
- break;
+ return rc;
+}
- case PLPGSQL_STMT_CLOSE:
- rc = exec_stmt_close(estate, (PLpgSQL_stmt_close *) stmt);
- break;
+/* ----------
+ * exec_stmts Iterate over a list of statements
+ * as long as their return code is OK
+ * ----------
+ */
+static int
+exec_stmts(PLpgSQL_execstate *estate, List *stmts)
+{
+ ListCell *s;
+ PLpgSQL_stmt *save_estmt = estate->err_stmt;
- case PLPGSQL_STMT_COMMIT:
- rc = exec_stmt_commit(estate, (PLpgSQL_stmt_commit *) stmt);
- break;
+ if (stmts == NIL)
+ {
+ /*
+ * Ensure we do a CHECK_FOR_INTERRUPTS() even though there is no
+ * statement. This prevents hangup in a tight loop if, for instance,
+ * there is a LOOP construct with an empty body.
+ */
+ CHECK_FOR_INTERRUPTS();
+ return PLPGSQL_RC_OK;
+ }
- case PLPGSQL_STMT_ROLLBACK:
- rc = exec_stmt_rollback(estate, (PLpgSQL_stmt_rollback *) stmt);
- break;
+ foreach(s, stmts)
+ {
+ PLpgSQL_stmt *stmt = (PLpgSQL_stmt *) lfirst(s);
+ int rc;
- default:
- /* point err_stmt to parent, since this one seems corrupt */
- estate->err_stmt = save_estmt;
- elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
- rc = -1; /* keep compiler quiet */
+ estate->err_stmt = stmt;
+ save_estmt = estate->err_stmt;
+
+
+ /* Let the plugin know that we are about to execute this statement */
+ if (*plpgsql_plugin_ptr && (*plpgsql_plugin_ptr)->stmt_beg)
+ ((*plpgsql_plugin_ptr)->stmt_beg) (estate, stmt);
+
+ CHECK_FOR_INTERRUPTS();
+
+ if (*plpgsql_plugin_ptr)
+ {
+ PG_TRY();
+ {
+ rc = exec_stmt(estate, stmt);
+ }
+ PG_CATCH();
+ {
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
}
+ else
+ rc = exec_stmt(estate, stmt);
/* Let the plugin know that we have finished executing this statement */
if (*plpgsql_plugin_ptr && (*plpgsql_plugin_ptr)->stmt_end)