From 4ae20801ff0c8e35b9a586e61a0427895ca485ed Mon Sep 17 00:00:00 2001 From: Rafia Sabih Date: Wed, 9 Sep 2026 12:58:04 +0200 Subject: [PATCH v1] Track last-refresh time and count for materialized views Add pg_matview_stat, a system catalog for each materialized view that has been populated with real data at least once. The timestamp of its last such refresh and how many times that has happened. It is exposed to users through a new pg_stat_matviews view, which joins it with pg_class/pg_namespace and lists every materialized view (refreshed or not). --- doc/src/sgml/catalogs.sgml | 81 ++++++++++++++++++ doc/src/sgml/monitoring.sgml | 105 ++++++++++++++++++++++++ src/backend/catalog/heap.c | 6 ++ src/backend/catalog/system_views.sql | 11 +++ src/backend/commands/matview.c | 109 +++++++++++++++++++++++++ src/include/catalog/Makefile | 3 +- src/include/catalog/catversion.h | 2 +- src/include/catalog/meson.build | 1 + src/include/catalog/pg_matview_stat.h | 64 +++++++++++++++ src/include/commands/matview.h | 2 + src/test/regress/expected/matview.out | 99 ++++++++++++++++++++++ src/test/regress/expected/oidjoins.out | 1 + src/test/regress/expected/rules.out | 9 ++ src/test/regress/sql/matview.sql | 60 ++++++++++++++ 14 files changed, 551 insertions(+), 2 deletions(-) create mode 100644 src/include/catalog/pg_matview_stat.h diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 78cce7e370d..d7ea9a894b5 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -200,6 +200,11 @@ metadata for large objects + + pg_matview_stat + materialized view refresh statistics + + pg_namespace schemas @@ -5117,6 +5122,82 @@ SCRAM-SHA-256$<iteration count>:&l + + <structname>pg_matview_stat</structname> + + + pg_matview_stat + + + + The catalog pg_matview_stat stores, for each + materialized view that has been populated with real data at least once, + the time of its last such refresh and how many times it has happened. + There is one row per materialized view that has ever been refreshed with + data; a materialized view that was only ever created or refreshed with + WITH NO DATA has no row here. + + + + This catalog is normally examined through the more convenient view + pg_stat_matviews, + which joins it with pg_class and + pg_namespace and lists every materialized + view, refreshed or not. + + + + <structname>pg_matview_stat</structname> Columns + + + + + Column Type + + + Description + + + + + + + + mvrelid oid + (references pg_class.oid) + + + Reference to the materialized view + + + + + + mvrefreshcount int8 + + + Number of times the materialized view has been refreshed with real + data (that is, without WITH NO DATA) + + + + + + mvlastrefresh timestamptz + + + Time of the most recent refresh that populated the materialized view + with real data. A subsequent REFRESH MATERIALIZED VIEW + ... WITH NO DATA does not change this column, so it keeps + showing the last time the view actually held valid data. + + + + +
+
+ + <structname>pg_namespace</structname> diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index b403fb990a7..9868bd55d74 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -616,6 +616,17 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser user tables are shown.
+ + pg_stat_matviewspg_stat_matviews + + One row for each materialized view in the current database, showing + the last time it was refreshed with real data and how many times + that has happened. See + + pg_stat_matviews for details. + + + pg_stat_autovacuum_scorespg_stat_autovacuum_scores @@ -4804,6 +4815,100 @@ description | Waiting for a newly initialized WAL file to reach durable storage + + <structname>pg_stat_matviews</structname> + + + pg_stat_matviews + + + + The pg_stat_matviews view will contain one row + for each materialized view in the current database, showing the last + time it was refreshed with real data and how many times that has + happened. Unlike most pg_stat_* views, this + information is not collected by the cumulative statistics subsystem: it + is tracked in the regular system catalog + pg_matview_stat, + so that it participates correctly in transactions -- if a + REFRESH MATERIALIZED VIEW is rolled back, the refresh + it would have recorded here is rolled back too. A materialized view + that has never been refreshed with real data (that is, it was only ever + created or refreshed WITH NO DATA) still appears in + this view, with null last_refresh and a + refresh_count of zero. + + + + <structname>pg_stat_matviews</structname> View + + + + + Column Type + + + Description + + + + + + + + relid oid + (references pg_class.oid) + + + OID of the materialized view + + + + + + schemaname name + + + Name of the schema that the materialized view is in + + + + + + matviewname name + + + Name of the materialized view + + + + + + last_refresh timestamp with time zone + + + Time of the most recent refresh that populated the materialized view + with real data, or null if it has never been so refreshed. A + REFRESH MATERIALIZED VIEW ... WITH NO DATA does + not update this. + + + + + + refresh_count bigint + + + Number of times the materialized view has been refreshed with real + data + + + + +
+ +
+ <structname>pg_stat_autovacuum_scores</structname> diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index a107193d7ab..eed1f14bbb2 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -54,6 +54,7 @@ #include "catalog/pg_tablespace.h" #include "catalog/pg_type.h" #include "catalog/storage.h" +#include "commands/matview.h" #include "commands/tablecmds.h" #include "commands/typecmds.h" #include "common/int.h" @@ -1938,6 +1939,11 @@ heap_drop_with_catalog(Oid relid) */ RemoveStatistics(relid, 0); + /* + * delete any recorded materialized view refresh statistics + */ + MatViewStatRemove(relid); + /* * delete attribute tuples */ diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 8612d99a890..ccbd569aa3a 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -156,6 +156,17 @@ CREATE VIEW pg_matviews AS LEFT JOIN pg_tablespace T ON (T.oid = C.reltablespace) WHERE C.relkind = 'm'; +CREATE VIEW pg_stat_matviews AS + SELECT + C.oid AS relid, + N.nspname AS schemaname, + C.relname AS matviewname, + S.mvlastrefresh AS last_refresh, + COALESCE(S.mvrefreshcount, 0::bigint) AS refresh_count + FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) + LEFT JOIN pg_matview_stat S ON (S.mvrelid = C.oid) + WHERE C.relkind = 'm'; + CREATE VIEW pg_indexes AS SELECT N.nspname AS schemaname, diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index 40748958eaf..ccd3586c22d 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -23,6 +23,7 @@ #include "catalog/indexing.h" #include "catalog/namespace.h" #include "catalog/pg_am.h" +#include "catalog/pg_matview_stat.h" #include "catalog/pg_opclass.h" #include "commands/matview.h" #include "commands/repack.h" @@ -36,10 +37,12 @@ #include "storage/lmgr.h" #include "tcop/tcopprot.h" #include "utils/builtins.h" +#include "utils/fmgroids.h" #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/snapmgr.h" #include "utils/syscache.h" +#include "utils/timestamp.h" typedef struct @@ -67,6 +70,7 @@ static void refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap, char relpersist static bool is_usable_unique_index(Relation indexRel); static void OpenMatViewIncrementalMaintenance(void); static void CloseMatViewIncrementalMaintenance(void); +static void MatViewStatRecordRefresh(Oid matviewOid); /* * SetMatViewPopulatedState @@ -108,6 +112,107 @@ SetMatViewPopulatedState(Relation relation, bool newstate) CommandCounterIncrement(); } +/* + * MatViewStatRecordRefresh + * Record, in pg_matview_stat, that matviewOid has just been refreshed + * with real data: bump its refresh count and set its last-refresh + * timestamp to now. + * + * Called only for refreshes that actually populate the view (i.e. not for + * REFRESH ... WITH NO DATA). There is no pre-existing row for a + * materialized view until its first such refresh, so this upserts: it + * updates the row if one exists, or inserts a new one (with a refresh + * count of 1) otherwise. + */ +static void +MatViewStatRecordRefresh(Oid matviewOid) +{ + Relation rel; + HeapTuple oldtup; + HeapTuple newtup; + TimestampTz now = GetCurrentTimestamp(); + + rel = table_open(MatViewStatRelationId, RowExclusiveLock); + + oldtup = SearchSysCacheCopy1(MATVIEWSTATRELID, ObjectIdGetDatum(matviewOid)); + if (HeapTupleIsValid(oldtup)) + { + bool nulls[Natts_pg_matview_stat]; + Datum values[Natts_pg_matview_stat]; + bool replaces[Natts_pg_matview_stat]; + Form_pg_matview_stat oldform = (Form_pg_matview_stat) GETSTRUCT(oldtup); + + memset(values, 0, sizeof(values)); + memset(nulls, false, sizeof(nulls)); + memset(replaces, false, sizeof(replaces)); + + replaces[Anum_pg_matview_stat_mvrefreshcount - 1] = true; + values[Anum_pg_matview_stat_mvrefreshcount - 1] = + Int64GetDatum(oldform->mvrefreshcount + 1); + + replaces[Anum_pg_matview_stat_mvlastrefresh - 1] = true; + values[Anum_pg_matview_stat_mvlastrefresh - 1] = TimestampTzGetDatum(now); + + newtup = heap_modify_tuple(oldtup, RelationGetDescr(rel), + values, nulls, replaces); + CatalogTupleUpdate(rel, &newtup->t_self, newtup); + + heap_freetuple(oldtup); + } + else + { + bool nulls[Natts_pg_matview_stat]; + Datum values[Natts_pg_matview_stat]; + + memset(values, 0, sizeof(values)); + memset(nulls, false, sizeof(nulls)); + + values[Anum_pg_matview_stat_mvrelid - 1] = ObjectIdGetDatum(matviewOid); + values[Anum_pg_matview_stat_mvrefreshcount - 1] = Int64GetDatum(1); + values[Anum_pg_matview_stat_mvlastrefresh - 1] = TimestampTzGetDatum(now); + + newtup = heap_form_tuple(RelationGetDescr(rel), values, nulls); + CatalogTupleInsert(rel, newtup); + } + + heap_freetuple(newtup); + table_close(rel, RowExclusiveLock); +} + +/* + * MatViewStatRemove + * Drop the pg_matview_stat row for relid, if any. + * + * Called from heap_drop_with_catalog() for every dropped relation, + * mirroring how RemoveStatistics() is called there; relations other than + * materialized views simply never have a matching row. + */ +void +MatViewStatRemove(Oid relid) +{ + Relation rel; + SysScanDesc scan; + ScanKeyData key; + HeapTuple tup; + + rel = table_open(MatViewStatRelationId, RowExclusiveLock); + + ScanKeyInit(&key, + Anum_pg_matview_stat_mvrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relid)); + + scan = systable_beginscan(rel, MatViewStatRelidIndexId, true, + NULL, 1, &key); + + while (HeapTupleIsValid(tup = systable_getnext(scan))) + CatalogTupleDelete(rel, &tup->t_self); + + systable_endscan(scan); + + table_close(rel, RowExclusiveLock); +} + /* * ExecRefreshMatView -- execute a REFRESH MATERIALIZED VIEW command * @@ -364,6 +469,10 @@ RefreshMatViewByOid(Oid matviewOid, bool is_create, bool skipData, pgstat_count_heap_insert(matviewRel, processed); } + /* Update pg_stat_matviews to record the refresh. */ + if (!skipData) + MatViewStatRecordRefresh(matviewOid); + table_close(matviewRel, NoLock); /* Roll back any GUC changes */ diff --git a/src/include/catalog/Makefile b/src/include/catalog/Makefile index 444fc76eed6..86e0db7c1c0 100644 --- a/src/include/catalog/Makefile +++ b/src/include/catalog/Makefile @@ -81,7 +81,8 @@ CATALOG_HEADERS := \ pg_publication_namespace.h \ pg_publication_rel.h \ pg_subscription.h \ - pg_subscription_rel.h + pg_subscription_rel.h \ + pg_matview_stat.h GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 0be73a52dce..4ec1f3cf4e3 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -57,6 +57,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 202609072 +#define CATALOG_VERSION_NO 202609091 #endif diff --git a/src/include/catalog/meson.build b/src/include/catalog/meson.build index bcc01c87c2e..a7ec397c2b7 100644 --- a/src/include/catalog/meson.build +++ b/src/include/catalog/meson.build @@ -69,6 +69,7 @@ catalog_headers = [ 'pg_publication_rel.h', 'pg_subscription.h', 'pg_subscription_rel.h', + 'pg_matview_stat.h', ] # The .dat files we need can just be listed alphabetically. diff --git a/src/include/catalog/pg_matview_stat.h b/src/include/catalog/pg_matview_stat.h new file mode 100644 index 00000000000..6acb6dbbcea --- /dev/null +++ b/src/include/catalog/pg_matview_stat.h @@ -0,0 +1,64 @@ +/*------------------------------------------------------------------------- + * + * pg_matview_stat.h + * definition of the "materialized view refresh statistics" system + * catalog (pg_matview_stat) + * + * pg_matview_stat records, per materialized view, when it was last + * populated with real data and how many times that has happened. + * + * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/catalog/pg_matview_stat.h + * + * NOTES + * The Catalog.pm module reads this file and derives schema + * information. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_MATVIEW_STAT_H +#define PG_MATVIEW_STAT_H + +#include "catalog/genbki.h" +#include "catalog/pg_matview_stat_d.h" + +/* ---------------- + * pg_matview_stat definition. cpp turns this into + * typedef struct FormData_pg_matview_stat + * ---------------- + */ +BEGIN_CATALOG_STRUCT + +CATALOG(pg_matview_stat,9716,MatViewStatRelationId) +{ + Oid mvrelid BKI_LOOKUP(pg_class); /* OID of the materialized + * view */ + int64 mvrefreshcount BKI_DEFAULT(0); /* number of times the matview + * has been populated with + * real data */ + + /* + * Although mvlastrefresh is a fixed-width type, it is allowed to be NULL + * (before the first real refresh). + */ +#ifdef CATALOG_VARLEN /* variable-length fields start here */ + + timestamptz mvlastrefresh BKI_FORCE_NULL; /* timestamp of the last + * refresh that actually + * populated the view, or + * NULL. REFRESH ... WITH NO + * DATA does not update this. */ +#endif +} FormData_pg_matview_stat; + +END_CATALOG_STRUCT + +typedef FormData_pg_matview_stat * Form_pg_matview_stat; + +DECLARE_UNIQUE_INDEX_PKEY(pg_matview_stat_mvrelid_index, 9718, MatViewStatRelidIndexId, pg_matview_stat, btree(mvrelid oid_ops)); + +MAKE_SYSCACHE(MATVIEWSTATRELID, pg_matview_stat_mvrelid_index, 16); + +#endif /* PG_MATVIEW_STAT_H */ diff --git a/src/include/commands/matview.h b/src/include/commands/matview.h index 738c731c1a9..e9a435975ca 100644 --- a/src/include/commands/matview.h +++ b/src/include/commands/matview.h @@ -33,4 +33,6 @@ extern DestReceiver *CreateTransientRelDestReceiver(Oid transientoid); extern bool MatViewIncrementalMaintenanceIsEnabled(void); +extern void MatViewStatRemove(Oid relid); + #endif /* MATVIEW_H */ diff --git a/src/test/regress/expected/matview.out b/src/test/regress/expected/matview.out index 0355720dfc6..78fe6b376a1 100644 --- a/src/test/regress/expected/matview.out +++ b/src/test/regress/expected/matview.out @@ -699,3 +699,102 @@ NOTICE: relation "matview_ine_tab" already exists, skipping (0 rows) DROP MATERIALIZED VIEW matview_ine_tab; +-- pg_stat_matviews: last-refresh timestamp and refresh count +CREATE MATERIALIZED VIEW mvtest_stat_mv1 AS SELECT * FROM mvtest_t; +CREATE MATERIALIZED VIEW mvtest_stat_mv2 AS SELECT * FROM mvtest_t WITH NO DATA; +-- freshly created WITH DATA: populated once already +SELECT matviewname, last_refresh IS NOT NULL AS refreshed, refresh_count + FROM pg_stat_matviews WHERE matviewname = 'mvtest_stat_mv1'; + matviewname | refreshed | refresh_count +-----------------+-----------+--------------- + mvtest_stat_mv1 | t | 1 +(1 row) + +-- freshly created WITH NO DATA: never refreshed, but still listed +SELECT matviewname, last_refresh, refresh_count + FROM pg_stat_matviews WHERE matviewname = 'mvtest_stat_mv2'; + matviewname | last_refresh | refresh_count +-----------------+--------------+--------------- + mvtest_stat_mv2 | | 0 +(1 row) + +-- a plain REFRESH bumps the count and advances the timestamp +SELECT last_refresh AS mv1_refresh_1 FROM pg_stat_matviews + WHERE matviewname = 'mvtest_stat_mv1' \gset +REFRESH MATERIALIZED VIEW mvtest_stat_mv1; +SELECT refresh_count, + last_refresh > :'mv1_refresh_1' AS advanced + FROM pg_stat_matviews WHERE matviewname = 'mvtest_stat_mv1'; + refresh_count | advanced +---------------+---------- + 2 | t +(1 row) + +-- REFRESH ... WITH NO DATA does not touch the recorded stats +SELECT last_refresh AS mv1_refresh_2 FROM pg_stat_matviews + WHERE matviewname = 'mvtest_stat_mv1' \gset +REFRESH MATERIALIZED VIEW mvtest_stat_mv1 WITH NO DATA; +SELECT refresh_count, + last_refresh = :'mv1_refresh_2' AS unchanged + FROM pg_stat_matviews WHERE matviewname = 'mvtest_stat_mv1'; + refresh_count | unchanged +---------------+----------- + 2 | t +(1 row) + +-- a rolled-back REFRESH does not leave a trace +BEGIN; +REFRESH MATERIALIZED VIEW mvtest_stat_mv1; +SELECT refresh_count FROM pg_stat_matviews WHERE matviewname = 'mvtest_stat_mv1'; + refresh_count +--------------- + 3 +(1 row) + +ROLLBACK; +SELECT refresh_count FROM pg_stat_matviews WHERE matviewname = 'mvtest_stat_mv1'; + refresh_count +--------------- + 2 +(1 row) + +-- REFRESH ... WITH NO DATA on a never-refreshed matview still records nothing +REFRESH MATERIALIZED VIEW mvtest_stat_mv2 WITH NO DATA; +SELECT last_refresh, refresh_count + FROM pg_stat_matviews WHERE matviewname = 'mvtest_stat_mv2'; + last_refresh | refresh_count +--------------+--------------- + | 0 +(1 row) + +-- an actual REFRESH populates it +REFRESH MATERIALIZED VIEW mvtest_stat_mv2; +SELECT last_refresh IS NOT NULL AS refreshed, refresh_count + FROM pg_stat_matviews WHERE matviewname = 'mvtest_stat_mv2'; + refreshed | refresh_count +-----------+--------------- + t | 1 +(1 row) + +-- REFRESH ... CONCURRENTLY is tracked the same way as a regular refresh +CREATE UNIQUE INDEX mvtest_stat_mv1_id ON mvtest_stat_mv1 (id); +REFRESH MATERIALIZED VIEW mvtest_stat_mv1; -- re-populate after the WITH NO DATA above +SELECT refresh_count AS mv1_count_before FROM pg_stat_matviews + WHERE matviewname = 'mvtest_stat_mv1' \gset +REFRESH MATERIALIZED VIEW CONCURRENTLY mvtest_stat_mv1; +SELECT refresh_count = :mv1_count_before + 1 AS bumped + FROM pg_stat_matviews WHERE matviewname = 'mvtest_stat_mv1'; + bumped +-------- + t +(1 row) + +-- dropping the matview removes its entry +DROP MATERIALIZED VIEW mvtest_stat_mv1; +SELECT count(*) FROM pg_stat_matviews WHERE matviewname = 'mvtest_stat_mv1'; + count +------- + 0 +(1 row) + +DROP MATERIALIZED VIEW mvtest_stat_mv2; diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out index 51b9608a668..8aef4f78c69 100644 --- a/src/test/regress/expected/oidjoins.out +++ b/src/test/regress/expected/oidjoins.out @@ -273,3 +273,4 @@ NOTICE: checking pg_subscription {subowner} => pg_authid {oid} NOTICE: checking pg_subscription {subserver} => pg_foreign_server {oid} NOTICE: checking pg_subscription_rel {srsubid} => pg_subscription {oid} NOTICE: checking pg_subscription_rel {srrelid} => pg_class {oid} +NOTICE: checking pg_matview_stat {mvrelid} => pg_class {oid} diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 1a29d46213e..dd1215600e0 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1981,6 +1981,15 @@ pg_stat_lock| SELECT locktype, fastpath_exceeded, stats_reset FROM pg_stat_get_lock() l(locktype, waits, wait_time, fastpath_exceeded, stats_reset); +pg_stat_matviews| SELECT c.oid AS relid, + n.nspname AS schemaname, + c.relname AS matviewname, + s.mvlastrefresh AS last_refresh, + COALESCE(s.mvrefreshcount, (0)::bigint) AS refresh_count + FROM ((pg_class c + LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) + LEFT JOIN pg_matview_stat s ON ((s.mvrelid = c.oid))) + WHERE (c.relkind = 'm'::"char"); pg_stat_progress_analyze| SELECT s.pid, s.datid, d.datname, diff --git a/src/test/regress/sql/matview.sql b/src/test/regress/sql/matview.sql index 934426b9ae8..fa6ef7ab129 100644 --- a/src/test/regress/sql/matview.sql +++ b/src/test/regress/sql/matview.sql @@ -318,3 +318,63 @@ EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF, BUFFERS OFF) CREATE MATERIALIZED VIEW IF NOT EXISTS matview_ine_tab AS SELECT 1 / 0 WITH NO DATA; -- ok DROP MATERIALIZED VIEW matview_ine_tab; + +-- pg_stat_matviews: last-refresh timestamp and refresh count +CREATE MATERIALIZED VIEW mvtest_stat_mv1 AS SELECT * FROM mvtest_t; +CREATE MATERIALIZED VIEW mvtest_stat_mv2 AS SELECT * FROM mvtest_t WITH NO DATA; + +-- freshly created WITH DATA: populated once already +SELECT matviewname, last_refresh IS NOT NULL AS refreshed, refresh_count + FROM pg_stat_matviews WHERE matviewname = 'mvtest_stat_mv1'; + +-- freshly created WITH NO DATA: never refreshed, but still listed +SELECT matviewname, last_refresh, refresh_count + FROM pg_stat_matviews WHERE matviewname = 'mvtest_stat_mv2'; + +-- a plain REFRESH bumps the count and advances the timestamp +SELECT last_refresh AS mv1_refresh_1 FROM pg_stat_matviews + WHERE matviewname = 'mvtest_stat_mv1' \gset +REFRESH MATERIALIZED VIEW mvtest_stat_mv1; +SELECT refresh_count, + last_refresh > :'mv1_refresh_1' AS advanced + FROM pg_stat_matviews WHERE matviewname = 'mvtest_stat_mv1'; + +-- REFRESH ... WITH NO DATA does not touch the recorded stats +SELECT last_refresh AS mv1_refresh_2 FROM pg_stat_matviews + WHERE matviewname = 'mvtest_stat_mv1' \gset +REFRESH MATERIALIZED VIEW mvtest_stat_mv1 WITH NO DATA; +SELECT refresh_count, + last_refresh = :'mv1_refresh_2' AS unchanged + FROM pg_stat_matviews WHERE matviewname = 'mvtest_stat_mv1'; + +-- a rolled-back REFRESH does not leave a trace +BEGIN; +REFRESH MATERIALIZED VIEW mvtest_stat_mv1; +SELECT refresh_count FROM pg_stat_matviews WHERE matviewname = 'mvtest_stat_mv1'; +ROLLBACK; +SELECT refresh_count FROM pg_stat_matviews WHERE matviewname = 'mvtest_stat_mv1'; + +-- REFRESH ... WITH NO DATA on a never-refreshed matview still records nothing +REFRESH MATERIALIZED VIEW mvtest_stat_mv2 WITH NO DATA; +SELECT last_refresh, refresh_count + FROM pg_stat_matviews WHERE matviewname = 'mvtest_stat_mv2'; + +-- an actual REFRESH populates it +REFRESH MATERIALIZED VIEW mvtest_stat_mv2; +SELECT last_refresh IS NOT NULL AS refreshed, refresh_count + FROM pg_stat_matviews WHERE matviewname = 'mvtest_stat_mv2'; + +-- REFRESH ... CONCURRENTLY is tracked the same way as a regular refresh +CREATE UNIQUE INDEX mvtest_stat_mv1_id ON mvtest_stat_mv1 (id); +REFRESH MATERIALIZED VIEW mvtest_stat_mv1; -- re-populate after the WITH NO DATA above +SELECT refresh_count AS mv1_count_before FROM pg_stat_matviews + WHERE matviewname = 'mvtest_stat_mv1' \gset +REFRESH MATERIALIZED VIEW CONCURRENTLY mvtest_stat_mv1; +SELECT refresh_count = :mv1_count_before + 1 AS bumped + FROM pg_stat_matviews WHERE matviewname = 'mvtest_stat_mv1'; + +-- dropping the matview removes its entry +DROP MATERIALIZED VIEW mvtest_stat_mv1; +SELECT count(*) FROM pg_stat_matviews WHERE matviewname = 'mvtest_stat_mv1'; + +DROP MATERIALIZED VIEW mvtest_stat_mv2; -- 2.39.5 (Apple Git-154)