pax_global_header00006660000000000000000000000064137354732510014524gustar00rootroot0000000000000052 comment=91d28cec9690eadec2f8a02fca6f792ac9734354 pg_track_settings-2.0.1/000077500000000000000000000000001373547325100152365ustar00rootroot00000000000000pg_track_settings-2.0.1/.gitignore000066400000000000000000000000501373547325100172210ustar00rootroot00000000000000.*.sw* pg_track_settings-*.zip results/ pg_track_settings-2.0.1/CHANGELOG.md000066400000000000000000000012071373547325100170470ustar00rootroot00000000000000Changelog ========= 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.0.1/LICENSE000066400000000000000000000016571373547325100162540ustar00rootroot00000000000000Copyright (c) 2015-2020, 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.0.1/META.json000066400000000000000000000022141373547325100166560ustar00rootroot00000000000000{ "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.0.1/Makefile000066400000000000000000000016061373547325100167010ustar00rootroot00000000000000EXTENSION = 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.0.1/README.md000066400000000000000000000101461373547325100165170ustar00rootroot00000000000000pg_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.0.1/debian/000077500000000000000000000000001373547325100164605ustar00rootroot00000000000000pg_track_settings-2.0.1/debian/changelog000066400000000000000000000004601373547325100203320ustar00rootroot00000000000000pg-track-settings (2.0.1-1) unstable; urgency=medium * New upstream version. -- Julien Rouhaud Fri, 02 Oct 2020 00:34:41 +0000 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.0.1/debian/control000066400000000000000000000020101373547325100200540ustar00rootroot00000000000000Source: pg-track-settings Section: database Priority: optional Maintainer: Julien Rouhaud Standards-Version: 4.5.0 Build-Depends: debhelper-compat (= 13), postgresql-server-dev-all (>= 141~) 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-12-pg-track-settings Architecture: all Depends: ${misc:Depends}, postgresql-12 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.0.1/debian/control.in000066400000000000000000000020261373547325100204700ustar00rootroot00000000000000Source: pg-track-settings Section: database Priority: optional Maintainer: Julien Rouhaud Standards-Version: 4.5.0 Build-Depends: debhelper-compat (= 13), postgresql-server-dev-all (>= 141~) 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.0.1/debian/copyright000066400000000000000000000016571373547325100204240ustar00rootroot00000000000000Copyright (c) 2015-2020, 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.0.1/debian/pgversions000066400000000000000000000000051373547325100205750ustar00rootroot000000000000009.4+ pg_track_settings-2.0.1/debian/rules000077500000000000000000000014011373547325100175340ustar00rootroot00000000000000#!/usr/bin/make -f PKGVER = $(shell dpkg-parsechangelog | awk -F '[:-]' '/^Version:/ { print substr($$2, 2) }') EXCLUDE = --exclude-vcs --exclude=debian include /usr/share/postgresql-common/pgxs_debian_control.mk override_dh_auto_build: # do nothing override_dh_auto_test: # nothing to do here, upstream tests used, see debian/tests/* override_dh_auto_install: # build all supported versions +pg_buildext loop postgresql-%v-pg-track-settings override_dh_installdocs: dh_installdocs --all README.md rm -rvf debian/*/usr/share/doc/postgresql-doc-* override_dh_installchangelogs: dh_installchangelogs CHANGELOG.md upstream orig: debian/control clean cd .. && tar czf pg-track-settings_$(PKGVER).orig.tar.gz $(EXCLUDE) pg-track-settings-$(PKGVER) %: dh $@ pg_track_settings-2.0.1/debian/source/000077500000000000000000000000001373547325100177605ustar00rootroot00000000000000pg_track_settings-2.0.1/debian/source/format000066400000000000000000000000141373547325100211660ustar00rootroot000000000000003.0 (quilt) pg_track_settings-2.0.1/debian/tests/000077500000000000000000000000001373547325100176225ustar00rootroot00000000000000pg_track_settings-2.0.1/debian/tests/control000066400000000000000000000001251373547325100212230ustar00rootroot00000000000000Depends: @, postgresql-server-dev-all Tests: installcheck Restrictions: allow-stderr pg_track_settings-2.0.1/debian/tests/installcheck000077500000000000000000000000551373547325100222140ustar00rootroot00000000000000#!/bin/sh set -eu pg_buildext installcheck pg_track_settings-2.0.1/debian/watch000066400000000000000000000001321373547325100175050ustar00rootroot00000000000000version=3 https://github.com/rjuju/pg_track_settings/releases .*/archive/([0-9].*).tar.gz pg_track_settings-2.0.1/expected/000077500000000000000000000000001373547325100170375ustar00rootroot00000000000000pg_track_settings-2.0.1/expected/pg_track_settings.out000066400000000000000000000275561373547325100233210ustar00rootroot00000000000000SET search_path = ''; SET timezone TO 'Europe/Paris'; CREATE EXTENSION pg_track_settings; -- test main config history SELECT COUNT(*) FROM public.pg_track_settings_history; count ------- 0 (1 row) SET work_mem = '10MB'; SELECT * FROM public.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 public.pg_track_settings_snapshot(); pg_track_settings_snapshot ---------------------------- t (1 row) SELECT name, setting_exists, setting, setting_pretty FROM public.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 public.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 public.pg_track_settings_snapshot(); pg_track_settings_snapshot ---------------------------- t (1 row) ALTER ROLE postgres SET work_mem = '2MB'; SELECT * FROM public.pg_track_settings_snapshot(); pg_track_settings_snapshot ---------------------------- t (1 row) ALTER ROLE postgres IN DATABASE postgres SET work_mem = '3MB'; SELECT * FROM public.pg_track_settings_snapshot(); pg_track_settings_snapshot ---------------------------- t (1 row) SELECT * FROM public.pg_track_settings_snapshot(); pg_track_settings_snapshot ---------------------------- t (1 row) SELECT COALESCE(datname, '-') AS datname, setrole::regrole, name, setting_exists, setting FROM public.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 public.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 public.pg_track_settings_snapshot(); pg_track_settings_snapshot ---------------------------- t (1 row) ALTER ROLE postgres RESET work_mem; SELECT * FROM public.pg_track_settings_snapshot(); pg_track_settings_snapshot ---------------------------- t (1 row) ALTER ROLE postgres IN DATABASE postgres RESET work_mem; SELECT * FROM public.pg_track_settings_snapshot(); pg_track_settings_snapshot ---------------------------- t (1 row) -- test pg_reboot SELECT COUNT(*) FROM public.pg_reboot; count ------- 1 (1 row) SELECT now() - ts > interval '2 second' FROM public.pg_reboot; ?column? ---------- t (1 row) SELECT now() - ts > interval '2 second' FROM public.pg_track_reboot_log(); ?column? ---------- t (1 row) -- test the reset SELECT * FROM public.pg_track_settings_reset(); pg_track_settings_reset ------------------------- (1 row) SELECT COUNT(*) FROM public.pg_track_settings_history; count ------- 0 (1 row) SELECT COUNT(*) FROM public.pg_track_settings_log('work_mem'); count ------- 0 (1 row) SELECT COUNT(*) FROM public.pg_track_settings_diff(now() - interval '1 hour', now()); count ------- 0 (1 row) SELECT COUNT(*) FROM public.pg_track_db_role_settings_log('work_mem'); count ------- 0 (1 row) SELECT COUNT(*) FROM public.pg_track_db_role_settings_diff(now() - interval '1 hour', now()); count ------- 0 (1 row) SELECT COUNT(*) FROM public.pg_reboot; count ------- 0 (1 row) -------------------------- -- test remote snapshot -- -------------------------- -- fake general settings INSERT INTO public.pg_track_settings_settings_src_tmp (srvid, ts, name, setting, current_setting) VALUES (1, '2019-01-01 00:00:00 CET', 'work_mem', '0', '0MB'); -- fake rds settings INSERT INTO public.pg_track_settings_rds_src_tmp (srvid, ts, name, setting, setdatabase, setrole) VALUES (1, '2019-01-01 00:00:00 CET', 'work_mem', '0MB', 123, 0); -- fake reboot settings INSERT INTO public.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'); SELECT public.pg_track_settings_snapshot_settings(1); pg_track_settings_snapshot_settings ------------------------------------- t (1 row) SELECT public.pg_track_settings_snapshot_rds(1); pg_track_settings_snapshot_rds -------------------------------- t (1 row) SELECT public.pg_track_settings_snapshot_reboot(1); pg_track_settings_snapshot_reboot ----------------------------------- t (1 row) -- fake general settings INSERT INTO public.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 public.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 public.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 public.pg_track_settings_snapshot_settings(1); pg_track_settings_snapshot_settings ------------------------------------- t (1 row) SELECT public.pg_track_settings_snapshot_rds(1); pg_track_settings_snapshot_rds -------------------------------- t (1 row) SELECT public.pg_track_settings_snapshot_reboot(1); pg_track_settings_snapshot_reboot ----------------------------------- t (1 row) -- test raw data SELECT * FROM public.pg_track_settings_list ORDER BY 1, 2; srvid | name -------+----------- 1 | something 1 | work_mem (2 rows) SELECT * FROM public.pg_track_settings_history ORDER BY 1, 2; srvid | ts | name | setting | is_dropped | setting_pretty -------+------------------------------+-----------+---------+------------+---------------- 1 | Tue Jan 01 00:00:00 2019 CET | work_mem | 0 | f | 0MB 1 | Wed Jan 02 01:00:00 2019 CET | work_mem | 10240 | f | 10MB 1 | Wed Jan 02 01:00:00 2019 CET | something | someval | f | someval (3 rows) SELECT * FROM public.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 public.pg_track_db_role_settings_history ORDER BY 1, 2; srvid | ts | name | setdatabase | setrole | setting | is_dropped -------+------------------------------+-----------+-------------+---------+---------+------------ 1 | Tue Jan 01 00:00:00 2019 CET | work_mem | 123 | 0 | 0MB | f 1 | Wed Jan 02 01:00:00 2019 CET | work_mem | 123 | 0 | 10MB | f 1 | Wed Jan 02 01:00:00 2019 CET | something | 0 | 456 | someval | f (3 rows) SELECT * FROM public.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 public.pg_track_settings_log('work_mem', 1) ORDER BY ts ASC; name | setting_exists | setting | setting_pretty ----------+----------------+---------+---------------- work_mem | t | 0 | 0MB work_mem | t | 10240 | 10MB (2 rows) SELECT name, from_setting, from_exists, to_setting, to_exists, from_setting_pretty, to_setting_pretty FROM public.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 | 0MB | 10MB (2 rows) SELECT * FROM public.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 | 0MB Wed Jan 02 01:00:00 2019 CET | 123 | 0 | work_mem | t | 10MB (2 rows) SELECT * FROM public.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 public.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) -- check that all data have been deleted after processing SELECT COUNT(*) FROM public.pg_track_settings_settings_src_tmp; count ------- 0 (1 row) SELECT COUNT(*) FROM public.pg_track_settings_rds_src_tmp; count ------- 0 (1 row) SELECT COUNT(*) FROM public.pg_track_settings_reboot_src_tmp; count ------- 0 (1 row) -- test the reset SELECT * FROM public.pg_track_settings_reset(1); pg_track_settings_reset ------------------------- (1 row) SELECT COUNT(*) FROM public.pg_track_settings_history; count ------- 0 (1 row) SELECT COUNT(*) FROM public.pg_track_settings_log('work_mem', 1); count ------- 0 (1 row) SELECT COUNT(*) FROM public.pg_track_settings_diff('-infinity', 'infinity', 1); count ------- 0 (1 row) SELECT COUNT(*) FROM public.pg_track_db_role_settings_log('work_mem', 1); count ------- 0 (1 row) SELECT COUNT(*) FROM public.pg_track_db_role_settings_diff('-infinity', 'infinity', 1); count ------- 0 (1 row) SELECT COUNT(*) FROM public.pg_reboot; count ------- 0 (1 row) pg_track_settings-2.0.1/pg_track_settings--1.0.0--1.0.1.sql000066400000000000000000000125341373547325100227150ustar00rootroot00000000000000-- This program is open source, licensed under the PostgreSQL License. -- For license terms, see the LICENSE file. -- -- Copyright (C) 2015-2020: 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.0.1/pg_track_settings--1.0.0.sql000066400000000000000000000234371373547325100222110ustar00rootroot00000000000000-- This program is open source, licensed under the PostgreSQL License. -- For license terms, see the LICENSE file. -- -- Copyright (C) 2015-2020: 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.0.1/pg_track_settings--1.0.1--1.1.0.sql000066400000000000000000000157341373547325100227230ustar00rootroot00000000000000-- This program is open source, licensed under the PostgreSQL License. -- For license terms, see the LICENSE file. -- -- Copyright (C) 2015-2020: 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.0.1/pg_track_settings--1.0.1.sql000066400000000000000000000235171373547325100222110ustar00rootroot00000000000000-- This program is open source, licensed under the PostgreSQL License. -- For license terms, see the LICENSE file. -- -- Copyright (C) 2015-2020: 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.0.1/pg_track_settings--1.1.0.sql000066400000000000000000000244231373547325100222060ustar00rootroot00000000000000-- This program is open source, licensed under the PostgreSQL License. -- For license terms, see the LICENSE file. -- -- Copyright (C) 2015-2020: 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.0.1/pg_track_settings--2.0.0--2.0.1.sql000066400000000000000000000070421373547325100227150ustar00rootroot00000000000000-- This program is open source, licensed under the PostgreSQL License. -- For license terms, see the LICENSE file. -- -- Copyright (C) 2015-2020: 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.0.1/pg_track_settings--2.0.0.sql000066400000000000000000000415101373547325100222020ustar00rootroot00000000000000-- This program is open source, licensed under the PostgreSQL License. -- For license terms, see the LICENSE file. -- -- Copyright (C) 2015-2020: 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.0.1/pg_track_settings--2.0.1.sql000066400000000000000000000415221373547325100222060ustar00rootroot00000000000000-- This program is open source, licensed under the PostgreSQL License. -- For license terms, see the LICENSE file. -- -- Copyright (C) 2015-2020: 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.0.1/pg_track_settings.control000066400000000000000000000002431373547325100223510ustar00rootroot00000000000000comment = 'Track settings changes' default_version = '2.0.1' module_pathname = '$libdir/pg_track_settings' superuser = false relocatable = false schema = 'public' pg_track_settings-2.0.1/test/000077500000000000000000000000001373547325100162155ustar00rootroot00000000000000pg_track_settings-2.0.1/test/sql/000077500000000000000000000000001373547325100170145ustar00rootroot00000000000000pg_track_settings-2.0.1/test/sql/pg_track_settings.sql000066400000000000000000000140761373547325100232570ustar00rootroot00000000000000SET search_path = ''; SET timezone TO 'Europe/Paris'; CREATE EXTENSION pg_track_settings; -- test main config history SELECT COUNT(*) FROM public.pg_track_settings_history; SET work_mem = '10MB'; SELECT * FROM public.pg_track_settings_snapshot(); SELECT pg_catalog.pg_sleep(1); SET work_mem = '5MB'; SELECT * FROM public.pg_track_settings_snapshot(); SELECT name, setting_exists, setting, setting_pretty FROM public.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 public.pg_track_settings_diff(now() - interval '500 ms', now()); -- test pg_db_role_settings ALTER DATABASE postgres SET work_mem = '1MB'; SELECT * FROM public.pg_track_settings_snapshot(); ALTER ROLE postgres SET work_mem = '2MB'; SELECT * FROM public.pg_track_settings_snapshot(); ALTER ROLE postgres IN DATABASE postgres SET work_mem = '3MB'; SELECT * FROM public.pg_track_settings_snapshot(); SELECT * FROM public.pg_track_settings_snapshot(); SELECT COALESCE(datname, '-') AS datname, setrole::regrole, name, setting_exists, setting FROM public.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 public.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 public.pg_track_settings_snapshot(); ALTER ROLE postgres RESET work_mem; SELECT * FROM public.pg_track_settings_snapshot(); ALTER ROLE postgres IN DATABASE postgres RESET work_mem; SELECT * FROM public.pg_track_settings_snapshot(); -- test pg_reboot SELECT COUNT(*) FROM public.pg_reboot; SELECT now() - ts > interval '2 second' FROM public.pg_reboot; SELECT now() - ts > interval '2 second' FROM public.pg_track_reboot_log(); -- test the reset SELECT * FROM public.pg_track_settings_reset(); SELECT COUNT(*) FROM public.pg_track_settings_history; SELECT COUNT(*) FROM public.pg_track_settings_log('work_mem'); SELECT COUNT(*) FROM public.pg_track_settings_diff(now() - interval '1 hour', now()); SELECT COUNT(*) FROM public.pg_track_db_role_settings_log('work_mem'); SELECT COUNT(*) FROM public.pg_track_db_role_settings_diff(now() - interval '1 hour', now()); SELECT COUNT(*) FROM public.pg_reboot; -------------------------- -- test remote snapshot -- -------------------------- -- fake general settings INSERT INTO public.pg_track_settings_settings_src_tmp (srvid, ts, name, setting, current_setting) VALUES (1, '2019-01-01 00:00:00 CET', 'work_mem', '0', '0MB'); -- fake rds settings INSERT INTO public.pg_track_settings_rds_src_tmp (srvid, ts, name, setting, setdatabase, setrole) VALUES (1, '2019-01-01 00:00:00 CET', 'work_mem', '0MB', 123, 0); -- fake reboot settings INSERT INTO public.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'); SELECT public.pg_track_settings_snapshot_settings(1); SELECT public.pg_track_settings_snapshot_rds(1); SELECT public.pg_track_settings_snapshot_reboot(1); -- fake general settings INSERT INTO public.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 public.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 public.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 public.pg_track_settings_snapshot_settings(1); SELECT public.pg_track_settings_snapshot_rds(1); SELECT public.pg_track_settings_snapshot_reboot(1); -- test raw data SELECT * FROM public.pg_track_settings_list ORDER BY 1, 2; SELECT * FROM public.pg_track_settings_history ORDER BY 1, 2; SELECT * FROM public.pg_track_db_role_settings_list ORDER BY 1, 2; SELECT * FROM public.pg_track_db_role_settings_history ORDER BY 1, 2; SELECT * FROM public.pg_reboot ORDER BY 1, 2; -- test functions SELECT name, setting_exists, setting, setting_pretty FROM public.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 public.pg_track_settings_diff('2019-01-01 01:00:00 CET', '2019-01-02 02:00:00 CET', 1); SELECT * FROM public.pg_track_db_role_settings_log('work_mem', 1) s ORDER BY ts ASC; SELECT * FROM public.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 public.pg_track_reboot_log(1); -- check that all data have been deleted after processing SELECT COUNT(*) FROM public.pg_track_settings_settings_src_tmp; SELECT COUNT(*) FROM public.pg_track_settings_rds_src_tmp; SELECT COUNT(*) FROM public.pg_track_settings_reboot_src_tmp; -- test the reset SELECT * FROM public.pg_track_settings_reset(1); SELECT COUNT(*) FROM public.pg_track_settings_history; SELECT COUNT(*) FROM public.pg_track_settings_log('work_mem', 1); SELECT COUNT(*) FROM public.pg_track_settings_diff('-infinity', 'infinity', 1); SELECT COUNT(*) FROM public.pg_track_db_role_settings_log('work_mem', 1); SELECT COUNT(*) FROM public.pg_track_db_role_settings_diff('-infinity', 'infinity', 1); SELECT COUNT(*) FROM public.pg_reboot;