@@ -47,6 +47,7 @@ typedef struct vacuumingOptions
4747bool process_toast ;
4848bool skip_database_stats ;
4949char * buffer_usage_limit ;
50+ bool missing_stats_only ;
5051}vacuumingOptions ;
5152
5253/* object filter options */
@@ -134,6 +135,7 @@ main(int argc, char *argv[])
134135{"no-process-toast" ,no_argument ,NULL ,11 },
135136{"no-process-main" ,no_argument ,NULL ,12 },
136137{"buffer-usage-limit" ,required_argument ,NULL ,13 },
138+ {"missing-stats-only" ,no_argument ,NULL ,14 },
137139{NULL ,0 ,NULL ,0 }
138140};
139141
@@ -281,6 +283,9 @@ main(int argc, char *argv[])
281283case 13 :
282284vacopts .buffer_usage_limit = escape_quotes (optarg );
283285break ;
286+ case 14 :
287+ vacopts .missing_stats_only = true;
288+ break ;
284289default :
285290/* getopt_long already emitted a complaint */
286291pg_log_error_hint ("Try \"%s --help\" for more information." ,progname );
@@ -366,6 +371,14 @@ main(int argc, char *argv[])
366371pg_fatal ("cannot use the \"%s\" option with the \"%s\" option" ,
367372"buffer-usage-limit" ,"full" );
368373
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+
369382/* fill cparams except for dbname, which is set below */
370383cparams .pghost = host ;
371384cparams .pgport = port ;
@@ -406,12 +419,14 @@ main(int argc, char *argv[])
406419if (analyze_in_stages )
407420{
408421int stage ;
422+ SimpleStringList * found_objs = NULL ;
409423
410424for (stage = 0 ;stage < ANALYZE_NUM_STAGES ;stage ++ )
411425{
412426vacuum_one_database (& cparams ,& vacopts ,
413427stage ,
414- & objects ,NULL ,
428+ & objects ,
429+ vacopts .missing_stats_only ?& found_objs :NULL ,
415430concurrentCons ,
416431progname ,echo ,quiet );
417432}
@@ -614,6 +629,13 @@ vacuum_one_database(ConnParams *cparams,
614629"--buffer-usage-limit" ,"16" );
615630}
616631
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+
617639/* skip_database_stats is used automatically if server supports it */
618640vacopts -> skip_database_stats = (PQserverVersion (conn ) >=160000 );
619641
@@ -838,6 +860,9 @@ retrieve_objects(PGconn *conn, vacuumingOptions *vacopts,
838860" FROM pg_catalog.pg_class c\n"
839861" JOIN pg_catalog.pg_namespace ns"
840862" 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"
841866" LEFT JOIN pg_catalog.pg_class t"
842867" ON c.reltoastrelid OPERATOR(pg_catalog.=) t.oid\n" );
843868
@@ -921,6 +946,84 @@ retrieve_objects(PGconn *conn, vacuumingOptions *vacopts,
921946vacopts -> min_mxid_age );
922947}
923948
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+
9241027/*
9251028 * Execute the catalog query. We use the default search_path for this
9261029 * query for consistency with table lookups done elsewhere by the user.
@@ -983,6 +1086,11 @@ vacuum_all_databases(ConnParams *cparams,
9831086
9841087if (analyze_in_stages )
9851088{
1089+ SimpleStringList * * found_objs = NULL ;
1090+
1091+ if (vacopts -> missing_stats_only )
1092+ found_objs = palloc0 (PQntuples (result )* sizeof (SimpleStringList * ));
1093+
9861094/*
9871095 * When analyzing all databases in stages, we analyze them all in the
9881096 * fastest stage first, so that initial statistics become available
@@ -999,7 +1107,8 @@ vacuum_all_databases(ConnParams *cparams,
9991107
10001108vacuum_one_database (cparams ,vacopts ,
10011109stage ,
1002- objects ,NULL ,
1110+ objects ,
1111+ vacopts -> missing_stats_only ?& found_objs [i ] :NULL ,
10031112concurrentCons ,
10041113progname ,echo ,quiet );
10051114}
@@ -1239,6 +1348,7 @@ help(const char *progname)
12391348printf (_ (" -j, --jobs=NUM use this many concurrent connections to vacuum\n" ));
12401349printf (_ (" --min-mxid-age=MXID_AGE minimum multixact ID age of tables to vacuum\n" ));
12411350printf (_ (" --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" ));
12421352printf (_ (" --no-index-cleanup don't remove index entries that point to dead tuples\n" ));
12431353printf (_ (" --no-process-main skip the main relation\n" ));
12441354printf (_ (" --no-process-toast skip the TOAST table associated with the table to vacuum\n" ));