5656#include "jit/jit.h"
5757#include "mb/pg_wchar.h"
5858#include "miscadmin.h"
59+ #include "nodes/nodeFuncs.h"
5960#include "nodes/queryjumble.h"
6061#include "optimizer/planner.h"
6162#include "parser/analyze.h"
@@ -116,6 +117,7 @@ typedef enum pgssVersion
116117 PGSS_V1_11 ,
117118 PGSS_V1_12 ,
118119 PGSS_V1_13 ,
120+ PGSS_V1_14 ,
119121} pgssVersion ;
120122
121123typedef enum pgssStoreKind
@@ -166,6 +168,7 @@ typedef struct Counters
166168 double sum_var_time [PGSS_NUMKIND ]; /* sum of variances in
167169 * planning/execution time in msec */
168170 int64 rows ; /* total # of retrieved or affected rows */
171+ int64 rows_scanned ; /* total # of rows scanned by scan nodes */
169172 int64 shared_blks_hit ; /* # of shared buffer hits */
170173 int64 shared_blks_read ; /* # of shared disk blocks read */
171174 int64 shared_blks_dirtied ; /* # of shared disk blocks dirtied */
@@ -327,6 +330,7 @@ PG_FUNCTION_INFO_V1(pg_stat_statements_1_10);
327330PG_FUNCTION_INFO_V1 (pg_stat_statements_1_11 );
328331PG_FUNCTION_INFO_V1 (pg_stat_statements_1_12 );
329332PG_FUNCTION_INFO_V1 (pg_stat_statements_1_13 );
333+ PG_FUNCTION_INFO_V1 (pg_stat_statements_1_14 );
330334PG_FUNCTION_INFO_V1 (pg_stat_statements );
331335PG_FUNCTION_INFO_V1 (pg_stat_statements_info );
332336
@@ -355,6 +359,7 @@ static void pgss_store(const char *query, int64 queryId,
355359 int query_location , int query_len ,
356360 pgssStoreKind kind ,
357361 double total_time , uint64 rows ,
362+ int64 rows_scanned ,
358363 const BufferUsage * bufusage ,
359364 const WalUsage * walusage ,
360365 const struct JitInstrumentation * jitusage ,
@@ -878,6 +883,7 @@ pgss_post_parse_analyze(ParseState *pstate, Query *query, JumbleState *jstate)
878883 PGSS_INVALID ,
879884 0 ,
880885 0 ,
886+ 0 ,
881887 NULL ,
882888 NULL ,
883889 NULL ,
@@ -960,6 +966,7 @@ pgss_planner(Query *parse,
960966 PGSS_PLAN ,
961967 INSTR_TIME_GET_MILLISEC (duration ),
962968 0 ,
969+ 0 ,
963970 & bufusage ,
964971 & walusage ,
965972 NULL ,
@@ -1071,6 +1078,74 @@ pgss_ExecutorFinish(QueryDesc *queryDesc)
10711078 PG_END_TRY ();
10721079}
10731080
1081+ /*
1082+ * Helper structure for collecting scan statistics
1083+ */
1084+ typedef struct ScanStats
1085+ {
1086+ int64 rows_scanned ; /* total rows scanned (before filtering) */
1087+ } ScanStats ;
1088+
1089+ /*
1090+ * Determine if a node is a scan node that reads from storage.
1091+ * For scan nodes, we want to count tuples before filter conditions are applied.
1092+ */
1093+ static inline bool
1094+ IsScanNode (PlanState * planstate )
1095+ {
1096+ switch (nodeTag (planstate ))
1097+ {
1098+ case T_SeqScanState :
1099+ case T_SampleScanState :
1100+ case T_IndexScanState :
1101+ case T_IndexOnlyScanState :
1102+ case T_BitmapHeapScanState :
1103+ case T_TidScanState :
1104+ case T_TidRangeScanState :
1105+ case T_ForeignScanState :
1106+ case T_CustomScanState :
1107+ return true;
1108+ default :
1109+ return false;
1110+ }
1111+ }
1112+
1113+ /*
1114+ * Walker function to collect scan statistics from all nodes.
1115+ * For scan nodes, rows_scanned = tuples output + tuples filtered.
1116+ */
1117+ static bool
1118+ pgss_collect_scan_stats_walker (PlanState * planstate , void * context )
1119+ {
1120+ ScanStats * stats = (ScanStats * ) context ;
1121+
1122+ if (planstate -> instrument && IsScanNode (planstate ))
1123+ {
1124+ /*
1125+ * For scan nodes, rows_scanned is the number of tuples produced plus
1126+ * the number of tuples filtered out by the scan's filter condition.
1127+ * This represents the total number of tuples read from storage.
1128+ */
1129+ double scanned = planstate -> instrument -> ntuples +
1130+ planstate -> instrument -> nfiltered1 ;
1131+
1132+ stats -> rows_scanned += (int64 ) scanned ;
1133+ }
1134+
1135+ return planstate_tree_walker (planstate , pgss_collect_scan_stats_walker , context );
1136+ }
1137+
1138+ /*
1139+ * Collect scan statistics from the entire plan tree.
1140+ */
1141+ static void
1142+ pgss_collect_scan_stats (PlanState * planstate , ScanStats * stats )
1143+ {
1144+ memset (stats , 0 , sizeof (ScanStats ));
1145+ if (planstate )
1146+ pgss_collect_scan_stats_walker (planstate , stats );
1147+ }
1148+
10741149/*
10751150 * ExecutorEnd hook: store results if needed
10761151 */
@@ -1082,19 +1157,28 @@ pgss_ExecutorEnd(QueryDesc *queryDesc)
10821157 if (queryId != INT64CONST (0 ) && queryDesc -> totaltime &&
10831158 pgss_enabled (nesting_level ))
10841159 {
1160+ ScanStats scan_stats ;
1161+
10851162 /*
10861163 * Make sure stats accumulation is done. (Note: it's okay if several
10871164 * levels of hook all do this.)
10881165 */
10891166 InstrEndLoop (queryDesc -> totaltime );
10901167
1168+ /*
1169+ * Collect scan statistics from the plan tree. This must be done
1170+ * before standard_ExecutorEnd which will destroy the planstate.
1171+ */
1172+ pgss_collect_scan_stats (queryDesc -> planstate , & scan_stats );
1173+
10911174 pgss_store (queryDesc -> sourceText ,
10921175 queryId ,
10931176 queryDesc -> plannedstmt -> stmt_location ,
10941177 queryDesc -> plannedstmt -> stmt_len ,
10951178 PGSS_EXEC ,
10961179 queryDesc -> totaltime -> total * 1000.0 , /* convert to msec */
10971180 queryDesc -> estate -> es_total_processed ,
1181+ scan_stats .rows_scanned ,
10981182 & queryDesc -> totaltime -> bufusage ,
10991183 & queryDesc -> totaltime -> walusage ,
11001184 queryDesc -> estate -> es_jit ? & queryDesc -> estate -> es_jit -> instr : NULL ,
@@ -1229,6 +1313,7 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
12291313 PGSS_EXEC ,
12301314 INSTR_TIME_GET_MILLISEC (duration ),
12311315 rows ,
1316+ 0 , /* rows_scanned not available for utility statements */
12321317 & bufusage ,
12331318 & walusage ,
12341319 NULL ,
@@ -1293,6 +1378,7 @@ pgss_store(const char *query, int64 queryId,
12931378 int query_location , int query_len ,
12941379 pgssStoreKind kind ,
12951380 double total_time , uint64 rows ,
1381+ int64 rows_scanned ,
12961382 const BufferUsage * bufusage ,
12971383 const WalUsage * walusage ,
12981384 const struct JitInstrumentation * jitusage ,
@@ -1460,6 +1546,7 @@ pgss_store(const char *query, int64 queryId,
14601546 }
14611547 }
14621548 entry -> counters .rows += rows ;
1549+ entry -> counters .rows_scanned += rows_scanned ;
14631550 entry -> counters .shared_blks_hit += bufusage -> shared_blks_hit ;
14641551 entry -> counters .shared_blks_read += bufusage -> shared_blks_read ;
14651552 entry -> counters .shared_blks_dirtied += bufusage -> shared_blks_dirtied ;
@@ -1581,7 +1668,8 @@ pg_stat_statements_reset(PG_FUNCTION_ARGS)
15811668#define PG_STAT_STATEMENTS_COLS_V1_11 49
15821669#define PG_STAT_STATEMENTS_COLS_V1_12 52
15831670#define PG_STAT_STATEMENTS_COLS_V1_13 54
1584- #define PG_STAT_STATEMENTS_COLS 54 /* maximum of above */
1671+ #define PG_STAT_STATEMENTS_COLS_V1_14 55
1672+ #define PG_STAT_STATEMENTS_COLS 55 /* maximum of above */
15851673
15861674/*
15871675 * Retrieve statement statistics.
@@ -1593,6 +1681,16 @@ pg_stat_statements_reset(PG_FUNCTION_ARGS)
15931681 * expected API version is identified by embedding it in the C name of the
15941682 * function. Unfortunately we weren't bright enough to do that for 1.1.
15951683 */
1684+ Datum
1685+ pg_stat_statements_1_14 (PG_FUNCTION_ARGS )
1686+ {
1687+ bool showtext = PG_GETARG_BOOL (0 );
1688+
1689+ pg_stat_statements_internal (fcinfo , PGSS_V1_14 , showtext );
1690+
1691+ return (Datum ) 0 ;
1692+ }
1693+
15961694Datum
15971695pg_stat_statements_1_13 (PG_FUNCTION_ARGS )
15981696{
@@ -1765,6 +1863,10 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
17651863 if (api_version != PGSS_V1_13 )
17661864 elog (ERROR , "incorrect number of output arguments" );
17671865 break ;
1866+ case PG_STAT_STATEMENTS_COLS_V1_14 :
1867+ if (api_version != PGSS_V1_14 )
1868+ elog (ERROR , "incorrect number of output arguments" );
1869+ break ;
17681870 default :
17691871 elog (ERROR , "incorrect number of output arguments" );
17701872 }
@@ -1948,6 +2050,8 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
19482050 }
19492051 }
19502052 values [i ++ ] = Int64GetDatumFast (tmp .rows );
2053+ if (api_version >= PGSS_V1_14 )
2054+ values [i ++ ] = Int64GetDatumFast (tmp .rows_scanned );
19512055 values [i ++ ] = Int64GetDatumFast (tmp .shared_blks_hit );
19522056 values [i ++ ] = Int64GetDatumFast (tmp .shared_blks_read );
19532057 if (api_version >= PGSS_V1_1 )
@@ -2038,6 +2142,7 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
20382142 api_version == PGSS_V1_11 ? PG_STAT_STATEMENTS_COLS_V1_11 :
20392143 api_version == PGSS_V1_12 ? PG_STAT_STATEMENTS_COLS_V1_12 :
20402144 api_version == PGSS_V1_13 ? PG_STAT_STATEMENTS_COLS_V1_13 :
2145+ api_version == PGSS_V1_14 ? PG_STAT_STATEMENTS_COLS_V1_14 :
20412146 -1 /* fail if you forget to update this assert */ ));
20422147
20432148 tuplestore_putvalues (rsinfo -> setResult , rsinfo -> setDesc , values , nulls );
0 commit comments