diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index ea6b260..9e16f0d 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -298,13 +298,10 @@ COMMIT;
 -- forgotten when another transaction commits before the stats are flushed.
 -- Pending stats are only flushed while the backend is idle, so run the
 -- TRUNCATE and ANALYZE cases inside a procedure to make sure they are not
--- flushed in between.  VACUUM cannot run in a procedure; if the stats happen
--- to get flushed between the INSERT and the VACUUM, that case still passes,
--- but doesn't test anything.
+-- flushed in between.  VACUUM is tested separately below.
 -----
 CREATE TABLE stats_pending_trunc(id int);
 CREATE TABLE stats_pending_analyze(id int);
-CREATE TABLE stats_pending_vacuum(id int);
 INSERT INTO stats_pending_trunc SELECT generate_series(1, 10);
 SELECT pg_stat_force_next_flush();
  pg_stat_force_next_flush 
@@ -326,8 +323,6 @@ BEGIN
 END
 $$;
 CALL stats_pending_proc();
-INSERT INTO stats_pending_vacuum SELECT generate_series(1, 10);
-VACUUM stats_pending_vacuum;
 SELECT pg_stat_force_next_flush();
  pg_stat_force_next_flush 
 --------------------------
@@ -341,11 +336,58 @@ SELECT relname, n_live_tup, n_dead_tup, n_mod_since_analyze
 -----------------------+------------+------------+---------------------
  stats_pending_analyze |          6 |          4 |                   0
  stats_pending_trunc   |          3 |          0 |                  13
- stats_pending_vacuum  |         10 |          0 |                  10
-(3 rows)
+(2 rows)
 
 DROP PROCEDURE stats_pending_proc();
-DROP TABLE stats_pending_trunc, stats_pending_analyze, stats_pending_vacuum;
+DROP TABLE stats_pending_trunc, stats_pending_analyze;
+-- VACUUM cannot run in a procedure, so let the VACUUM command do the INSERT
+-- itself: analyzing stats_vacuum_src evaluates its index expression, which
+-- (while armed) inserts into stats_vacuum_pending, whose TOAST table the same
+-- command vacuums next.  The backend is never idle in between, so nothing
+-- can be flushed, and VACUUM (ANALYZE) doesn't analyze TOAST tables, so the
+-- TOAST table's stats come from VACUUM only.  reltuples is set by that VACUUM.
+-- stats_vacuum_expr() is labeled IMMUTABLE only so that it can be indexed.
+CREATE TABLE stats_vacuum_pending(t text);
+ALTER TABLE stats_vacuum_pending ALTER t SET STORAGE EXTERNAL;
+CREATE TABLE stats_vacuum_src(id int);
+CREATE FUNCTION stats_vacuum_fill() RETURNS void LANGUAGE plpgsql AS $$
+BEGIN
+  INSERT INTO public.stats_vacuum_pending
+    SELECT repeat('x', 10000) FROM generate_series(1, 10);
+END
+$$;
+CREATE FUNCTION stats_vacuum_expr(int) RETURNS int IMMUTABLE
+  LANGUAGE plpgsql AS $$
+BEGIN
+  IF current_setting('stats_vacuum.armed', true) = 'on' THEN
+    PERFORM public.stats_vacuum_fill();
+  END IF;
+  RETURN $1;
+END
+$$;
+CREATE INDEX ON stats_vacuum_src (stats_vacuum_expr(id));
+INSERT INTO stats_vacuum_src VALUES (1);
+SET stats_vacuum.armed = on;
+VACUUM (ANALYZE) stats_vacuum_src, stats_vacuum_pending;
+RESET stats_vacuum.armed;
+SELECT pg_stat_force_next_flush();
+ pg_stat_force_next_flush 
+--------------------------
+ 
+(1 row)
+
+SELECT s.n_live_tup > 0 AS toasted, s.n_live_tup = c.reltuples AS live_ok,
+       s.n_dead_tup
+  FROM pg_class c JOIN pg_stat_all_tables s ON s.relid = c.oid
+ WHERE c.oid = (SELECT reltoastrelid FROM pg_class
+                 WHERE oid = 'stats_vacuum_pending'::regclass);
+ toasted | live_ok | n_dead_tup 
+---------+---------+------------
+ t       | t       |          0
+(1 row)
+
+DROP TABLE stats_vacuum_src, stats_vacuum_pending;
+DROP FUNCTION stats_vacuum_expr(int), stats_vacuum_fill();
 -----
 -- Basic tests for pg_stat_all_indexes
 -----
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index d446a7f..b9c65af 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -159,13 +159,10 @@ COMMIT;
 -- forgotten when another transaction commits before the stats are flushed.
 -- Pending stats are only flushed while the backend is idle, so run the
 -- TRUNCATE and ANALYZE cases inside a procedure to make sure they are not
--- flushed in between.  VACUUM cannot run in a procedure; if the stats happen
--- to get flushed between the INSERT and the VACUUM, that case still passes,
--- but doesn't test anything.
+-- flushed in between.  VACUUM is tested separately below.
 -----
 CREATE TABLE stats_pending_trunc(id int);
 CREATE TABLE stats_pending_analyze(id int);
-CREATE TABLE stats_pending_vacuum(id int);
 INSERT INTO stats_pending_trunc SELECT generate_series(1, 10);
 SELECT pg_stat_force_next_flush();
 CREATE PROCEDURE stats_pending_proc() LANGUAGE plpgsql AS $$
@@ -182,14 +179,51 @@ BEGIN
 END
 $$;
 CALL stats_pending_proc();
-INSERT INTO stats_pending_vacuum SELECT generate_series(1, 10);
-VACUUM stats_pending_vacuum;
 SELECT pg_stat_force_next_flush();
 SELECT relname, n_live_tup, n_dead_tup, n_mod_since_analyze
   FROM pg_stat_user_tables
  WHERE relname LIKE 'stats_pending_%' ORDER BY relname;
 DROP PROCEDURE stats_pending_proc();
-DROP TABLE stats_pending_trunc, stats_pending_analyze, stats_pending_vacuum;
+DROP TABLE stats_pending_trunc, stats_pending_analyze;
+
+-- VACUUM cannot run in a procedure, so let the VACUUM command do the INSERT
+-- itself: analyzing stats_vacuum_src evaluates its index expression, which
+-- (while armed) inserts into stats_vacuum_pending, whose TOAST table the same
+-- command vacuums next.  The backend is never idle in between, so nothing
+-- can be flushed, and VACUUM (ANALYZE) doesn't analyze TOAST tables, so the
+-- TOAST table's stats come from VACUUM only.  reltuples is set by that VACUUM.
+-- stats_vacuum_expr() is labeled IMMUTABLE only so that it can be indexed.
+CREATE TABLE stats_vacuum_pending(t text);
+ALTER TABLE stats_vacuum_pending ALTER t SET STORAGE EXTERNAL;
+CREATE TABLE stats_vacuum_src(id int);
+CREATE FUNCTION stats_vacuum_fill() RETURNS void LANGUAGE plpgsql AS $$
+BEGIN
+  INSERT INTO public.stats_vacuum_pending
+    SELECT repeat('x', 10000) FROM generate_series(1, 10);
+END
+$$;
+CREATE FUNCTION stats_vacuum_expr(int) RETURNS int IMMUTABLE
+  LANGUAGE plpgsql AS $$
+BEGIN
+  IF current_setting('stats_vacuum.armed', true) = 'on' THEN
+    PERFORM public.stats_vacuum_fill();
+  END IF;
+  RETURN $1;
+END
+$$;
+CREATE INDEX ON stats_vacuum_src (stats_vacuum_expr(id));
+INSERT INTO stats_vacuum_src VALUES (1);
+SET stats_vacuum.armed = on;
+VACUUM (ANALYZE) stats_vacuum_src, stats_vacuum_pending;
+RESET stats_vacuum.armed;
+SELECT pg_stat_force_next_flush();
+SELECT s.n_live_tup > 0 AS toasted, s.n_live_tup = c.reltuples AS live_ok,
+       s.n_dead_tup
+  FROM pg_class c JOIN pg_stat_all_tables s ON s.relid = c.oid
+ WHERE c.oid = (SELECT reltoastrelid FROM pg_class
+                 WHERE oid = 'stats_vacuum_pending'::regclass);
+DROP TABLE stats_vacuum_src, stats_vacuum_pending;
+DROP FUNCTION stats_vacuum_expr(int), stats_vacuum_fill();
 
 -----
 -- Basic tests for pg_stat_all_indexes
