@@ -118,6 +118,7 @@ typedef enum pgssVersion
118118 PGSS_V1_12 ,
119119 PGSS_V1_13 ,
120120 PGSS_V1_14 ,
121+ PGSS_V1_15 ,
121122} pgssVersion ;
122123
123124typedef enum pgssStoreKind
@@ -169,6 +170,7 @@ typedef struct Counters
169170 * planning/execution time in msec */
170171 int64 rows ; /* total # of retrieved or affected rows */
171172 int64 rows_scanned ; /* total # of rows scanned by scan nodes */
173+ int64 rows_filtered ; /* total # of rows filtered out by quals */
172174 int64 shared_blks_hit ; /* # of shared buffer hits */
173175 int64 shared_blks_read ; /* # of shared disk blocks read */
174176 int64 shared_blks_dirtied ; /* # of shared disk blocks dirtied */
@@ -331,6 +333,7 @@ PG_FUNCTION_INFO_V1(pg_stat_statements_1_11);
331333PG_FUNCTION_INFO_V1 (pg_stat_statements_1_12 );
332334PG_FUNCTION_INFO_V1 (pg_stat_statements_1_13 );
333335PG_FUNCTION_INFO_V1 (pg_stat_statements_1_14 );
336+ PG_FUNCTION_INFO_V1 (pg_stat_statements_1_15 );
334337PG_FUNCTION_INFO_V1 (pg_stat_statements );
335338PG_FUNCTION_INFO_V1 (pg_stat_statements_info );
336339
@@ -360,6 +363,7 @@ static void pgss_store(const char *query, int64 queryId,
360363 pgssStoreKind kind ,
361364 double total_time , uint64 rows ,
362365 int64 rows_scanned ,
366+ int64 rows_filtered ,
363367 const BufferUsage * bufusage ,
364368 const WalUsage * walusage ,
365369 const struct JitInstrumentation * jitusage ,
@@ -884,6 +888,7 @@ pgss_post_parse_analyze(ParseState *pstate, Query *query, JumbleState *jstate)
884888 0 ,
885889 0 ,
886890 0 ,
891+ 0 ,
887892 NULL ,
888893 NULL ,
889894 NULL ,
@@ -967,6 +972,7 @@ pgss_planner(Query *parse,
967972 INSTR_TIME_GET_MILLISEC (duration ),
968973 0 ,
969974 0 ,
975+ 0 ,
970976 & bufusage ,
971977 & walusage ,
972978 NULL ,
@@ -1008,6 +1014,20 @@ pgss_planner(Query *parse,
10081014static void
10091015pgss_ExecutorStart (QueryDesc * queryDesc , int eflags )
10101016{
1017+ /*
1018+ * If we're going to track this query, ensure per-node instrumentation is
1019+ * enabled so we can collect rows_scanned and rows_filtered statistics.
1020+ * This must be done before calling standard_ExecutorStart, which is when
1021+ * the instrumentation structures are allocated for each plan node.
1022+ *
1023+ * We use INSTRUMENT_ALL to ensure full instrumentation, including timing
1024+ * which is needed for proper tuple counting in some code paths.
1025+ */
1026+ if (pgss_enabled (nesting_level ) && queryDesc -> plannedstmt -> queryId != INT64CONST (0 ))
1027+ {
1028+ queryDesc -> instrument_options |= INSTRUMENT_ALL ;
1029+ }
1030+
10111031 if (prev_ExecutorStart )
10121032 prev_ExecutorStart (queryDesc , eflags );
10131033 else
@@ -1084,6 +1104,7 @@ pgss_ExecutorFinish(QueryDesc *queryDesc)
10841104typedef struct ScanStats
10851105{
10861106 int64 rows_scanned ; /* total rows scanned (before filtering) */
1107+ int64 rows_filtered ; /* total rows filtered by all nodes */
10871108} ScanStats ;
10881109
10891110/*
@@ -1113,23 +1134,48 @@ IsScanNode(PlanState *planstate)
11131134/*
11141135 * Walker function to collect scan statistics from all nodes.
11151136 * For scan nodes, rows_scanned = tuples output + tuples filtered.
1137+ * For all nodes, rows_filtered = sum of nfiltered1 + nfiltered2.
11161138 */
11171139static bool
11181140pgss_collect_scan_stats_walker (PlanState * planstate , void * context )
11191141{
11201142 ScanStats * stats = (ScanStats * ) context ;
11211143
1122- if (planstate -> instrument && IsScanNode ( planstate ) )
1144+ if (planstate -> instrument )
11231145 {
1146+ Instrumentation * instr = planstate -> instrument ;
1147+
11241148 /*
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.
1149+ * Get the total tuple count for this node. The tuples are tracked in
1150+ * two places: 'ntuples' holds the count from completed cycles, and
1151+ * 'tuplecount' holds the count from the current (possibly incomplete)
1152+ * cycle. We need both to get an accurate total.
1153+ *
1154+ * Note: InstrEndLoop() would normally move tuplecount to ntuples, but
1155+ * it returns early if 'running' is false (which happens after each
1156+ * InstrStopNode call), so we can't rely on it here.
11281157 */
1129- double scanned = planstate -> instrument -> ntuples +
1130- planstate -> instrument -> nfiltered1 ;
1158+ double node_tuples = instr -> ntuples + instr -> tuplecount ;
11311159
1132- stats -> rows_scanned += (int64 ) scanned ;
1160+ /*
1161+ * Collect rows_filtered from all nodes. nfiltered1 tracks tuples
1162+ * removed by scanqual or joinqual, nfiltered2 tracks tuples removed
1163+ * by "other" quals.
1164+ */
1165+ stats -> rows_filtered += (int64 ) (instr -> nfiltered1 + instr -> nfiltered2 );
1166+
1167+ if (IsScanNode (planstate ))
1168+ {
1169+ /*
1170+ * For scan nodes, rows_scanned is the number of tuples produced
1171+ * plus the number of tuples filtered out by the scan's filter
1172+ * condition. This represents the total number of tuples read
1173+ * from storage.
1174+ */
1175+ double scanned = node_tuples + instr -> nfiltered1 ;
1176+
1177+ stats -> rows_scanned += (int64 ) scanned ;
1178+ }
11331179 }
11341180
11351181 return planstate_tree_walker (planstate , pgss_collect_scan_stats_walker , context );
@@ -1179,6 +1225,7 @@ pgss_ExecutorEnd(QueryDesc *queryDesc)
11791225 queryDesc -> totaltime -> total * 1000.0 , /* convert to msec */
11801226 queryDesc -> estate -> es_total_processed ,
11811227 scan_stats .rows_scanned ,
1228+ scan_stats .rows_filtered ,
11821229 & queryDesc -> totaltime -> bufusage ,
11831230 & queryDesc -> totaltime -> walusage ,
11841231 queryDesc -> estate -> es_jit ? & queryDesc -> estate -> es_jit -> instr : NULL ,
@@ -1314,6 +1361,7 @@ pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
13141361 INSTR_TIME_GET_MILLISEC (duration ),
13151362 rows ,
13161363 0 , /* rows_scanned not available for utility statements */
1364+ 0 , /* rows_filtered not available for utility statements */
13171365 & bufusage ,
13181366 & walusage ,
13191367 NULL ,
@@ -1379,6 +1427,7 @@ pgss_store(const char *query, int64 queryId,
13791427 pgssStoreKind kind ,
13801428 double total_time , uint64 rows ,
13811429 int64 rows_scanned ,
1430+ int64 rows_filtered ,
13821431 const BufferUsage * bufusage ,
13831432 const WalUsage * walusage ,
13841433 const struct JitInstrumentation * jitusage ,
@@ -1547,6 +1596,7 @@ pgss_store(const char *query, int64 queryId,
15471596 }
15481597 entry -> counters .rows += rows ;
15491598 entry -> counters .rows_scanned += rows_scanned ;
1599+ entry -> counters .rows_filtered += rows_filtered ;
15501600 entry -> counters .shared_blks_hit += bufusage -> shared_blks_hit ;
15511601 entry -> counters .shared_blks_read += bufusage -> shared_blks_read ;
15521602 entry -> counters .shared_blks_dirtied += bufusage -> shared_blks_dirtied ;
@@ -1669,7 +1719,8 @@ pg_stat_statements_reset(PG_FUNCTION_ARGS)
16691719#define PG_STAT_STATEMENTS_COLS_V1_12 52
16701720#define PG_STAT_STATEMENTS_COLS_V1_13 54
16711721#define PG_STAT_STATEMENTS_COLS_V1_14 55
1672- #define PG_STAT_STATEMENTS_COLS 55 /* maximum of above */
1722+ #define PG_STAT_STATEMENTS_COLS_V1_15 56
1723+ #define PG_STAT_STATEMENTS_COLS 56 /* maximum of above */
16731724
16741725/*
16751726 * Retrieve statement statistics.
@@ -1681,6 +1732,16 @@ pg_stat_statements_reset(PG_FUNCTION_ARGS)
16811732 * expected API version is identified by embedding it in the C name of the
16821733 * function. Unfortunately we weren't bright enough to do that for 1.1.
16831734 */
1735+ Datum
1736+ pg_stat_statements_1_15 (PG_FUNCTION_ARGS )
1737+ {
1738+ bool showtext = PG_GETARG_BOOL (0 );
1739+
1740+ pg_stat_statements_internal (fcinfo , PGSS_V1_15 , showtext );
1741+
1742+ return (Datum ) 0 ;
1743+ }
1744+
16841745Datum
16851746pg_stat_statements_1_14 (PG_FUNCTION_ARGS )
16861747{
@@ -1867,6 +1928,10 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
18671928 if (api_version != PGSS_V1_14 )
18681929 elog (ERROR , "incorrect number of output arguments" );
18691930 break ;
1931+ case PG_STAT_STATEMENTS_COLS_V1_15 :
1932+ if (api_version != PGSS_V1_15 )
1933+ elog (ERROR , "incorrect number of output arguments" );
1934+ break ;
18701935 default :
18711936 elog (ERROR , "incorrect number of output arguments" );
18721937 }
@@ -2052,6 +2117,8 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
20522117 values [i ++ ] = Int64GetDatumFast (tmp .rows );
20532118 if (api_version >= PGSS_V1_14 )
20542119 values [i ++ ] = Int64GetDatumFast (tmp .rows_scanned );
2120+ if (api_version >= PGSS_V1_15 )
2121+ values [i ++ ] = Int64GetDatumFast (tmp .rows_filtered );
20552122 values [i ++ ] = Int64GetDatumFast (tmp .shared_blks_hit );
20562123 values [i ++ ] = Int64GetDatumFast (tmp .shared_blks_read );
20572124 if (api_version >= PGSS_V1_1 )
@@ -2143,6 +2210,7 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
21432210 api_version == PGSS_V1_12 ? PG_STAT_STATEMENTS_COLS_V1_12 :
21442211 api_version == PGSS_V1_13 ? PG_STAT_STATEMENTS_COLS_V1_13 :
21452212 api_version == PGSS_V1_14 ? PG_STAT_STATEMENTS_COLS_V1_14 :
2213+ api_version == PGSS_V1_15 ? PG_STAT_STATEMENTS_COLS_V1_15 :
21462214 -1 /* fail if you forget to update this assert */ ));
21472215
21482216 tuplestore_putvalues (rsinfo -> setResult , rsinfo -> setDesc , values , nulls );
0 commit comments