v3-0001-increment-exec-nest-level-more-often.patch
text/x-diff
Filename: v3-0001-increment-exec-nest-level-more-often.patch
Type: text/x-diff
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
Series: patch v3-0001
| File | + | − |
|---|---|---|
| contrib/pg_stat_statements/expected/level_tracking.out | 55 | 0 |
| contrib/pg_stat_statements/pg_stat_statements.c | 46 | 21 |
| contrib/pg_stat_statements/sql/level_tracking.sql | 33 | 0 |
diff --git a/contrib/pg_stat_statements/expected/level_tracking.out b/contrib/pg_stat_statements/expected/level_tracking.out
index d924c87b41..e7b29e9c99 100644
--- a/contrib/pg_stat_statements/expected/level_tracking.out
+++ b/contrib/pg_stat_statements/expected/level_tracking.out
@@ -67,6 +67,61 @@ SELECT toplevel, calls, query FROM pg_stat_statements
t | 1 | SET pg_stat_statements.track = 'all'
(7 rows)
+-- DO block - top-level tracking without utility.
+SET pg_stat_statements.track = 'top';
+SET pg_stat_statements.track_utility = FALSE;
+SELECT pg_stat_statements_reset();
+ pg_stat_statements_reset
+--------------------------
+
+(1 row)
+
+DELETE FROM stats_track_tab;
+DO $$
+BEGIN
+ DELETE FROM stats_track_tab;
+END; $$;
+DO LANGUAGE plpgsql $$
+BEGIN
+ -- this is a SELECT
+ PERFORM 'hello world'::TEXT;
+END; $$;
+SELECT toplevel, calls, query FROM pg_stat_statements
+ ORDER BY query COLLATE "C", toplevel;
+ toplevel | calls | query
+----------+-------+-----------------------------------
+ t | 1 | DELETE FROM stats_track_tab
+ t | 1 | SELECT pg_stat_statements_reset()
+(2 rows)
+
+-- DO block - all-level tracking without utility.
+SET pg_stat_statements.track = 'all';
+SELECT pg_stat_statements_reset();
+ pg_stat_statements_reset
+--------------------------
+
+(1 row)
+
+DELETE FROM stats_track_tab;
+DO $$
+BEGIN
+ DELETE FROM stats_track_tab;
+END; $$;
+DO LANGUAGE plpgsql $$
+BEGIN
+ -- this is a SELECT
+ PERFORM 'hello world'::TEXT;
+END; $$;
+SELECT toplevel, calls, query FROM pg_stat_statements
+ ORDER BY query COLLATE "C", toplevel;
+ toplevel | calls | query
+----------+-------+-----------------------------------
+ f | 1 | DELETE FROM stats_track_tab
+ t | 1 | DELETE FROM stats_track_tab
+ f | 1 | SELECT $1::TEXT
+ t | 1 | SELECT pg_stat_statements_reset()
+(4 rows)
+
-- PL/pgSQL function - top-level tracking.
SET pg_stat_statements.track = 'top';
SET pg_stat_statements.track_utility = FALSE;
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index 767b6ceb10..da5ef7d0b7 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -99,13 +99,6 @@ static const uint32 PGSS_PG_MAJOR_VERSION = PG_VERSION_NUM / 100;
#define USAGE_DEALLOC_PERCENT 5 /* free this % of entries at once */
#define IS_STICKY(c) ((c.calls[PGSS_PLAN] + c.calls[PGSS_EXEC]) == 0)
-/*
- * Utility statements that pgss_ProcessUtility and pgss_post_parse_analyze
- * ignores.
- */
-#define PGSS_HANDLED_UTILITY(n) (!IsA(n, ExecuteStmt) && \
- !IsA(n, PrepareStmt))
-
/*
* Extension version number, for supporting older extension versions' objects
*/
@@ -840,12 +833,14 @@ pgss_post_parse_analyze(ParseState *pstate, Query *query, JumbleState *jstate)
return;
/*
- * Clear queryId for prepared statements related utility, as those will
- * inherit from the underlying statement's one.
+ * If it's EXECUTE, clear the queryId so that stats will accumulate for
+ * the underlying PREPARE. But don't do this if we're not tracking
+ * utility statements, to avoid messing up another extension that might be
+ * tracking them.
*/
if (query->utilityStmt)
{
- if (pgss_track_utility && !PGSS_HANDLED_UTILITY(query->utilityStmt))
+ if (pgss_track_utility && IsA(query->utilityStmt, ExecuteStmt))
{
query->queryId = UINT64CONST(0);
return;
@@ -1097,6 +1092,7 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
uint64 saved_queryId = pstmt->queryId;
int saved_stmt_location = pstmt->stmt_location;
int saved_stmt_len = pstmt->stmt_len;
+ bool enabled = pgss_track_utility && pgss_enabled(exec_nested_level);
/*
* Force utility statements to get queryId zero. We do this even in cases
@@ -1112,7 +1108,7 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
* that user configured another extension to handle utility statements
* only.
*/
- if (pgss_enabled(exec_nested_level) && pgss_track_utility)
+ if (enabled)
pstmt->queryId = UINT64CONST(0);
/*
@@ -1124,11 +1120,13 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
* hash table entry for the PREPARE (with hash calculated from the query
* string), and then a different one with the same query string (but hash
* calculated from the query tree) would be used to accumulate costs of
- * ensuing EXECUTEs. This would be confusing, and inconsistent with other
- * cases where planning time is not included at all.
+ * ensuing EXECUTEs. This would be confusing. Since PREPARE doesn't
+ * actually run the planner (only parse+rewrite), its costs are generally
+ * pretty negligible and it seems okay to just ignore it.
*/
- if (pgss_track_utility && pgss_enabled(exec_nested_level) &&
- PGSS_HANDLED_UTILITY(parsetree))
+ if (enabled &&
+ !IsA(parsetree, ExecuteStmt) &&
+ !IsA(parsetree, PrepareStmt))
{
instr_time start;
instr_time duration;
@@ -1206,14 +1204,41 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
}
else
{
- if (prev_ProcessUtility)
- prev_ProcessUtility(pstmt, queryString, readOnlyTree,
- context, params, queryEnv,
- dest, qc);
- else
- standard_ProcessUtility(pstmt, queryString, readOnlyTree,
+ /*
+ * Even though we're not tracking execution time for this statement,
+ * we must still increment the nesting level, to ensure that functions
+ * evaluated within it are not seen as top-level calls. But don't do
+ * so for EXECUTE; that way, when control reaches pgss_planner or
+ * pgss_ExecutorStart, we will treat the costs as top-level if
+ * appropriate. Likewise, don't bump for PREPARE, so that parse
+ * analysis will treat the statement as top-level if appropriate.
+ *
+ * To be absolutely certain we don't mess up the nesting level,
+ * evaluate the bump_level condition just once.
+ */
+ bool bump_level =
+ !IsA(parsetree, ExecuteStmt) &&
+ !IsA(parsetree, PrepareStmt);
+
+ if (bump_level)
+ exec_nested_level++;
+ PG_TRY();
+ {
+ if (prev_ProcessUtility)
+ prev_ProcessUtility(pstmt, queryString, readOnlyTree,
context, params, queryEnv,
dest, qc);
+ else
+ standard_ProcessUtility(pstmt, queryString, readOnlyTree,
+ context, params, queryEnv,
+ dest, qc);
+ }
+ PG_FINALLY();
+ {
+ if (bump_level)
+ exec_nested_level--;
+ }
+ PG_END_TRY();
}
}
diff --git a/contrib/pg_stat_statements/sql/level_tracking.sql b/contrib/pg_stat_statements/sql/level_tracking.sql
index 0c20b8ce69..eab881a43b 100644
--- a/contrib/pg_stat_statements/sql/level_tracking.sql
+++ b/contrib/pg_stat_statements/sql/level_tracking.sql
@@ -33,6 +33,39 @@ END; $$;
SELECT toplevel, calls, query FROM pg_stat_statements
ORDER BY query COLLATE "C", toplevel;
+-- DO block - top-level tracking without utility.
+SET pg_stat_statements.track = 'top';
+SET pg_stat_statements.track_utility = FALSE;
+SELECT pg_stat_statements_reset();
+DELETE FROM stats_track_tab;
+DO $$
+BEGIN
+ DELETE FROM stats_track_tab;
+END; $$;
+DO LANGUAGE plpgsql $$
+BEGIN
+ -- this is a SELECT
+ PERFORM 'hello world'::TEXT;
+END; $$;
+SELECT toplevel, calls, query FROM pg_stat_statements
+ ORDER BY query COLLATE "C", toplevel;
+
+-- DO block - all-level tracking without utility.
+SET pg_stat_statements.track = 'all';
+SELECT pg_stat_statements_reset();
+DELETE FROM stats_track_tab;
+DO $$
+BEGIN
+ DELETE FROM stats_track_tab;
+END; $$;
+DO LANGUAGE plpgsql $$
+BEGIN
+ -- this is a SELECT
+ PERFORM 'hello world'::TEXT;
+END; $$;
+SELECT toplevel, calls, query FROM pg_stat_statements
+ ORDER BY query COLLATE "C", toplevel;
+
-- PL/pgSQL function - top-level tracking.
SET pg_stat_statements.track = 'top';
SET pg_stat_statements.track_utility = FALSE;