From 60a686d38d7becb0602bc88f49bd2d7d841712cd Mon Sep 17 00:00:00 2001 From: Sami Imseih Date: Mon, 12 Oct 2026 23:09:43 +0000 Subject: [PATCH v1 1/1] Allow extensions to set nesting_level --- .../pg_stat_statements/pg_stat_statements.c | 20 +++++++++++++++++-- src/backend/tcop/pquery.c | 1 + src/include/executor/execdesc.h | 20 +++++++++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c index 4562222c9fc..8e380d06467 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -356,6 +356,7 @@ static void pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString, DestReceiver *dest, QueryCompletion *qc); static void pgss_store(const char *query, int64 queryId, int query_location, int query_len, + bool toplevel, pgssStoreKind kind, double total_time, uint64 rows, const BufferUsage *bufusage, @@ -866,6 +867,7 @@ pgss_post_parse_analyze(ParseState *pstate, Query *query, const JumbleState *jst query->queryId, query->stmt_location, query->stmt_len, + nesting_level == 0, PGSS_INVALID, 0, 0, @@ -948,6 +950,7 @@ pgss_planner(Query *parse, parse->queryId, parse->stmt_location, parse->stmt_len, + nesting_level == 0, PGSS_PLAN, INSTR_TIME_GET_MILLISEC(duration), 0, @@ -1001,6 +1004,9 @@ pgss_ExecutorStart(QueryDesc *queryDesc, int eflags) { /* Request all summary instrumentation, i.e. timing, buffers and WAL */ queryDesc->query_instr_options |= INSTRUMENT_ALL; + + /* Remember the nesting level for ExecutorEnd */ + queryDesc->start_nesting_level = nesting_level; } if (prev_ExecutorStart) @@ -1059,13 +1065,21 @@ pgss_ExecutorEnd(QueryDesc *queryDesc) { int64 queryId = queryDesc->plannedstmt->queryId; + /* + * Use the nesting level saved at ExecutorStart rather than the current + * one, since the query may finish at a different level, as for a cursor + * closed at COMMIT. If no level was saved, the query was not tracked at + * ExecutorStart. + */ if (queryId != INT64CONST(0) && queryDesc->query_instr && - pgss_enabled(nesting_level)) + queryDesc->start_nesting_level >= 0 && + pgss_enabled(queryDesc->start_nesting_level)) { pgss_store(queryDesc->sourceText, queryId, queryDesc->plannedstmt->stmt_location, queryDesc->plannedstmt->stmt_len, + queryDesc->start_nesting_level == 0, PGSS_EXEC, INSTR_TIME_GET_MILLISEC(queryDesc->query_instr->total), queryDesc->estate->es_total_processed, @@ -1202,6 +1216,7 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString, saved_queryId, saved_stmt_location, saved_stmt_len, + nesting_level == 0, PGSS_EXEC, INSTR_TIME_GET_MILLISEC(duration), rows, @@ -1267,6 +1282,7 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString, static void pgss_store(const char *query, int64 queryId, int query_location, int query_len, + bool toplevel, pgssStoreKind kind, double total_time, uint64 rows, const BufferUsage *bufusage, @@ -1310,7 +1326,7 @@ pgss_store(const char *query, int64 queryId, key.userid = GetUserId(); key.dbid = MyDatabaseId; key.queryid = queryId; - key.toplevel = (nesting_level == 0); + key.toplevel = toplevel; /* Lookup the hash table entry with shared lock. */ LWLockAcquire(&pgss->lock.lock, LW_SHARED); diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c index fa2ef20f178..8d72c870ec1 100644 --- a/src/backend/tcop/pquery.c +++ b/src/backend/tcop/pquery.c @@ -87,6 +87,7 @@ CreateQueryDesc(PlannedStmt *plannedstmt, qd->queryEnv = queryEnv; qd->instrument_options = instrument_options; /* instrumentation wanted? */ qd->query_instr_options = 0; + qd->start_nesting_level = -1; /* null these fields until set by ExecutorStart */ qd->tupDesc = NULL; diff --git a/src/include/executor/execdesc.h b/src/include/executor/execdesc.h index 37c2576e4bc..e946343e7e7 100644 --- a/src/include/executor/execdesc.h +++ b/src/include/executor/execdesc.h @@ -45,6 +45,26 @@ typedef struct QueryDesc int query_instr_options; /* OR of InstrumentOption flags for * query_instr */ + /* + * Nesting level at which this query was started, or -1 if not set. + * + * A query can end at a different nesting level than it started at. For + * example, a WITH HOLD cursor declared at top level is closed at COMMIT. + * An extension that needs the starting level in ExecutorEnd can save it + * here from its ExecutorStart hook. + * + * A statement sent by the client is at level 0, and so is one run by + * EXECUTE. A query started by another statement, while that statement is + * being planned, executed or processed as a utility command, is one level + * deeper. For example, the query of a top-level DECLARE CURSOR, EXPLAIN + * or CREATE TABLE AS is at level 1, and so is a query run by a function + * called from a top-level statement. This is how, for example, + * pg_stat_statements counts levels. + * + * An extension that counts levels differently must not set this field. + */ + int start_nesting_level; + /* These fields are set by ExecutorStart */ TupleDesc tupDesc; /* descriptor for result tuples */ EState *estate; /* executor's query-wide state */ -- 2.54.0 (Apple Git-157)