@@ -47,6 +47,7 @@ typedef struct vacuumingOptions
47
47
bool process_toast ;
48
48
bool skip_database_stats ;
49
49
char * buffer_usage_limit ;
50
+ bool missing_stats_only ;
50
51
} vacuumingOptions ;
51
52
52
53
/* object filter options */
@@ -134,6 +135,7 @@ main(int argc, char *argv[])
134
135
{"no-process-toast" , no_argument , NULL , 11 },
135
136
{"no-process-main" , no_argument , NULL , 12 },
136
137
{"buffer-usage-limit" , required_argument , NULL , 13 },
138
+ {"missing-stats-only" , no_argument , NULL , 14 },
137
139
{NULL , 0 , NULL , 0 }
138
140
};
139
141
@@ -281,6 +283,9 @@ main(int argc, char *argv[])
281
283
case 13 :
282
284
vacopts .buffer_usage_limit = escape_quotes (optarg );
283
285
break ;
286
+ case 14 :
287
+ vacopts .missing_stats_only = true;
288
+ break ;
284
289
default :
285
290
/* getopt_long already emitted a complaint */
286
291
pg_log_error_hint ("Try \"%s --help\" for more information." , progname );
@@ -366,6 +371,14 @@ main(int argc, char *argv[])
366
371
pg_fatal ("cannot use the \"%s\" option with the \"%s\" option" ,
367
372
"buffer-usage-limit" , "full" );
368
373
374
+ /*
375
+ * Prohibit --missing-stats-only without --analyze-only or
376
+ * --analyze-in-stages.
377
+ */
378
+ if (vacopts .missing_stats_only && !vacopts .analyze_only )
379
+ pg_fatal ("cannot use the \"%s\" option without \"%s\" or \"%s\"" ,
380
+ "missing-stats-only" , "analyze-only" , "analyze-in-stages" );
381
+
369
382
/* fill cparams except for dbname, which is set below */
370
383
cparams .pghost = host ;
371
384
cparams .pgport = port ;
@@ -406,12 +419,14 @@ main(int argc, char *argv[])
406
419
if (analyze_in_stages )
407
420
{
408
421
int stage ;
422
+ SimpleStringList * found_objs = NULL ;
409
423
410
424
for (stage = 0 ; stage < ANALYZE_NUM_STAGES ; stage ++ )
411
425
{
412
426
vacuum_one_database (& cparams , & vacopts ,
413
427
stage ,
414
- & objects , NULL ,
428
+ & objects ,
429
+ vacopts .missing_stats_only ? & found_objs : NULL ,
415
430
concurrentCons ,
416
431
progname , echo , quiet );
417
432
}
@@ -614,6 +629,13 @@ vacuum_one_database(ConnParams *cparams,
614
629
"--buffer-usage-limit" , "16" );
615
630
}
616
631
632
+ if (vacopts -> missing_stats_only && PQserverVersion (conn ) < 150000 )
633
+ {
634
+ PQfinish (conn );
635
+ pg_fatal ("cannot use the \"%s\" option on server versions older than PostgreSQL %s" ,
636
+ "--missing-stats-only" , "15" );
637
+ }
638
+
617
639
/* skip_database_stats is used automatically if server supports it */
618
640
vacopts -> skip_database_stats = (PQserverVersion (conn ) >= 160000 );
619
641
@@ -838,6 +860,9 @@ retrieve_objects(PGconn *conn, vacuumingOptions *vacopts,
838
860
" FROM pg_catalog.pg_class c\n"
839
861
" JOIN pg_catalog.pg_namespace ns"
840
862
" ON c.relnamespace OPERATOR(pg_catalog.=) ns.oid\n"
863
+ " CROSS JOIN LATERAL (SELECT c.relkind IN ("
864
+ CppAsString2 (RELKIND_PARTITIONED_TABLE ) ", "
865
+ CppAsString2 (RELKIND_PARTITIONED_INDEX ) ")) as p (inherited)\n"
841
866
" LEFT JOIN pg_catalog.pg_class t"
842
867
" ON c.reltoastrelid OPERATOR(pg_catalog.=) t.oid\n" );
843
868
@@ -921,6 +946,84 @@ retrieve_objects(PGconn *conn, vacuumingOptions *vacopts,
921
946
vacopts -> min_mxid_age );
922
947
}
923
948
949
+ if (vacopts -> missing_stats_only )
950
+ {
951
+ appendPQExpBufferStr (& catalog_query , " AND (\n" );
952
+
953
+ /* regular stats */
954
+ appendPQExpBufferStr (& catalog_query ,
955
+ " EXISTS (SELECT NULL FROM pg_catalog.pg_attribute a\n"
956
+ " WHERE a.attrelid OPERATOR(pg_catalog.=) c.oid\n"
957
+ " AND c.reltuples OPERATOR(pg_catalog.!=) 0::pg_catalog.float4\n"
958
+ " AND a.attnum OPERATOR(pg_catalog.>) 0::pg_catalog.int2\n"
959
+ " AND NOT a.attisdropped\n"
960
+ " AND a.attstattarget IS DISTINCT FROM 0::pg_catalog.int2\n"
961
+ " AND NOT EXISTS (SELECT NULL FROM pg_catalog.pg_statistic s\n"
962
+ " WHERE s.starelid OPERATOR(pg_catalog.=) a.attrelid\n"
963
+ " AND s.staattnum OPERATOR(pg_catalog.=) a.attnum\n"
964
+ " AND s.stainherit OPERATOR(pg_catalog.=) p.inherited))\n" );
965
+
966
+ /* extended stats */
967
+ appendPQExpBufferStr (& catalog_query ,
968
+ " OR EXISTS (SELECT NULL FROM pg_catalog.pg_statistic_ext e\n"
969
+ " WHERE e.stxrelid OPERATOR(pg_catalog.=) c.oid\n"
970
+ " AND c.reltuples OPERATOR(pg_catalog.!=) 0::pg_catalog.float4\n"
971
+ " AND e.stxstattarget IS DISTINCT FROM 0::pg_catalog.int2\n"
972
+ " AND NOT EXISTS (SELECT NULL FROM pg_catalog.pg_statistic_ext_data d\n"
973
+ " WHERE d.stxoid OPERATOR(pg_catalog.=) e.oid\n"
974
+ " AND d.stxdinherit OPERATOR(pg_catalog.=) p.inherited))\n" );
975
+
976
+ /* expression indexes */
977
+ appendPQExpBufferStr (& catalog_query ,
978
+ " OR EXISTS (SELECT NULL FROM pg_catalog.pg_attribute a\n"
979
+ " JOIN pg_catalog.pg_index i"
980
+ " ON i.indexrelid OPERATOR(pg_catalog.=) a.attrelid\n"
981
+ " WHERE i.indrelid OPERATOR(pg_catalog.=) c.oid\n"
982
+ " AND c.reltuples OPERATOR(pg_catalog.!=) 0::pg_catalog.float4\n"
983
+ " AND i.indkey[a.attnum OPERATOR(pg_catalog.-) 1::pg_catalog.int2]"
984
+ " OPERATOR(pg_catalog.=) 0::pg_catalog.int2\n"
985
+ " AND a.attnum OPERATOR(pg_catalog.>) 0::pg_catalog.int2\n"
986
+ " AND NOT a.attisdropped\n"
987
+ " AND a.attstattarget IS DISTINCT FROM 0::pg_catalog.int2\n"
988
+ " AND NOT EXISTS (SELECT NULL FROM pg_catalog.pg_statistic s\n"
989
+ " WHERE s.starelid OPERATOR(pg_catalog.=) a.attrelid\n"
990
+ " AND s.staattnum OPERATOR(pg_catalog.=) a.attnum\n"
991
+ " AND s.stainherit OPERATOR(pg_catalog.=) p.inherited))\n" );
992
+
993
+ /* inheritance and regular stats */
994
+ appendPQExpBufferStr (& catalog_query ,
995
+ " OR EXISTS (SELECT NULL FROM pg_catalog.pg_attribute a\n"
996
+ " WHERE a.attrelid OPERATOR(pg_catalog.=) c.oid\n"
997
+ " AND c.reltuples OPERATOR(pg_catalog.!=) 0::pg_catalog.float4\n"
998
+ " AND a.attnum OPERATOR(pg_catalog.>) 0::pg_catalog.int2\n"
999
+ " AND NOT a.attisdropped\n"
1000
+ " AND a.attstattarget IS DISTINCT FROM 0::pg_catalog.int2\n"
1001
+ " AND c.relhassubclass\n"
1002
+ " AND NOT p.inherited\n"
1003
+ " AND EXISTS (SELECT NULL FROM pg_catalog.pg_inherits h\n"
1004
+ " WHERE h.inhparent OPERATOR(pg_catalog.=) c.oid)\n"
1005
+ " AND NOT EXISTS (SELECT NULL FROM pg_catalog.pg_statistic s\n"
1006
+ " WHERE s.starelid OPERATOR(pg_catalog.=) a.attrelid\n"
1007
+ " AND s.staattnum OPERATOR(pg_catalog.=) a.attnum\n"
1008
+ " AND s.stainherit))\n" );
1009
+
1010
+ /* inheritance and extended stats */
1011
+ appendPQExpBufferStr (& catalog_query ,
1012
+ " OR EXISTS (SELECT NULL FROM pg_catalog.pg_statistic_ext e\n"
1013
+ " WHERE e.stxrelid OPERATOR(pg_catalog.=) c.oid\n"
1014
+ " AND c.reltuples OPERATOR(pg_catalog.!=) 0::pg_catalog.float4\n"
1015
+ " AND e.stxstattarget IS DISTINCT FROM 0::pg_catalog.int2\n"
1016
+ " AND c.relhassubclass\n"
1017
+ " AND NOT p.inherited\n"
1018
+ " AND EXISTS (SELECT NULL FROM pg_catalog.pg_inherits h\n"
1019
+ " WHERE h.inhparent OPERATOR(pg_catalog.=) c.oid)\n"
1020
+ " AND NOT EXISTS (SELECT NULL FROM pg_catalog.pg_statistic_ext_data d\n"
1021
+ " WHERE d.stxoid OPERATOR(pg_catalog.=) e.oid\n"
1022
+ " AND d.stxdinherit))\n" );
1023
+
1024
+ appendPQExpBufferStr (& catalog_query , " )\n" );
1025
+ }
1026
+
924
1027
/*
925
1028
* Execute the catalog query. We use the default search_path for this
926
1029
* query for consistency with table lookups done elsewhere by the user.
@@ -983,6 +1086,11 @@ vacuum_all_databases(ConnParams *cparams,
983
1086
984
1087
if (analyze_in_stages )
985
1088
{
1089
+ SimpleStringList * * found_objs = NULL ;
1090
+
1091
+ if (vacopts -> missing_stats_only )
1092
+ found_objs = palloc0 (PQntuples (result ) * sizeof (SimpleStringList * ));
1093
+
986
1094
/*
987
1095
* When analyzing all databases in stages, we analyze them all in the
988
1096
* fastest stage first, so that initial statistics become available
@@ -999,7 +1107,8 @@ vacuum_all_databases(ConnParams *cparams,
999
1107
1000
1108
vacuum_one_database (cparams , vacopts ,
1001
1109
stage ,
1002
- objects , NULL ,
1110
+ objects ,
1111
+ vacopts -> missing_stats_only ? & found_objs [i ] : NULL ,
1003
1112
concurrentCons ,
1004
1113
progname , echo , quiet );
1005
1114
}
@@ -1239,6 +1348,7 @@ help(const char *progname)
1239
1348
printf (_ (" -j, --jobs=NUM use this many concurrent connections to vacuum\n" ));
1240
1349
printf (_ (" --min-mxid-age=MXID_AGE minimum multixact ID age of tables to vacuum\n" ));
1241
1350
printf (_ (" --min-xid-age=XID_AGE minimum transaction ID age of tables to vacuum\n" ));
1351
+ printf (_ (" --missing-stats-only only analyze relations with missing statistics\n" ));
1242
1352
printf (_ (" --no-index-cleanup don't remove index entries that point to dead tuples\n" ));
1243
1353
printf (_ (" --no-process-main skip the main relation\n" ));
1244
1354
printf (_ (" --no-process-toast skip the TOAST table associated with the table to vacuum\n" ));
0 commit comments