pax_global_header00006660000000000000000000000064144276406250014524gustar00rootroot0000000000000052 comment=36a23f3c6d684c40140da1a24b1c3ee0e707b77a pg_track_settings-2.1.2/000077500000000000000000000000001442764062500152405ustar00rootroot00000000000000pg_track_settings-2.1.2/.github/000077500000000000000000000000001442764062500166005ustar00rootroot00000000000000pg_track_settings-2.1.2/.github/workflows/000077500000000000000000000000001442764062500206355ustar00rootroot00000000000000pg_track_settings-2.1.2/.github/workflows/regression.yml000066400000000000000000000014601442764062500235410ustar00rootroot00000000000000name: Build on: [push, pull_request] jobs: build: runs-on: ubuntu-latest defaults: run: shell: sh strategy: matrix: # support 9.5 and later (9.4 doesn't have regrole) pgversion: [ 9.5, 9.6, 10, 11, 12, 13, 14, 15, 16 ] env: PGVERSION: ${{ matrix.pgversion }} steps: - name: checkout uses: actions/checkout@v2 - name: install pg run: | sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -v $PGVERSION -p -i sudo -u postgres createuser -s "$USER" - name: build run: | make PROFILE="-Werror" sudo -E make install - name: test run: | make installcheck - name: show regression diffs if: ${{ failure() }} run: | cat regression.diffs pg_track_settings-2.1.2/.gitignore000066400000000000000000000000501442764062500172230ustar00rootroot00000000000000.*.sw* pg_track_settings-*.zip results/ pg_track_settings-2.1.2/CHANGELOG.md000066400000000000000000000025451442764062500170570ustar00rootroot00000000000000Changelog ========= 2023-05-13 2.1.2: ----------------- - Fix concurrent remote snapshot (Julien Rouhaud) - Fix role / db setting snapshot when all previous settings have been removed (Julien Rouhaud, per report from github user sajiljosephs) 2022-09-21 2.1.1: ------------------ - Fix regression tests on PG 11 an earlier (Christoph Berg) - Run regression tests in a GitHub workflow (Christoph Berg) 2022-09-20 2.1.0: ------------------ - Allow installation in a custom schema (Julien Rouhaud) - debian packaging improvements (Chrstoph Berg) - Make the extension compatible with EDB fork of postgres (Julien Rouhaud, per report from github user manishnew09 and help from Thomas Reiss) - various regression testss improvements (Julien Rouhaud) 2020-10-02 2.0.1: ------------------ - Fix handling of dropped pg_db_role_setting entries. Thanks to Adrien Nayrat for the report. 2019-09-05 2.0.0: ------------------ - Add support for remote snapshot mode that will be available with powa 4 (thanks to github user Ikrar-k for testing and bug reporting) - Add pg_track_reboot_log function 2018-07-15 version 1.0.1: ------------------------- **Bug fixes**: - Fix issue leading to duplicated role settings changes when several roles exists (Adrien Nayrat). 2015-12-06 version 1.0.0: ------------------------- - First version of pg_track_settings. pg_track_settings-2.1.2/LICENSE000066400000000000000000000016571442764062500162560ustar00rootroot00000000000000Copyright (c) 2015-2023, Julien Rouhaud Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies. IN NO EVENT SHALL Julien Rouhaud BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF Julien Rouhaud HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Julien Rouhaud SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND Julien Rouhaud HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. pg_track_settings-2.1.2/META.json000066400000000000000000000022141442764062500166600ustar00rootroot00000000000000{ "name": "pg_track_settings", "abstract": "A simple extension which keep track of postgresql settings modifications", "version": "__VERSION__", "maintainer": "Julien Rouhaud ", "license": "postgresql", "release_status": "stable", "provides": { "pg_track_settings": { "abstract": "A simple extension which keep track of postgresql settings modifications", "file": "pg_track_settings.sql", "docfile": "README.md", "version": "__VERSION__" } }, "resources": { "bugtracker": { "web": "http://github.com/rjuju/pg_track_settings/issues/" }, "repository": { "url": "git://github.com/rjuju/pg_track_settings.git", "web": "http://github.com/rjuju/pg_track_settings/", "type": "git" } }, "prereqs": { "runtime": { "requires": { "PostgreSQL": "9.1.0" } } }, "generated_by": "Julien Rouhaud", "meta-spec": { "version": "1.0.0", "url": "http://pgxn.org/meta/spec.txt" }, "tags": [ "monitoring", "administration" ] } pg_track_settings-2.1.2/Makefile000066400000000000000000000016061442764062500167030ustar00rootroot00000000000000EXTENSION = pg_track_settings EXTVERSION = $(shell grep default_version $(EXTENSION).control | sed -e "s/default_version[[:space:]]*=[[:space:]]*'\([^']*\)'/\1/") TESTS = $(wildcard test/sql/*.sql) REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS)) REGRESS_OPTS = --inputdir=test DOCS = $(wildcard README.md) DATA = $(wildcard *--*.sql) PG_CONFIG = pg_config PGXS = $(shell $(PG_CONFIG) --pgxs) include $(PGXS) all: release-zip: all git archive --format zip --prefix=${EXTENSION}-${EXTVERSION}/ --output ./${EXTENSION}-${EXTVERSION}.zip HEAD unzip ./${EXTENSION}-$(EXTVERSION).zip rm ./${EXTENSION}-$(EXTVERSION).zip rm ./${EXTENSION}-$(EXTVERSION)/.gitignore sed -i -e "s/__VERSION__/$(EXTVERSION)/g" ./${EXTENSION}-$(EXTVERSION)/META.json zip -r ./${EXTENSION}-$(EXTVERSION).zip ./${EXTENSION}-$(EXTVERSION)/ rm ./${EXTENSION}-$(EXTVERSION) -rf pg_track_settings-2.1.2/README.md000066400000000000000000000101461442764062500165210ustar00rootroot00000000000000pg_track_settings ================= pg_track_settings is a small extension that helps you keep track of postgresql settings configuration. It provides a function (**pg_track_settings_snapshot()**), that must be called regularly. At each call, it will store the settings that have been changed since last call. It will also track the postgresql start time if it's different from the last one. This extension tracks both overall settings (the **pg_settings** view) and overloaded settings (the **pg_db_role_setting** table). Usage ----- - Create the extension in any database: CREATE EXTENSION pg_track_settings; Then make sure the **pg_track_settings_snapshot()** function called. Cron or PoWA can be used for that. Functions --------- - `pg_track_settings_snapshot()`: collect the current settings value. - `pg_track_settings(timestamptz)`: return all settings at the specified timestamp. Current time is used if no timestamped specified. - `pg_track_settings_diff(timestamptz, timestamptz)`: return all settings that have changed between the two specified timestamps. - `pg_track_settings_log(text)`: return the history of a specific setting. - `pg_track_db_role_settings(timestamptz)`: return all overloaded settings at the specified timestamp. Current time is used if no timestamped specified. - `pg_track_db_role_settings_diff(timestamptz, timestamptz)`: return all overloaded settings that have changed between the two specified timestamps. - `pg_track_db_role_settings_log(text)`: return the history of a specific overloaded setting. Example ------- Call a first time the snapshot function to get the initial values: postgres=# select pg_track_settings_snapshot() ---------------------------- t (1 row) A first snapshot is now taken: postgres=# select DISTINCT ts FROM pg_track_settings_history ; ts ------------------------------- 2015-01-25 01:00:37.449846+01 (1 row) Let's assume the configuration changed, and reload the conf: postgres=# select pg_reload_conf(); pg_reload_conf ---------------- t (1 row) Call again the snapshot function: postgres=# select * from pg_track_settings_snapshot(); pg_track_settings_snapshot ---------------------------- t (1 row) Now, we can check what settings changed: postgres=# SELECT * FROM pg_track_settings_diff(now() - interval '2 minutes', now()); name | from_setting | from_exists | to_setting | to_exists ---------------------+--------------|-------------|------------|---------- checkpoint_segments | 30 | t | 35 | t (1 row) And the detailed history of this setting: postgres=# SELECT * FROM pg_track_settings_log('checkpoint_segments'); ts | name | setting_exists | setting -------------------------------+---------------------+----------------+--------- 2015-01-25 01:01:42.581682+01 | checkpoint_segments | t | 35 2015-01-25 01:00:37.449846+01 | checkpoint_segments | t | 30 (2 rows) And you can retrieve all the PostgreSQL configuration at a specific timestamp: postgres=# SELECT * FROM pg_track_settings('2015-01-25 01:01:00'); name | setting ------------------------------+--------- [...] checkpoint_completion_target | 0.9 checkpoint_segments | 30 checkpoint_timeout | 300 [...] The same functions are provided for per role and/or database settings ( **ALTER ROLE ... SET**, **ALTER ROLE ... IN DATABASE ... SET** and **ALTER DATABASE ... SET** commands): - pg\_track\_db\_role\_settings\_diff() - pg\_track\_db\_role\_settings\_log() - pg\_track\_db\_role\_settings() We also have the history of postgres start time: postgres=# SELECT * FROM pg_reboot; ts ------------------------------- 2015-01-25 00:39:43.609195+01 (1 row) Please also note that all the history will be saved in a pg\_dump / pg\_dumpall backup. If you need the clear this history, the function **pg\_track\_settings\_reset()** will do that for you. pg_track_settings-2.1.2/debian/000077500000000000000000000000001442764062500164625ustar00rootroot00000000000000pg_track_settings-2.1.2/debian/changelog000066400000000000000000000035241442764062500203400ustar00rootroot00000000000000pg-track-settings (2.1.2-1) unstable; urgency=medium * New upstream version. -- Julien Rouhaud Sat, 13 May 2023 15:50:49 +0800 pg-track-settings (2.1.1-3) unstable; urgency=medium [ Debian Janitor ] * Remove constraints unnecessary since buster (oldstable): + Build-Depends: Drop versioned constraint on postgresql-server-dev-all. -- Christoph Berg Wed, 02 Nov 2022 12:24:17 +0100 pg-track-settings (2.1.1-2) unstable; urgency=medium * Team upload for PostgreSQL 15. -- Christoph Berg Fri, 21 Oct 2022 11:04:29 +0200 pg-track-settings (2.1.1-1) unstable; urgency=medium * Team upload with new upstream version. -- Christoph Berg Wed, 21 Sep 2022 14:20:44 +0200 pg-track-settings (2.1.0-1) unstable; urgency=medium * New upstream version. -- Julien Rouhaud Tue, 20 Sep 2022 11:58:27 +0800 pg-track-settings (2.0.1-2) unstable; urgency=medium * Team upload for PostgreSQL 14. -- Christoph Berg Fri, 15 Oct 2021 15:54:21 +0200 pg-track-settings (2.0.1-1) unstable; urgency=medium * New upstream version. -- Julien Rouhaud Mon, 16 Nov 2020 16:46:05 +0100 pg-track-settings (2.0.0-3) unstable; urgency=medium * Team upload. * Testsuite wants to run as postgres, so require root for autopkgtest. -- Christoph Berg Tue, 27 Oct 2020 10:59:52 +0100 pg-track-settings (2.0.0-2) unstable; urgency=medium * Team upload for PostgreSQL 13. * Use dh --with pgxs_loop. * R³: no. * debian/tests: Use 'make' instead of postgresql-server-dev-all. -- Christoph Berg Mon, 19 Oct 2020 11:56:59 +0200 pg-track-settings (2.0.0-1) unstable; urgency=medium * Initial release. -- Julien Rouhaud Fri, 07 Aug 2020 16:19:41 +0200 pg_track_settings-2.1.2/debian/control000066400000000000000000000020261442764062500200650ustar00rootroot00000000000000Source: pg-track-settings Section: database Priority: optional Maintainer: Julien Rouhaud Standards-Version: 4.6.1 Build-Depends: debhelper-compat (= 13), postgresql-server-dev-all Rules-Requires-Root: no Homepage: https://powa.readthedocs.io/ Vcs-Browser: https://github.com/rjuju/pg_track_settings Vcs-Git: https://github.com/rjuju/pg_track_settings.git Package: postgresql-15-pg-track-settings Architecture: all Depends: ${misc:Depends}, postgresql-15 Description: PostgreSQL extension tracking of configuration settings pg_track_settings is a small PostgreSQL extension that helps you keep track of PostgreSQL settings configuration. . It provides a function (pg_track_settings_snapshot()), that must be called regularly. At each call, it will store the settings that have been changed since last call. It will also track the postgresql start time if it's different from the last one. . This extension tracks both overall settings (the pg_settings view) and overloaded settings (the pg_db_role_setting table). pg_track_settings-2.1.2/debian/control.in000066400000000000000000000020441442764062500204720ustar00rootroot00000000000000Source: pg-track-settings Section: database Priority: optional Maintainer: Julien Rouhaud Standards-Version: 4.6.1 Build-Depends: debhelper-compat (= 13), postgresql-server-dev-all Rules-Requires-Root: no Homepage: https://powa.readthedocs.io/ Vcs-Browser: https://github.com/rjuju/pg_track_settings Vcs-Git: https://github.com/rjuju/pg_track_settings.git Package: postgresql-PGVERSION-pg-track-settings Architecture: all Depends: ${misc:Depends}, postgresql-PGVERSION Description: PostgreSQL extension tracking of configuration settings pg_track_settings is a small PostgreSQL extension that helps you keep track of PostgreSQL settings configuration. . It provides a function (pg_track_settings_snapshot()), that must be called regularly. At each call, it will store the settings that have been changed since last call. It will also track the postgresql start time if it's different from the last one. . This extension tracks both overall settings (the pg_settings view) and overloaded settings (the pg_db_role_setting table). pg_track_settings-2.1.2/debian/copyright000066400000000000000000000016571442764062500204260ustar00rootroot00000000000000Copyright (c) 2015-2023, Julien Rouhaud Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies. IN NO EVENT SHALL Julien Rouhaud BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF Julien Rouhaud HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Julien Rouhaud SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND Julien Rouhaud HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. pg_track_settings-2.1.2/debian/pgversions000066400000000000000000000000051442764062500205770ustar00rootroot000000000000009.4+ pg_track_settings-2.1.2/debian/rules000077500000000000000000000007651442764062500175520ustar00rootroot00000000000000#!/usr/bin/make -f PKGVER = $(shell dpkg-parsechangelog | awk -F '[:-]' '/^Version:/ { print substr($$2, 2) }') EXCLUDE = --exclude-vcs --exclude=debian override_dh_installdocs: dh_installdocs --all README.md rm -rvf debian/*/usr/share/doc/postgresql-doc-* override_dh_pgxs_test: # defer testing to autopkgtest, tests want to run as postgres orig: debian/control clean cd .. && tar czf pg-track-settings_$(PKGVER).orig.tar.gz $(EXCLUDE) pg-track-settings-$(PKGVER) %: dh $@ --with pgxs_loop pg_track_settings-2.1.2/debian/source/000077500000000000000000000000001442764062500177625ustar00rootroot00000000000000pg_track_settings-2.1.2/debian/source/format000066400000000000000000000000141442764062500211700ustar00rootroot000000000000003.0 (quilt) pg_track_settings-2.1.2/debian/tests/000077500000000000000000000000001442764062500176245ustar00rootroot00000000000000pg_track_settings-2.1.2/debian/tests/control000066400000000000000000000001141442764062500212230ustar00rootroot00000000000000Depends: @, make Tests: installcheck Restrictions: allow-stderr, needs-root pg_track_settings-2.1.2/debian/tests/installcheck000077500000000000000000000000551442764062500222160ustar00rootroot00000000000000#!/bin/sh set -eu pg_buildext installcheck pg_track_settings-2.1.2/debian/watch000066400000000000000000000001161442764062500175110ustar00rootroot00000000000000version=3 https://github.com/rjuju/pg_track_settings/tags .*/([0-9.]*).tar.gz pg_track_settings-2.1.2/expected/000077500000000000000000000000001442764062500170415ustar00rootroot00000000000000pg_track_settings-2.1.2/expected/pg_track_settings.out000066400000000000000000000363251442764062500233150ustar00rootroot00000000000000SET search_path = ''; SET timezone TO 'Europe/Paris'; -- Remove any known per db setting set by pg_regress DO $$ DECLARE dbname text = current_database(); s text; BEGIN FOREACH s IN ARRAY ARRAY['lc_messages', 'lc_monetary', 'lc_numeric', 'lc_time', 'bytea_output', 'timezone_abbreviations'] LOOP EXECUTE format('ALTER DATABASE %I RESET %s', dbname, s); END LOOP; END; $$ LANGUAGE plpgsql; -- There shouldn't be any db/role setting left. It's unfortunately not -- guaranteed to be the case if the regression tests are run on a non-default -- cluster. SELECT d.datname, s.setconfig FROM pg_db_role_setting s JOIN pg_database d on s.setdatabase = d.oid; datname | setconfig ---------+----------- (0 rows) CREATE SCHEMA "PGTS"; -- Extension should be installable in a custom schema CREATE EXTENSION pg_track_settings WITH SCHEMA "PGTS"; -- But not relocatable ALTER EXTENSION pg_track_settings SET SCHEMA public; ERROR: extension "pg_track_settings" does not support SET SCHEMA -- Check the relations that aren't dumped WITH ext AS ( SELECT c.oid, c.relname FROM pg_depend d JOIN pg_extension e ON d.refclassid = 'pg_extension'::regclass AND e.oid = d.refobjid AND e.extname = 'pg_track_settings' JOIN pg_class c ON d.classid = 'pg_class'::regclass AND c.oid = d.objid ), dmp AS ( SELECT unnest(extconfig) AS oid FROM pg_extension WHERE extname = 'pg_track_settings' ) SELECT ext.relname FROM ext LEFT JOIN dmp USING (oid) WHERE dmp.oid IS NULL ORDER BY ext.relname::text COLLATE "C"; relname ------------------------------------ pg_track_settings_rds_src_tmp pg_track_settings_reboot_src_tmp pg_track_settings_settings_src_tmp (3 rows) -- Check that all objects are stored in the expected schema WITH ext AS ( SELECT pg_describe_object(d.classid, d.objid, d.objsubid) AS descr FROM pg_depend d JOIN pg_extension e ON d.refclassid = 'pg_extension'::regclass AND e.oid = d.refobjid AND e.extname = 'pg_track_settings' ) SELECT descr FROM ext WHERE descr NOT like '%"PGTS".%' ORDER BY descr COLLATE "C"; descr ------- (0 rows) -- test main config history SELECT COUNT(*) FROM "PGTS".pg_track_settings_history; count ------- 0 (1 row) SET work_mem = '10MB'; SELECT * FROM "PGTS".pg_track_settings_snapshot(); pg_track_settings_snapshot ---------------------------- t (1 row) SELECT pg_catalog.pg_sleep(1); pg_sleep ---------- (1 row) SET work_mem = '5MB'; SELECT * FROM "PGTS".pg_track_settings_snapshot(); pg_track_settings_snapshot ---------------------------- t (1 row) SELECT name, setting_exists, setting, setting_pretty FROM "PGTS".pg_track_settings_log('work_mem') ORDER BY ts ASC; name | setting_exists | setting | setting_pretty ----------+----------------+---------+---------------- work_mem | t | 10240 | 10MB work_mem | t | 5120 | 5MB (2 rows) SELECT name, from_setting, from_exists, to_setting, to_exists, from_setting_pretty, to_setting_pretty FROM "PGTS".pg_track_settings_diff(now() - interval '500 ms', now()); name | from_setting | from_exists | to_setting | to_exists | from_setting_pretty | to_setting_pretty ----------+--------------+-------------+------------+-----------+---------------------+------------------- work_mem | 10240 | t | 5120 | t | 10MB | 5MB (1 row) -- test pg_db_role_settings ALTER DATABASE postgres SET work_mem = '1MB'; SELECT * FROM "PGTS".pg_track_settings_snapshot(); pg_track_settings_snapshot ---------------------------- t (1 row) ALTER ROLE postgres SET work_mem = '2MB'; SELECT * FROM "PGTS".pg_track_settings_snapshot(); pg_track_settings_snapshot ---------------------------- t (1 row) ALTER ROLE postgres IN DATABASE postgres SET work_mem = '3MB'; SELECT * FROM "PGTS".pg_track_settings_snapshot(); pg_track_settings_snapshot ---------------------------- t (1 row) SELECT * FROM "PGTS".pg_track_settings_snapshot(); pg_track_settings_snapshot ---------------------------- t (1 row) SELECT COALESCE(datname, '-') AS datname, setrole::regrole, name, setting_exists, setting FROM "PGTS".pg_track_db_role_settings_log('work_mem') s LEFT JOIN pg_database d ON d.oid = s.setdatabase ORDER BY ts ASC; datname | setrole | name | setting_exists | setting ----------+----------+----------+----------------+--------- postgres | - | work_mem | t | 1MB - | postgres | work_mem | t | 2MB postgres | postgres | work_mem | t | 3MB (3 rows) SELECT COALESCE(datname, '-') AS datname, setrole::regrole, name, from_setting, from_exists, to_setting, to_exists FROM "PGTS".pg_track_db_role_settings_diff(now() - interval '10 min', now()) s LEFT JOIN pg_database d ON d.oid = s.setdatabase WHERE name = 'work_mem' ORDER BY 1, 2, 3; datname | setrole | name | from_setting | from_exists | to_setting | to_exists ----------+----------+----------+--------------+-------------+------------+----------- - | postgres | work_mem | | f | 2MB | t postgres | - | work_mem | | f | 1MB | t postgres | postgres | work_mem | | f | 3MB | t (3 rows) ALTER DATABASE postgres RESET work_mem; SELECT * FROM "PGTS".pg_track_settings_snapshot(); pg_track_settings_snapshot ---------------------------- t (1 row) ALTER ROLE postgres RESET work_mem; SELECT * FROM "PGTS".pg_track_settings_snapshot(); pg_track_settings_snapshot ---------------------------- t (1 row) ALTER ROLE postgres IN DATABASE postgres RESET work_mem; SELECT * FROM "PGTS".pg_track_settings_snapshot(); pg_track_settings_snapshot ---------------------------- t (1 row) -- test pg_reboot SELECT COUNT(*) FROM "PGTS".pg_reboot; count ------- 1 (1 row) SELECT now() - ts > interval '2 second' FROM "PGTS".pg_reboot; ?column? ---------- t (1 row) SELECT now() - ts > interval '2 second' FROM "PGTS".pg_track_reboot_log(); ?column? ---------- t (1 row) -- test the reset SELECT * FROM "PGTS".pg_track_settings_reset(); pg_track_settings_reset ------------------------- (1 row) SELECT COUNT(*) FROM "PGTS".pg_track_settings_history; count ------- 0 (1 row) SELECT COUNT(*) FROM "PGTS".pg_track_settings_log('work_mem'); count ------- 0 (1 row) SELECT COUNT(*) FROM "PGTS".pg_track_settings_diff(now() - interval '1 hour', now()); count ------- 0 (1 row) SELECT COUNT(*) FROM "PGTS".pg_track_db_role_settings_log('work_mem'); count ------- 0 (1 row) SELECT COUNT(*) FROM "PGTS".pg_track_db_role_settings_diff(now() - interval '1 hour', now()); count ------- 0 (1 row) SELECT COUNT(*) FROM "PGTS".pg_reboot; count ------- 0 (1 row) -------------------------- -- test remote snapshot -- -------------------------- -- fake general settings INSERT INTO "PGTS".pg_track_settings_settings_src_tmp (srvid, ts, name, setting, current_setting) VALUES (1, '2019-01-01 00:00:00 CET', 'work_mem', '0', '1MB'), (2, '2019-01-02 00:00:00 CET', 'work_mem', '0', '2MB'); -- fake rds settings INSERT INTO "PGTS".pg_track_settings_rds_src_tmp (srvid, ts, name, setting, setdatabase, setrole) VALUES (1, '2019-01-01 00:00:00 CET', 'work_mem', '1MB', 123, 0), (2, '2019-01-02 00:00:00 CET', 'work_mem', '2MB', 456, 0); -- fake reboot settings INSERT INTO "PGTS".pg_track_settings_reboot_src_tmp (srvid, ts, postmaster_ts) VALUES (1, '2019-01-01 00:01:00 CET', '2019-01-01 00:00:00 CET'), (2, '2019-01-02 00:01:00 CET', '2019-01-02 00:00:00 CET'); SELECT "PGTS".pg_track_settings_snapshot_settings(1); pg_track_settings_snapshot_settings ------------------------------------- t (1 row) SELECT "PGTS".pg_track_settings_snapshot_rds(1); pg_track_settings_snapshot_rds -------------------------------- t (1 row) SELECT "PGTS".pg_track_settings_snapshot_reboot(1); pg_track_settings_snapshot_reboot ----------------------------------- t (1 row) -- snapshot of remote server 1 shouldn't impact data for server 2 SELECT srvid, count(*) FROM "PGTS".pg_track_settings_settings_src_tmp GROUP BY srvid; srvid | count -------+------- 2 | 1 (1 row) SELECT srvid, count(*) FROM "PGTS".pg_track_settings_rds_src_tmp GROUP BY srvid; srvid | count -------+------- 2 | 1 (1 row) SELECT srvid, count(*) FROM "PGTS".pg_track_settings_reboot_src_tmp GROUP BY srvid; srvid | count -------+------- 2 | 1 (1 row) -- fake general settings INSERT INTO "PGTS".pg_track_settings_settings_src_tmp (srvid, ts, name, setting, current_setting) VALUES -- previously untreated data that should be discarded (1, '2019-01-02 00:00:00 CET', 'work_mem', '5120', '5MB'), -- data that should be processed (1, '2019-01-02 01:00:00 CET', 'work_mem', '10240', '10MB'), (1, '2019-01-02 01:00:00 CET', 'something', 'someval', 'someval'); -- fake rds settings INSERT INTO "PGTS".pg_track_settings_rds_src_tmp (srvid, ts, name, setting, setdatabase, setrole) VALUES -- previously untreated data that should be discarded (1, '2019-01-02 00:00:00 CET', 'work_mem', '5MB', 123, 0), -- data that should be processed (1, '2019-01-02 01:00:00 CET', 'work_mem', '10MB', 123, 0), (1, '2019-01-02 01:00:00 CET', 'something', 'someval', 0, 456); -- fake reboot settings INSERT INTO "PGTS".pg_track_settings_reboot_src_tmp (srvid, ts, postmaster_ts) VALUES -- previously untreated data that should not be discarded (1, '2019-01-02 00:01:00 CET', '2019-01-02 00:00:00 CET'), -- data that should also be processed (1, '2019-01-02 02:01:00 CET', '2019-01-02 01:00:00 CET'); SELECT "PGTS".pg_track_settings_snapshot_settings(1); pg_track_settings_snapshot_settings ------------------------------------- t (1 row) SELECT "PGTS".pg_track_settings_snapshot_rds(1); pg_track_settings_snapshot_rds -------------------------------- t (1 row) SELECT "PGTS".pg_track_settings_snapshot_reboot(1); pg_track_settings_snapshot_reboot ----------------------------------- t (1 row) -- test raw data SELECT * FROM "PGTS".pg_track_settings_list ORDER BY 1, 2; srvid | name -------+----------- 1 | something 1 | work_mem (2 rows) SELECT * FROM "PGTS".pg_track_settings_history ORDER BY 1, 2, 3; srvid | ts | name | setting | is_dropped | setting_pretty -------+------------------------------+-----------+---------+------------+---------------- 1 | Tue Jan 01 00:00:00 2019 CET | work_mem | 0 | f | 1MB 1 | Wed Jan 02 01:00:00 2019 CET | something | someval | f | someval 1 | Wed Jan 02 01:00:00 2019 CET | work_mem | 10240 | f | 10MB (3 rows) SELECT * FROM "PGTS".pg_track_db_role_settings_list ORDER BY 1, 2; srvid | name | setdatabase | setrole -------+-----------+-------------+--------- 1 | something | 0 | 456 1 | work_mem | 123 | 0 (2 rows) SELECT * FROM "PGTS".pg_track_db_role_settings_history ORDER BY 1, 2, 3; srvid | ts | name | setdatabase | setrole | setting | is_dropped -------+------------------------------+-----------+-------------+---------+---------+------------ 1 | Tue Jan 01 00:00:00 2019 CET | work_mem | 123 | 0 | 1MB | f 1 | Wed Jan 02 01:00:00 2019 CET | something | 0 | 456 | someval | f 1 | Wed Jan 02 01:00:00 2019 CET | work_mem | 123 | 0 | 10MB | f (3 rows) SELECT * FROM "PGTS".pg_reboot ORDER BY 1, 2; srvid | ts -------+------------------------------ 1 | Tue Jan 01 00:00:00 2019 CET 1 | Wed Jan 02 00:00:00 2019 CET 1 | Wed Jan 02 01:00:00 2019 CET (3 rows) -- test functions SELECT name, setting_exists, setting, setting_pretty FROM "PGTS".pg_track_settings_log('work_mem', 1) ORDER BY ts ASC; name | setting_exists | setting | setting_pretty ----------+----------------+---------+---------------- work_mem | t | 0 | 1MB work_mem | t | 10240 | 10MB (2 rows) SELECT name, from_setting, from_exists, to_setting, to_exists, from_setting_pretty, to_setting_pretty FROM "PGTS".pg_track_settings_diff('2019-01-01 01:00:00 CET', '2019-01-02 02:00:00 CET', 1); name | from_setting | from_exists | to_setting | to_exists | from_setting_pretty | to_setting_pretty -----------+--------------+-------------+------------+-----------+---------------------+------------------- something | | f | someval | t | | someval work_mem | 0 | t | 10240 | t | 1MB | 10MB (2 rows) SELECT * FROM "PGTS".pg_track_db_role_settings_log('work_mem', 1) s ORDER BY ts ASC; ts | setdatabase | setrole | name | setting_exists | setting ------------------------------+-------------+---------+----------+----------------+--------- Tue Jan 01 00:00:00 2019 CET | 123 | 0 | work_mem | t | 1MB Wed Jan 02 01:00:00 2019 CET | 123 | 0 | work_mem | t | 10MB (2 rows) SELECT * FROM "PGTS".pg_track_db_role_settings_diff('2018-12-31 02:00:00 CET', '2019-01-02 03:00:00 CET', 1) s WHERE name = 'work_mem' ORDER BY 1, 2, 3; setdatabase | setrole | name | from_setting | from_exists | to_setting | to_exists -------------+---------+----------+--------------+-------------+------------+----------- 123 | 0 | work_mem | | f | 10MB | t (1 row) SELECT * FROM "PGTS".pg_track_reboot_log(1); ts ------------------------------ Tue Jan 01 00:00:00 2019 CET Wed Jan 02 00:00:00 2019 CET Wed Jan 02 01:00:00 2019 CET (3 rows) -- snapshot the pending server 2 SELECT "PGTS".pg_track_settings_snapshot_settings(2); pg_track_settings_snapshot_settings ------------------------------------- t (1 row) SELECT "PGTS".pg_track_settings_snapshot_rds(2); pg_track_settings_snapshot_rds -------------------------------- t (1 row) SELECT "PGTS".pg_track_settings_snapshot_reboot(2); pg_track_settings_snapshot_reboot ----------------------------------- t (1 row) -- check that all data have been deleted after processing SELECT COUNT(*) FROM "PGTS".pg_track_settings_settings_src_tmp; count ------- 0 (1 row) SELECT COUNT(*) FROM "PGTS".pg_track_settings_rds_src_tmp; count ------- 0 (1 row) SELECT COUNT(*) FROM "PGTS".pg_track_settings_reboot_src_tmp; count ------- 0 (1 row) -- test the reset SELECT * FROM "PGTS".pg_track_settings_reset(1); pg_track_settings_reset ------------------------- (1 row) SELECT srvid, COUNT(*) FROM "PGTS".pg_track_settings_history GROUP BY srvid; srvid | count -------+------- 2 | 1 (1 row) SELECT COUNT(*) FROM "PGTS".pg_track_settings_log('work_mem', 1); count ------- 0 (1 row) SELECT COUNT(*) FROM "PGTS".pg_track_settings_diff('-infinity', 'infinity', 1); count ------- 0 (1 row) SELECT COUNT(*) FROM "PGTS".pg_track_db_role_settings_log('work_mem', 1); count ------- 0 (1 row) SELECT COUNT(*) FROM "PGTS".pg_track_db_role_settings_diff('-infinity', 'infinity', 1); count ------- 0 (1 row) SELECT srvid, COUNT(*) FROM "PGTS".pg_track_db_role_settings_history GROUP BY srvid; srvid | count -------+------- 2 | 1 (1 row) SELECT srvid, COUNT(*) FROM "PGTS".pg_reboot GROUP BY srvid; srvid | count -------+------- 2 | 1 (1 row) pg_track_settings-2.1.2/pg_track_settings--1.0.0--1.0.1.sql000066400000000000000000000125341442764062500227170ustar00rootroot00000000000000-- This program is open source, licensed under the PostgreSQL License. -- For license terms, see the LICENSE file. -- -- Copyright (C) 2015-2022: Julien Rouhaud -- complain if script is sourced in psql, rather than via ALTER EXTENSION \echo Use "ALTER EXTENSION pg_track_settings" to load this file. \quit SET client_encoding = 'UTF8'; CREATE OR REPLACE FUNCTION pg_track_settings_snapshot() RETURNS boolean AS $_$ BEGIN -- Handle dropped GUC WITH dropped AS ( SELECT l.name FROM pg_track_settings_list l LEFT JOIN pg_settings s ON s.name = l.name WHERE s.name IS NULL ), mark_dropped AS ( INSERT INTO pg_track_settings_history (ts, name, setting, is_dropped) SELECT now(), name, NULL, true FROM dropped ) DELETE FROM pg_track_settings_list l USING dropped d WHERE d.name = l.name; -- Insert missing settings INSERT INTO pg_track_settings_list (name) SELECT name FROM pg_settings s WHERE NOT EXISTS (SELECT 1 FROM pg_track_settings_list l WHERE l.name = s.name ); -- Detect changed GUC, insert new vals WITH last_snapshot AS ( SELECT name, setting FROM ( SELECT name, setting, row_number() OVER (PARTITION BY NAME ORDER BY ts DESC) rownum FROM pg_track_settings_history ) all_snapshots WHERE rownum = 1 ) INSERT INTO pg_track_settings_history (ts, name, setting) SELECT now(), s.name, s.setting FROM pg_settings s LEFT JOIN last_snapshot l ON l.name = s.name WHERE l.name IS NULL OR l.setting IS DISTINCT FROM s.setting; -- Handle dropped db_role_setting WITH rds AS ( SELECT setdatabase, setrole, (regexp_split_to_array(unnest(setconfig),'=')::text[])[1] as name, (regexp_split_to_array(unnest(setconfig),'=')::text[])[2] as setting FROM pg_db_role_setting ), dropped AS ( SELECT l.setdatabase, l.setrole, l.name FROM pg_track_db_role_settings_list l LEFT JOIN rds s ON ( s.setdatabase = l.setdatabase AND s.setrole = l.setrole AND s.name = l.name ) WHERE s.setdatabase IS NULL AND s.setrole IS NULL AND s.name IS NULL ), mark_dropped AS ( INSERT INTO pg_track_db_role_settings_history (ts, setdatabase, setrole, name, setting, is_dropped) SELECT now(), setdatabase, setrole, name, NULL, true FROM dropped ) DELETE FROM pg_track_db_role_settings_list l USING dropped d WHERE d.setdatabase = l.setdatabase AND d.setrole = l.setrole AND d.name = l.name; -- Insert missing settings WITH rds AS ( SELECT setdatabase, setrole, (regexp_split_to_array(unnest(setconfig),'=')::text[])[1] as name, (regexp_split_to_array(unnest(setconfig),'=')::text[])[2] as setting FROM pg_db_role_setting ) INSERT INTO pg_track_db_role_settings_list (setdatabase, setrole, name) SELECT setdatabase, setrole, name FROM rds s WHERE NOT EXISTS (SELECT 1 FROM pg_track_db_role_settings_list l WHERE l.setdatabase = s.setdatabase AND l.setrole = l.setrole AND l.name = s.name ); -- Detect changed GUC, insert new vals WITH rds AS ( SELECT setdatabase, setrole, (regexp_split_to_array(unnest(setconfig),'=')::text[])[1] as name, (regexp_split_to_array(unnest(setconfig),'=')::text[])[2] as setting FROM pg_db_role_setting ), last_snapshot AS ( SELECT setdatabase, setrole, name, setting FROM ( SELECT setdatabase, setrole, name, setting, row_number() OVER (PARTITION BY name, setdatabase, setrole ORDER BY ts DESC) rownum FROM pg_track_db_role_settings_history ) all_snapshots WHERE rownum = 1 ) INSERT INTO pg_track_db_role_settings_history (ts, setdatabase, setrole, name, setting) SELECT now(), s.setdatabase, s.setrole, s.name, s.setting FROM rds s LEFT JOIN last_snapshot l ON l.setdatabase = s.setdatabase AND l.setrole = s.setrole AND l.name = s.name WHERE l.setdatabase IS NULL AND l.setrole IS NULL AND l.name IS NULL OR l.setting IS DISTINCT FROM s.setting; -- Detect is postmaster restarted since last call WITH last_reboot AS ( SELECT t FROM pg_postmaster_start_time() t ) INSERT INTO pg_reboot (ts) SELECT t FROM last_reboot lr WHERE NOT EXISTS (SELECT 1 FROM pg_reboot r WHERE r.ts = lr.t ); RETURN true; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pg_track_db_role_settings(_ts timestamp with time zone DEFAULT now()) RETURNS TABLE (setdatabase oid, setrole oid, name text, setting text) AS $_$ BEGIN RETURN QUERY SELECT s.setdatabase, s.setrole, s.name, s.setting FROM ( SELECT h.setdatabase, h.setrole, h.name, h.setting, h.is_dropped, row_number() OVER (PARTITION BY h.name, h.setdatabase, h.setrole ORDER BY h.ts DESC) AS rownum FROM pg_track_db_role_settings_history h WHERE ts <= _ts ) s WHERE s.rownum = 1 AND NOT s.is_dropped ORDER BY s.setdatabase, s.setrole, s.name; END; $_$ LANGUAGE plpgsql; pg_track_settings-2.1.2/pg_track_settings--1.0.0.sql000066400000000000000000000234371442764062500222130ustar00rootroot00000000000000-- This program is open source, licensed under the PostgreSQL License. -- For license terms, see the LICENSE file. -- -- Copyright (C) 2015-2023: Julien Rouhaud -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pg_track_settings" to load this file. \quit SET client_encoding = 'UTF8'; CREATE TABLE pg_track_settings_list ( name text PRIMARY KEY ); SELECT pg_catalog.pg_extension_config_dump('pg_track_settings_list', ''); CREATE TABLE pg_track_settings_history ( ts timestamp with time zone, name text, setting text, is_dropped boolean NOT NULL DEFAULT false, PRIMARY KEY(ts, name) ); SELECT pg_catalog.pg_extension_config_dump('pg_track_settings_history', ''); CREATE TABLE pg_track_db_role_settings_list ( name text, setdatabase oid, setrole oid, PRIMARY KEY (name, setdatabase, setrole) ); SELECT pg_catalog.pg_extension_config_dump('pg_track_db_role_settings_list', ''); CREATE TABLE pg_track_db_role_settings_history ( ts timestamp with time zone, name text, setdatabase oid, setrole oid, setting text, is_dropped boolean NOT NULL DEFAULT false, PRIMARY KEY(ts, name, setdatabase, setrole) ); SELECT pg_catalog.pg_extension_config_dump('pg_track_db_role_settings_history', ''); CREATE TABLE pg_reboot ( ts timestamp with time zone PRIMARY KEY ); SELECT pg_catalog.pg_extension_config_dump('pg_reboot', ''); CREATE OR REPLACE FUNCTION pg_track_settings_snapshot() RETURNS boolean AS $_$ BEGIN -- Handle dropped GUC WITH dropped AS ( SELECT l.name FROM pg_track_settings_list l LEFT JOIN pg_settings s ON s.name = l.name WHERE s.name IS NULL ), mark_dropped AS ( INSERT INTO pg_track_settings_history (ts, name, setting, is_dropped) SELECT now(), name, NULL, true FROM dropped ) DELETE FROM pg_track_settings_list l USING dropped d WHERE d.name = l.name; -- Insert missing settings INSERT INTO pg_track_settings_list (name) SELECT name FROM pg_settings s WHERE NOT EXISTS (SELECT 1 FROM pg_track_settings_list l WHERE l.name = s.name ); -- Detect changed GUC, insert new vals WITH last_snapshot AS ( SELECT name, setting FROM ( SELECT name, setting, row_number() OVER (PARTITION BY NAME ORDER BY ts DESC) rownum FROM pg_track_settings_history ) all_snapshots WHERE rownum = 1 ) INSERT INTO pg_track_settings_history (ts, name, setting) SELECT now(), s.name, s.setting FROM pg_settings s LEFT JOIN last_snapshot l ON l.name = s.name WHERE l.name IS NULL OR l.setting IS DISTINCT FROM s.setting; -- Handle dropped db_role_setting WITH rds AS ( SELECT setdatabase, setrole, (regexp_split_to_array(unnest(setconfig),'=')::text[])[1] as name, (regexp_split_to_array(unnest(setconfig),'=')::text[])[2] as setting FROM pg_db_role_setting ), dropped AS ( SELECT l.setdatabase, l.setrole, l.name FROM pg_track_db_role_settings_list l LEFT JOIN rds s ON ( s.setdatabase = l.setdatabase AND s.setrole = l.setrole AND s.name = l.name ) WHERE s.setdatabase IS NULL AND s.setrole IS NULL AND s.name IS NULL ), mark_dropped AS ( INSERT INTO pg_track_db_role_settings_history (ts, setdatabase, setrole, name, setting, is_dropped) SELECT now(), setdatabase, setrole, name, NULL, true FROM dropped ) DELETE FROM pg_track_db_role_settings_list l USING dropped d WHERE d.setdatabase = l.setdatabase AND d.setrole = l.setrole AND d.name = l.name; -- Insert missing settings WITH rds AS ( SELECT setdatabase, setrole, (regexp_split_to_array(unnest(setconfig),'=')::text[])[1] as name, (regexp_split_to_array(unnest(setconfig),'=')::text[])[2] as setting FROM pg_db_role_setting ) INSERT INTO pg_track_db_role_settings_list (setdatabase, setrole, name) SELECT setdatabase, setrole, name FROM rds s WHERE NOT EXISTS (SELECT 1 FROM pg_track_db_role_settings_list l WHERE l.setdatabase = s.setdatabase AND l.setrole = l.setrole AND l.name = s.name ); -- Detect changed GUC, insert new vals WITH rds AS ( SELECT setdatabase, setrole, (regexp_split_to_array(unnest(setconfig),'=')::text[])[1] as name, (regexp_split_to_array(unnest(setconfig),'=')::text[])[2] as setting FROM pg_db_role_setting ), last_snapshot AS ( SELECT setdatabase, setrole, name, setting FROM ( SELECT setdatabase, setrole, name, setting, row_number() OVER (PARTITION BY NAME ORDER BY ts DESC) rownum FROM pg_track_db_role_settings_history ) all_snapshots WHERE rownum = 1 ) INSERT INTO pg_track_db_role_settings_history (ts, setdatabase, setrole, name, setting) SELECT now(), s.setdatabase, s.setrole, s.name, s.setting FROM rds s LEFT JOIN last_snapshot l ON l.setdatabase = s.setdatabase AND l.setrole = s.setrole AND l.name = s.name WHERE l.setdatabase IS NULL AND l.setrole IS NULL AND l.name IS NULL OR l.setting IS DISTINCT FROM s.setting; -- Detect is postmaster restarted since last call WITH last_reboot AS ( SELECT t FROM pg_postmaster_start_time() t ) INSERT INTO pg_reboot (ts) SELECT t FROM last_reboot lr WHERE NOT EXISTS (SELECT 1 FROM pg_reboot r WHERE r.ts = lr.t ); RETURN true; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pg_track_settings(_ts timestamp with time zone DEFAULT now()) RETURNS TABLE (name text, setting text) AS $_$ BEGIN RETURN QUERY SELECT s.name, s.setting FROM ( SELECT h.name, h.setting, h.is_dropped, row_number() OVER (PARTITION BY h.name ORDER BY h.ts DESC) AS rownum FROM pg_track_settings_history h WHERE ts <= _ts ) s WHERE s.rownum = 1 AND NOT s.is_dropped ORDER BY s.name; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pg_track_db_role_settings(_ts timestamp with time zone DEFAULT now()) RETURNS TABLE (setdatabase oid, setrole oid, name text, setting text) AS $_$ BEGIN RETURN QUERY SELECT s.setdatabase, s.setrole, s.name, s.setting FROM ( SELECT h.setdatabase, h.setrole, h.name, h.setting, h.is_dropped, row_number() OVER (PARTITION BY h.name ORDER BY h.ts DESC) AS rownum FROM pg_track_db_role_settings_history h WHERE ts <= _ts ) s WHERE s.rownum = 1 AND NOT s.is_dropped ORDER BY s.setdatabase, s.setrole, s.name; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pg_track_settings_diff(_from timestamp with time zone, _to timestamp with time zone) RETURNS TABLE (name text, from_setting text, from_exists boolean, to_setting text, to_exists boolean) AS $_$ BEGIN RETURN QUERY SELECT COALESCE(s1.name, s2.name), s1.setting AS from_setting, CASE WHEN s1.setting IS NULL THEN false ELSE true END, s2.setting AS to_setting, CASE WHEN s2.setting IS NULL THEN false ELSE true END FROM pg_track_settings(_from) s1 FULL OUTER JOIN pg_track_settings(_to) s2 ON s2.name = s1.name WHERE s1.setting IS DISTINCT FROM s2.setting ORDER BY 1; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pg_track_db_role_settings_diff(_from timestamp with time zone, _to timestamp with time zone) RETURNS TABLE (setdatabase oid, setrole oid, name text, from_setting text, from_exists boolean, to_setting text, to_exists boolean) AS $_$ BEGIN RETURN QUERY SELECT COALESCE(s1.setdatabase, s2.setdatabase), COALESCE(s1.setrole, s2.setrole), COALESCE(s1.name, s2.name), s1.setting AS from_setting, CASE WHEN s1.setting IS NULL THEN false ELSE true END, s2.setting AS to_setting, CASE WHEN s2.setting IS NULL THEN false ELSE true END FROM pg_track_db_role_settings(_from) s1 FULL OUTER JOIN pg_track_db_role_settings(_to) s2 ON s2.setdatabase = s1.setdatabase AND s2.setrole = s1.setrole AND s2.name = s1.name WHERE s1.setdatabase IS DISTINCT FROM s2.setdatabase AND s1.setrole IS DISTINCT FROM s2.setrole AND s1.setting IS DISTINCT FROM s2.setting ORDER BY 1, 2, 3; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pg_track_settings_log(_name text) RETURNS TABLE (ts timestamp with time zone, name text, setting_exists boolean, setting text) AS $_$ BEGIN RETURN QUERY SELECT h.ts, h.name, NOT h.is_dropped, h.setting FROM pg_track_settings_history h WHERE h.name = _name ORDER BY ts DESC; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pg_track_db_role_settings_log(_name text) RETURNS TABLE (ts timestamp with time zone, setdatabase oid, setrole oid, name text, setting_exists boolean, setting text) AS $_$ BEGIN RETURN QUERY SELECT h.ts, h.setdatabase, h.setrole, h.name, NOT h.is_dropped, h.setting FROM pg_track_db_role_settings_history h WHERE h.name = _name ORDER BY ts, setdatabase, setrole DESC; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pg_track_settings_reset() RETURNS void AS $_$ BEGIN TRUNCATE pg_track_settings_list; TRUNCATE pg_track_settings_history; TRUNCATE pg_track_db_role_settings_list; TRUNCATE pg_track_db_role_settings_history; TRUNCATE pg_reboot; END; $_$ LANGUAGE plpgsql; pg_track_settings-2.1.2/pg_track_settings--1.0.1--1.1.0.sql000066400000000000000000000157341442764062500227250ustar00rootroot00000000000000-- This program is open source, licensed under the PostgreSQL License. -- For license terms, see the LICENSE file. -- -- Copyright (C) 2015-2022: Julien Rouhaud -- complain if script is sourced in psql, rather than via ALTER EXTENSION \echo Use "ALTER EXTENSION pg_track_settings" to load this file. \quit SET client_encoding = 'UTF8'; ALTER TABLE pg_track_settings_history ALTER COLUMN name SET NOT NULL; ALTER TABLE pg_track_settings_history ADD COLUMN setting_pretty text; CREATE OR REPLACE FUNCTION pg_track_settings_snapshot() RETURNS boolean AS $_$ BEGIN -- Handle dropped GUC WITH dropped AS ( SELECT l.name FROM pg_track_settings_list l LEFT JOIN pg_settings s ON s.name = l.name WHERE s.name IS NULL ), mark_dropped AS ( INSERT INTO pg_track_settings_history (ts, name, setting, setting_pretty, is_dropped) SELECT now(), name, NULL, NULL, true FROM dropped ) DELETE FROM pg_track_settings_list l USING dropped d WHERE d.name = l.name; -- Insert missing settings INSERT INTO pg_track_settings_list (name) SELECT name FROM pg_settings s WHERE NOT EXISTS (SELECT 1 FROM pg_track_settings_list l WHERE l.name = s.name ); -- Detect changed GUC, insert new vals WITH last_snapshot AS ( SELECT name, setting FROM ( SELECT name, setting, row_number() OVER (PARTITION BY NAME ORDER BY ts DESC) rownum FROM pg_track_settings_history ) all_snapshots WHERE rownum = 1 ) INSERT INTO pg_track_settings_history (ts, name, setting, setting_pretty) SELECT now(), s.name, s.setting, pg_catalog.current_setting(s.name) FROM pg_settings s LEFT JOIN last_snapshot l ON l.name = s.name WHERE l.name IS NULL OR l.setting IS DISTINCT FROM s.setting; -- Handle dropped db_role_setting WITH rds AS ( SELECT setdatabase, setrole, (regexp_split_to_array(unnest(setconfig),'=')::text[])[1] as name, (regexp_split_to_array(unnest(setconfig),'=')::text[])[2] as setting FROM pg_db_role_setting ), dropped AS ( SELECT l.setdatabase, l.setrole, l.name FROM pg_track_db_role_settings_list l LEFT JOIN rds s ON ( s.setdatabase = l.setdatabase AND s.setrole = l.setrole AND s.name = l.name ) WHERE s.setdatabase IS NULL AND s.setrole IS NULL AND s.name IS NULL ), mark_dropped AS ( INSERT INTO pg_track_db_role_settings_history (ts, setdatabase, setrole, name, setting, is_dropped) SELECT now(), setdatabase, setrole, name, NULL, true FROM dropped ) DELETE FROM pg_track_db_role_settings_list l USING dropped d WHERE d.setdatabase = l.setdatabase AND d.setrole = l.setrole AND d.name = l.name; -- Insert missing settings WITH rds AS ( SELECT setdatabase, setrole, (regexp_split_to_array(unnest(setconfig),'=')::text[])[1] as name, (regexp_split_to_array(unnest(setconfig),'=')::text[])[2] as setting FROM pg_db_role_setting ) INSERT INTO pg_track_db_role_settings_list (setdatabase, setrole, name) SELECT setdatabase, setrole, name FROM rds s WHERE NOT EXISTS (SELECT 1 FROM pg_track_db_role_settings_list l WHERE l.setdatabase = s.setdatabase AND l.setrole = l.setrole AND l.name = s.name ); -- Detect changed GUC, insert new vals WITH rds AS ( SELECT setdatabase, setrole, (regexp_split_to_array(unnest(setconfig),'=')::text[])[1] as name, (regexp_split_to_array(unnest(setconfig),'=')::text[])[2] as setting FROM pg_db_role_setting ), last_snapshot AS ( SELECT setdatabase, setrole, name, setting FROM ( SELECT setdatabase, setrole, name, setting, row_number() OVER (PARTITION BY name, setdatabase, setrole ORDER BY ts DESC) rownum FROM pg_track_db_role_settings_history ) all_snapshots WHERE rownum = 1 ) INSERT INTO pg_track_db_role_settings_history (ts, setdatabase, setrole, name, setting) SELECT now(), s.setdatabase, s.setrole, s.name, s.setting FROM rds s LEFT JOIN last_snapshot l ON l.setdatabase = s.setdatabase AND l.setrole = s.setrole AND l.name = s.name WHERE l.setdatabase IS NULL AND l.setrole IS NULL AND l.name IS NULL OR l.setting IS DISTINCT FROM s.setting; -- Detect is postmaster restarted since last call WITH last_reboot AS ( SELECT t FROM pg_postmaster_start_time() t ) INSERT INTO pg_reboot (ts) SELECT t FROM last_reboot lr WHERE NOT EXISTS (SELECT 1 FROM pg_reboot r WHERE r.ts = lr.t ); RETURN true; END; $_$ LANGUAGE plpgsql; /* end of pg_track_settings_snapshot() */ DROP FUNCTION pg_track_settings(timestamp with time zone); CREATE OR REPLACE FUNCTION pg_track_settings(_ts timestamp with time zone DEFAULT now()) RETURNS TABLE (name text, setting text, setting_pretty text) AS $_$ BEGIN RETURN QUERY SELECT s.name, s.setting, s.setting_pretty FROM ( SELECT h.name, h.setting, h.setting_pretty, h.is_dropped, row_number() OVER (PARTITION BY h.name ORDER BY h.ts DESC) AS rownum FROM pg_track_settings_history h WHERE ts <= _ts ) s WHERE s.rownum = 1 AND NOT s.is_dropped ORDER BY s.name; END; $_$ LANGUAGE plpgsql; DROP FUNCTION pg_track_settings_diff(timestamp with time zone,timestamp with time zone); CREATE OR REPLACE FUNCTION pg_track_settings_diff(_from timestamp with time zone, _to timestamp with time zone) RETURNS TABLE (name text, from_setting text, from_exists boolean, to_setting text, to_exists boolean, from_setting_pretty text, to_setting_pretty text) AS $_$ BEGIN RETURN QUERY SELECT COALESCE(s1.name, s2.name), s1.setting AS from_setting, CASE WHEN s1.setting IS NULL THEN false ELSE true END, s2.setting AS to_setting, CASE WHEN s2.setting IS NULL THEN false ELSE true END, s1.setting_pretty AS from_setting_pretty, s2.setting_pretty AS to_setting_pretty FROM pg_track_settings(_from) s1 FULL OUTER JOIN pg_track_settings(_to) s2 ON s2.name = s1.name WHERE s1.setting IS DISTINCT FROM s2.setting ORDER BY 1; END; $_$ LANGUAGE plpgsql; DROP FUNCTION pg_track_settings_log(text); CREATE OR REPLACE FUNCTION pg_track_settings_log(_name text) RETURNS TABLE (ts timestamp with time zone, name text, setting_exists boolean, setting text, setting_pretty text) AS $_$ BEGIN RETURN QUERY SELECT h.ts, h.name, NOT h.is_dropped, h.setting, h.setting_pretty FROM pg_track_settings_history h WHERE h.name = _name ORDER BY ts DESC; END; $_$ LANGUAGE plpgsql; pg_track_settings-2.1.2/pg_track_settings--1.0.1.sql000066400000000000000000000235171442764062500222130ustar00rootroot00000000000000-- This program is open source, licensed under the PostgreSQL License. -- For license terms, see the LICENSE file. -- -- Copyright (C) 2015-2023: Julien Rouhaud -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pg_track_settings" to load this file. \quit SET client_encoding = 'UTF8'; CREATE TABLE pg_track_settings_list ( name text PRIMARY KEY ); SELECT pg_catalog.pg_extension_config_dump('pg_track_settings_list', ''); CREATE TABLE pg_track_settings_history ( ts timestamp with time zone, name text, setting text, is_dropped boolean NOT NULL DEFAULT false, PRIMARY KEY(ts, name) ); SELECT pg_catalog.pg_extension_config_dump('pg_track_settings_history', ''); CREATE TABLE pg_track_db_role_settings_list ( name text, setdatabase oid, setrole oid, PRIMARY KEY (name, setdatabase, setrole) ); SELECT pg_catalog.pg_extension_config_dump('pg_track_db_role_settings_list', ''); CREATE TABLE pg_track_db_role_settings_history ( ts timestamp with time zone, name text, setdatabase oid, setrole oid, setting text, is_dropped boolean NOT NULL DEFAULT false, PRIMARY KEY(ts, name, setdatabase, setrole) ); SELECT pg_catalog.pg_extension_config_dump('pg_track_db_role_settings_history', ''); CREATE TABLE pg_reboot ( ts timestamp with time zone PRIMARY KEY ); SELECT pg_catalog.pg_extension_config_dump('pg_reboot', ''); CREATE OR REPLACE FUNCTION pg_track_settings_snapshot() RETURNS boolean AS $_$ BEGIN -- Handle dropped GUC WITH dropped AS ( SELECT l.name FROM pg_track_settings_list l LEFT JOIN pg_settings s ON s.name = l.name WHERE s.name IS NULL ), mark_dropped AS ( INSERT INTO pg_track_settings_history (ts, name, setting, is_dropped) SELECT now(), name, NULL, true FROM dropped ) DELETE FROM pg_track_settings_list l USING dropped d WHERE d.name = l.name; -- Insert missing settings INSERT INTO pg_track_settings_list (name) SELECT name FROM pg_settings s WHERE NOT EXISTS (SELECT 1 FROM pg_track_settings_list l WHERE l.name = s.name ); -- Detect changed GUC, insert new vals WITH last_snapshot AS ( SELECT name, setting FROM ( SELECT name, setting, row_number() OVER (PARTITION BY NAME ORDER BY ts DESC) rownum FROM pg_track_settings_history ) all_snapshots WHERE rownum = 1 ) INSERT INTO pg_track_settings_history (ts, name, setting) SELECT now(), s.name, s.setting FROM pg_settings s LEFT JOIN last_snapshot l ON l.name = s.name WHERE l.name IS NULL OR l.setting IS DISTINCT FROM s.setting; -- Handle dropped db_role_setting WITH rds AS ( SELECT setdatabase, setrole, (regexp_split_to_array(unnest(setconfig),'=')::text[])[1] as name, (regexp_split_to_array(unnest(setconfig),'=')::text[])[2] as setting FROM pg_db_role_setting ), dropped AS ( SELECT l.setdatabase, l.setrole, l.name FROM pg_track_db_role_settings_list l LEFT JOIN rds s ON ( s.setdatabase = l.setdatabase AND s.setrole = l.setrole AND s.name = l.name ) WHERE s.setdatabase IS NULL AND s.setrole IS NULL AND s.name IS NULL ), mark_dropped AS ( INSERT INTO pg_track_db_role_settings_history (ts, setdatabase, setrole, name, setting, is_dropped) SELECT now(), setdatabase, setrole, name, NULL, true FROM dropped ) DELETE FROM pg_track_db_role_settings_list l USING dropped d WHERE d.setdatabase = l.setdatabase AND d.setrole = l.setrole AND d.name = l.name; -- Insert missing settings WITH rds AS ( SELECT setdatabase, setrole, (regexp_split_to_array(unnest(setconfig),'=')::text[])[1] as name, (regexp_split_to_array(unnest(setconfig),'=')::text[])[2] as setting FROM pg_db_role_setting ) INSERT INTO pg_track_db_role_settings_list (setdatabase, setrole, name) SELECT setdatabase, setrole, name FROM rds s WHERE NOT EXISTS (SELECT 1 FROM pg_track_db_role_settings_list l WHERE l.setdatabase = s.setdatabase AND l.setrole = l.setrole AND l.name = s.name ); -- Detect changed GUC, insert new vals WITH rds AS ( SELECT setdatabase, setrole, (regexp_split_to_array(unnest(setconfig),'=')::text[])[1] as name, (regexp_split_to_array(unnest(setconfig),'=')::text[])[2] as setting FROM pg_db_role_setting ), last_snapshot AS ( SELECT setdatabase, setrole, name, setting FROM ( SELECT setdatabase, setrole, name, setting, row_number() OVER (PARTITION BY name, setdatabase, setrole ORDER BY ts DESC) rownum FROM pg_track_db_role_settings_history ) all_snapshots WHERE rownum = 1 ) INSERT INTO pg_track_db_role_settings_history (ts, setdatabase, setrole, name, setting) SELECT now(), s.setdatabase, s.setrole, s.name, s.setting FROM rds s LEFT JOIN last_snapshot l ON l.setdatabase = s.setdatabase AND l.setrole = s.setrole AND l.name = s.name WHERE l.setdatabase IS NULL AND l.setrole IS NULL AND l.name IS NULL OR l.setting IS DISTINCT FROM s.setting; -- Detect is postmaster restarted since last call WITH last_reboot AS ( SELECT t FROM pg_postmaster_start_time() t ) INSERT INTO pg_reboot (ts) SELECT t FROM last_reboot lr WHERE NOT EXISTS (SELECT 1 FROM pg_reboot r WHERE r.ts = lr.t ); RETURN true; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pg_track_settings(_ts timestamp with time zone DEFAULT now()) RETURNS TABLE (name text, setting text) AS $_$ BEGIN RETURN QUERY SELECT s.name, s.setting FROM ( SELECT h.name, h.setting, h.is_dropped, row_number() OVER (PARTITION BY h.name ORDER BY h.ts DESC) AS rownum FROM pg_track_settings_history h WHERE ts <= _ts ) s WHERE s.rownum = 1 AND NOT s.is_dropped ORDER BY s.name; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pg_track_db_role_settings(_ts timestamp with time zone DEFAULT now()) RETURNS TABLE (setdatabase oid, setrole oid, name text, setting text) AS $_$ BEGIN RETURN QUERY SELECT s.setdatabase, s.setrole, s.name, s.setting FROM ( SELECT h.setdatabase, h.setrole, h.name, h.setting, h.is_dropped, row_number() OVER (PARTITION BY h.name, h.setdatabase, h.setrole ORDER BY h.ts DESC) AS rownum FROM pg_track_db_role_settings_history h WHERE ts <= _ts ) s WHERE s.rownum = 1 AND NOT s.is_dropped ORDER BY s.setdatabase, s.setrole, s.name; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pg_track_settings_diff(_from timestamp with time zone, _to timestamp with time zone) RETURNS TABLE (name text, from_setting text, from_exists boolean, to_setting text, to_exists boolean) AS $_$ BEGIN RETURN QUERY SELECT COALESCE(s1.name, s2.name), s1.setting AS from_setting, CASE WHEN s1.setting IS NULL THEN false ELSE true END, s2.setting AS to_setting, CASE WHEN s2.setting IS NULL THEN false ELSE true END FROM pg_track_settings(_from) s1 FULL OUTER JOIN pg_track_settings(_to) s2 ON s2.name = s1.name WHERE s1.setting IS DISTINCT FROM s2.setting ORDER BY 1; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pg_track_db_role_settings_diff(_from timestamp with time zone, _to timestamp with time zone) RETURNS TABLE (setdatabase oid, setrole oid, name text, from_setting text, from_exists boolean, to_setting text, to_exists boolean) AS $_$ BEGIN RETURN QUERY SELECT COALESCE(s1.setdatabase, s2.setdatabase), COALESCE(s1.setrole, s2.setrole), COALESCE(s1.name, s2.name), s1.setting AS from_setting, CASE WHEN s1.setting IS NULL THEN false ELSE true END, s2.setting AS to_setting, CASE WHEN s2.setting IS NULL THEN false ELSE true END FROM pg_track_db_role_settings(_from) s1 FULL OUTER JOIN pg_track_db_role_settings(_to) s2 ON s2.setdatabase = s1.setdatabase AND s2.setrole = s1.setrole AND s2.name = s1.name WHERE s1.setdatabase IS DISTINCT FROM s2.setdatabase AND s1.setrole IS DISTINCT FROM s2.setrole AND s1.setting IS DISTINCT FROM s2.setting ORDER BY 1, 2, 3; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pg_track_settings_log(_name text) RETURNS TABLE (ts timestamp with time zone, name text, setting_exists boolean, setting text) AS $_$ BEGIN RETURN QUERY SELECT h.ts, h.name, NOT h.is_dropped, h.setting FROM pg_track_settings_history h WHERE h.name = _name ORDER BY ts DESC; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pg_track_db_role_settings_log(_name text) RETURNS TABLE (ts timestamp with time zone, setdatabase oid, setrole oid, name text, setting_exists boolean, setting text) AS $_$ BEGIN RETURN QUERY SELECT h.ts, h.setdatabase, h.setrole, h.name, NOT h.is_dropped, h.setting FROM pg_track_db_role_settings_history h WHERE h.name = _name ORDER BY ts, setdatabase, setrole DESC; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pg_track_settings_reset() RETURNS void AS $_$ BEGIN TRUNCATE pg_track_settings_list; TRUNCATE pg_track_settings_history; TRUNCATE pg_track_db_role_settings_list; TRUNCATE pg_track_db_role_settings_history; TRUNCATE pg_reboot; END; $_$ LANGUAGE plpgsql; pg_track_settings-2.1.2/pg_track_settings--1.1.0.sql000066400000000000000000000244231442764062500222100ustar00rootroot00000000000000-- This program is open source, licensed under the PostgreSQL License. -- For license terms, see the LICENSE file. -- -- Copyright (C) 2015-2022: Julien Rouhaud -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pg_track_settings" to load this file. \quit SET client_encoding = 'UTF8'; CREATE TABLE pg_track_settings_list ( name text PRIMARY KEY ); SELECT pg_catalog.pg_extension_config_dump('pg_track_settings_list', ''); CREATE TABLE pg_track_settings_history ( ts timestamp with time zone, name text NOT NULL, setting text, is_dropped boolean NOT NULL DEFAULT false, setting_pretty text, PRIMARY KEY(ts, name) ); SELECT pg_catalog.pg_extension_config_dump('pg_track_settings_history', ''); CREATE TABLE pg_track_db_role_settings_list ( name text, setdatabase oid, setrole oid, PRIMARY KEY (name, setdatabase, setrole) ); SELECT pg_catalog.pg_extension_config_dump('pg_track_db_role_settings_list', ''); CREATE TABLE pg_track_db_role_settings_history ( ts timestamp with time zone, name text, setdatabase oid, setrole oid, setting text, is_dropped boolean NOT NULL DEFAULT false, PRIMARY KEY(ts, name, setdatabase, setrole) ); SELECT pg_catalog.pg_extension_config_dump('pg_track_db_role_settings_history', ''); CREATE TABLE pg_reboot ( ts timestamp with time zone PRIMARY KEY ); SELECT pg_catalog.pg_extension_config_dump('pg_reboot', ''); CREATE OR REPLACE FUNCTION pg_track_settings_snapshot() RETURNS boolean AS $_$ BEGIN -- Handle dropped GUC WITH dropped AS ( SELECT l.name FROM pg_track_settings_list l LEFT JOIN pg_settings s ON s.name = l.name WHERE s.name IS NULL ), mark_dropped AS ( INSERT INTO pg_track_settings_history (ts, name, setting, setting_pretty, is_dropped) SELECT now(), name, NULL, NULL, true FROM dropped ) DELETE FROM pg_track_settings_list l USING dropped d WHERE d.name = l.name; -- Insert missing settings INSERT INTO pg_track_settings_list (name) SELECT name FROM pg_settings s WHERE NOT EXISTS (SELECT 1 FROM pg_track_settings_list l WHERE l.name = s.name ); -- Detect changed GUC, insert new vals WITH last_snapshot AS ( SELECT name, setting FROM ( SELECT name, setting, row_number() OVER (PARTITION BY NAME ORDER BY ts DESC) rownum FROM pg_track_settings_history ) all_snapshots WHERE rownum = 1 ) INSERT INTO pg_track_settings_history (ts, name, setting, setting_pretty) SELECT now(), s.name, s.setting, pg_catalog.current_setting(s.name) FROM pg_settings s LEFT JOIN last_snapshot l ON l.name = s.name WHERE l.name IS NULL OR l.setting IS DISTINCT FROM s.setting; -- Handle dropped db_role_setting WITH rds AS ( SELECT setdatabase, setrole, (regexp_split_to_array(unnest(setconfig),'=')::text[])[1] as name, (regexp_split_to_array(unnest(setconfig),'=')::text[])[2] as setting FROM pg_db_role_setting ), dropped AS ( SELECT l.setdatabase, l.setrole, l.name FROM pg_track_db_role_settings_list l LEFT JOIN rds s ON ( s.setdatabase = l.setdatabase AND s.setrole = l.setrole AND s.name = l.name ) WHERE s.setdatabase IS NULL AND s.setrole IS NULL AND s.name IS NULL ), mark_dropped AS ( INSERT INTO pg_track_db_role_settings_history (ts, setdatabase, setrole, name, setting, is_dropped) SELECT now(), setdatabase, setrole, name, NULL, true FROM dropped ) DELETE FROM pg_track_db_role_settings_list l USING dropped d WHERE d.setdatabase = l.setdatabase AND d.setrole = l.setrole AND d.name = l.name; -- Insert missing settings WITH rds AS ( SELECT setdatabase, setrole, (regexp_split_to_array(unnest(setconfig),'=')::text[])[1] as name, (regexp_split_to_array(unnest(setconfig),'=')::text[])[2] as setting FROM pg_db_role_setting ) INSERT INTO pg_track_db_role_settings_list (setdatabase, setrole, name) SELECT setdatabase, setrole, name FROM rds s WHERE NOT EXISTS (SELECT 1 FROM pg_track_db_role_settings_list l WHERE l.setdatabase = s.setdatabase AND l.setrole = l.setrole AND l.name = s.name ); -- Detect changed GUC, insert new vals WITH rds AS ( SELECT setdatabase, setrole, (regexp_split_to_array(unnest(setconfig),'=')::text[])[1] as name, (regexp_split_to_array(unnest(setconfig),'=')::text[])[2] as setting FROM pg_db_role_setting ), last_snapshot AS ( SELECT setdatabase, setrole, name, setting FROM ( SELECT setdatabase, setrole, name, setting, row_number() OVER (PARTITION BY name, setdatabase, setrole ORDER BY ts DESC) rownum FROM pg_track_db_role_settings_history ) all_snapshots WHERE rownum = 1 ) INSERT INTO pg_track_db_role_settings_history (ts, setdatabase, setrole, name, setting) SELECT now(), s.setdatabase, s.setrole, s.name, s.setting FROM rds s LEFT JOIN last_snapshot l ON l.setdatabase = s.setdatabase AND l.setrole = s.setrole AND l.name = s.name WHERE l.setdatabase IS NULL AND l.setrole IS NULL AND l.name IS NULL OR l.setting IS DISTINCT FROM s.setting; -- Detect is postmaster restarted since last call WITH last_reboot AS ( SELECT t FROM pg_postmaster_start_time() t ) INSERT INTO pg_reboot (ts) SELECT t FROM last_reboot lr WHERE NOT EXISTS (SELECT 1 FROM pg_reboot r WHERE r.ts = lr.t ); RETURN true; END; $_$ LANGUAGE plpgsql; /* end of pg_track_settings_snapshot() */ CREATE OR REPLACE FUNCTION pg_track_settings(_ts timestamp with time zone DEFAULT now()) RETURNS TABLE (name text, setting text, setting_pretty text) AS $_$ BEGIN RETURN QUERY SELECT s.name, s.setting, s.setting_pretty FROM ( SELECT h.name, h.setting, h.setting_pretty, h.is_dropped, row_number() OVER (PARTITION BY h.name ORDER BY h.ts DESC) AS rownum FROM pg_track_settings_history h WHERE ts <= _ts ) s WHERE s.rownum = 1 AND NOT s.is_dropped ORDER BY s.name; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pg_track_db_role_settings(_ts timestamp with time zone DEFAULT now()) RETURNS TABLE (setdatabase oid, setrole oid, name text, setting text) AS $_$ BEGIN RETURN QUERY SELECT s.setdatabase, s.setrole, s.name, s.setting FROM ( SELECT h.setdatabase, h.setrole, h.name, h.setting, h.is_dropped, row_number() OVER (PARTITION BY h.name, h.setdatabase, h.setrole ORDER BY h.ts DESC) AS rownum FROM pg_track_db_role_settings_history h WHERE ts <= _ts ) s WHERE s.rownum = 1 AND NOT s.is_dropped ORDER BY s.setdatabase, s.setrole, s.name; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pg_track_settings_diff(_from timestamp with time zone, _to timestamp with time zone) RETURNS TABLE (name text, from_setting text, from_exists boolean, to_setting text, to_exists boolean, from_setting_pretty text, to_setting_pretty text) AS $_$ BEGIN RETURN QUERY SELECT COALESCE(s1.name, s2.name), s1.setting AS from_setting, CASE WHEN s1.setting IS NULL THEN false ELSE true END, s2.setting AS to_setting, CASE WHEN s2.setting IS NULL THEN false ELSE true END, s1.setting_pretty AS from_setting_pretty, s2.setting_pretty AS to_setting_pretty FROM pg_track_settings(_from) s1 FULL OUTER JOIN pg_track_settings(_to) s2 ON s2.name = s1.name WHERE s1.setting IS DISTINCT FROM s2.setting ORDER BY 1; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pg_track_db_role_settings_diff(_from timestamp with time zone, _to timestamp with time zone) RETURNS TABLE (setdatabase oid, setrole oid, name text, from_setting text, from_exists boolean, to_setting text, to_exists boolean) AS $_$ BEGIN RETURN QUERY SELECT COALESCE(s1.setdatabase, s2.setdatabase), COALESCE(s1.setrole, s2.setrole), COALESCE(s1.name, s2.name), s1.setting AS from_setting, CASE WHEN s1.setting IS NULL THEN false ELSE true END, s2.setting AS to_setting, CASE WHEN s2.setting IS NULL THEN false ELSE true END FROM pg_track_db_role_settings(_from) s1 FULL OUTER JOIN pg_track_db_role_settings(_to) s2 ON s2.setdatabase = s1.setdatabase AND s2.setrole = s1.setrole AND s2.name = s1.name WHERE s1.setdatabase IS DISTINCT FROM s2.setdatabase AND s1.setrole IS DISTINCT FROM s2.setrole AND s1.setting IS DISTINCT FROM s2.setting ORDER BY 1, 2, 3; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pg_track_settings_log(_name text) RETURNS TABLE (ts timestamp with time zone, name text, setting_exists boolean, setting text, setting_pretty text) AS $_$ BEGIN RETURN QUERY SELECT h.ts, h.name, NOT h.is_dropped, h.setting, h.setting_pretty FROM pg_track_settings_history h WHERE h.name = _name ORDER BY ts DESC; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pg_track_db_role_settings_log(_name text) RETURNS TABLE (ts timestamp with time zone, setdatabase oid, setrole oid, name text, setting_exists boolean, setting text) AS $_$ BEGIN RETURN QUERY SELECT h.ts, h.setdatabase, h.setrole, h.name, NOT h.is_dropped, h.setting FROM pg_track_db_role_settings_history h WHERE h.name = _name ORDER BY ts, setdatabase, setrole DESC; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pg_track_settings_reset() RETURNS void AS $_$ BEGIN TRUNCATE pg_track_settings_list; TRUNCATE pg_track_settings_history; TRUNCATE pg_track_db_role_settings_list; TRUNCATE pg_track_db_role_settings_history; TRUNCATE pg_reboot; END; $_$ LANGUAGE plpgsql; pg_track_settings-2.1.2/pg_track_settings--2.0.0--2.0.1.sql000066400000000000000000000070421442764062500227170ustar00rootroot00000000000000-- This program is open source, licensed under the PostgreSQL License. -- For license terms, see the LICENSE file. -- -- Copyright (C) 2015-2022: Julien Rouhaud -- complain if script is sourced in psql, rather than via ALTER EXTENSION \echo Use "ALTER EXTENSION pg_track_settings" to load this file. \quit SET LOCAL client_encoding = 'UTF8'; CREATE OR REPLACE FUNCTION pg_track_settings_snapshot_rds(_srvid integer) RETURNS boolean AS $_$ DECLARE _snap_ts timestamp with time zone; BEGIN SELECT max(ts) INTO _snap_ts FROM public.pg_track_settings_rds_src(_srvid); -- this function should have been called for previously saved data. If -- not, probably somethig went wrong, so discard those data IF (_srvid != 0) THEN DELETE FROM public.pg_track_settings_rds_src_tmp WHERE ts != _snap_ts; END IF; -- Handle dropped db_role_setting WITH rds AS ( SELECT * FROM public.pg_track_settings_rds_src(_srvid) ), dropped AS ( SELECT _snap_ts AS ts, l.setdatabase, l.setrole, l.name FROM public.pg_track_db_role_settings_list l LEFT JOIN rds s ON ( s.setdatabase = l.setdatabase AND s.setrole = l.setrole AND s.name = l.name ) WHERE l.srvid = _srvid AND s.setdatabase IS NULL AND s.setrole IS NULL AND s.name IS NULL ), mark_dropped AS ( INSERT INTO public.pg_track_db_role_settings_history (srvid, ts, setdatabase, setrole, name, setting, is_dropped) SELECT _srvid, ts, d.setdatabase, d.setrole, d.name, NULL, true FROM dropped AS d ) DELETE FROM public.pg_track_db_role_settings_list l USING dropped d WHERE l.srvid = _srvid AND d.setdatabase = l.setdatabase AND d.setrole = l.setrole AND d.name = l.name; -- Insert missing settings WITH rds AS ( SELECT * FROM public.pg_track_settings_rds_src(_srvid) ) INSERT INTO public.pg_track_db_role_settings_list (srvid, setdatabase, setrole, name) SELECT _srvid, setdatabase, setrole, name FROM rds s WHERE NOT EXISTS (SELECT 1 FROM public.pg_track_db_role_settings_list l WHERE l.srvid = _srvid AND l.setdatabase = s.setdatabase AND l.setrole = l.setrole AND l.name = s.name ); -- Detect changed GUC, insert new vals WITH rds AS ( SELECT * FROM public.pg_track_settings_rds_src(_srvid) ), last_snapshot AS ( SELECT setdatabase, setrole, name, setting FROM ( SELECT setdatabase, setrole, name, setting, row_number() OVER (PARTITION BY name, setdatabase, setrole ORDER BY ts DESC) AS rownum FROM public.pg_track_db_role_settings_history WHERE srvid = _srvid ) all_snapshots WHERE rownum = 1 ) INSERT INTO public.pg_track_db_role_settings_history (srvid, ts, setdatabase, setrole, name, setting) SELECT _srvid, s.ts, s.setdatabase, s.setrole, s.name, s.setting FROM rds s LEFT JOIN last_snapshot l ON l.setdatabase = s.setdatabase AND l.setrole = s.setrole AND l.name = s.name WHERE (l.setdatabase IS NULL AND l.setrole IS NULL AND l.name IS NULL) OR (l.setting IS DISTINCT FROM s.setting); IF (_srvid != 0) THEN DELETE FROM public.pg_track_settings_rds_src_tmp WHERE srvid = _srvid; END IF; RETURN true; END; $_$ LANGUAGE plpgsql; /* end of pg_track_settings_snapshot_rds() */ pg_track_settings-2.1.2/pg_track_settings--2.0.0.sql000066400000000000000000000415101442764062500222040ustar00rootroot00000000000000-- This program is open source, licensed under the PostgreSQL License. -- For license terms, see the LICENSE file. -- -- Copyright (C) 2015-2022: Julien Rouhaud -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pg_track_settings" to load this file. \quit SET client_encoding = 'UTF8'; CREATE UNLOGGED TABLE public.pg_track_settings_settings_src_tmp ( srvid integer NOT NULL, ts timestamp with time zone NOT NULL, name text NOT NULL, setting text, current_setting text ); -- no need to backup this table CREATE TABLE public.pg_track_settings_list ( srvid integer NOT NULL, name text, PRIMARY KEY (srvid, name) ); SELECT pg_catalog.pg_extension_config_dump('public.pg_track_settings_list', ''); CREATE TABLE public.pg_track_settings_history ( srvid integer NOT NULL, ts timestamp with time zone, name text NOT NULL, setting text, is_dropped boolean NOT NULL DEFAULT false, setting_pretty text, PRIMARY KEY(srvid, ts, name) ); SELECT pg_catalog.pg_extension_config_dump('public.pg_track_settings_history', ''); CREATE UNLOGGED TABLE public.pg_track_settings_rds_src_tmp ( srvid integer NOT NULL, ts timestamp with time zone NOT NULL, name text NOT NULL, setting text, setdatabase oid NOT NULL, setrole oid NOT NULL ); -- no need to backup this table CREATE TABLE public.pg_track_db_role_settings_list ( srvid integer, name text, setdatabase oid, setrole oid, PRIMARY KEY (srvid, name, setdatabase, setrole) ); SELECT pg_catalog.pg_extension_config_dump('public.pg_track_db_role_settings_list', ''); CREATE TABLE public.pg_track_db_role_settings_history ( srvid INTEGER NOT NULL, ts timestamp with time zone, name text, setdatabase oid, setrole oid, setting text, is_dropped boolean NOT NULL DEFAULT false, PRIMARY KEY(srvid, ts, name, setdatabase, setrole) ); SELECT pg_catalog.pg_extension_config_dump('public.pg_track_db_role_settings_history', ''); CREATE UNLOGGED TABLE public.pg_track_settings_reboot_src_tmp ( srvid integer NOT NULL, ts timestamp with time zone NOT NULL, postmaster_ts timestamp with time zone NOT NULL ); -- no need to backup this table CREATE TABLE public.pg_reboot ( srvid integer NOT NULL, ts timestamp with time zone, PRIMARY KEY (srvid, ts) ); SELECT pg_catalog.pg_extension_config_dump('public.pg_reboot', ''); ---------------------- -- source functions -- ---------------------- CREATE OR REPLACE FUNCTION pg_track_settings_settings_src ( IN _srvid integer, OUT ts timestamp with time zone, OUT name text, OUT setting text, OUT current_setting text ) RETURNS SETOF record AS $PROC$ BEGIN IF (_srvid = 0) THEN RETURN QUERY SELECT now(), s.name, s.setting, pg_catalog.current_setting(s.name) FROM pg_catalog.pg_settings s; ELSE RETURN QUERY SELECT s.ts, s.name, s.setting, s.current_setting FROM public.pg_track_settings_settings_src_tmp s; END IF; END; $PROC$ LANGUAGE plpgsql; /* end of pg_track_settings_settings_src */ CREATE OR REPLACE FUNCTION pg_track_settings_rds_src ( IN _srvid integer, OUT ts timestamp with time zone, OUT name text, OUT setting text, OUT setdatabase oid, OUT setrole oid ) RETURNS SETOF record AS $PROC$ BEGIN IF (_srvid = 0) THEN RETURN QUERY SELECT now(), (regexp_split_to_array(unnest(s.setconfig),'=')::text[])[1] AS name, (regexp_split_to_array(unnest(s.setconfig),'=')::text[])[2] AS setting, s.setdatabase, s.setrole FROM pg_catalog.pg_db_role_setting s; ELSE RETURN QUERY SELECT s.ts, s.name, s.setting, s.setdatabase, s.setrole FROM public.pg_track_settings_rds_src_tmp s; END IF; END; $PROC$ LANGUAGE plpgsql; /* end of pg_track_settings_rds_src */ CREATE OR REPLACE FUNCTION pg_track_settings_reboot_src ( IN _srvid integer, OUT ts timestamp with time zone, OUT postmaster_ts timestamp with time zone ) RETURNS SETOF record AS $PROC$ BEGIN IF (_srvid = 0) THEN RETURN QUERY SELECT now(), pg_postmaster_start_time(); ELSE RETURN QUERY SELECT s.ts, s.postmaster_ts FROM public.pg_track_settings_reboot_src_tmp s; END IF; END; $PROC$ LANGUAGE plpgsql; /* end of pg_track_settings_reboot_src */ ------------------------ -- snapshot functions -- ------------------------ CREATE OR REPLACE FUNCTION pg_track_settings_snapshot_settings(_srvid integer) RETURNS boolean AS $_$ DECLARE _snap_ts timestamp with time zone = NULL; BEGIN SELECT max(ts) INTO _snap_ts FROM public.pg_track_settings_settings_src(_srvid); -- this function should have been called for previously saved data. If -- not, probably somethig went wrong, so discard those data IF (_srvid != 0) THEN DELETE FROM public.pg_track_settings_settings_src_tmp WHERE ts != _snap_ts; END IF; -- Handle dropped GUC WITH src AS ( SELECT * FROM public.pg_track_settings_settings_src(_srvid) ), dropped AS ( SELECT s.ts, l.srvid, l.name FROM public.pg_track_settings_list l LEFT JOIN src s ON s.name = l.name WHERE l.srvid = _srvid AND s.name IS NULL ), mark_dropped AS ( INSERT INTO public.pg_track_settings_history (srvid, ts, name, setting, setting_pretty, is_dropped) SELECT srvid, COALESCE(_snap_ts, now()), name, NULL, NULL, true FROM dropped ) DELETE FROM public.pg_track_settings_list l USING dropped d WHERE d.name = l.name AND d.srvid = l.srvid AND l.srvid = _srvid; -- Insert missing settings INSERT INTO public.pg_track_settings_list (srvid, name) SELECT _srvid, name FROM public.pg_track_settings_settings_src(_srvid) s WHERE NOT EXISTS (SELECT 1 FROM public.pg_track_settings_list l WHERE l.srvid = _srvid AND l.name = s.name ); -- Detect changed GUC, insert new vals WITH src AS ( SELECT * FROM public.pg_track_settings_settings_src(_srvid) ), last_snapshot AS ( SELECT srvid, name, setting FROM ( SELECT srvid, name, setting, row_number() OVER (PARTITION BY NAME ORDER BY ts DESC) AS rownum FROM public.pg_track_settings_history h WHERE h.srvid = _srvid ) all_snapshots WHERE rownum = 1 ) INSERT INTO public.pg_track_settings_history (srvid, ts, name, setting, setting_pretty) SELECT _srvid, s.ts, s.name, s.setting, s.current_setting FROM src s LEFT JOIN last_snapshot l ON l.name = s.name WHERE ( l.name IS NULL OR l.setting IS DISTINCT FROM s.setting ); IF (_srvid != 0) THEN DELETE FROM public.pg_track_settings_settings_src_tmp WHERE srvid = _srvid; END IF; RETURN true; END; $_$ LANGUAGE plpgsql; /* end of pg_track_settings_snapshot_settings() */ CREATE OR REPLACE FUNCTION pg_track_settings_snapshot_rds(_srvid integer) RETURNS boolean AS $_$ DECLARE _snap_ts timestamp with time zone; BEGIN SELECT max(ts) INTO _snap_ts FROM public.pg_track_settings_rds_src(_srvid); -- this function should have been called for previously saved data. If -- not, probably somethig went wrong, so discard those data IF (_srvid != 0) THEN DELETE FROM public.pg_track_settings_rds_src_tmp WHERE ts != _snap_ts; END IF; -- Handle dropped db_role_setting WITH rds AS ( SELECT * FROM public.pg_track_settings_rds_src(_srvid) ), dropped AS ( SELECT s.ts, l.setdatabase, l.setrole, l.name FROM public.pg_track_db_role_settings_list l LEFT JOIN rds s ON ( s.setdatabase = l.setdatabase AND s.setrole = l.setrole AND s.name = l.name ) WHERE l.srvid = _srvid AND s.setdatabase IS NULL AND s.setrole IS NULL AND s.name IS NULL ), mark_dropped AS ( INSERT INTO public.pg_track_db_role_settings_history (srvid, ts, setdatabase, setrole, name, setting, is_dropped) SELECT _srvid, ts, d.setdatabase, d.setrole, d.name, NULL, true FROM dropped AS d ) DELETE FROM public.pg_track_db_role_settings_list l USING dropped d WHERE l.srvid = _srvid AND d.setdatabase = l.setdatabase AND d.setrole = l.setrole AND d.name = l.name; -- Insert missing settings WITH rds AS ( SELECT * FROM public.pg_track_settings_rds_src(_srvid) ) INSERT INTO public.pg_track_db_role_settings_list (srvid, setdatabase, setrole, name) SELECT _srvid, setdatabase, setrole, name FROM rds s WHERE NOT EXISTS (SELECT 1 FROM public.pg_track_db_role_settings_list l WHERE l.srvid = _srvid AND l.setdatabase = s.setdatabase AND l.setrole = l.setrole AND l.name = s.name ); -- Detect changed GUC, insert new vals WITH rds AS ( SELECT * FROM public.pg_track_settings_rds_src(_srvid) ), last_snapshot AS ( SELECT setdatabase, setrole, name, setting FROM ( SELECT setdatabase, setrole, name, setting, row_number() OVER (PARTITION BY name, setdatabase, setrole ORDER BY ts DESC) AS rownum FROM public.pg_track_db_role_settings_history WHERE srvid = _srvid ) all_snapshots WHERE rownum = 1 ) INSERT INTO public.pg_track_db_role_settings_history (srvid, ts, setdatabase, setrole, name, setting) SELECT _srvid, s.ts, s.setdatabase, s.setrole, s.name, s.setting FROM rds s LEFT JOIN last_snapshot l ON l.setdatabase = s.setdatabase AND l.setrole = s.setrole AND l.name = s.name WHERE (l.setdatabase IS NULL AND l.setrole IS NULL AND l.name IS NULL) OR (l.setting IS DISTINCT FROM s.setting); IF (_srvid != 0) THEN DELETE FROM public.pg_track_settings_rds_src_tmp WHERE srvid = _srvid; END IF; RETURN true; END; $_$ LANGUAGE plpgsql; /* end of pg_track_settings_snapshot_rds() */ CREATE OR REPLACE FUNCTION public.pg_track_settings_snapshot_reboot(_srvid integer) RETURNS boolean AS $_$ BEGIN -- Detect is postmaster restarted since last call WITH last_reboot AS ( SELECT t.postmaster_ts FROM public.pg_track_settings_reboot_src(_srvid) t ) INSERT INTO public.pg_reboot (srvid, ts) SELECT _srvid, lr.postmaster_ts FROM last_reboot lr WHERE NOT EXISTS (SELECT 1 FROM public.pg_reboot r WHERE r.srvid = _srvid AND r.ts = lr.postmaster_ts AND r.srvid = _srvid ); IF (_srvid != 0) THEN DELETE FROM public.pg_track_settings_reboot_src_tmp WHERE srvid = _srvid; END IF; RETURN true; END; $_$ LANGUAGE plpgsql; /* end of pg_track_settings_snapshot_reboot() */ -- global function doing all the work for local instance, kept for backward -- compatibility CREATE OR REPLACE FUNCTION pg_track_settings_snapshot() RETURNS boolean AS $_$ BEGIN PERFORM public.pg_track_settings_snapshot_settings(0); PERFORM public.pg_track_settings_snapshot_rds(0); PERFORM public.pg_track_settings_snapshot_reboot(0); RETURN true; END; $_$ LANGUAGE plpgsql; /* end of pg_track_settings_snapshot() */ CREATE OR REPLACE FUNCTION public.pg_track_settings( _ts timestamp with time zone DEFAULT now(), _srvid integer DEFAULT 0) RETURNS TABLE (name text, setting text, setting_pretty text) AS $_$ BEGIN RETURN QUERY SELECT s.name, s.setting, s.setting_pretty FROM ( SELECT h.name, h.setting, h.setting_pretty, h.is_dropped, row_number() OVER (PARTITION BY h.name ORDER BY h.ts DESC) AS rownum FROM public.pg_track_settings_history h WHERE h.srvid = _srvid AND h.ts <= _ts ) s WHERE s.rownum = 1 AND NOT s.is_dropped ORDER BY s.name; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION public.pg_track_db_role_settings( _ts timestamp with time zone DEFAULT now(), _srvid integer DEFAULT 0) RETURNS TABLE (setdatabase oid, setrole oid, name text, setting text) AS $_$ BEGIN RETURN QUERY SELECT s.setdatabase, s.setrole, s.name, s.setting FROM ( SELECT h.setdatabase, h.setrole, h.name, h.setting, h.is_dropped, row_number() OVER (PARTITION BY h.name, h.setdatabase, h.setrole ORDER BY h.ts DESC) AS rownum FROM public.pg_track_db_role_settings_history h WHERE h.srvid = _srvid AND h.ts <= _ts ) s WHERE s.rownum = 1 AND NOT s.is_dropped ORDER BY s.setdatabase, s.setrole, s.name; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION public.pg_track_settings_diff( _from timestamp with time zone, _to timestamp with time zone, _srvid integer DEFAULT 0) RETURNS TABLE (name text, from_setting text, from_exists boolean, to_setting text, to_exists boolean, from_setting_pretty text, to_setting_pretty text) AS $_$ BEGIN RETURN QUERY SELECT COALESCE(s1.name, s2.name), s1.setting AS from_setting, CASE WHEN s1.setting IS NULL THEN false ELSE true END, s2.setting AS to_setting, CASE WHEN s2.setting IS NULL THEN false ELSE true END, s1.setting_pretty AS from_setting_pretty, s2.setting_pretty AS to_setting_pretty FROM public.pg_track_settings(_from, _srvid) s1 FULL OUTER JOIN public.pg_track_settings(_to, _srvid) s2 ON s2.name = s1.name WHERE s1.setting IS DISTINCT FROM s2.setting ORDER BY 1; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION public.pg_track_db_role_settings_diff( _from timestamp with time zone, _to timestamp with time zone, _srvid integer DEFAULT 0) RETURNS TABLE (setdatabase oid, setrole oid, name text, from_setting text, from_exists boolean, to_setting text, to_exists boolean) AS $_$ BEGIN RETURN QUERY SELECT COALESCE(s1.setdatabase, s2.setdatabase), COALESCE(s1.setrole, s2.setrole), COALESCE(s1.name, s2.name), s1.setting AS from_setting, CASE WHEN s1.setting IS NULL THEN false ELSE true END, s2.setting AS to_setting, CASE WHEN s2.setting IS NULL THEN false ELSE true END FROM public.pg_track_db_role_settings(_from, _srvid) s1 FULL OUTER JOIN public.pg_track_db_role_settings(_to, _srvid) s2 ON s2.setdatabase = s1.setdatabase AND s2.setrole = s1.setrole AND s2.name = s1.name WHERE s1.setdatabase IS DISTINCT FROM s2.setdatabase AND s1.setrole IS DISTINCT FROM s2.setrole AND s1.setting IS DISTINCT FROM s2.setting ORDER BY 1, 2, 3; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pg_track_settings_log( _name text, _srvid integer DEFAULT 0) RETURNS TABLE (ts timestamp with time zone, name text, setting_exists boolean, setting text, setting_pretty text) AS $_$ BEGIN RETURN QUERY SELECT h.ts, h.name, NOT h.is_dropped, h.setting, h.setting_pretty FROM public.pg_track_settings_history h WHERE h.srvid = _srvid AND h.name = _name ORDER BY ts DESC; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pg_track_db_role_settings_log( _name text, _srvid integer DEFAULT 0) RETURNS TABLE (ts timestamp with time zone, setdatabase oid, setrole oid, name text, setting_exists boolean, setting text) AS $_$ BEGIN RETURN QUERY SELECT h.ts, h.setdatabase, h.setrole, h.name, NOT h.is_dropped, h.setting FROM public.pg_track_db_role_settings_history h WHERE h.srvid = _srvid AND h.name = _name ORDER BY ts, setdatabase, setrole DESC; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pg_track_reboot_log(_srvid integer DEFAULT 0) RETURNS TABLE (ts timestamp with time zone) AS $_$ BEGIN RETURN QUERY SELECT r.ts FROM public.pg_reboot r WHERE r.srvid = _srvid ORDER BY r.ts; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION public.pg_track_settings_reset(_srvid integer DEFAULT 0) RETURNS void AS $_$ BEGIN DELETE FROM public.pg_track_settings_settings_src_tmp WHERE srvid = _srvid; DELETE FROM public.pg_track_settings_rds_src_tmp WHERE srvid = _srvid; DELETE FROM public.pg_track_settings_reboot_src_tmp WHERE srvid = _srvid; DELETE FROM public.pg_track_settings_list WHERE srvid = _srvid; DELETE FROM public.pg_track_settings_history WHERE srvid = _srvid; DELETE FROM public.pg_track_db_role_settings_list WHERE srvid = _srvid; DELETE FROM public.pg_track_db_role_settings_history WHERE srvid = _srvid; DELETE FROM public.pg_reboot WHERE srvid = _srvid; END; $_$ LANGUAGE plpgsql; pg_track_settings-2.1.2/pg_track_settings--2.0.1--2.1.0.sql000066400000000000000000000172011442764062500227160ustar00rootroot00000000000000-- This program is open source, licensed under the PostgreSQL License. -- For license terms, see the LICENSE file. -- -- Copyright (C) 2015-2022: Julien Rouhaud -- complain if script is sourced in psql, rather than via ALTER EXTENSION \echo Use "ALTER EXTENSION pg_track_settings" to load this file. \quit SET LOCAL client_encoding = 'UTF8'; CREATE OR REPLACE FUNCTION @extschema@.pg_track_settings_snapshot_settings(_srvid integer) RETURNS boolean AS $_$ DECLARE _snap_ts timestamp with time zone = NULL; BEGIN SELECT max(ts) INTO _snap_ts FROM @extschema@.pg_track_settings_settings_src(_srvid); -- this function should have been called for previously saved data. If -- not, probably somethig went wrong, so discard those data IF (_srvid != 0) THEN DELETE FROM @extschema@.pg_track_settings_settings_src_tmp WHERE ts != _snap_ts; END IF; -- Handle dropped GUC WITH src AS ( SELECT * FROM @extschema@.pg_track_settings_settings_src(_srvid) ), dropped AS ( SELECT s.ts, l.srvid, l.name FROM @extschema@.pg_track_settings_list l LEFT JOIN src s ON s.name = l.name WHERE l.srvid = _srvid AND s.name IS NULL ), mark_dropped AS ( INSERT INTO @extschema@.pg_track_settings_history (srvid, ts, name, setting, setting_pretty, is_dropped) SELECT srvid, COALESCE(_snap_ts, now()), name, NULL, NULL, true FROM dropped ) DELETE FROM @extschema@.pg_track_settings_list l USING dropped d WHERE d.name = l.name AND d.srvid = l.srvid AND l.srvid = _srvid; -- Insert missing settings INSERT INTO @extschema@.pg_track_settings_list (srvid, name) SELECT _srvid, name FROM @extschema@.pg_track_settings_settings_src(_srvid) s WHERE NOT EXISTS (SELECT 1 FROM @extschema@.pg_track_settings_list l WHERE l.srvid = _srvid AND l.name = s.name ); -- Detect changed GUC, insert new vals WITH src AS ( SELECT * FROM @extschema@.pg_track_settings_settings_src(_srvid) ), last_snapshot AS ( SELECT srvid, name, setting FROM ( SELECT srvid, name, setting, row_number() OVER (PARTITION BY NAME ORDER BY ts DESC) AS rn FROM @extschema@.pg_track_settings_history h WHERE h.srvid = _srvid ) all_snapshots WHERE all_snapshots.rn = 1 ) INSERT INTO @extschema@.pg_track_settings_history (srvid, ts, name, setting, setting_pretty) SELECT _srvid, s.ts, s.name, s.setting, s.current_setting FROM src s LEFT JOIN last_snapshot l ON l.name = s.name WHERE ( l.name IS NULL OR l.setting IS DISTINCT FROM s.setting ); IF (_srvid != 0) THEN DELETE FROM @extschema@.pg_track_settings_settings_src_tmp WHERE srvid = _srvid; END IF; RETURN true; END; $_$ LANGUAGE plpgsql; /* end of pg_track_settings_snapshot_settings() */ CREATE OR REPLACE FUNCTION @extschema@.pg_track_settings_snapshot_rds(_srvid integer) RETURNS boolean AS $_$ DECLARE _snap_ts timestamp with time zone; BEGIN SELECT max(ts) INTO _snap_ts FROM @extschema@.pg_track_settings_rds_src(_srvid); -- this function should have been called for previously saved data. If -- not, probably somethig went wrong, so discard those data IF (_srvid != 0) THEN DELETE FROM @extschema@.pg_track_settings_rds_src_tmp WHERE ts != _snap_ts; END IF; -- Handle dropped db_role_setting WITH rds AS ( SELECT * FROM @extschema@.pg_track_settings_rds_src(_srvid) ), dropped AS ( SELECT _snap_ts AS ts, l.setdatabase, l.setrole, l.name FROM @extschema@.pg_track_db_role_settings_list l LEFT JOIN rds s ON ( s.setdatabase = l.setdatabase AND s.setrole = l.setrole AND s.name = l.name ) WHERE l.srvid = _srvid AND s.setdatabase IS NULL AND s.setrole IS NULL AND s.name IS NULL ), mark_dropped AS ( INSERT INTO @extschema@.pg_track_db_role_settings_history (srvid, ts, setdatabase, setrole, name, setting, is_dropped) SELECT _srvid, ts, d.setdatabase, d.setrole, d.name, NULL, true FROM dropped AS d ) DELETE FROM @extschema@.pg_track_db_role_settings_list l USING dropped d WHERE l.srvid = _srvid AND d.setdatabase = l.setdatabase AND d.setrole = l.setrole AND d.name = l.name; -- Insert missing settings WITH rds AS ( SELECT * FROM @extschema@.pg_track_settings_rds_src(_srvid) ) INSERT INTO @extschema@.pg_track_db_role_settings_list (srvid, setdatabase, setrole, name) SELECT _srvid, setdatabase, setrole, name FROM rds s WHERE NOT EXISTS (SELECT 1 FROM @extschema@.pg_track_db_role_settings_list l WHERE l.srvid = _srvid AND l.setdatabase = s.setdatabase AND l.setrole = l.setrole AND l.name = s.name ); -- Detect changed GUC, insert new vals WITH rds AS ( SELECT * FROM @extschema@.pg_track_settings_rds_src(_srvid) ), last_snapshot AS ( SELECT setdatabase, setrole, name, setting FROM ( SELECT setdatabase, setrole, name, setting, row_number() OVER (PARTITION BY name, setdatabase, setrole ORDER BY ts DESC) AS rn FROM @extschema@.pg_track_db_role_settings_history WHERE srvid = _srvid ) all_snapshots WHERE all_snapshots.rn = 1 ) INSERT INTO @extschema@.pg_track_db_role_settings_history (srvid, ts, setdatabase, setrole, name, setting) SELECT _srvid, s.ts, s.setdatabase, s.setrole, s.name, s.setting FROM rds s LEFT JOIN last_snapshot l ON l.setdatabase = s.setdatabase AND l.setrole = s.setrole AND l.name = s.name WHERE (l.setdatabase IS NULL AND l.setrole IS NULL AND l.name IS NULL) OR (l.setting IS DISTINCT FROM s.setting); IF (_srvid != 0) THEN DELETE FROM @extschema@.pg_track_settings_rds_src_tmp WHERE srvid = _srvid; END IF; RETURN true; END; $_$ LANGUAGE plpgsql; /* end of pg_track_settings_snapshot_rds() */ CREATE OR REPLACE FUNCTION @extschema@.pg_track_settings( _ts timestamp with time zone DEFAULT now(), _srvid integer DEFAULT 0) RETURNS TABLE (name text, setting text, setting_pretty text) AS $_$ BEGIN RETURN QUERY SELECT s.name, s.setting, s.setting_pretty FROM ( SELECT h.name, h.setting, h.setting_pretty, h.is_dropped, row_number() OVER (PARTITION BY h.name ORDER BY h.ts DESC) AS rn FROM @extschema@.pg_track_settings_history h WHERE h.srvid = _srvid AND h.ts <= _ts ) s WHERE s.rn = 1 AND NOT s.is_dropped ORDER BY s.name; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION @extschema@.pg_track_db_role_settings( _ts timestamp with time zone DEFAULT now(), _srvid integer DEFAULT 0) RETURNS TABLE (setdatabase oid, setrole oid, name text, setting text) AS $_$ BEGIN RETURN QUERY SELECT s.setdatabase, s.setrole, s.name, s.setting FROM ( SELECT h.setdatabase, h.setrole, h.name, h.setting, h.is_dropped, row_number() OVER (PARTITION BY h.name, h.setdatabase, h.setrole ORDER BY h.ts DESC) AS rn FROM @extschema@.pg_track_db_role_settings_history h WHERE h.srvid = _srvid AND h.ts <= _ts ) s WHERE s.rn = 1 AND NOT s.is_dropped ORDER BY s.setdatabase, s.setrole, s.name; END; $_$ LANGUAGE plpgsql; pg_track_settings-2.1.2/pg_track_settings--2.0.1.sql000066400000000000000000000415221442764062500222100ustar00rootroot00000000000000-- This program is open source, licensed under the PostgreSQL License. -- For license terms, see the LICENSE file. -- -- Copyright (C) 2015-2022: Julien Rouhaud -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pg_track_settings" to load this file. \quit SET client_encoding = 'UTF8'; CREATE UNLOGGED TABLE public.pg_track_settings_settings_src_tmp ( srvid integer NOT NULL, ts timestamp with time zone NOT NULL, name text NOT NULL, setting text, current_setting text ); -- no need to backup this table CREATE TABLE public.pg_track_settings_list ( srvid integer NOT NULL, name text, PRIMARY KEY (srvid, name) ); SELECT pg_catalog.pg_extension_config_dump('public.pg_track_settings_list', ''); CREATE TABLE public.pg_track_settings_history ( srvid integer NOT NULL, ts timestamp with time zone, name text NOT NULL, setting text, is_dropped boolean NOT NULL DEFAULT false, setting_pretty text, PRIMARY KEY(srvid, ts, name) ); SELECT pg_catalog.pg_extension_config_dump('public.pg_track_settings_history', ''); CREATE UNLOGGED TABLE public.pg_track_settings_rds_src_tmp ( srvid integer NOT NULL, ts timestamp with time zone NOT NULL, name text NOT NULL, setting text, setdatabase oid NOT NULL, setrole oid NOT NULL ); -- no need to backup this table CREATE TABLE public.pg_track_db_role_settings_list ( srvid integer, name text, setdatabase oid, setrole oid, PRIMARY KEY (srvid, name, setdatabase, setrole) ); SELECT pg_catalog.pg_extension_config_dump('public.pg_track_db_role_settings_list', ''); CREATE TABLE public.pg_track_db_role_settings_history ( srvid INTEGER NOT NULL, ts timestamp with time zone, name text, setdatabase oid, setrole oid, setting text, is_dropped boolean NOT NULL DEFAULT false, PRIMARY KEY(srvid, ts, name, setdatabase, setrole) ); SELECT pg_catalog.pg_extension_config_dump('public.pg_track_db_role_settings_history', ''); CREATE UNLOGGED TABLE public.pg_track_settings_reboot_src_tmp ( srvid integer NOT NULL, ts timestamp with time zone NOT NULL, postmaster_ts timestamp with time zone NOT NULL ); -- no need to backup this table CREATE TABLE public.pg_reboot ( srvid integer NOT NULL, ts timestamp with time zone, PRIMARY KEY (srvid, ts) ); SELECT pg_catalog.pg_extension_config_dump('public.pg_reboot', ''); ---------------------- -- source functions -- ---------------------- CREATE OR REPLACE FUNCTION pg_track_settings_settings_src ( IN _srvid integer, OUT ts timestamp with time zone, OUT name text, OUT setting text, OUT current_setting text ) RETURNS SETOF record AS $PROC$ BEGIN IF (_srvid = 0) THEN RETURN QUERY SELECT now(), s.name, s.setting, pg_catalog.current_setting(s.name) FROM pg_catalog.pg_settings s; ELSE RETURN QUERY SELECT s.ts, s.name, s.setting, s.current_setting FROM public.pg_track_settings_settings_src_tmp s; END IF; END; $PROC$ LANGUAGE plpgsql; /* end of pg_track_settings_settings_src */ CREATE OR REPLACE FUNCTION pg_track_settings_rds_src ( IN _srvid integer, OUT ts timestamp with time zone, OUT name text, OUT setting text, OUT setdatabase oid, OUT setrole oid ) RETURNS SETOF record AS $PROC$ BEGIN IF (_srvid = 0) THEN RETURN QUERY SELECT now(), (regexp_split_to_array(unnest(s.setconfig),'=')::text[])[1] AS name, (regexp_split_to_array(unnest(s.setconfig),'=')::text[])[2] AS setting, s.setdatabase, s.setrole FROM pg_catalog.pg_db_role_setting s; ELSE RETURN QUERY SELECT s.ts, s.name, s.setting, s.setdatabase, s.setrole FROM public.pg_track_settings_rds_src_tmp s; END IF; END; $PROC$ LANGUAGE plpgsql; /* end of pg_track_settings_rds_src */ CREATE OR REPLACE FUNCTION pg_track_settings_reboot_src ( IN _srvid integer, OUT ts timestamp with time zone, OUT postmaster_ts timestamp with time zone ) RETURNS SETOF record AS $PROC$ BEGIN IF (_srvid = 0) THEN RETURN QUERY SELECT now(), pg_postmaster_start_time(); ELSE RETURN QUERY SELECT s.ts, s.postmaster_ts FROM public.pg_track_settings_reboot_src_tmp s; END IF; END; $PROC$ LANGUAGE plpgsql; /* end of pg_track_settings_reboot_src */ ------------------------ -- snapshot functions -- ------------------------ CREATE OR REPLACE FUNCTION pg_track_settings_snapshot_settings(_srvid integer) RETURNS boolean AS $_$ DECLARE _snap_ts timestamp with time zone = NULL; BEGIN SELECT max(ts) INTO _snap_ts FROM public.pg_track_settings_settings_src(_srvid); -- this function should have been called for previously saved data. If -- not, probably somethig went wrong, so discard those data IF (_srvid != 0) THEN DELETE FROM public.pg_track_settings_settings_src_tmp WHERE ts != _snap_ts; END IF; -- Handle dropped GUC WITH src AS ( SELECT * FROM public.pg_track_settings_settings_src(_srvid) ), dropped AS ( SELECT s.ts, l.srvid, l.name FROM public.pg_track_settings_list l LEFT JOIN src s ON s.name = l.name WHERE l.srvid = _srvid AND s.name IS NULL ), mark_dropped AS ( INSERT INTO public.pg_track_settings_history (srvid, ts, name, setting, setting_pretty, is_dropped) SELECT srvid, COALESCE(_snap_ts, now()), name, NULL, NULL, true FROM dropped ) DELETE FROM public.pg_track_settings_list l USING dropped d WHERE d.name = l.name AND d.srvid = l.srvid AND l.srvid = _srvid; -- Insert missing settings INSERT INTO public.pg_track_settings_list (srvid, name) SELECT _srvid, name FROM public.pg_track_settings_settings_src(_srvid) s WHERE NOT EXISTS (SELECT 1 FROM public.pg_track_settings_list l WHERE l.srvid = _srvid AND l.name = s.name ); -- Detect changed GUC, insert new vals WITH src AS ( SELECT * FROM public.pg_track_settings_settings_src(_srvid) ), last_snapshot AS ( SELECT srvid, name, setting FROM ( SELECT srvid, name, setting, row_number() OVER (PARTITION BY NAME ORDER BY ts DESC) AS rownum FROM public.pg_track_settings_history h WHERE h.srvid = _srvid ) all_snapshots WHERE rownum = 1 ) INSERT INTO public.pg_track_settings_history (srvid, ts, name, setting, setting_pretty) SELECT _srvid, s.ts, s.name, s.setting, s.current_setting FROM src s LEFT JOIN last_snapshot l ON l.name = s.name WHERE ( l.name IS NULL OR l.setting IS DISTINCT FROM s.setting ); IF (_srvid != 0) THEN DELETE FROM public.pg_track_settings_settings_src_tmp WHERE srvid = _srvid; END IF; RETURN true; END; $_$ LANGUAGE plpgsql; /* end of pg_track_settings_snapshot_settings() */ CREATE OR REPLACE FUNCTION pg_track_settings_snapshot_rds(_srvid integer) RETURNS boolean AS $_$ DECLARE _snap_ts timestamp with time zone; BEGIN SELECT max(ts) INTO _snap_ts FROM public.pg_track_settings_rds_src(_srvid); -- this function should have been called for previously saved data. If -- not, probably somethig went wrong, so discard those data IF (_srvid != 0) THEN DELETE FROM public.pg_track_settings_rds_src_tmp WHERE ts != _snap_ts; END IF; -- Handle dropped db_role_setting WITH rds AS ( SELECT * FROM public.pg_track_settings_rds_src(_srvid) ), dropped AS ( SELECT _snap_ts AS ts, l.setdatabase, l.setrole, l.name FROM public.pg_track_db_role_settings_list l LEFT JOIN rds s ON ( s.setdatabase = l.setdatabase AND s.setrole = l.setrole AND s.name = l.name ) WHERE l.srvid = _srvid AND s.setdatabase IS NULL AND s.setrole IS NULL AND s.name IS NULL ), mark_dropped AS ( INSERT INTO public.pg_track_db_role_settings_history (srvid, ts, setdatabase, setrole, name, setting, is_dropped) SELECT _srvid, ts, d.setdatabase, d.setrole, d.name, NULL, true FROM dropped AS d ) DELETE FROM public.pg_track_db_role_settings_list l USING dropped d WHERE l.srvid = _srvid AND d.setdatabase = l.setdatabase AND d.setrole = l.setrole AND d.name = l.name; -- Insert missing settings WITH rds AS ( SELECT * FROM public.pg_track_settings_rds_src(_srvid) ) INSERT INTO public.pg_track_db_role_settings_list (srvid, setdatabase, setrole, name) SELECT _srvid, setdatabase, setrole, name FROM rds s WHERE NOT EXISTS (SELECT 1 FROM public.pg_track_db_role_settings_list l WHERE l.srvid = _srvid AND l.setdatabase = s.setdatabase AND l.setrole = l.setrole AND l.name = s.name ); -- Detect changed GUC, insert new vals WITH rds AS ( SELECT * FROM public.pg_track_settings_rds_src(_srvid) ), last_snapshot AS ( SELECT setdatabase, setrole, name, setting FROM ( SELECT setdatabase, setrole, name, setting, row_number() OVER (PARTITION BY name, setdatabase, setrole ORDER BY ts DESC) AS rownum FROM public.pg_track_db_role_settings_history WHERE srvid = _srvid ) all_snapshots WHERE rownum = 1 ) INSERT INTO public.pg_track_db_role_settings_history (srvid, ts, setdatabase, setrole, name, setting) SELECT _srvid, s.ts, s.setdatabase, s.setrole, s.name, s.setting FROM rds s LEFT JOIN last_snapshot l ON l.setdatabase = s.setdatabase AND l.setrole = s.setrole AND l.name = s.name WHERE (l.setdatabase IS NULL AND l.setrole IS NULL AND l.name IS NULL) OR (l.setting IS DISTINCT FROM s.setting); IF (_srvid != 0) THEN DELETE FROM public.pg_track_settings_rds_src_tmp WHERE srvid = _srvid; END IF; RETURN true; END; $_$ LANGUAGE plpgsql; /* end of pg_track_settings_snapshot_rds() */ CREATE OR REPLACE FUNCTION public.pg_track_settings_snapshot_reboot(_srvid integer) RETURNS boolean AS $_$ BEGIN -- Detect is postmaster restarted since last call WITH last_reboot AS ( SELECT t.postmaster_ts FROM public.pg_track_settings_reboot_src(_srvid) t ) INSERT INTO public.pg_reboot (srvid, ts) SELECT _srvid, lr.postmaster_ts FROM last_reboot lr WHERE NOT EXISTS (SELECT 1 FROM public.pg_reboot r WHERE r.srvid = _srvid AND r.ts = lr.postmaster_ts AND r.srvid = _srvid ); IF (_srvid != 0) THEN DELETE FROM public.pg_track_settings_reboot_src_tmp WHERE srvid = _srvid; END IF; RETURN true; END; $_$ LANGUAGE plpgsql; /* end of pg_track_settings_snapshot_reboot() */ -- global function doing all the work for local instance, kept for backward -- compatibility CREATE OR REPLACE FUNCTION pg_track_settings_snapshot() RETURNS boolean AS $_$ BEGIN PERFORM public.pg_track_settings_snapshot_settings(0); PERFORM public.pg_track_settings_snapshot_rds(0); PERFORM public.pg_track_settings_snapshot_reboot(0); RETURN true; END; $_$ LANGUAGE plpgsql; /* end of pg_track_settings_snapshot() */ CREATE OR REPLACE FUNCTION public.pg_track_settings( _ts timestamp with time zone DEFAULT now(), _srvid integer DEFAULT 0) RETURNS TABLE (name text, setting text, setting_pretty text) AS $_$ BEGIN RETURN QUERY SELECT s.name, s.setting, s.setting_pretty FROM ( SELECT h.name, h.setting, h.setting_pretty, h.is_dropped, row_number() OVER (PARTITION BY h.name ORDER BY h.ts DESC) AS rownum FROM public.pg_track_settings_history h WHERE h.srvid = _srvid AND h.ts <= _ts ) s WHERE s.rownum = 1 AND NOT s.is_dropped ORDER BY s.name; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION public.pg_track_db_role_settings( _ts timestamp with time zone DEFAULT now(), _srvid integer DEFAULT 0) RETURNS TABLE (setdatabase oid, setrole oid, name text, setting text) AS $_$ BEGIN RETURN QUERY SELECT s.setdatabase, s.setrole, s.name, s.setting FROM ( SELECT h.setdatabase, h.setrole, h.name, h.setting, h.is_dropped, row_number() OVER (PARTITION BY h.name, h.setdatabase, h.setrole ORDER BY h.ts DESC) AS rownum FROM public.pg_track_db_role_settings_history h WHERE h.srvid = _srvid AND h.ts <= _ts ) s WHERE s.rownum = 1 AND NOT s.is_dropped ORDER BY s.setdatabase, s.setrole, s.name; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION public.pg_track_settings_diff( _from timestamp with time zone, _to timestamp with time zone, _srvid integer DEFAULT 0) RETURNS TABLE (name text, from_setting text, from_exists boolean, to_setting text, to_exists boolean, from_setting_pretty text, to_setting_pretty text) AS $_$ BEGIN RETURN QUERY SELECT COALESCE(s1.name, s2.name), s1.setting AS from_setting, CASE WHEN s1.setting IS NULL THEN false ELSE true END, s2.setting AS to_setting, CASE WHEN s2.setting IS NULL THEN false ELSE true END, s1.setting_pretty AS from_setting_pretty, s2.setting_pretty AS to_setting_pretty FROM public.pg_track_settings(_from, _srvid) s1 FULL OUTER JOIN public.pg_track_settings(_to, _srvid) s2 ON s2.name = s1.name WHERE s1.setting IS DISTINCT FROM s2.setting ORDER BY 1; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION public.pg_track_db_role_settings_diff( _from timestamp with time zone, _to timestamp with time zone, _srvid integer DEFAULT 0) RETURNS TABLE (setdatabase oid, setrole oid, name text, from_setting text, from_exists boolean, to_setting text, to_exists boolean) AS $_$ BEGIN RETURN QUERY SELECT COALESCE(s1.setdatabase, s2.setdatabase), COALESCE(s1.setrole, s2.setrole), COALESCE(s1.name, s2.name), s1.setting AS from_setting, CASE WHEN s1.setting IS NULL THEN false ELSE true END, s2.setting AS to_setting, CASE WHEN s2.setting IS NULL THEN false ELSE true END FROM public.pg_track_db_role_settings(_from, _srvid) s1 FULL OUTER JOIN public.pg_track_db_role_settings(_to, _srvid) s2 ON s2.setdatabase = s1.setdatabase AND s2.setrole = s1.setrole AND s2.name = s1.name WHERE s1.setdatabase IS DISTINCT FROM s2.setdatabase AND s1.setrole IS DISTINCT FROM s2.setrole AND s1.setting IS DISTINCT FROM s2.setting ORDER BY 1, 2, 3; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pg_track_settings_log( _name text, _srvid integer DEFAULT 0) RETURNS TABLE (ts timestamp with time zone, name text, setting_exists boolean, setting text, setting_pretty text) AS $_$ BEGIN RETURN QUERY SELECT h.ts, h.name, NOT h.is_dropped, h.setting, h.setting_pretty FROM public.pg_track_settings_history h WHERE h.srvid = _srvid AND h.name = _name ORDER BY ts DESC; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pg_track_db_role_settings_log( _name text, _srvid integer DEFAULT 0) RETURNS TABLE (ts timestamp with time zone, setdatabase oid, setrole oid, name text, setting_exists boolean, setting text) AS $_$ BEGIN RETURN QUERY SELECT h.ts, h.setdatabase, h.setrole, h.name, NOT h.is_dropped, h.setting FROM public.pg_track_db_role_settings_history h WHERE h.srvid = _srvid AND h.name = _name ORDER BY ts, setdatabase, setrole DESC; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION pg_track_reboot_log(_srvid integer DEFAULT 0) RETURNS TABLE (ts timestamp with time zone) AS $_$ BEGIN RETURN QUERY SELECT r.ts FROM public.pg_reboot r WHERE r.srvid = _srvid ORDER BY r.ts; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION public.pg_track_settings_reset(_srvid integer DEFAULT 0) RETURNS void AS $_$ BEGIN DELETE FROM public.pg_track_settings_settings_src_tmp WHERE srvid = _srvid; DELETE FROM public.pg_track_settings_rds_src_tmp WHERE srvid = _srvid; DELETE FROM public.pg_track_settings_reboot_src_tmp WHERE srvid = _srvid; DELETE FROM public.pg_track_settings_list WHERE srvid = _srvid; DELETE FROM public.pg_track_settings_history WHERE srvid = _srvid; DELETE FROM public.pg_track_db_role_settings_list WHERE srvid = _srvid; DELETE FROM public.pg_track_db_role_settings_history WHERE srvid = _srvid; DELETE FROM public.pg_reboot WHERE srvid = _srvid; END; $_$ LANGUAGE plpgsql; pg_track_settings-2.1.2/pg_track_settings--2.1.0--2.1.2.sql000066400000000000000000000211621442764062500227210ustar00rootroot00000000000000-- This program is open source, licensed under the PostgreSQL License. -- For license terms, see the LICENSE file. -- -- Copyright (C) 2015-2023: Julien Rouhaud -- complain if script is sourced in psql, rather than via ALTER EXTENSION \echo Use "ALTER EXTENSION pg_track_settings" to load this file. \quit CREATE OR REPLACE FUNCTION @extschema@.pg_track_settings_settings_src ( IN _srvid integer, OUT ts timestamp with time zone, OUT name text, OUT setting text, OUT current_setting text ) RETURNS SETOF record AS $PROC$ BEGIN IF (_srvid = 0) THEN RETURN QUERY SELECT now(), s.name, s.setting, pg_catalog.current_setting(s.name) FROM pg_catalog.pg_settings s; ELSE RETURN QUERY SELECT s.ts, s.name, s.setting, s.current_setting FROM @extschema@.pg_track_settings_settings_src_tmp s WHERE srvid = _srvid; END IF; END; $PROC$ LANGUAGE plpgsql; /* end of pg_track_settings_settings_src */ CREATE OR REPLACE FUNCTION @extschema@.pg_track_settings_rds_src ( IN _srvid integer, OUT ts timestamp with time zone, OUT name text, OUT setting text, OUT setdatabase oid, OUT setrole oid ) RETURNS SETOF record AS $PROC$ BEGIN IF (_srvid = 0) THEN RETURN QUERY SELECT now(), (regexp_split_to_array(unnest(s.setconfig),'=')::text[])[1] AS name, (regexp_split_to_array(unnest(s.setconfig),'=')::text[])[2] AS setting, s.setdatabase, s.setrole FROM pg_catalog.pg_db_role_setting s; ELSE RETURN QUERY SELECT s.ts, s.name, s.setting, s.setdatabase, s.setrole FROM @extschema@.pg_track_settings_rds_src_tmp s WHERE srvid = _srvid; END IF; END; $PROC$ LANGUAGE plpgsql; /* end of pg_track_settings_rds_src */ CREATE OR REPLACE FUNCTION @extschema@.pg_track_settings_reboot_src ( IN _srvid integer, OUT ts timestamp with time zone, OUT postmaster_ts timestamp with time zone ) RETURNS SETOF record AS $PROC$ BEGIN IF (_srvid = 0) THEN RETURN QUERY SELECT now(), pg_postmaster_start_time(); ELSE RETURN QUERY SELECT s.ts, s.postmaster_ts FROM @extschema@.pg_track_settings_reboot_src_tmp s WHERE srvid = _srvid; END IF; END; $PROC$ LANGUAGE plpgsql; /* end of pg_track_settings_reboot_src */ CREATE OR REPLACE FUNCTION @extschema@.pg_track_settings_snapshot_settings(_srvid integer) RETURNS boolean AS $_$ DECLARE _snap_ts timestamp with time zone = NULL; BEGIN SELECT max(ts) INTO _snap_ts FROM @extschema@.pg_track_settings_settings_src(_srvid); -- this function should have been called for previously saved data. If -- not, probably somethig went wrong, so discard those data IF (_srvid != 0) THEN DELETE FROM @extschema@.pg_track_settings_settings_src_tmp WHERE ts != _snap_ts AND srvid = _srvid; END IF; -- Handle dropped GUC WITH src AS ( SELECT * FROM @extschema@.pg_track_settings_settings_src(_srvid) ), dropped AS ( SELECT s.ts, l.srvid, l.name FROM @extschema@.pg_track_settings_list l LEFT JOIN src s ON s.name = l.name WHERE l.srvid = _srvid AND s.name IS NULL ), mark_dropped AS ( INSERT INTO @extschema@.pg_track_settings_history (srvid, ts, name, setting, setting_pretty, is_dropped) SELECT srvid, COALESCE(_snap_ts, now()), name, NULL, NULL, true FROM dropped ) DELETE FROM @extschema@.pg_track_settings_list l USING dropped d WHERE d.name = l.name AND d.srvid = l.srvid AND l.srvid = _srvid; -- Insert missing settings INSERT INTO @extschema@.pg_track_settings_list (srvid, name) SELECT _srvid, name FROM @extschema@.pg_track_settings_settings_src(_srvid) s WHERE NOT EXISTS (SELECT 1 FROM @extschema@.pg_track_settings_list l WHERE l.srvid = _srvid AND l.name = s.name ); -- Detect changed GUC, insert new vals WITH src AS ( SELECT * FROM @extschema@.pg_track_settings_settings_src(_srvid) ), last_snapshot AS ( SELECT srvid, name, setting FROM ( SELECT srvid, name, setting, row_number() OVER (PARTITION BY NAME ORDER BY ts DESC) AS rn FROM @extschema@.pg_track_settings_history h WHERE h.srvid = _srvid ) all_snapshots WHERE all_snapshots.rn = 1 ) INSERT INTO @extschema@.pg_track_settings_history (srvid, ts, name, setting, setting_pretty) SELECT _srvid, s.ts, s.name, s.setting, s.current_setting FROM src s LEFT JOIN last_snapshot l ON l.name = s.name WHERE ( l.name IS NULL OR l.setting IS DISTINCT FROM s.setting ); IF (_srvid != 0) THEN DELETE FROM @extschema@.pg_track_settings_settings_src_tmp WHERE srvid = _srvid; END IF; RETURN true; END; $_$ LANGUAGE plpgsql; /* end of pg_track_settings_snapshot_settings() */ CREATE OR REPLACE FUNCTION @extschema@.pg_track_settings_snapshot_rds(_srvid integer) RETURNS boolean AS $_$ DECLARE _snap_ts timestamp with time zone; BEGIN -- If all pg_db_role_setting have been removed, we won't get a snapshot ts -- but we may still have to record that some settings have been removed. -- In that case simply use now(), as that extension doesn't guarantee the -- timestamp to be more precise than the snapshot interval, and there's -- isn't any better timestamp to use anyway. SELECT coalesce(max(ts), now()) INTO _snap_ts FROM @extschema@.pg_track_settings_rds_src(_srvid); -- this function should have been called for previously saved data. If -- not, probably somethig went wrong, so discard those data IF (_srvid != 0) THEN DELETE FROM @extschema@.pg_track_settings_rds_src_tmp WHERE ts != _snap_ts AND srvid = _srvid; END IF; -- Handle dropped db_role_setting WITH rds AS ( SELECT * FROM @extschema@.pg_track_settings_rds_src(_srvid) ), dropped AS ( SELECT _snap_ts AS ts, l.setdatabase, l.setrole, l.name FROM @extschema@.pg_track_db_role_settings_list l LEFT JOIN rds s ON ( s.setdatabase = l.setdatabase AND s.setrole = l.setrole AND s.name = l.name ) WHERE l.srvid = _srvid AND s.setdatabase IS NULL AND s.setrole IS NULL AND s.name IS NULL ), mark_dropped AS ( INSERT INTO @extschema@.pg_track_db_role_settings_history (srvid, ts, setdatabase, setrole, name, setting, is_dropped) SELECT _srvid, ts, d.setdatabase, d.setrole, d.name, NULL, true FROM dropped AS d ) DELETE FROM @extschema@.pg_track_db_role_settings_list l USING dropped d WHERE l.srvid = _srvid AND d.setdatabase = l.setdatabase AND d.setrole = l.setrole AND d.name = l.name; -- Insert missing settings WITH rds AS ( SELECT * FROM @extschema@.pg_track_settings_rds_src(_srvid) ) INSERT INTO @extschema@.pg_track_db_role_settings_list (srvid, setdatabase, setrole, name) SELECT _srvid, setdatabase, setrole, name FROM rds s WHERE NOT EXISTS (SELECT 1 FROM @extschema@.pg_track_db_role_settings_list l WHERE l.srvid = _srvid AND l.setdatabase = s.setdatabase AND l.setrole = l.setrole AND l.name = s.name ); -- Detect changed GUC, insert new vals WITH rds AS ( SELECT * FROM @extschema@.pg_track_settings_rds_src(_srvid) ), last_snapshot AS ( SELECT setdatabase, setrole, name, setting FROM ( SELECT setdatabase, setrole, name, setting, row_number() OVER (PARTITION BY name, setdatabase, setrole ORDER BY ts DESC) AS rn FROM @extschema@.pg_track_db_role_settings_history WHERE srvid = _srvid ) all_snapshots WHERE all_snapshots.rn = 1 ) INSERT INTO @extschema@.pg_track_db_role_settings_history (srvid, ts, setdatabase, setrole, name, setting) SELECT _srvid, s.ts, s.setdatabase, s.setrole, s.name, s.setting FROM rds s LEFT JOIN last_snapshot l ON l.setdatabase = s.setdatabase AND l.setrole = s.setrole AND l.name = s.name WHERE (l.setdatabase IS NULL AND l.setrole IS NULL AND l.name IS NULL) OR (l.setting IS DISTINCT FROM s.setting); IF (_srvid != 0) THEN DELETE FROM @extschema@.pg_track_settings_rds_src_tmp WHERE srvid = _srvid; END IF; RETURN true; END; $_$ LANGUAGE plpgsql; /* end of pg_track_settings_snapshot_rds() */ pg_track_settings-2.1.2/pg_track_settings--2.1.0.sql000066400000000000000000000424421442764062500222120ustar00rootroot00000000000000-- This program is open source, licensed under the PostgreSQL License. -- For license terms, see the LICENSE file. -- -- Copyright (C) 2015-2023: Julien Rouhaud -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pg_track_settings" to load this file. \quit SET client_encoding = 'UTF8'; CREATE UNLOGGED TABLE @extschema@.pg_track_settings_settings_src_tmp ( srvid integer NOT NULL, ts timestamp with time zone NOT NULL, name text NOT NULL, setting text, current_setting text ); -- no need to backup this table CREATE TABLE @extschema@.pg_track_settings_list ( srvid integer NOT NULL, name text, PRIMARY KEY (srvid, name) ); SELECT pg_catalog.pg_extension_config_dump('@extschema@.pg_track_settings_list', ''); CREATE TABLE @extschema@.pg_track_settings_history ( srvid integer NOT NULL, ts timestamp with time zone, name text NOT NULL, setting text, is_dropped boolean NOT NULL DEFAULT false, setting_pretty text, PRIMARY KEY(srvid, ts, name) ); SELECT pg_catalog.pg_extension_config_dump('@extschema@.pg_track_settings_history', ''); CREATE UNLOGGED TABLE @extschema@.pg_track_settings_rds_src_tmp ( srvid integer NOT NULL, ts timestamp with time zone NOT NULL, name text NOT NULL, setting text, setdatabase oid NOT NULL, setrole oid NOT NULL ); -- no need to backup this table CREATE TABLE @extschema@.pg_track_db_role_settings_list ( srvid integer, name text, setdatabase oid, setrole oid, PRIMARY KEY (srvid, name, setdatabase, setrole) ); SELECT pg_catalog.pg_extension_config_dump('@extschema@.pg_track_db_role_settings_list', ''); CREATE TABLE @extschema@.pg_track_db_role_settings_history ( srvid INTEGER NOT NULL, ts timestamp with time zone, name text, setdatabase oid, setrole oid, setting text, is_dropped boolean NOT NULL DEFAULT false, PRIMARY KEY(srvid, ts, name, setdatabase, setrole) ); SELECT pg_catalog.pg_extension_config_dump('@extschema@.pg_track_db_role_settings_history', ''); CREATE UNLOGGED TABLE @extschema@.pg_track_settings_reboot_src_tmp ( srvid integer NOT NULL, ts timestamp with time zone NOT NULL, postmaster_ts timestamp with time zone NOT NULL ); -- no need to backup this table CREATE TABLE @extschema@.pg_reboot ( srvid integer NOT NULL, ts timestamp with time zone, PRIMARY KEY (srvid, ts) ); SELECT pg_catalog.pg_extension_config_dump('@extschema@.pg_reboot', ''); ---------------------- -- source functions -- ---------------------- CREATE OR REPLACE FUNCTION @extschema@.pg_track_settings_settings_src ( IN _srvid integer, OUT ts timestamp with time zone, OUT name text, OUT setting text, OUT current_setting text ) RETURNS SETOF record AS $PROC$ BEGIN IF (_srvid = 0) THEN RETURN QUERY SELECT now(), s.name, s.setting, pg_catalog.current_setting(s.name) FROM pg_catalog.pg_settings s; ELSE RETURN QUERY SELECT s.ts, s.name, s.setting, s.current_setting FROM @extschema@.pg_track_settings_settings_src_tmp s; END IF; END; $PROC$ LANGUAGE plpgsql; /* end of pg_track_settings_settings_src */ CREATE OR REPLACE FUNCTION @extschema@.pg_track_settings_rds_src ( IN _srvid integer, OUT ts timestamp with time zone, OUT name text, OUT setting text, OUT setdatabase oid, OUT setrole oid ) RETURNS SETOF record AS $PROC$ BEGIN IF (_srvid = 0) THEN RETURN QUERY SELECT now(), (regexp_split_to_array(unnest(s.setconfig),'=')::text[])[1] AS name, (regexp_split_to_array(unnest(s.setconfig),'=')::text[])[2] AS setting, s.setdatabase, s.setrole FROM pg_catalog.pg_db_role_setting s; ELSE RETURN QUERY SELECT s.ts, s.name, s.setting, s.setdatabase, s.setrole FROM @extschema@.pg_track_settings_rds_src_tmp s; END IF; END; $PROC$ LANGUAGE plpgsql; /* end of pg_track_settings_rds_src */ CREATE OR REPLACE FUNCTION @extschema@.pg_track_settings_reboot_src ( IN _srvid integer, OUT ts timestamp with time zone, OUT postmaster_ts timestamp with time zone ) RETURNS SETOF record AS $PROC$ BEGIN IF (_srvid = 0) THEN RETURN QUERY SELECT now(), pg_postmaster_start_time(); ELSE RETURN QUERY SELECT s.ts, s.postmaster_ts FROM @extschema@.pg_track_settings_reboot_src_tmp s; END IF; END; $PROC$ LANGUAGE plpgsql; /* end of pg_track_settings_reboot_src */ ------------------------ -- snapshot functions -- ------------------------ CREATE OR REPLACE FUNCTION @extschema@.pg_track_settings_snapshot_settings(_srvid integer) RETURNS boolean AS $_$ DECLARE _snap_ts timestamp with time zone = NULL; BEGIN SELECT max(ts) INTO _snap_ts FROM @extschema@.pg_track_settings_settings_src(_srvid); -- this function should have been called for previously saved data. If -- not, probably somethig went wrong, so discard those data IF (_srvid != 0) THEN DELETE FROM @extschema@.pg_track_settings_settings_src_tmp WHERE ts != _snap_ts; END IF; -- Handle dropped GUC WITH src AS ( SELECT * FROM @extschema@.pg_track_settings_settings_src(_srvid) ), dropped AS ( SELECT s.ts, l.srvid, l.name FROM @extschema@.pg_track_settings_list l LEFT JOIN src s ON s.name = l.name WHERE l.srvid = _srvid AND s.name IS NULL ), mark_dropped AS ( INSERT INTO @extschema@.pg_track_settings_history (srvid, ts, name, setting, setting_pretty, is_dropped) SELECT srvid, COALESCE(_snap_ts, now()), name, NULL, NULL, true FROM dropped ) DELETE FROM @extschema@.pg_track_settings_list l USING dropped d WHERE d.name = l.name AND d.srvid = l.srvid AND l.srvid = _srvid; -- Insert missing settings INSERT INTO @extschema@.pg_track_settings_list (srvid, name) SELECT _srvid, name FROM @extschema@.pg_track_settings_settings_src(_srvid) s WHERE NOT EXISTS (SELECT 1 FROM @extschema@.pg_track_settings_list l WHERE l.srvid = _srvid AND l.name = s.name ); -- Detect changed GUC, insert new vals WITH src AS ( SELECT * FROM @extschema@.pg_track_settings_settings_src(_srvid) ), last_snapshot AS ( SELECT srvid, name, setting FROM ( SELECT srvid, name, setting, row_number() OVER (PARTITION BY NAME ORDER BY ts DESC) AS rn FROM @extschema@.pg_track_settings_history h WHERE h.srvid = _srvid ) all_snapshots WHERE all_snapshots.rn = 1 ) INSERT INTO @extschema@.pg_track_settings_history (srvid, ts, name, setting, setting_pretty) SELECT _srvid, s.ts, s.name, s.setting, s.current_setting FROM src s LEFT JOIN last_snapshot l ON l.name = s.name WHERE ( l.name IS NULL OR l.setting IS DISTINCT FROM s.setting ); IF (_srvid != 0) THEN DELETE FROM @extschema@.pg_track_settings_settings_src_tmp WHERE srvid = _srvid; END IF; RETURN true; END; $_$ LANGUAGE plpgsql; /* end of pg_track_settings_snapshot_settings() */ CREATE OR REPLACE FUNCTION @extschema@.pg_track_settings_snapshot_rds(_srvid integer) RETURNS boolean AS $_$ DECLARE _snap_ts timestamp with time zone; BEGIN SELECT max(ts) INTO _snap_ts FROM @extschema@.pg_track_settings_rds_src(_srvid); -- this function should have been called for previously saved data. If -- not, probably somethig went wrong, so discard those data IF (_srvid != 0) THEN DELETE FROM @extschema@.pg_track_settings_rds_src_tmp WHERE ts != _snap_ts; END IF; -- Handle dropped db_role_setting WITH rds AS ( SELECT * FROM @extschema@.pg_track_settings_rds_src(_srvid) ), dropped AS ( SELECT _snap_ts AS ts, l.setdatabase, l.setrole, l.name FROM @extschema@.pg_track_db_role_settings_list l LEFT JOIN rds s ON ( s.setdatabase = l.setdatabase AND s.setrole = l.setrole AND s.name = l.name ) WHERE l.srvid = _srvid AND s.setdatabase IS NULL AND s.setrole IS NULL AND s.name IS NULL ), mark_dropped AS ( INSERT INTO @extschema@.pg_track_db_role_settings_history (srvid, ts, setdatabase, setrole, name, setting, is_dropped) SELECT _srvid, ts, d.setdatabase, d.setrole, d.name, NULL, true FROM dropped AS d ) DELETE FROM @extschema@.pg_track_db_role_settings_list l USING dropped d WHERE l.srvid = _srvid AND d.setdatabase = l.setdatabase AND d.setrole = l.setrole AND d.name = l.name; -- Insert missing settings WITH rds AS ( SELECT * FROM @extschema@.pg_track_settings_rds_src(_srvid) ) INSERT INTO @extschema@.pg_track_db_role_settings_list (srvid, setdatabase, setrole, name) SELECT _srvid, setdatabase, setrole, name FROM rds s WHERE NOT EXISTS (SELECT 1 FROM @extschema@.pg_track_db_role_settings_list l WHERE l.srvid = _srvid AND l.setdatabase = s.setdatabase AND l.setrole = l.setrole AND l.name = s.name ); -- Detect changed GUC, insert new vals WITH rds AS ( SELECT * FROM @extschema@.pg_track_settings_rds_src(_srvid) ), last_snapshot AS ( SELECT setdatabase, setrole, name, setting FROM ( SELECT setdatabase, setrole, name, setting, row_number() OVER (PARTITION BY name, setdatabase, setrole ORDER BY ts DESC) AS rn FROM @extschema@.pg_track_db_role_settings_history WHERE srvid = _srvid ) all_snapshots WHERE all_snapshots.rn = 1 ) INSERT INTO @extschema@.pg_track_db_role_settings_history (srvid, ts, setdatabase, setrole, name, setting) SELECT _srvid, s.ts, s.setdatabase, s.setrole, s.name, s.setting FROM rds s LEFT JOIN last_snapshot l ON l.setdatabase = s.setdatabase AND l.setrole = s.setrole AND l.name = s.name WHERE (l.setdatabase IS NULL AND l.setrole IS NULL AND l.name IS NULL) OR (l.setting IS DISTINCT FROM s.setting); IF (_srvid != 0) THEN DELETE FROM @extschema@.pg_track_settings_rds_src_tmp WHERE srvid = _srvid; END IF; RETURN true; END; $_$ LANGUAGE plpgsql; /* end of pg_track_settings_snapshot_rds() */ CREATE OR REPLACE FUNCTION @extschema@.pg_track_settings_snapshot_reboot(_srvid integer) RETURNS boolean AS $_$ BEGIN -- Detect is postmaster restarted since last call WITH last_reboot AS ( SELECT t.postmaster_ts FROM @extschema@.pg_track_settings_reboot_src(_srvid) t ) INSERT INTO @extschema@.pg_reboot (srvid, ts) SELECT _srvid, lr.postmaster_ts FROM last_reboot lr WHERE NOT EXISTS (SELECT 1 FROM @extschema@.pg_reboot r WHERE r.srvid = _srvid AND r.ts = lr.postmaster_ts AND r.srvid = _srvid ); IF (_srvid != 0) THEN DELETE FROM @extschema@.pg_track_settings_reboot_src_tmp WHERE srvid = _srvid; END IF; RETURN true; END; $_$ LANGUAGE plpgsql; /* end of pg_track_settings_snapshot_reboot() */ -- global function doing all the work for local instance, kept for backward -- compatibility CREATE OR REPLACE FUNCTION @extschema@.pg_track_settings_snapshot() RETURNS boolean AS $_$ BEGIN PERFORM @extschema@.pg_track_settings_snapshot_settings(0); PERFORM @extschema@.pg_track_settings_snapshot_rds(0); PERFORM @extschema@.pg_track_settings_snapshot_reboot(0); RETURN true; END; $_$ LANGUAGE plpgsql; /* end of pg_track_settings_snapshot() */ CREATE OR REPLACE FUNCTION @extschema@.pg_track_settings( _ts timestamp with time zone DEFAULT now(), _srvid integer DEFAULT 0) RETURNS TABLE (name text, setting text, setting_pretty text) AS $_$ BEGIN RETURN QUERY SELECT s.name, s.setting, s.setting_pretty FROM ( SELECT h.name, h.setting, h.setting_pretty, h.is_dropped, row_number() OVER (PARTITION BY h.name ORDER BY h.ts DESC) AS rn FROM @extschema@.pg_track_settings_history h WHERE h.srvid = _srvid AND h.ts <= _ts ) s WHERE s.rn = 1 AND NOT s.is_dropped ORDER BY s.name; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION @extschema@.pg_track_db_role_settings( _ts timestamp with time zone DEFAULT now(), _srvid integer DEFAULT 0) RETURNS TABLE (setdatabase oid, setrole oid, name text, setting text) AS $_$ BEGIN RETURN QUERY SELECT s.setdatabase, s.setrole, s.name, s.setting FROM ( SELECT h.setdatabase, h.setrole, h.name, h.setting, h.is_dropped, row_number() OVER (PARTITION BY h.name, h.setdatabase, h.setrole ORDER BY h.ts DESC) AS rn FROM @extschema@.pg_track_db_role_settings_history h WHERE h.srvid = _srvid AND h.ts <= _ts ) s WHERE s.rn = 1 AND NOT s.is_dropped ORDER BY s.setdatabase, s.setrole, s.name; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION @extschema@.pg_track_settings_diff( _from timestamp with time zone, _to timestamp with time zone, _srvid integer DEFAULT 0) RETURNS TABLE (name text, from_setting text, from_exists boolean, to_setting text, to_exists boolean, from_setting_pretty text, to_setting_pretty text) AS $_$ BEGIN RETURN QUERY SELECT COALESCE(s1.name, s2.name), s1.setting AS from_setting, CASE WHEN s1.setting IS NULL THEN false ELSE true END, s2.setting AS to_setting, CASE WHEN s2.setting IS NULL THEN false ELSE true END, s1.setting_pretty AS from_setting_pretty, s2.setting_pretty AS to_setting_pretty FROM @extschema@.pg_track_settings(_from, _srvid) s1 FULL OUTER JOIN @extschema@.pg_track_settings(_to, _srvid) s2 ON s2.name = s1.name WHERE s1.setting IS DISTINCT FROM s2.setting ORDER BY 1; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION @extschema@.pg_track_db_role_settings_diff( _from timestamp with time zone, _to timestamp with time zone, _srvid integer DEFAULT 0) RETURNS TABLE (setdatabase oid, setrole oid, name text, from_setting text, from_exists boolean, to_setting text, to_exists boolean) AS $_$ BEGIN RETURN QUERY SELECT COALESCE(s1.setdatabase, s2.setdatabase), COALESCE(s1.setrole, s2.setrole), COALESCE(s1.name, s2.name), s1.setting AS from_setting, CASE WHEN s1.setting IS NULL THEN false ELSE true END, s2.setting AS to_setting, CASE WHEN s2.setting IS NULL THEN false ELSE true END FROM @extschema@.pg_track_db_role_settings(_from, _srvid) s1 FULL OUTER JOIN @extschema@.pg_track_db_role_settings(_to, _srvid) s2 ON s2.setdatabase = s1.setdatabase AND s2.setrole = s1.setrole AND s2.name = s1.name WHERE s1.setdatabase IS DISTINCT FROM s2.setdatabase AND s1.setrole IS DISTINCT FROM s2.setrole AND s1.setting IS DISTINCT FROM s2.setting ORDER BY 1, 2, 3; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION @extschema@.pg_track_settings_log( _name text, _srvid integer DEFAULT 0) RETURNS TABLE (ts timestamp with time zone, name text, setting_exists boolean, setting text, setting_pretty text) AS $_$ BEGIN RETURN QUERY SELECT h.ts, h.name, NOT h.is_dropped, h.setting, h.setting_pretty FROM @extschema@.pg_track_settings_history h WHERE h.srvid = _srvid AND h.name = _name ORDER BY ts DESC; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION @extschema@.pg_track_db_role_settings_log( _name text, _srvid integer DEFAULT 0) RETURNS TABLE (ts timestamp with time zone, setdatabase oid, setrole oid, name text, setting_exists boolean, setting text) AS $_$ BEGIN RETURN QUERY SELECT h.ts, h.setdatabase, h.setrole, h.name, NOT h.is_dropped, h.setting FROM @extschema@.pg_track_db_role_settings_history h WHERE h.srvid = _srvid AND h.name = _name ORDER BY ts, setdatabase, setrole DESC; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION @extschema@.pg_track_reboot_log(_srvid integer DEFAULT 0) RETURNS TABLE (ts timestamp with time zone) AS $_$ BEGIN RETURN QUERY SELECT r.ts FROM @extschema@.pg_reboot r WHERE r.srvid = _srvid ORDER BY r.ts; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION @extschema@.pg_track_settings_reset(_srvid integer DEFAULT 0) RETURNS void AS $_$ BEGIN DELETE FROM @extschema@.pg_track_settings_settings_src_tmp WHERE srvid = _srvid; DELETE FROM @extschema@.pg_track_settings_rds_src_tmp WHERE srvid = _srvid; DELETE FROM @extschema@.pg_track_settings_reboot_src_tmp WHERE srvid = _srvid; DELETE FROM @extschema@.pg_track_settings_list WHERE srvid = _srvid; DELETE FROM @extschema@.pg_track_settings_history WHERE srvid = _srvid; DELETE FROM @extschema@.pg_track_db_role_settings_list WHERE srvid = _srvid; DELETE FROM @extschema@.pg_track_db_role_settings_history WHERE srvid = _srvid; DELETE FROM @extschema@.pg_reboot WHERE srvid = _srvid; END; $_$ LANGUAGE plpgsql; pg_track_settings-2.1.2/pg_track_settings--2.1.2.sql000066400000000000000000000434101442764062500222100ustar00rootroot00000000000000-- This program is open source, licensed under the PostgreSQL License. -- For license terms, see the LICENSE file. -- -- Copyright (C) 2015-2023: Julien Rouhaud -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION pg_track_settings" to load this file. \quit CREATE UNLOGGED TABLE @extschema@.pg_track_settings_settings_src_tmp ( srvid integer NOT NULL, ts timestamp with time zone NOT NULL, name text NOT NULL, setting text, current_setting text ); -- no need to backup this table CREATE TABLE @extschema@.pg_track_settings_list ( srvid integer NOT NULL, name text, PRIMARY KEY (srvid, name) ); SELECT pg_catalog.pg_extension_config_dump('@extschema@.pg_track_settings_list', ''); CREATE TABLE @extschema@.pg_track_settings_history ( srvid integer NOT NULL, ts timestamp with time zone, name text NOT NULL, setting text, is_dropped boolean NOT NULL DEFAULT false, setting_pretty text, PRIMARY KEY(srvid, ts, name) ); SELECT pg_catalog.pg_extension_config_dump('@extschema@.pg_track_settings_history', ''); CREATE UNLOGGED TABLE @extschema@.pg_track_settings_rds_src_tmp ( srvid integer NOT NULL, ts timestamp with time zone NOT NULL, name text NOT NULL, setting text, setdatabase oid NOT NULL, setrole oid NOT NULL ); -- no need to backup this table CREATE TABLE @extschema@.pg_track_db_role_settings_list ( srvid integer, name text, setdatabase oid, setrole oid, PRIMARY KEY (srvid, name, setdatabase, setrole) ); SELECT pg_catalog.pg_extension_config_dump('@extschema@.pg_track_db_role_settings_list', ''); CREATE TABLE @extschema@.pg_track_db_role_settings_history ( srvid INTEGER NOT NULL, ts timestamp with time zone, name text, setdatabase oid, setrole oid, setting text, is_dropped boolean NOT NULL DEFAULT false, PRIMARY KEY(srvid, ts, name, setdatabase, setrole) ); SELECT pg_catalog.pg_extension_config_dump('@extschema@.pg_track_db_role_settings_history', ''); CREATE UNLOGGED TABLE @extschema@.pg_track_settings_reboot_src_tmp ( srvid integer NOT NULL, ts timestamp with time zone NOT NULL, postmaster_ts timestamp with time zone NOT NULL ); -- no need to backup this table CREATE TABLE @extschema@.pg_reboot ( srvid integer NOT NULL, ts timestamp with time zone, PRIMARY KEY (srvid, ts) ); SELECT pg_catalog.pg_extension_config_dump('@extschema@.pg_reboot', ''); ---------------------- -- source functions -- ---------------------- CREATE OR REPLACE FUNCTION @extschema@.pg_track_settings_settings_src ( IN _srvid integer, OUT ts timestamp with time zone, OUT name text, OUT setting text, OUT current_setting text ) RETURNS SETOF record AS $PROC$ BEGIN IF (_srvid = 0) THEN RETURN QUERY SELECT now(), s.name, s.setting, pg_catalog.current_setting(s.name) FROM pg_catalog.pg_settings s; ELSE RETURN QUERY SELECT s.ts, s.name, s.setting, s.current_setting FROM @extschema@.pg_track_settings_settings_src_tmp s WHERE srvid = _srvid; END IF; END; $PROC$ LANGUAGE plpgsql; /* end of pg_track_settings_settings_src */ CREATE OR REPLACE FUNCTION @extschema@.pg_track_settings_rds_src ( IN _srvid integer, OUT ts timestamp with time zone, OUT name text, OUT setting text, OUT setdatabase oid, OUT setrole oid ) RETURNS SETOF record AS $PROC$ BEGIN IF (_srvid = 0) THEN RETURN QUERY SELECT now(), (regexp_split_to_array(unnest(s.setconfig),'=')::text[])[1] AS name, (regexp_split_to_array(unnest(s.setconfig),'=')::text[])[2] AS setting, s.setdatabase, s.setrole FROM pg_catalog.pg_db_role_setting s; ELSE RETURN QUERY SELECT s.ts, s.name, s.setting, s.setdatabase, s.setrole FROM @extschema@.pg_track_settings_rds_src_tmp s WHERE srvid = _srvid; END IF; END; $PROC$ LANGUAGE plpgsql; /* end of pg_track_settings_rds_src */ CREATE OR REPLACE FUNCTION @extschema@.pg_track_settings_reboot_src ( IN _srvid integer, OUT ts timestamp with time zone, OUT postmaster_ts timestamp with time zone ) RETURNS SETOF record AS $PROC$ BEGIN IF (_srvid = 0) THEN RETURN QUERY SELECT now(), pg_postmaster_start_time(); ELSE RETURN QUERY SELECT s.ts, s.postmaster_ts FROM @extschema@.pg_track_settings_reboot_src_tmp s WHERE srvid = _srvid; END IF; END; $PROC$ LANGUAGE plpgsql; /* end of pg_track_settings_reboot_src */ ------------------------ -- snapshot functions -- ------------------------ CREATE OR REPLACE FUNCTION @extschema@.pg_track_settings_snapshot_settings(_srvid integer) RETURNS boolean AS $_$ DECLARE _snap_ts timestamp with time zone = NULL; BEGIN SELECT max(ts) INTO _snap_ts FROM @extschema@.pg_track_settings_settings_src(_srvid); -- this function should have been called for previously saved data. If -- not, probably somethig went wrong, so discard those data IF (_srvid != 0) THEN DELETE FROM @extschema@.pg_track_settings_settings_src_tmp WHERE ts != _snap_ts AND srvid = _srvid; END IF; -- Handle dropped GUC WITH src AS ( SELECT * FROM @extschema@.pg_track_settings_settings_src(_srvid) ), dropped AS ( SELECT s.ts, l.srvid, l.name FROM @extschema@.pg_track_settings_list l LEFT JOIN src s ON s.name = l.name WHERE l.srvid = _srvid AND s.name IS NULL ), mark_dropped AS ( INSERT INTO @extschema@.pg_track_settings_history (srvid, ts, name, setting, setting_pretty, is_dropped) SELECT srvid, COALESCE(_snap_ts, now()), name, NULL, NULL, true FROM dropped ) DELETE FROM @extschema@.pg_track_settings_list l USING dropped d WHERE d.name = l.name AND d.srvid = l.srvid AND l.srvid = _srvid; -- Insert missing settings INSERT INTO @extschema@.pg_track_settings_list (srvid, name) SELECT _srvid, name FROM @extschema@.pg_track_settings_settings_src(_srvid) s WHERE NOT EXISTS (SELECT 1 FROM @extschema@.pg_track_settings_list l WHERE l.srvid = _srvid AND l.name = s.name ); -- Detect changed GUC, insert new vals WITH src AS ( SELECT * FROM @extschema@.pg_track_settings_settings_src(_srvid) ), last_snapshot AS ( SELECT srvid, name, setting FROM ( SELECT srvid, name, setting, row_number() OVER (PARTITION BY NAME ORDER BY ts DESC) AS rn FROM @extschema@.pg_track_settings_history h WHERE h.srvid = _srvid ) all_snapshots WHERE all_snapshots.rn = 1 ) INSERT INTO @extschema@.pg_track_settings_history (srvid, ts, name, setting, setting_pretty) SELECT _srvid, s.ts, s.name, s.setting, s.current_setting FROM src s LEFT JOIN last_snapshot l ON l.name = s.name WHERE ( l.name IS NULL OR l.setting IS DISTINCT FROM s.setting ); IF (_srvid != 0) THEN DELETE FROM @extschema@.pg_track_settings_settings_src_tmp WHERE srvid = _srvid; END IF; RETURN true; END; $_$ LANGUAGE plpgsql; /* end of pg_track_settings_snapshot_settings() */ CREATE OR REPLACE FUNCTION @extschema@.pg_track_settings_snapshot_rds(_srvid integer) RETURNS boolean AS $_$ DECLARE _snap_ts timestamp with time zone; BEGIN -- If all pg_db_role_setting have been removed, we won't get a snapshot ts -- but we may still have to record that some settings have been removed. -- In that case simply use now(), as that extension doesn't guarantee the -- timestamp to be more precise than the snapshot interval, and there's -- isn't any better timestamp to use anyway. SELECT coalesce(max(ts), now()) INTO _snap_ts FROM @extschema@.pg_track_settings_rds_src(_srvid); -- this function should have been called for previously saved data. If -- not, probably somethig went wrong, so discard those data IF (_srvid != 0) THEN DELETE FROM @extschema@.pg_track_settings_rds_src_tmp WHERE ts != _snap_ts AND srvid = _srvid; END IF; -- Handle dropped db_role_setting WITH rds AS ( SELECT * FROM @extschema@.pg_track_settings_rds_src(_srvid) ), dropped AS ( SELECT _snap_ts AS ts, l.setdatabase, l.setrole, l.name FROM @extschema@.pg_track_db_role_settings_list l LEFT JOIN rds s ON ( s.setdatabase = l.setdatabase AND s.setrole = l.setrole AND s.name = l.name ) WHERE l.srvid = _srvid AND s.setdatabase IS NULL AND s.setrole IS NULL AND s.name IS NULL ), mark_dropped AS ( INSERT INTO @extschema@.pg_track_db_role_settings_history (srvid, ts, setdatabase, setrole, name, setting, is_dropped) SELECT _srvid, ts, d.setdatabase, d.setrole, d.name, NULL, true FROM dropped AS d ) DELETE FROM @extschema@.pg_track_db_role_settings_list l USING dropped d WHERE l.srvid = _srvid AND d.setdatabase = l.setdatabase AND d.setrole = l.setrole AND d.name = l.name; -- Insert missing settings WITH rds AS ( SELECT * FROM @extschema@.pg_track_settings_rds_src(_srvid) ) INSERT INTO @extschema@.pg_track_db_role_settings_list (srvid, setdatabase, setrole, name) SELECT _srvid, setdatabase, setrole, name FROM rds s WHERE NOT EXISTS (SELECT 1 FROM @extschema@.pg_track_db_role_settings_list l WHERE l.srvid = _srvid AND l.setdatabase = s.setdatabase AND l.setrole = l.setrole AND l.name = s.name ); -- Detect changed GUC, insert new vals WITH rds AS ( SELECT * FROM @extschema@.pg_track_settings_rds_src(_srvid) ), last_snapshot AS ( SELECT setdatabase, setrole, name, setting FROM ( SELECT setdatabase, setrole, name, setting, row_number() OVER (PARTITION BY name, setdatabase, setrole ORDER BY ts DESC) AS rn FROM @extschema@.pg_track_db_role_settings_history WHERE srvid = _srvid ) all_snapshots WHERE all_snapshots.rn = 1 ) INSERT INTO @extschema@.pg_track_db_role_settings_history (srvid, ts, setdatabase, setrole, name, setting) SELECT _srvid, s.ts, s.setdatabase, s.setrole, s.name, s.setting FROM rds s LEFT JOIN last_snapshot l ON l.setdatabase = s.setdatabase AND l.setrole = s.setrole AND l.name = s.name WHERE (l.setdatabase IS NULL AND l.setrole IS NULL AND l.name IS NULL) OR (l.setting IS DISTINCT FROM s.setting); IF (_srvid != 0) THEN DELETE FROM @extschema@.pg_track_settings_rds_src_tmp WHERE srvid = _srvid; END IF; RETURN true; END; $_$ LANGUAGE plpgsql; /* end of pg_track_settings_snapshot_rds() */ CREATE OR REPLACE FUNCTION @extschema@.pg_track_settings_snapshot_reboot(_srvid integer) RETURNS boolean AS $_$ BEGIN -- Detect is postmaster restarted since last call WITH last_reboot AS ( SELECT t.postmaster_ts FROM @extschema@.pg_track_settings_reboot_src(_srvid) t ) INSERT INTO @extschema@.pg_reboot (srvid, ts) SELECT _srvid, lr.postmaster_ts FROM last_reboot lr WHERE NOT EXISTS (SELECT 1 FROM @extschema@.pg_reboot r WHERE r.srvid = _srvid AND r.ts = lr.postmaster_ts AND r.srvid = _srvid ); IF (_srvid != 0) THEN DELETE FROM @extschema@.pg_track_settings_reboot_src_tmp WHERE srvid = _srvid; END IF; RETURN true; END; $_$ LANGUAGE plpgsql; /* end of pg_track_settings_snapshot_reboot() */ -- global function doing all the work for local instance, kept for backward -- compatibility CREATE OR REPLACE FUNCTION @extschema@.pg_track_settings_snapshot() RETURNS boolean AS $_$ BEGIN PERFORM @extschema@.pg_track_settings_snapshot_settings(0); PERFORM @extschema@.pg_track_settings_snapshot_rds(0); PERFORM @extschema@.pg_track_settings_snapshot_reboot(0); RETURN true; END; $_$ LANGUAGE plpgsql; /* end of pg_track_settings_snapshot() */ CREATE OR REPLACE FUNCTION @extschema@.pg_track_settings( _ts timestamp with time zone DEFAULT now(), _srvid integer DEFAULT 0) RETURNS TABLE (name text, setting text, setting_pretty text) AS $_$ BEGIN RETURN QUERY SELECT s.name, s.setting, s.setting_pretty FROM ( SELECT h.name, h.setting, h.setting_pretty, h.is_dropped, row_number() OVER (PARTITION BY h.name ORDER BY h.ts DESC) AS rn FROM @extschema@.pg_track_settings_history h WHERE h.srvid = _srvid AND h.ts <= _ts ) s WHERE s.rn = 1 AND NOT s.is_dropped ORDER BY s.name; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION @extschema@.pg_track_db_role_settings( _ts timestamp with time zone DEFAULT now(), _srvid integer DEFAULT 0) RETURNS TABLE (setdatabase oid, setrole oid, name text, setting text) AS $_$ BEGIN RETURN QUERY SELECT s.setdatabase, s.setrole, s.name, s.setting FROM ( SELECT h.setdatabase, h.setrole, h.name, h.setting, h.is_dropped, row_number() OVER (PARTITION BY h.name, h.setdatabase, h.setrole ORDER BY h.ts DESC) AS rn FROM @extschema@.pg_track_db_role_settings_history h WHERE h.srvid = _srvid AND h.ts <= _ts ) s WHERE s.rn = 1 AND NOT s.is_dropped ORDER BY s.setdatabase, s.setrole, s.name; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION @extschema@.pg_track_settings_diff( _from timestamp with time zone, _to timestamp with time zone, _srvid integer DEFAULT 0) RETURNS TABLE (name text, from_setting text, from_exists boolean, to_setting text, to_exists boolean, from_setting_pretty text, to_setting_pretty text) AS $_$ BEGIN RETURN QUERY SELECT COALESCE(s1.name, s2.name), s1.setting AS from_setting, CASE WHEN s1.setting IS NULL THEN false ELSE true END, s2.setting AS to_setting, CASE WHEN s2.setting IS NULL THEN false ELSE true END, s1.setting_pretty AS from_setting_pretty, s2.setting_pretty AS to_setting_pretty FROM @extschema@.pg_track_settings(_from, _srvid) s1 FULL OUTER JOIN @extschema@.pg_track_settings(_to, _srvid) s2 ON s2.name = s1.name WHERE s1.setting IS DISTINCT FROM s2.setting ORDER BY 1; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION @extschema@.pg_track_db_role_settings_diff( _from timestamp with time zone, _to timestamp with time zone, _srvid integer DEFAULT 0) RETURNS TABLE (setdatabase oid, setrole oid, name text, from_setting text, from_exists boolean, to_setting text, to_exists boolean) AS $_$ BEGIN RETURN QUERY SELECT COALESCE(s1.setdatabase, s2.setdatabase), COALESCE(s1.setrole, s2.setrole), COALESCE(s1.name, s2.name), s1.setting AS from_setting, CASE WHEN s1.setting IS NULL THEN false ELSE true END, s2.setting AS to_setting, CASE WHEN s2.setting IS NULL THEN false ELSE true END FROM @extschema@.pg_track_db_role_settings(_from, _srvid) s1 FULL OUTER JOIN @extschema@.pg_track_db_role_settings(_to, _srvid) s2 ON s2.setdatabase = s1.setdatabase AND s2.setrole = s1.setrole AND s2.name = s1.name WHERE s1.setdatabase IS DISTINCT FROM s2.setdatabase AND s1.setrole IS DISTINCT FROM s2.setrole AND s1.setting IS DISTINCT FROM s2.setting ORDER BY 1, 2, 3; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION @extschema@.pg_track_settings_log( _name text, _srvid integer DEFAULT 0) RETURNS TABLE (ts timestamp with time zone, name text, setting_exists boolean, setting text, setting_pretty text) AS $_$ BEGIN RETURN QUERY SELECT h.ts, h.name, NOT h.is_dropped, h.setting, h.setting_pretty FROM @extschema@.pg_track_settings_history h WHERE h.srvid = _srvid AND h.name = _name ORDER BY ts DESC; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION @extschema@.pg_track_db_role_settings_log( _name text, _srvid integer DEFAULT 0) RETURNS TABLE (ts timestamp with time zone, setdatabase oid, setrole oid, name text, setting_exists boolean, setting text) AS $_$ BEGIN RETURN QUERY SELECT h.ts, h.setdatabase, h.setrole, h.name, NOT h.is_dropped, h.setting FROM @extschema@.pg_track_db_role_settings_history h WHERE h.srvid = _srvid AND h.name = _name ORDER BY ts, setdatabase, setrole DESC; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION @extschema@.pg_track_reboot_log(_srvid integer DEFAULT 0) RETURNS TABLE (ts timestamp with time zone) AS $_$ BEGIN RETURN QUERY SELECT r.ts FROM @extschema@.pg_reboot r WHERE r.srvid = _srvid ORDER BY r.ts; END; $_$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION @extschema@.pg_track_settings_reset(_srvid integer DEFAULT 0) RETURNS void AS $_$ BEGIN DELETE FROM @extschema@.pg_track_settings_settings_src_tmp WHERE srvid = _srvid; DELETE FROM @extschema@.pg_track_settings_rds_src_tmp WHERE srvid = _srvid; DELETE FROM @extschema@.pg_track_settings_reboot_src_tmp WHERE srvid = _srvid; DELETE FROM @extschema@.pg_track_settings_list WHERE srvid = _srvid; DELETE FROM @extschema@.pg_track_settings_history WHERE srvid = _srvid; DELETE FROM @extschema@.pg_track_db_role_settings_list WHERE srvid = _srvid; DELETE FROM @extschema@.pg_track_db_role_settings_history WHERE srvid = _srvid; DELETE FROM @extschema@.pg_reboot WHERE srvid = _srvid; END; $_$ LANGUAGE plpgsql; pg_track_settings-2.1.2/pg_track_settings.control000066400000000000000000000002211442764062500223470ustar00rootroot00000000000000comment = 'Track settings changes' default_version = '2.1.2' module_pathname = '$libdir/pg_track_settings' superuser = false relocatable = false pg_track_settings-2.1.2/test/000077500000000000000000000000001442764062500162175ustar00rootroot00000000000000pg_track_settings-2.1.2/test/sql/000077500000000000000000000000001442764062500170165ustar00rootroot00000000000000pg_track_settings-2.1.2/test/sql/pg_track_settings.sql000066400000000000000000000211311442764062500232470ustar00rootroot00000000000000SET search_path = ''; SET timezone TO 'Europe/Paris'; -- Remove any known per db setting set by pg_regress DO $$ DECLARE dbname text = current_database(); s text; BEGIN FOREACH s IN ARRAY ARRAY['lc_messages', 'lc_monetary', 'lc_numeric', 'lc_time', 'bytea_output', 'timezone_abbreviations'] LOOP EXECUTE format('ALTER DATABASE %I RESET %s', dbname, s); END LOOP; END; $$ LANGUAGE plpgsql; -- There shouldn't be any db/role setting left. It's unfortunately not -- guaranteed to be the case if the regression tests are run on a non-default -- cluster. SELECT d.datname, s.setconfig FROM pg_db_role_setting s JOIN pg_database d on s.setdatabase = d.oid; CREATE SCHEMA "PGTS"; -- Extension should be installable in a custom schema CREATE EXTENSION pg_track_settings WITH SCHEMA "PGTS"; -- But not relocatable ALTER EXTENSION pg_track_settings SET SCHEMA public; -- Check the relations that aren't dumped WITH ext AS ( SELECT c.oid, c.relname FROM pg_depend d JOIN pg_extension e ON d.refclassid = 'pg_extension'::regclass AND e.oid = d.refobjid AND e.extname = 'pg_track_settings' JOIN pg_class c ON d.classid = 'pg_class'::regclass AND c.oid = d.objid ), dmp AS ( SELECT unnest(extconfig) AS oid FROM pg_extension WHERE extname = 'pg_track_settings' ) SELECT ext.relname FROM ext LEFT JOIN dmp USING (oid) WHERE dmp.oid IS NULL ORDER BY ext.relname::text COLLATE "C"; -- Check that all objects are stored in the expected schema WITH ext AS ( SELECT pg_describe_object(d.classid, d.objid, d.objsubid) AS descr FROM pg_depend d JOIN pg_extension e ON d.refclassid = 'pg_extension'::regclass AND e.oid = d.refobjid AND e.extname = 'pg_track_settings' ) SELECT descr FROM ext WHERE descr NOT like '%"PGTS".%' ORDER BY descr COLLATE "C"; -- test main config history SELECT COUNT(*) FROM "PGTS".pg_track_settings_history; SET work_mem = '10MB'; SELECT * FROM "PGTS".pg_track_settings_snapshot(); SELECT pg_catalog.pg_sleep(1); SET work_mem = '5MB'; SELECT * FROM "PGTS".pg_track_settings_snapshot(); SELECT name, setting_exists, setting, setting_pretty FROM "PGTS".pg_track_settings_log('work_mem') ORDER BY ts ASC; SELECT name, from_setting, from_exists, to_setting, to_exists, from_setting_pretty, to_setting_pretty FROM "PGTS".pg_track_settings_diff(now() - interval '500 ms', now()); -- test pg_db_role_settings ALTER DATABASE postgres SET work_mem = '1MB'; SELECT * FROM "PGTS".pg_track_settings_snapshot(); ALTER ROLE postgres SET work_mem = '2MB'; SELECT * FROM "PGTS".pg_track_settings_snapshot(); ALTER ROLE postgres IN DATABASE postgres SET work_mem = '3MB'; SELECT * FROM "PGTS".pg_track_settings_snapshot(); SELECT * FROM "PGTS".pg_track_settings_snapshot(); SELECT COALESCE(datname, '-') AS datname, setrole::regrole, name, setting_exists, setting FROM "PGTS".pg_track_db_role_settings_log('work_mem') s LEFT JOIN pg_database d ON d.oid = s.setdatabase ORDER BY ts ASC; SELECT COALESCE(datname, '-') AS datname, setrole::regrole, name, from_setting, from_exists, to_setting, to_exists FROM "PGTS".pg_track_db_role_settings_diff(now() - interval '10 min', now()) s LEFT JOIN pg_database d ON d.oid = s.setdatabase WHERE name = 'work_mem' ORDER BY 1, 2, 3; ALTER DATABASE postgres RESET work_mem; SELECT * FROM "PGTS".pg_track_settings_snapshot(); ALTER ROLE postgres RESET work_mem; SELECT * FROM "PGTS".pg_track_settings_snapshot(); ALTER ROLE postgres IN DATABASE postgres RESET work_mem; SELECT * FROM "PGTS".pg_track_settings_snapshot(); -- test pg_reboot SELECT COUNT(*) FROM "PGTS".pg_reboot; SELECT now() - ts > interval '2 second' FROM "PGTS".pg_reboot; SELECT now() - ts > interval '2 second' FROM "PGTS".pg_track_reboot_log(); -- test the reset SELECT * FROM "PGTS".pg_track_settings_reset(); SELECT COUNT(*) FROM "PGTS".pg_track_settings_history; SELECT COUNT(*) FROM "PGTS".pg_track_settings_log('work_mem'); SELECT COUNT(*) FROM "PGTS".pg_track_settings_diff(now() - interval '1 hour', now()); SELECT COUNT(*) FROM "PGTS".pg_track_db_role_settings_log('work_mem'); SELECT COUNT(*) FROM "PGTS".pg_track_db_role_settings_diff(now() - interval '1 hour', now()); SELECT COUNT(*) FROM "PGTS".pg_reboot; -------------------------- -- test remote snapshot -- -------------------------- -- fake general settings INSERT INTO "PGTS".pg_track_settings_settings_src_tmp (srvid, ts, name, setting, current_setting) VALUES (1, '2019-01-01 00:00:00 CET', 'work_mem', '0', '1MB'), (2, '2019-01-02 00:00:00 CET', 'work_mem', '0', '2MB'); -- fake rds settings INSERT INTO "PGTS".pg_track_settings_rds_src_tmp (srvid, ts, name, setting, setdatabase, setrole) VALUES (1, '2019-01-01 00:00:00 CET', 'work_mem', '1MB', 123, 0), (2, '2019-01-02 00:00:00 CET', 'work_mem', '2MB', 456, 0); -- fake reboot settings INSERT INTO "PGTS".pg_track_settings_reboot_src_tmp (srvid, ts, postmaster_ts) VALUES (1, '2019-01-01 00:01:00 CET', '2019-01-01 00:00:00 CET'), (2, '2019-01-02 00:01:00 CET', '2019-01-02 00:00:00 CET'); SELECT "PGTS".pg_track_settings_snapshot_settings(1); SELECT "PGTS".pg_track_settings_snapshot_rds(1); SELECT "PGTS".pg_track_settings_snapshot_reboot(1); -- snapshot of remote server 1 shouldn't impact data for server 2 SELECT srvid, count(*) FROM "PGTS".pg_track_settings_settings_src_tmp GROUP BY srvid; SELECT srvid, count(*) FROM "PGTS".pg_track_settings_rds_src_tmp GROUP BY srvid; SELECT srvid, count(*) FROM "PGTS".pg_track_settings_reboot_src_tmp GROUP BY srvid; -- fake general settings INSERT INTO "PGTS".pg_track_settings_settings_src_tmp (srvid, ts, name, setting, current_setting) VALUES -- previously untreated data that should be discarded (1, '2019-01-02 00:00:00 CET', 'work_mem', '5120', '5MB'), -- data that should be processed (1, '2019-01-02 01:00:00 CET', 'work_mem', '10240', '10MB'), (1, '2019-01-02 01:00:00 CET', 'something', 'someval', 'someval'); -- fake rds settings INSERT INTO "PGTS".pg_track_settings_rds_src_tmp (srvid, ts, name, setting, setdatabase, setrole) VALUES -- previously untreated data that should be discarded (1, '2019-01-02 00:00:00 CET', 'work_mem', '5MB', 123, 0), -- data that should be processed (1, '2019-01-02 01:00:00 CET', 'work_mem', '10MB', 123, 0), (1, '2019-01-02 01:00:00 CET', 'something', 'someval', 0, 456); -- fake reboot settings INSERT INTO "PGTS".pg_track_settings_reboot_src_tmp (srvid, ts, postmaster_ts) VALUES -- previously untreated data that should not be discarded (1, '2019-01-02 00:01:00 CET', '2019-01-02 00:00:00 CET'), -- data that should also be processed (1, '2019-01-02 02:01:00 CET', '2019-01-02 01:00:00 CET'); SELECT "PGTS".pg_track_settings_snapshot_settings(1); SELECT "PGTS".pg_track_settings_snapshot_rds(1); SELECT "PGTS".pg_track_settings_snapshot_reboot(1); -- test raw data SELECT * FROM "PGTS".pg_track_settings_list ORDER BY 1, 2; SELECT * FROM "PGTS".pg_track_settings_history ORDER BY 1, 2, 3; SELECT * FROM "PGTS".pg_track_db_role_settings_list ORDER BY 1, 2; SELECT * FROM "PGTS".pg_track_db_role_settings_history ORDER BY 1, 2, 3; SELECT * FROM "PGTS".pg_reboot ORDER BY 1, 2; -- test functions SELECT name, setting_exists, setting, setting_pretty FROM "PGTS".pg_track_settings_log('work_mem', 1) ORDER BY ts ASC; SELECT name, from_setting, from_exists, to_setting, to_exists, from_setting_pretty, to_setting_pretty FROM "PGTS".pg_track_settings_diff('2019-01-01 01:00:00 CET', '2019-01-02 02:00:00 CET', 1); SELECT * FROM "PGTS".pg_track_db_role_settings_log('work_mem', 1) s ORDER BY ts ASC; SELECT * FROM "PGTS".pg_track_db_role_settings_diff('2018-12-31 02:00:00 CET', '2019-01-02 03:00:00 CET', 1) s WHERE name = 'work_mem' ORDER BY 1, 2, 3; SELECT * FROM "PGTS".pg_track_reboot_log(1); -- snapshot the pending server 2 SELECT "PGTS".pg_track_settings_snapshot_settings(2); SELECT "PGTS".pg_track_settings_snapshot_rds(2); SELECT "PGTS".pg_track_settings_snapshot_reboot(2); -- check that all data have been deleted after processing SELECT COUNT(*) FROM "PGTS".pg_track_settings_settings_src_tmp; SELECT COUNT(*) FROM "PGTS".pg_track_settings_rds_src_tmp; SELECT COUNT(*) FROM "PGTS".pg_track_settings_reboot_src_tmp; -- test the reset SELECT * FROM "PGTS".pg_track_settings_reset(1); SELECT srvid, COUNT(*) FROM "PGTS".pg_track_settings_history GROUP BY srvid; SELECT COUNT(*) FROM "PGTS".pg_track_settings_log('work_mem', 1); SELECT COUNT(*) FROM "PGTS".pg_track_settings_diff('-infinity', 'infinity', 1); SELECT COUNT(*) FROM "PGTS".pg_track_db_role_settings_log('work_mem', 1); SELECT COUNT(*) FROM "PGTS".pg_track_db_role_settings_diff('-infinity', 'infinity', 1); SELECT srvid, COUNT(*) FROM "PGTS".pg_track_db_role_settings_history GROUP BY srvid; SELECT srvid, COUNT(*) FROM "PGTS".pg_reboot GROUP BY srvid;