@@ -47,6 +47,7 @@ typedef struct vacuumingOptions
4747 bool process_toast ;
4848 bool skip_database_stats ;
4949 char * buffer_usage_limit ;
50+ bool missing_only ;
5051} vacuumingOptions ;
5152
5253/* object filter options */
@@ -128,6 +129,7 @@ main(int argc, char *argv[])
128129 {"no-process-toast" , no_argument , NULL , 11 },
129130 {"no-process-main" , no_argument , NULL , 12 },
130131 {"buffer-usage-limit" , required_argument , NULL , 13 },
132+ {"missing-only" , no_argument , NULL , 14 },
131133 {NULL , 0 , NULL , 0 }
132134 };
133135
@@ -275,6 +277,9 @@ main(int argc, char *argv[])
275277 case 13 :
276278 vacopts .buffer_usage_limit = escape_quotes (optarg );
277279 break ;
280+ case 14 :
281+ vacopts .missing_only = true;
282+ break ;
278283 default :
279284 /* getopt_long already emitted a complaint */
280285 pg_log_error_hint ("Try \"%s --help\" for more information." , progname );
@@ -360,6 +365,11 @@ main(int argc, char *argv[])
360365 pg_fatal ("cannot use the \"%s\" option with the \"%s\" option" ,
361366 "buffer-usage-limit" , "full" );
362367
368+ /* Prohibit --missing-only without --analyze-only or --analyze-in-stages */
369+ if (vacopts .missing_only && !vacopts .analyze_only )
370+ pg_fatal ("cannot use the \"%s\" option without \"%s\" or \"%s\"" ,
371+ "missing-only" , "analyze-only" , "analyze-in-stages" );
372+
363373 /* fill cparams except for dbname, which is set below */
364374 cparams .pghost = host ;
365375 cparams .pgport = port ;
@@ -584,6 +594,13 @@ vacuum_one_database(ConnParams *cparams,
584594 "--buffer-usage-limit" , "16" );
585595 }
586596
597+ if (vacopts -> missing_only && PQserverVersion (conn ) < 150000 )
598+ {
599+ PQfinish (conn );
600+ pg_fatal ("cannot use the \"%s\" option on server versions older than PostgreSQL %s" ,
601+ "--missing-only" , "15" );
602+ }
603+
587604 /* skip_database_stats is used automatically if server supports it */
588605 vacopts -> skip_database_stats = (PQserverVersion (conn ) >= 160000 );
589606
@@ -672,6 +689,7 @@ vacuum_one_database(ConnParams *cparams,
672689 " FROM pg_catalog.pg_class c\n"
673690 " JOIN pg_catalog.pg_namespace ns"
674691 " ON c.relnamespace OPERATOR(pg_catalog.=) ns.oid\n"
692+ " CROSS JOIN LATERAL (SELECT c.relkind IN ('p', 'I')) as p (inherited)\n"
675693 " LEFT JOIN pg_catalog.pg_class t"
676694 " ON c.reltoastrelid OPERATOR(pg_catalog.=) t.oid\n" );
677695
@@ -755,6 +773,79 @@ vacuum_one_database(ConnParams *cparams,
755773 vacopts -> min_mxid_age );
756774 }
757775
776+ if (vacopts -> missing_only )
777+ {
778+ appendPQExpBufferStr (& catalog_query , " AND (\n" );
779+
780+ /* regular stats */
781+ appendPQExpBufferStr (& catalog_query ,
782+ " EXISTS (SELECT NULL FROM pg_catalog.pg_attribute a\n"
783+ " WHERE a.attrelid OPERATOR(pg_catalog.=) c.oid\n"
784+ " AND c.reltuples OPERATOR(pg_catalog.!=) 0::pg_catalog.float4\n"
785+ " AND a.attnum OPERATOR(pg_catalog.>) 0::pg_catalog.int2\n"
786+ " AND NOT a.attisdropped\n"
787+ " AND a.attstattarget IS DISTINCT FROM 0::pg_catalog.int2\n"
788+ " AND NOT EXISTS (SELECT NULL FROM pg_catalog.pg_statistic s\n"
789+ " WHERE s.starelid OPERATOR(pg_catalog.=) a.attrelid\n"
790+ " AND s.staattnum OPERATOR(pg_catalog.=) a.attnum\n"
791+ " AND s.stainherit OPERATOR(pg_catalog.=) p.inherited))\n" );
792+
793+ /* extended stats */
794+ appendPQExpBufferStr (& catalog_query ,
795+ " OR EXISTS (SELECT NULL FROM pg_catalog.pg_statistic_ext e\n"
796+ " WHERE e.stxrelid OPERATOR(pg_catalog.=) c.oid\n"
797+ " AND c.reltuples OPERATOR(pg_catalog.!=) 0::pg_catalog.float4\n"
798+ " AND e.stxstattarget IS DISTINCT FROM 0::pg_catalog.int2\n"
799+ " AND NOT EXISTS (SELECT NULL FROM pg_catalog.pg_statistic_ext_data d\n"
800+ " WHERE d.stxoid OPERATOR(pg_catalog.=) e.oid\n"
801+ " AND d.stxdinherit OPERATOR(pg_catalog.=) p.inherited))\n" );
802+
803+ /* expression indexes */
804+ appendPQExpBufferStr (& catalog_query ,
805+ " OR EXISTS (SELECT NULL FROM pg_catalog.pg_index i\n"
806+ " CROSS JOIN LATERAL pg_catalog.unnest(i.indkey) WITH ORDINALITY u (attnum, ord)\n"
807+ " WHERE i.indrelid OPERATOR(pg_catalog.=) c.oid\n"
808+ " AND c.reltuples OPERATOR(pg_catalog.!=) 0::pg_catalog.float4\n"
809+ " AND i.indexprs IS NOT NULL\n"
810+ " AND NOT EXISTS (SELECT NULL FROM pg_catalog.pg_statistic s\n"
811+ " WHERE s.starelid OPERATOR(pg_catalog.=) i.indexrelid\n"
812+ " AND s.staattnum OPERATOR(pg_catalog.=) u.ord\n"
813+ " AND s.stainherit OPERATOR(pg_catalog.=) p.inherited))\n" );
814+
815+ /* table inheritance and regular stats */
816+ appendPQExpBufferStr (& catalog_query ,
817+ " OR EXISTS (SELECT NULL FROM pg_catalog.pg_attribute a\n"
818+ " WHERE a.attrelid OPERATOR(pg_catalog.=) c.oid\n"
819+ " AND c.reltuples OPERATOR(pg_catalog.!=) 0::pg_catalog.float4\n"
820+ " AND a.attnum OPERATOR(pg_catalog.>) 0::pg_catalog.int2\n"
821+ " AND NOT a.attisdropped\n"
822+ " AND a.attstattarget IS DISTINCT FROM 0::pg_catalog.int2\n"
823+ " AND c.relhassubclass\n"
824+ " AND NOT p.inherited\n"
825+ " AND EXISTS (SELECT NULL FROM pg_catalog.pg_inherits h\n"
826+ " WHERE h.inhparent OPERATOR(pg_catalog.=) c.oid)\n"
827+ " AND NOT EXISTS (SELECT NULL FROM pg_catalog.pg_statistic s\n"
828+ " WHERE s.starelid OPERATOR(pg_catalog.=) a.attrelid\n"
829+ " AND s.staattnum OPERATOR(pg_catalog.=) a.attnum\n"
830+ " AND s.stainherit))\n" );
831+
832+ /* table inheritance and extended stats */
833+ appendPQExpBufferStr (& catalog_query ,
834+ " OR EXISTS (SELECT NULL FROM pg_catalog.pg_statistic_ext e\n"
835+ " WHERE e.stxrelid OPERATOR(pg_catalog.=) c.oid\n"
836+ " AND c.reltuples OPERATOR(pg_catalog.!=) 0::pg_catalog.float4\n"
837+ " AND e.stxstattarget IS DISTINCT FROM 0::pg_catalog.int2\n"
838+ " AND c.relhassubclass\n"
839+ " AND NOT p.inherited\n"
840+ " AND EXISTS (SELECT NULL FROM pg_catalog.pg_inherits h\n"
841+ " WHERE h.inhparent OPERATOR(pg_catalog.=) c.oid)\n"
842+ " AND NOT EXISTS (SELECT NULL FROM pg_catalog.pg_statistic_ext_data d\n"
843+ " WHERE d.stxoid OPERATOR(pg_catalog.=) e.oid\n"
844+ " AND d.stxdinherit))\n" );
845+
846+ appendPQExpBufferStr (& catalog_query , " )\n" );
847+ }
848+
758849 /*
759850 * Execute the catalog query. We use the default search_path for this
760851 * query for consistency with table lookups done elsewhere by the user.
@@ -1181,6 +1272,7 @@ help(const char *progname)
11811272 printf (_ (" -j, --jobs=NUM use this many concurrent connections to vacuum\n" ));
11821273 printf (_ (" --min-mxid-age=MXID_AGE minimum multixact ID age of tables to vacuum\n" ));
11831274 printf (_ (" --min-xid-age=XID_AGE minimum transaction ID age of tables to vacuum\n" ));
1275+ printf (_ (" --missing-only only analyze relations with missing statistics\n" ));
11841276 printf (_ (" --no-index-cleanup don't remove index entries that point to dead tuples\n" ));
11851277 printf (_ (" --no-process-main skip the main relation\n" ));
11861278 printf (_ (" --no-process-toast skip the TOAST table associated with the table to vacuum\n" ));
0 commit comments