From 0a0a481cc0218bf59b290a4097fb08da6ef696cf Mon Sep 17 00:00:00 2001 From: Anthonin Bonnefoy Date: Tue, 8 Oct 2024 08:45:39 +0200 Subject: Extract nested query from PrepareStmt Previously, executing a prepared query would report the full "PREPARE x AS ..." in pg_stat_statements. This patch extracts the Insert/Select/Delete/Update statement nested in prepare and report only the relevant query part. --- .../expected/level_tracking.out | 2 +- .../pg_stat_statements/expected/planning.out | 10 +++--- .../pg_stat_statements/expected/select.out | 2 +- .../pg_stat_statements/expected/utility.out | 19 +++++++++-- contrib/pg_stat_statements/sql/planning.sql | 4 +-- contrib/pg_stat_statements/sql/utility.sql | 4 +++ src/backend/commands/prepare.c | 33 +++++++++++++++++-- 7 files changed, 61 insertions(+), 13 deletions(-) diff --git a/contrib/pg_stat_statements/expected/level_tracking.out b/contrib/pg_stat_statements/expected/level_tracking.out index 320cb93b597..d36bdaa665b 100644 --- a/contrib/pg_stat_statements/expected/level_tracking.out +++ b/contrib/pg_stat_statements/expected/level_tracking.out @@ -886,8 +886,8 @@ SELECT toplevel, calls, query FROM pg_stat_statements t | 1 | CREATE TEMPORARY TABLE pgss_ctas_1 AS SELECT $1 t | 1 | CREATE TEMPORARY TABLE pgss_ctas_2 AS EXECUTE test_prepare_pgss t | 1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t - f | 1 | PREPARE test_prepare_pgss AS select generate_series($1, $2) f | 1 | SELECT $1 + f | 1 | select generate_series($1, $2) (5 rows) -- Create Table As, top-level tracking. diff --git a/contrib/pg_stat_statements/expected/planning.out b/contrib/pg_stat_statements/expected/planning.out index 9effd11fdc8..3ee1928cbe9 100644 --- a/contrib/pg_stat_statements/expected/planning.out +++ b/contrib/pg_stat_statements/expected/planning.out @@ -58,7 +58,7 @@ SELECT 42; (1 row) SELECT plans, calls, rows, query FROM pg_stat_statements - WHERE query NOT LIKE 'PREPARE%' ORDER BY query COLLATE "C"; + WHERE query NOT LIKE 'SELECT COUNT%' ORDER BY query COLLATE "C"; plans | calls | rows | query -------+-------+------+---------------------------------------------------------- 0 | 1 | 0 | ALTER TABLE stats_plan_test ADD COLUMN x int @@ -72,10 +72,10 @@ SELECT plans, calls, rows, query FROM pg_stat_statements -- for the prepared statement we expect at least one replan, but cache -- invalidations could force more SELECT plans >= 2 AND plans <= calls AS plans_ok, calls, rows, query FROM pg_stat_statements - WHERE query LIKE 'PREPARE%' ORDER BY query COLLATE "C"; - plans_ok | calls | rows | query -----------+-------+------+------------------------------------------------------- - t | 4 | 4 | PREPARE prep1 AS SELECT COUNT(*) FROM stats_plan_test + WHERE query LIKE 'SELECT COUNT%' ORDER BY query COLLATE "C"; + plans_ok | calls | rows | query +----------+-------+------+-------------------------------------- + t | 4 | 4 | SELECT COUNT(*) FROM stats_plan_test (1 row) -- Cleanup diff --git a/contrib/pg_stat_statements/expected/select.out b/contrib/pg_stat_statements/expected/select.out index dd6c756f67d..217a2c0b2bc 100644 --- a/contrib/pg_stat_statements/expected/select.out +++ b/contrib/pg_stat_statements/expected/select.out @@ -127,7 +127,6 @@ DEALLOCATE pgss_test; SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; calls | rows | query -------+------+------------------------------------------------------------------------------ - 1 | 1 | PREPARE pgss_test (int) AS SELECT $1, $2 LIMIT $3 4 | 4 | SELECT $1 + | | -- multiline + | | AS "text" @@ -137,6 +136,7 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; 2 | 2 | SELECT $1 AS "int" 1 | 2 | SELECT $1 AS i UNION SELECT $2 ORDER BY i 1 | 1 | SELECT $1 || $2 + 1 | 1 | SELECT $1, $2 LIMIT $3 0 | 0 | SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C" 1 | 1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t 1 | 2 | WITH t(f) AS ( + diff --git a/contrib/pg_stat_statements/expected/utility.out b/contrib/pg_stat_statements/expected/utility.out index 060d4416dd7..6c0a4a543c2 100644 --- a/contrib/pg_stat_statements/expected/utility.out +++ b/contrib/pg_stat_statements/expected/utility.out @@ -532,6 +532,19 @@ EXECUTE stat_select (2); 2 (1 row) +PREPARE stat_select_2 AS SELECT $1, $2 AS a\; PREPARE stat_select_3 AS SELECT $1, $2, $3 AS a; +EXECUTE stat_select_2 (2, 3); + ?column? | a +----------+--- + 2 | 3 +(1 row) + +EXECUTE stat_select_3 (2, 3, 4); + ?column? | ?column? | a +----------+----------+--- + 2 | 3 | 4 +(1 row) + DEALLOCATE PREPARE stat_select; DEALLOCATE ALL; DEALLOCATE PREPARE ALL; @@ -540,10 +553,12 @@ SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; -------+------+---------------------------------------------------- 2 | 0 | DEALLOCATE $1 2 | 0 | DEALLOCATE ALL - 2 | 2 | PREPARE stat_select AS SELECT $1 AS a + 2 | 2 | SELECT $1 AS a 1 | 1 | SELECT $1 as a + 1 | 1 | SELECT $1, $2 AS a + 1 | 1 | SELECT $1, $2, $3 AS a 1 | 1 | SELECT pg_stat_statements_reset() IS NOT NULL AS t -(5 rows) +(7 rows) SELECT pg_stat_statements_reset() IS NOT NULL AS t; t diff --git a/contrib/pg_stat_statements/sql/planning.sql b/contrib/pg_stat_statements/sql/planning.sql index 46f5d9b951c..9cfe206b3b0 100644 --- a/contrib/pg_stat_statements/sql/planning.sql +++ b/contrib/pg_stat_statements/sql/planning.sql @@ -20,11 +20,11 @@ SELECT 42; SELECT 42; SELECT 42; SELECT plans, calls, rows, query FROM pg_stat_statements - WHERE query NOT LIKE 'PREPARE%' ORDER BY query COLLATE "C"; + WHERE query NOT LIKE 'SELECT COUNT%' ORDER BY query COLLATE "C"; -- for the prepared statement we expect at least one replan, but cache -- invalidations could force more SELECT plans >= 2 AND plans <= calls AS plans_ok, calls, rows, query FROM pg_stat_statements - WHERE query LIKE 'PREPARE%' ORDER BY query COLLATE "C"; + WHERE query LIKE 'SELECT COUNT%' ORDER BY query COLLATE "C"; -- Cleanup DROP TABLE stats_plan_test; diff --git a/contrib/pg_stat_statements/sql/utility.sql b/contrib/pg_stat_statements/sql/utility.sql index dd97203c210..7a7df695910 100644 --- a/contrib/pg_stat_statements/sql/utility.sql +++ b/contrib/pg_stat_statements/sql/utility.sql @@ -270,6 +270,10 @@ EXECUTE stat_select (1); DEALLOCATE stat_select; PREPARE stat_select AS SELECT $1 AS a; EXECUTE stat_select (2); +PREPARE stat_select_2 AS SELECT $1, $2 AS a\; PREPARE stat_select_3 AS SELECT $1, $2, $3 AS a; +EXECUTE stat_select_2 (2, 3); +EXECUTE stat_select_3 (2, 3, 4); + DEALLOCATE PREPARE stat_select; DEALLOCATE ALL; DEALLOCATE PREPARE ALL; diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c index a93f970a292..39aa447cf98 100644 --- a/src/backend/commands/prepare.c +++ b/src/backend/commands/prepare.c @@ -77,8 +77,37 @@ PrepareQuery(ParseState *pstate, PrepareStmt *stmt, */ rawstmt = makeNode(RawStmt); rawstmt->stmt = stmt->query; - rawstmt->stmt_location = stmt_location; - rawstmt->stmt_len = stmt_len; + + switch (nodeTag(stmt->query)) + { + case T_InsertStmt: + rawstmt->stmt_location = ((InsertStmt *) stmt->query)->location; + break; + case T_DeleteStmt: + rawstmt->stmt_location = ((DeleteStmt *) stmt->query)->location; + break; + case T_UpdateStmt: + rawstmt->stmt_location = ((UpdateStmt *) stmt->query)->location; + break; + case T_MergeStmt: + rawstmt->stmt_location = ((MergeStmt *) stmt->query)->location; + break; + case T_SelectStmt: + rawstmt->stmt_location = ((SelectStmt *) stmt->query)->location; + rawstmt->stmt_len = ((SelectStmt *) stmt->query)->stmt_len; + break; + default: + elog(ERROR, "unexpected node type: %d", (int) nodeTag(stmt->query)); + break; + } + + /* + * stmt_len will be defined for SelectStmt within parentheses. If it's + * defined, use it. Otherwise, we need to compute the new length based on + * the new statement location and the initial location+length. + */ + if (stmt_len > 0 && rawstmt->stmt_len == 0) + rawstmt->stmt_len = stmt_len - (rawstmt->stmt_location - stmt_location); /* * Create the CachedPlanSource before we do parse analysis, since it needs -- 2.39.5 (Apple Git-154)