From 249b0409b181311bb1c375311e43eb767b5c3bdd Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Tue, 27 Sep 2022 10:44:42 +0200 Subject: [PATCH] Fix pg_stat_statements for MERGE We weren't jumbling the merge action list, so wildly different commands would be considered to use the same query ID. Add that, mention it in the docs, and some test lines. Backpatch to 15. Author: Tatsu Reviewed-by: Julien Rouhaud Discussion: https://postgr.es/m/d87e391694db75a038abc3b2597828e8@oss.nttdata.com --- .../expected/pg_stat_statements.out | 41 ++++++++++++++++++- .../sql/pg_stat_statements.sql | 22 ++++++++++ doc/src/sgml/pgstatstatements.sgml | 4 +- src/backend/nodes/nodeFuncs.c | 4 +- src/backend/utils/misc/queryjumble.c | 11 +++++ 5 files changed, 77 insertions(+), 5 deletions(-) diff --git a/contrib/pg_stat_statements/expected/pg_stat_statements.out b/contrib/pg_stat_statements/expected/pg_stat_statements.out index ff0166fb9d..9ac5c87c3a 100644 --- a/contrib/pg_stat_statements/expected/pg_stat_statements.out +++ b/contrib/pg_stat_statements/expected/pg_stat_statements.out @@ -222,12 +222,51 @@ SELECT * FROM test WHERE a IN (1, 2, 3, 4, 5); 3 | c (8 rows) +-- MERGE +MERGE INTO test USING test st ON (st.a = test.a AND st.a >= 4) + WHEN MATCHED THEN UPDATE SET b = st.b || st.a::text; +MERGE INTO test USING test st ON (st.a = test.a AND st.a >= 4) + WHEN MATCHED THEN UPDATE SET b = test.b || st.a::text; +MERGE INTO test USING test st ON (st.a = test.a AND st.a >= 4) + WHEN MATCHED AND length(st.b) > 1 THEN UPDATE SET b = test.b || st.a::text; +MERGE INTO test USING test st ON (st.a = test.a) + WHEN NOT MATCHED THEN INSERT (a, b) VALUES (0, NULL); +MERGE INTO test USING test st ON (st.a = test.a) + WHEN NOT MATCHED THEN INSERT VALUES (0, NULL); -- same as above +MERGE INTO test USING test st ON (st.a = test.a) + WHEN NOT MATCHED THEN INSERT (b, a) VALUES (NULL, 0); +MERGE INTO test USING test st ON (st.a = test.a) + WHEN NOT MATCHED THEN INSERT (a) VALUES (0); +MERGE INTO test USING test st ON (st.a = test.a AND st.a >= 4) + WHEN MATCHED THEN DELETE; +MERGE INTO test USING test st ON (st.a = test.a AND st.a >= 4) + WHEN MATCHED THEN DO NOTHING; +MERGE INTO test USING test st ON (st.a = test.a AND st.a >= 4) + WHEN NOT MATCHED THEN DO NOTHING; SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; query | calls | rows ------------------------------------------------------------------------------+-------+------ DELETE FROM test WHERE a > $1 | 1 | 1 INSERT INTO test (a, b) VALUES ($1, $2), ($3, $4), ($5, $6) | 1 | 3 INSERT INTO test VALUES(generate_series($1, $2), $3) | 1 | 10 + MERGE INTO test USING test st ON (st.a = test.a AND st.a >= $1) +| 1 | 6 + WHEN MATCHED AND length(st.b) > $2 THEN UPDATE SET b = test.b || st.a::text | | + MERGE INTO test USING test st ON (st.a = test.a AND st.a >= $1) +| 1 | 6 + WHEN MATCHED THEN DELETE | | + MERGE INTO test USING test st ON (st.a = test.a AND st.a >= $1) +| 1 | 0 + WHEN MATCHED THEN DO NOTHING | | + MERGE INTO test USING test st ON (st.a = test.a AND st.a >= $1) +| 1 | 6 + WHEN MATCHED THEN UPDATE SET b = st.b || st.a::text | | + MERGE INTO test USING test st ON (st.a = test.a AND st.a >= $1) +| 1 | 6 + WHEN MATCHED THEN UPDATE SET b = test.b || st.a::text | | + MERGE INTO test USING test st ON (st.a = test.a AND st.a >= $1) +| 1 | 0 + WHEN NOT MATCHED THEN DO NOTHING | | + MERGE INTO test USING test st ON (st.a = test.a) +| 1 | 0 + WHEN NOT MATCHED THEN INSERT (a) VALUES ($1) | | + MERGE INTO test USING test st ON (st.a = test.a) +| 2 | 0 + WHEN NOT MATCHED THEN INSERT (a, b) VALUES ($1, $2) | | + MERGE INTO test USING test st ON (st.a = test.a) +| 1 | 0 + WHEN NOT MATCHED THEN INSERT (b, a) VALUES ($1, $2) | | SELECT * FROM test ORDER BY a | 1 | 12 SELECT * FROM test WHERE a > $1 ORDER BY a | 2 | 4 SELECT * FROM test WHERE a IN ($1, $2, $3, $4, $5) | 1 | 8 @@ -235,7 +274,7 @@ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 0 | 0 UPDATE test SET b = $1 WHERE a = $2 | 6 | 6 UPDATE test SET b = $1 WHERE a > $2 | 1 | 3 -(10 rows) +(19 rows) -- -- INSERT, UPDATE, DELETE on test table to validate WAL generation metrics diff --git a/contrib/pg_stat_statements/sql/pg_stat_statements.sql b/contrib/pg_stat_statements/sql/pg_stat_statements.sql index a01f183727..8f5c866225 100644 --- a/contrib/pg_stat_statements/sql/pg_stat_statements.sql +++ b/contrib/pg_stat_statements/sql/pg_stat_statements.sql @@ -100,6 +100,28 @@ SELECT * FROM test ORDER BY a; -- SELECT with IN clause SELECT * FROM test WHERE a IN (1, 2, 3, 4, 5); +-- MERGE +MERGE INTO test USING test st ON (st.a = test.a AND st.a >= 4) + WHEN MATCHED THEN UPDATE SET b = st.b || st.a::text; +MERGE INTO test USING test st ON (st.a = test.a AND st.a >= 4) + WHEN MATCHED THEN UPDATE SET b = test.b || st.a::text; +MERGE INTO test USING test st ON (st.a = test.a AND st.a >= 4) + WHEN MATCHED AND length(st.b) > 1 THEN UPDATE SET b = test.b || st.a::text; +MERGE INTO test USING test st ON (st.a = test.a) + WHEN NOT MATCHED THEN INSERT (a, b) VALUES (0, NULL); +MERGE INTO test USING test st ON (st.a = test.a) + WHEN NOT MATCHED THEN INSERT VALUES (0, NULL); -- same as above +MERGE INTO test USING test st ON (st.a = test.a) + WHEN NOT MATCHED THEN INSERT (b, a) VALUES (NULL, 0); +MERGE INTO test USING test st ON (st.a = test.a) + WHEN NOT MATCHED THEN INSERT (a) VALUES (0); +MERGE INTO test USING test st ON (st.a = test.a AND st.a >= 4) + WHEN MATCHED THEN DELETE; +MERGE INTO test USING test st ON (st.a = test.a AND st.a >= 4) + WHEN MATCHED THEN DO NOTHING; +MERGE INTO test USING test st ON (st.a = test.a AND st.a >= 4) + WHEN NOT MATCHED THEN DO NOTHING; + SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; -- diff --git a/doc/src/sgml/pgstatstatements.sgml b/doc/src/sgml/pgstatstatements.sgml index ecf6cd6bf3..ea90365c7f 100644 --- a/doc/src/sgml/pgstatstatements.sgml +++ b/doc/src/sgml/pgstatstatements.sgml @@ -487,7 +487,7 @@ Plannable queries (that is, SELECT, INSERT, - UPDATE, and DELETE) are combined into a single + UPDATE, DELETE, and MERGE) are combined into a single pg_stat_statements entry whenever they have identical query structures according to an internal hash calculation. Typically, two queries will be considered the same for this purpose if they are @@ -783,7 +783,7 @@ pg_stat_statements.track_utility controls whether utility commands are tracked by the module. Utility commands are all those other than SELECT, INSERT, - UPDATE and DELETE. + UPDATE, DELETE, and MERGE. The default value is on. Only superusers can change this setting. diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 724d076674..0a7b22f97e 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -2259,10 +2259,10 @@ expression_tree_walker_impl(Node *node, { MergeAction *action = (MergeAction *) node; - if (WALK(action->targetList)) - return true; if (WALK(action->qual)) return true; + if (WALK(action->targetList)) + return true; } break; case T_PartitionPruneStepOp: diff --git a/src/backend/utils/misc/queryjumble.c b/src/backend/utils/misc/queryjumble.c index 6e75acda27..a8508463e7 100644 --- a/src/backend/utils/misc/queryjumble.c +++ b/src/backend/utils/misc/queryjumble.c @@ -248,6 +248,7 @@ JumbleQueryInternal(JumbleState *jstate, Query *query) JumbleExpr(jstate, (Node *) query->cteList); JumbleRangeTable(jstate, query->rtable); JumbleExpr(jstate, (Node *) query->jointree); + JumbleExpr(jstate, (Node *) query->mergeActionList); JumbleExpr(jstate, (Node *) query->targetList); JumbleExpr(jstate, (Node *) query->onConflict); JumbleExpr(jstate, (Node *) query->returningList); @@ -738,6 +739,16 @@ JumbleExpr(JumbleState *jstate, Node *node) JumbleExpr(jstate, (Node *) conf->exclRelTlist); } break; + case T_MergeAction: + { + MergeAction *mergeaction = (MergeAction *) node; + + APP_JUMB(mergeaction->matched); + APP_JUMB(mergeaction->commandType); + JumbleExpr(jstate, mergeaction->qual); + JumbleExpr(jstate, (Node *) mergeaction->targetList); + } + break; case T_List: foreach(temp, (List *) node) {