├── .github ├── powa-archivist.toml.template └── workflows │ ├── powa_archivist.yml │ ├── powa_archivist_git.yml │ └── tests.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTORS.md ├── INSTALL.md ├── LICENSE.md ├── META.json ├── Makefile ├── PL_funcs.md ├── README.md ├── expected ├── 00_setup.out ├── 00_setup_1.out ├── 01_general.out ├── 02_remote_api.out ├── 03_db_module.out ├── 04_catalog.out ├── 05_module.out ├── 10_acl.out └── 99_cleanup.out ├── install_all.sql ├── powa--5.0.0--5.0.1.sql ├── powa--5.0.0.sql ├── powa--5.0.1--5.0.2.sql ├── powa--5.0.1.sql ├── powa--5.0.2--5.0.3.sql ├── powa--5.0.2.sql ├── powa--5.0.3.sql ├── powa.c ├── powa.control ├── reindent.sh └── sql ├── 00_setup.sql ├── 01_general.sql ├── 02_remote_api.sql ├── 03_db_module.sql ├── 04_catalog.sql ├── 05_module.sql ├── 10_acl.sql └── 99_cleanup.sql /.github/powa-archivist.toml.template: -------------------------------------------------------------------------------- 1 | extname = 'powa' 2 | from = '%%FROM_VER%%' 3 | to = '%%TO_VER%%' 4 | extra_queries = [ 5 | ] 6 | pre_upgrade_queries = [ 7 | 'SELECT powa_take_snapshot()', 8 | 'RESET application_name', 9 | ] 10 | -------------------------------------------------------------------------------- /.github/workflows/powa_archivist.yml: -------------------------------------------------------------------------------- 1 | name: Trigger build and push of powa-archivist image 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | env: 8 | TARGET_REPO: "powa-podman" 9 | EVENT_TYPE: "powa-archivist" 10 | 11 | jobs: 12 | trigger_build: 13 | name: Trigger build and push of powa-archivist in powa-podman repo 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Trigger the powa-archivist-git repository dispatch 17 | run: | 18 | # Set variables 19 | org="${{ github.repository_owner }}" 20 | repo="${{ env.TARGET_REPO }}" 21 | event_type="${{ env.EVENT_TYPE }}" 22 | 23 | curl -L \ 24 | -X POST \ 25 | -H "Accept: application/vnd.github+json" \ 26 | -H "Authorization: Bearer ${{ secrets.DISPATCH_TOKEN }}" \ 27 | -H "X-GitHub-Api-Version: 2022-11-28" \ 28 | https://api.github.com/repos/${org}/${repo}/dispatches \ 29 | -d "{\"event_type\": \"${event_type}\"}" 30 | -------------------------------------------------------------------------------- /.github/workflows/powa_archivist_git.yml: -------------------------------------------------------------------------------- 1 | name: Trigger build and push of powa-archivist-git image 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | 7 | env: 8 | TARGET_REPO: "powa-podman" 9 | EVENT_TYPE: "powa-archivist-git" 10 | 11 | jobs: 12 | trigger_build: 13 | name: Trigger build and push of powa-archivist-git in powa-podman repo 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Trigger the powa-archivist-git repository dispatch 17 | run: | 18 | # Set variables 19 | org="${{ github.repository_owner }}" 20 | repo="${{ env.TARGET_REPO }}" 21 | event_type="${{ env.EVENT_TYPE }}" 22 | 23 | curl -L \ 24 | -X POST \ 25 | -H "Accept: application/vnd.github+json" \ 26 | -H "Authorization: Bearer ${{ secrets.DISPATCH_TOKEN }}" \ 27 | -H "X-GitHub-Api-Version: 2022-11-28" \ 28 | https://api.github.com/repos/${org}/${repo}/dispatches \ 29 | -d "{\"event_type\": \"${event_type}\"}" 30 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Run powa-archivist tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | env: 12 | DATADIR: /dev/shm/data 13 | LOGFILE: /dev/shm/data/logfile 14 | RUST: 1.75.0 15 | 16 | jobs: 17 | powa-archivist_tests: 18 | name: powa-archivist tests 19 | runs-on: ${{ matrix.os }} 20 | 21 | strategy: 22 | matrix: 23 | postgres_major_version: [ 24 | "9.5", 25 | "9.6", 26 | "10", 27 | "11", 28 | "12", 29 | "13", 30 | "14", 31 | "15", 32 | "16" 33 | ] 34 | os: ["ubuntu-22.04"] 35 | 36 | steps: 37 | - uses: actions/checkout@v4 38 | 39 | - name: Set up prerequisites and environment 40 | run: | 41 | echo "************ CLEAN IMAGE ***********" 42 | sudo apt remove -y '^postgres.*' '^libpq.*' 43 | echo "" 44 | 45 | echo "********* REPOSITORY SET UP ********" 46 | sudo apt-get install -y wget gnupg 47 | sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' 48 | wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - 49 | sudo apt-get update -y -qq --fix-missing 50 | echo "" 51 | 52 | echo "*********** ENVIRONMENT ************" 53 | export PG_MAJOR_VERSION=${{ matrix.postgres_major_version }} 54 | echo "PG_MAJOR_VERSION=$PG_MAJOR_VERSION" >> $GITHUB_ENV 55 | echo "MAKEFLAGS=$MAKEFLAGS -j $(grep -c ^processor /proc/cpuinfo)" >> $GITHUB_ENV 56 | echo "" 57 | 58 | echo "******** INSTALL POSTGRES **********" 59 | sudo apt-get install -y \ 60 | postgresql-$PG_MAJOR_VERSION \ 61 | postgresql-server-dev-$PG_MAJOR_VERSION \ 62 | postgresql-contrib-$PG_MAJOR_VERSION 63 | echo "" 64 | 65 | echo "******* INSTALL DEPENDENCIES *******" 66 | sudo apt-get install -y \ 67 | gcc \ 68 | make \ 69 | build-essential \ 70 | pkg-config 71 | echo "" 72 | 73 | echo "********** READJUST PATH ***********" 74 | export PATH=$(pg_config --bindir):$PATH 75 | echo "PATH=$PATH" >> $GITHUB_ENV 76 | cat $GITHUB_ENV 77 | echo "" 78 | 79 | - name: Start a postgres ${{ matrix.postgres_major_version }} server 80 | run: | 81 | sudo chmod a+rwx /var/run/postgresql/ 82 | pg_ctl -D $DATADIR initdb 83 | echo "shared_preload_libraries = 'pg_stat_statements'" >> $DATADIR/postgresql.conf 84 | pg_ctl -D $DATADIR -l $LOGFILE start || cat $LOGFILE 85 | # a sleep is required for pg9.6 (at least) 86 | sleep 1 87 | psql -c 'select 1 as ok' postgres 88 | 89 | - name: Build and install powa-archivist for postgres ${{ matrix.postgres_major_version }} 90 | run: | 91 | make 92 | sudo make install 93 | 94 | - name: Run powa-archivist tests for postgres ${{ matrix.postgres_major_version }} 95 | run: make installcheck || ( errcode=$?; cat regression.diffs && exit $errcode ) 96 | 97 | - name: Check pg_dump for postgres ${{ matrix.postgres_major_version }} 98 | run: pg_dump -d contrib_regression 99 | 100 | - name: Check extension install vs upgrade for postgres ${{ matrix.postgres_major_version }} 101 | run: | 102 | # install dependencies 103 | sudo apt-get install -y silversearcher-ag 104 | 105 | # install rust 106 | rustup toolchain install ${{ env.RUST }} 107 | 108 | # install pg_validate_extupgrade 109 | git clone https://github.com/rjuju/pg_validate_extupgrade.git 110 | cd pg_validate_extupgrade 111 | cargo build 112 | cd .. 113 | 114 | # roles created by an extensions are not removed in case of rollback, 115 | # so we create one of the default pseudo predefined roles to make sure 116 | # that no pseudo predefined roles will automatically be created 117 | createuser powa_admin 118 | 119 | # CREATAE EXTENSION ... CASCADE is only supported on pg9.6+ 120 | if [[ "${{ matrix.postgres_major_version }}" == "9.5" ]]; then 121 | psql -Xc "CREATE EXTENSION btree_gist" postgres 122 | psql -Xc "CREATE EXTENSION pg_stat_statements" postgres 123 | fi 124 | 125 | # get the default extension version 126 | to_ver=$(ag default_version powa.control | ag -o "(\d+\.?)+") 127 | echo "to_ver: ${to_ver}" 128 | 129 | # Check the number of extension scripts containing the default versions 130 | nb=$(ls *--${to_ver}.sql | wc -l) 131 | 132 | # If only one sql script found with the default version, it should be a 133 | # new major version that is allowed to no provide an upgrade script. 134 | if [[ ${nb} -eq 1 ]]; then 135 | # Check that it's a new major version 136 | echo "${to_ver}" | ag '\.0\.0$' 137 | 138 | echo "New major version without ugprade script" 139 | exit 0 140 | fi 141 | 142 | # Get the previous version 143 | from_ver=$(ls *--*--${to_ver}.sql \ 144 | | ag -o 'powa--\d+\.\d+\.\d+' \ 145 | | ag -o "\d+\.\d+\.\d+") 146 | 147 | # Generate the config file 148 | cat .github/powa-archivist.toml.template \ 149 | | sed "s/%%FROM_VER%%/${from_ver}/" \ 150 | | sed "s/%%TO_VER%%/${to_ver}/" \ 151 | > powa-archivist.toml 152 | 153 | # Run pg_validate_extupgrade 154 | ./pg_validate_extupgrade/target/debug/pg_validate_extupgrade \ 155 | -d postgres \ 156 | -c ./powa-archivist.toml 157 | 158 | - name: Stop the running postgres ${{ matrix.postgres_major_version }} server 159 | run: pg_ctl -D $DATADIR stop 160 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.so 3 | *.zip 4 | results/ 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 5.0.2 2 | 3 | - Performance 4 | - Smooth out the coalesces and purges (Marc Cousin and Julien Rouhaud) 5 | - Bugfixes 6 | - Fix DROP EXTENSION issues when powa is installed (Julien Rouhaud, thanks 7 | to Michael Vitale for the report) 8 | - Fix aggregation of pg_stat_user_functions datasource (Marc Cousin) 9 | - Fix pg_stat_replication compatibility on pg12 and below (Julien Rouhaud) 10 | - Fix powa_delete_and_purge_server to also purge `*_src_tmp tables` (Julien 11 | Rouhaud, thanks to github user jw1u1 for the report) 12 | - Misc 13 | - Fix compatibility with pg18 (Georgy Shelkovy) 14 | 15 | ## 5.0.1 16 | 17 | - Bugfixes 18 | - Fix pg_dump error on powa_module_config (Julien Rouhaud, thanks to github 19 | user guruguruguru for the report) 20 | - Fix powa_delete_and_purge_server function when pg_track_settings is 21 | installed (Julien Rouhaud, thanks to Thomas Reiss for report) 22 | 23 | ## 5.0.0 24 | 25 | This is a major rework of PoWA. Please note that there is no upgrade procedure 26 | to switch to this version. You need to remove the old one and install the new 27 | 5.0.0 version. 28 | 29 | - Breaking changes 30 | - Remove support for postgres 9.4 (Julien Rouhaud) 31 | - New feature 32 | - Allow installation of any extensions in any schema (Julien Rouhaud) 33 | - Introduce snapshot of per-database object (remote mode only) (Julien 34 | Rouhaud) 35 | - Introduce snapshot of per-database catalogs (remote mode only) (Julien 36 | Rouhaud) 37 | - Add powa_* pseudo predefined roles to ease permissions (Julien Rouhaud) 38 | - Add pg_stat_activity metrics (Julien Rouhaud) 39 | - Add pg_stat_archiver metrics (Julien Rouhaud) 40 | - Add pg_stat_replication metrics (Julien Rouhaud) 41 | - Add pg_stat_replication_slots metrics (Julien Rouhaud) 42 | - Add pg_stat_database metrics (Julien Rouhaud) 43 | - Add pg_stat_io metrics (Julien Rouhaud) 44 | - Add JIT metrics in pg_stat_statements 1.10 and 1.11 45 | - Add pg_stat_database_conflicts metrics (Julien Rouhaud) 46 | - Add pg_stat_slru metrics (Julien Rouhaud) 47 | - Add pg_stat_wal metrics (Julien Rouhaud) 48 | - Add pg_stat_wal_receiver metrics (Julien Rouhaud) 49 | - Add pg_stat_subscription metrics (Julien Rouhaud) 50 | - Add pg_stat_subscription_stats metrics (Julien Rouhaud) 51 | - Bugfixes 52 | - Fix long standing bug in pg_stat_kcache metrics calculation (Julien 53 | Rouhaud) 54 | - Misc 55 | - Add compatibility with postgres 17 (Julien Rouhaud) 56 | - Remove powa_stat_* handling from powa_functions (Julien Rouhaud) 57 | - Add compatibility with pg_stat_statements 1.11 (Julien Rouhaud) 58 | - Improve aggregated record lookup performance (Marc Cousin, Julien Rouhaud) 59 | 60 | ## 4.2.2 61 | 62 | - Bugfixes 63 | - Fix the toplevel field position in powa_statements_src function (Julien 64 | Rouhaud, thanks to Thomas Reiss for the report) 65 | 66 | ## 4.2.1 67 | 68 | - Bugfixes 69 | - Fix an issue in 4.1.4 - 4.2.0 extension script that can prevent 70 | upgrading. Versions 4.2.0 and 4.2.1 are identical, version 4.2.1 only 71 | exist to provide a fixed 4.1.4 - 4.2.1 direct ugprade script (Julien 72 | Rouhaud, thanks to Yuriy Vountesmery for the report) 73 | 74 | ## 4.2.0 75 | 76 | - New feature 77 | - Add pg_stat_statements.toplevel field (Marc Cousin, Julien Rouhaud) 78 | - Bugfixes 79 | - Fix a possible long waiting time when the background worker is asleep and 80 | another session is waiting for a pending event, like a DROP DATABASE 81 | (Julien Rouhaud, thanks for github user anikin-aa for the report) 82 | - Fix pg_stat_kcache support when not all metrics are available (Julien 83 | Rouhaud) 84 | - Misc 85 | - Add compatibility with postgres 16 (Julien Rouhaud) 86 | - Immediately exit the bgworker in binary upgrade mode, which could lead to 87 | data corruption in the powa database (Julien Rouhaud) 88 | 89 | ## 4.1.4 90 | 91 | - Bugfixes 92 | - Fix compatibility with standard_conforming_strings = off (Julien Rouhaud, 93 | per report from github user arnobnq) 94 | - Misc 95 | - Add compatibility with postgres 15 96 | 97 | ## 4.1.3 98 | 99 | - Bugfixes 100 | - Handle possibly duplicated query in powa_statements_src (Julien Rouhaud, 101 | per report from github user gjedeer) 102 | - Fix column name and ORDER BY in 103 | powa_qualstats_aggregate_constvalues_current function (Adrien Nayrat) 104 | - Fix powa_kcache_src compatibility with pgsk 2.1- (Julien Rouhaud, per 105 | report from github user hrawulwa) 106 | - Fix powa_wait_sampling_unregister() to accept a server id (Julien 107 | Rouhaud) 108 | - Performance improvement 109 | - Rewrite powa_qualstats_aggregate_constvalues_current with Window 110 | Function, making those at least twice as fast (Adrien Nayrat) 111 | - Misc 112 | - Narrow the error condition in powa_prevent_concurrent_snapshot() (Denis 113 | Laxalde) 114 | - Make sure that GUC won't leak the extension script (Julien Rouhaud) 115 | - Don't rely on public being in search_path in event trigger code (Julien 116 | Rouhaud) 117 | ## 4.1.2 118 | 119 | - Bugfixes 120 | - Fix remote server removal by adding ON UPDATE / ON DELETE CASCADE clause 121 | for the powa_extensions foreign keys. Thanks to Andriy Bartash for the 122 | report. 123 | - Fix pg_qualstats snapshot. Thanks to github user alepaes1975 for the 124 | report. 125 | - Clear `*_src_tmp` tables on reset operation. Those tables are supposed 126 | to be empty, but if anything goes wrong and those tables contains 127 | problematic data, it could prevent users from cleaning up the situation. 128 | Thanks to Marc Cousin for the report. 129 | 130 | ## 4.1.1 131 | 132 | - Bugfixes 133 | - Fix regression tests for version 4.1 (Christoph Berg) 134 | 135 | ## 4.1.0 136 | 137 | - New features: 138 | - Add compatibility with pg_stat_statements 1.8 / pg13 (Julien Rouhaud) 139 | - Clean up statements that haven't been executed during the configured 140 | retention interval (Andriy Bartash) 141 | - Store the postgres and external extension versios in PoWA catalog (Julien 142 | Rouhaud) 143 | - Add compatibility with pg_stat_kcache 2.2 (Julien Rouhaud) 144 | - Don't require to load 'powa' anymore (Julien Rouhaud) 145 | - Bugfixes 146 | - Ignore quals that don't match powa_statements row during snapshot. This 147 | fixes pg_qualstats foreign key errors that can happen during snapshots 148 | (Julien Rouhaud) 149 | - Fix pg_wait_sampling counters if multiple users run the same queries 150 | (Marc Cousin and Julien Rouhaud) 151 | 152 | ## 4.0.1 153 | 154 | - Bugfixes 155 | - Fix typo in powa_all_relations_history_db_div 156 | - Fix regression tests 157 | 158 | ## 4.0.0 159 | 160 | This is a major rework of PoWA. Please note that there is no upgrade procedure 161 | to switch to this version. You need to remove the old one and install the new 162 | 4.0.0 version. 163 | 164 | - New features: 165 | - Add a remote capture mode, allowing to gather data from a remote server 166 | and store them on a central repository. This avoids the overhead of 167 | storig and processing performance data on the local instance, and also 168 | allows using PoWA on hot-standby server (Julien Rouhaud, Thanks to Adrien 169 | Nayrat for extensive testing). 170 | - Store new metrics added in pg_stat_kcache 2.1.0 (Julien Rouhaud) 171 | - Aggregate relation statistics per database (Alexander Kukushkin) 172 | - Add support for pg_qualstats 2.0.0 (Julien Rouhaud) 173 | - Add a query_cleanup column to powa_functions (Julien Rouhaud) 174 | - Miscellaneous: 175 | - Add support for makefile option NO_PGXS (Julien Rouhaud) 176 | - Cleanup old databases after the expiration period, and stop gathering 177 | data belonging to dropped database when doing the snapshots (Marc Cousin) 178 | - Fix possible bug with background worker type in pg_stat_activity 179 | (github user ppetrov91) 180 | - Add some missing indexes (Julien Rouhaud, thanks to PoWA for noticing) 181 | - Add compatibility with upcoming pg13 (Julien Rouhaud) 182 | - Reduce noise is powa is disabled and the target database doesn't exist 183 | (Julien Rouhaud) 184 | - Bugfix 185 | - Schema qualify powa_take_snapshot() call, so powa can work without public 186 | being in the superuser search_path (Julien Rouhaud) 187 | - Fix powa_snapshot_metas dump config (Julien Rouhaud, reported by Adrien 188 | Nayrat) 189 | - Fix long standing bug in pg_qualstats aggregation 190 | - Fix typos in SQL comments (Magnus Hagander) 191 | 192 | ## 3.2.0 (2018-10-14) 193 | 194 | - New features: 195 | - Add support for pg_wait_sampling extension (Julien Rouhaud) 196 | - Miscellaneous: 197 | - Reduce logs when PoWA is deactivated (Julien Rouhaud) 198 | - Bugfix: 199 | - Fix possible bug if an error happens during the stats retrieval (Julien 200 | Rouhaud) 201 | 202 | ## 3.1.2 (2018-05-30) 203 | 204 | - Miscellaneous: 205 | - Add pg11 compatibility (Julien Rouhaud) 206 | - Catch errors in Debian packaging test script (Christoph Berg, spotted by 207 | Niels Thykier) 208 | 209 | ## 3.1.1 (2017-09-19) 210 | 211 | - Bugfix: 212 | - Fix unsafe coding with sighup handler (Andreas Seltenreich, Julien 213 | Rouhaud) 214 | - Make sure we wait at least powa.frequency between two snapshot (Marc Cousin 215 | and Julien Rouhaud) 216 | - Fix win32 portability of compute_powa_frequeny() (Julien Rouhaud) 217 | - Don't try to read dbentry->tables if it's NULL (Julien Rouhaud) 218 | - Fix compilation for platform with HAVE_CLOCK_GETTIME (Julien Rouhaud, 219 | reported by Maxence Ahlouche) 220 | - Miscellaneous: 221 | - Add pg10 Compatibility (Julien Rouhaud) 222 | - Only execute once the powa_stat functions (Julien Rouhaud) 223 | 224 | ## 3.1.0 (2016-07-29) 225 | 226 | - Fix issue leading to impossibility to stop the worker without shutting down 227 | the database 228 | - Fix cluster wide statistics to get fresh values 229 | - Report PoWA collector activity in pg_stat_activity and process title 230 | - add a new powa.debug parameter 231 | - Purge at the same frequency as we coalesce. We just don't do both at the same iteration 232 | - Fix bloat issue 233 | - Add + and / operators on powa types to get delta and counters per second 234 | given two records 235 | 236 | ## 3.0.1 (2016-02-09) 237 | 238 | - Don't track 2PC related statements, as they're not normalized by 239 | pg_stat_statements. Upgrade script will do all the needed cleanup. 240 | - Restore the install_all.sql file to easily setup PoWA. 241 | - Maintain a cache of pg_database to allow seeing dropped database in the UI. 242 | See issue https://github.com/powa-team/powa/issues/63 243 | - Don't try to load PoWA if it's not in shared_preload_libraries 244 | 245 | ## 3.0.0 (2015-11-06) 246 | 247 | This is a major rework of PoWA. Please note that there is no upgrade procedure 248 | to switch to this version. You need to remove the old one and install the new 249 | 3.0.0 version. 250 | 251 | - Handle pg_qualtats 0.0.7 252 | - Sample cluster wide statistics, for relations and functions 253 | - Fix the powa reset function, and rename it to powa_reset() 254 | - Add min/max records to improve performance when analyzing big time interval 255 | - Allow disabling some statistics sampling 256 | - Handle pg_track_settings extension 257 | - Add a GUC to ignore some users activity in sampled data 258 | 259 | ## 2.0.1 (2015-07-27) 260 | 261 | - Handle creation/suppression of supported extensions. 262 | - Remove the install_all script 263 | 264 | ## 2.0 (2015-02-06) 265 | 266 | Major rework of the extension. PoWA 2 is now only compatible with PostgreSQL 267 | version 9.4 and above. PoWA 2 is also now compatible with external extensions, 268 | such as [pg_qualstats](https://github.com/powa-team/pg_qualstats) or 269 | [pg_stat_kcache](https://github.com/powa-team/pg_stat_kcache). Third-part 270 | extensions can also now be implemented easily. 271 | 272 | The UI is also now in a [new repository](https://github.com/powa-team/powa-web), 273 | with more frequent release cycle. 274 | 275 | ## 1.2.1 (2015-01-16) 276 | 277 | No changes in core. 278 | 279 | New features and changes in UI : 280 | - UI is now compatible with mojolicious 5.0 and more 281 | - UI can now connect to multiple servers, and credentials can be specified for each server 282 | - Use ISO 8601 timestamp format 283 | - Add POWA_CONFIG_FILE variable to specify config file location 284 | - Better charts display on small screens 285 | 286 | When upgrading from 1.2: 287 | - No change on the extension 288 | - the format of the database section of the powa.conf has changed, to allow multiple servers specification. Please read INSTALL.md for more details about it. 289 | 290 | ## 1.2 (2014-10-27) 291 | 292 | News features and fixes in core : 293 | - Display more metrics : temporary data, I/O time, average runtime 294 | - Fix timestamp for snapshots 295 | - DEALLOCATE and BEGIN statements are now ignored 296 | - PoWA history tables are now marked as "to be dumped" by pg_dump 297 | - Improve performance for "per database aggregated stats" 298 | 299 | News features and changes in UI : 300 | - Follow the selected time interval between each page 301 | - Add a title to each page 302 | - Display metrics for each query page 303 | - Move database selector as a menu entry 304 | - Display human readable metrics 305 | - Fix empty graph bug 306 | 307 | When upgrading from older versions : 308 | - Upgrade the core with ALTER EXTENSION powa UPDATE. 309 | - The format of the database section of the powa.conf has changed. The new format is : 310 | 311 | "dbname" : "powa", 312 | "host" : "127.0.0.1", 313 | "port" : "5432", 314 | 315 | (instead of one line containing the dbi:Pg connection info) 316 | 317 | 318 | ## 1.1 (2014-08-18) 319 | 320 | **POWA is now production ready** 321 | 322 | Features: 323 | 324 | - Various UI improvments 325 | - More documentation 326 | - New demo mode 327 | - Plugin support 328 | - The code is now under the PostgreSQL license 329 | - New website 330 | - New logo 331 | 332 | Bug fixes: 333 | 334 | - Use a temporary table for unpacked records to avoid unnecessary bloat 335 | 336 | 337 | ## 1.0 (2014-06-13) 338 | 339 | **Hello World ! This is the first public release of POWA** 340 | 341 | Features: 342 | 343 | - Web UI based on Mojolicious 344 | - Graph and dynamic charts 345 | - Packed the code as an extension 346 | - PL functions 347 | 348 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | Contributors to PoWA : 2 | 3 | * Marc Cousin 4 | * Julien Rouhaud 5 | * Damien Clochard 6 | * Thomas Reiss 7 | * Hyunjun Kim 8 | * Raghu Ram 9 | * Rodolphe Quiédeville 10 | * Ahmed Bessifi 11 | * Christopher Liu 12 | * menardorama 13 | * Victor D 14 | * Justin Miller 15 | * Arthur Lutz 16 | * Luis Pinto Da Costa 17 | * Ronan Dunklau 18 | * Christoph Berg 19 | * Palle Girgensohn 20 | * Maxence Ahlouche 21 | * Stéphane Tachoires 22 | * trourance 23 | * indreek 24 | * Andreas Seltenreich 25 | * edechaux 26 | * ppetrov91 27 | * Ikrar-k 28 | * care1e55 29 | * optimadba 30 | * pessonnier 31 | * Andriy Bartash 32 | * TezkaRabota 33 | * github user alepaes1975 34 | * Denis Laxalde 35 | * Gowtham Raj Elangovan 36 | * Mickaël Guérin 37 | * github user hrawulwa 38 | * Magnus Hagander 39 | * Adrien Nayrat 40 | * github user gjedeer 41 | * github user arnobnq 42 | * github user anikin-aa 43 | * Yuriy Vountesmery 44 | * Georgy Shelkovy 45 | * github user Nickuru 46 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | PostgreSQL Workload Analyzer detailled installation guide 2 | ========================================================= 3 | 4 | Read [README.md](https://github.com/powa-team/powa/blob/master/README.md) and 5 | [the official documentation](http://powa.readthedocs.io/) for further details 6 | about PoWA. 7 | 8 | PoWA requires PostgreSQL 9.5 or more. This documentation assumes you're using 9 | the version 15 of PostgreSQL. 10 | 11 | The following documentation describes the detailed installation steps to install 12 | PoWA. 13 | 14 | 15 | Download powa-archivist from the website 16 | ---------------------------------------- 17 | 18 | The latest stable version should be used. It can be downloaded from 19 | [github](https://github.com/powa-team/powa-archivist/releases/latest). 20 | 21 | This documentation assumes that the version is 4.2.2, and you downloaded 22 | the .zip file. 23 | 24 | Unpack the downloaded file 25 | -------------------------- 26 | 27 | ``` 28 | cd /usr/src 29 | unzip powa-REL_4_2_2.zip 30 | ``` 31 | 32 | Compile and install the software 33 | -------------------------------- 34 | 35 | Before proceeding, be sure to have a compiler installed and the appropriate PostgreSQL development packages. Something like 36 | ``` 37 | apt-get install postgresql-server-dev-15 38 | ``` 39 | or 40 | ``` 41 | yum install postgresql94-devel 42 | ``` 43 | 44 | Then: 45 | ``` 46 | cd /usr/src/powa-REL_4_2_2 47 | make 48 | ``` 49 | 50 | If everything goes fine, you will have this kind of output : 51 | ``` 52 | gcc -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Wendif-labels -Wmissing-format-attribute -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -g -fpic -I. [...] -c -o powa.o powa.c 53 | gcc -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Wendif-labels -Wmissing-format-attribute -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -g -fpic [...] -shared -o powa.so powa.o 54 | ``` 55 | 56 | Install the software : 57 | 58 | - This step has to be made with the user that has installed PostgreSQL. If you 59 | have used a package, it will be certainly be root. If so: 60 | ``` 61 | sudo make install 62 | ``` 63 | Else, sudo into the user that owns your PostgreSQL executables, and 64 | ``` 65 | make install 66 | ``` 67 | 68 | It should output something like the following : 69 | ``` 70 | /bin/mkdir -p '/usr/pgsql-15/share/extension' 71 | /bin/mkdir -p '/usr/pgsql-15/share/extension' 72 | /bin/mkdir -p '/usr/pgsql-15/lib' 73 | /bin/mkdir -p '/usr/pgsql-15/share/doc/extension' 74 | /usr/bin/install -c -m 644 ./powa.control '/usr/pgsql-15/share/extension/' 75 | /usr/bin/install -c -m 644 ./powa--2.0.1.sql ./powa--2.0-2.0.1.sql ./powa--3.0.0.sql '/usr/pgsql-15/share/extension/' 76 | /usr/bin/install -c -m 755 powa.so '/usr/pgsql-15/postgresql-15.7/lib/' 77 | /usr/bin/install -c -m 644 ./README.md '/usr/pgsql-15/share/doc/extension/' 78 | ``` 79 | 80 | 81 | Create a PoWA database and create required extensions 82 | ----------------------------------------------------- 83 | 84 | Note: if you are upgrading from a previous PoWA release, please consult the 85 | upgrading section at the end of this file. 86 | 87 | 88 | First, connect to PostgreSQL as administrator : 89 | ``` 90 | bash-4.1$ psql 91 | psql (9.3.5) 92 | Type "help" for help. 93 | postgres=# create database powa; 94 | CREATE DATABASE 95 | postgres=# \c powa 96 | You are now connected to database "powa" as user "postgres". 97 | powa=# create extension pg_stat_statements ; 98 | CREATE EXTENSION 99 | powa=# create extension btree_gist ; 100 | CREATE EXTENSION 101 | powa=# create extension powa; 102 | CREATE EXTENSION 103 | powa=# \dt 104 | List of relations 105 | Schema | Name | Type | Owner 106 | --------+---------------------------------+-------+---------- 107 | public | powa_functions | table | postgres 108 | public | powa_last_aggregation | table | postgres 109 | public | powa_last_purge | table | postgres 110 | public | powa_statements | table | postgres 111 | public | powa_statements_history | table | postgres 112 | public | powa_statements_history_current | table | postgres 113 | [...] 114 | ``` 115 | 116 | 117 | Modify the configuration files 118 | ------------------------------ 119 | 120 | In `postgresql.conf`: 121 | 122 | Change the `shared_preload_libraries` appropriately : 123 | ``` 124 | shared_preload_libraries = 'powa,pg_stat_statements'# (change requires restart) 125 | ``` 126 | 127 | If possible (check with pg_test_timing), activate track_io_timing on your instance, in postgresql.conf: 128 | 129 | ``` 130 | track_io_timing = on 131 | ``` 132 | 133 | Other GUC variables are available. Read [README.md](https://github.com/powa-team/powa/blob/master/README.md) for further details. 134 | 135 | In `pg_hba.conf`: 136 | 137 | Add an entry if needed for the PostgreSQL user(s) that need to connect on the GUI. 138 | For instance, assuming a `local connection` on database `powa`, allowing any user: 139 | 140 | `host powa all 127.0.0.1/32 md5` 141 | 142 | Restart PostgreSQL 143 | ------------------ 144 | 145 | As root, run the following command : 146 | ``` 147 | service postgresql-9.3 restart 148 | ``` 149 | 150 | PostgreSQL should output the following messages in the log files : 151 | ``` 152 | 2014-07-25 03:48:20 IST LOG: registering background worker "powa" 153 | 2014-07-25 03:48:20 IST LOG: loaded library "powa" 154 | 2014-07-25 03:48:20 IST LOG: loaded library "pg_stat_statements" 155 | ``` 156 | 157 | Upgrading from a previous version of PoWA 158 | ----------------------------------------- 159 | 160 | If you already have an older PoWA installation, you can simply upgrade PoWA with the following steps : 161 | 162 | First, connect to PostgreSQL as administrator and update the extension : 163 | ``` 164 | bash-4.1$ psql powa 165 | psql (9.3.5) 166 | Type "help" for help. 167 | powa=# ALTER EXTENSION powa UPDATE ; 168 | ALTER EXTENSION 169 | ``` 170 | 171 | However, due to a lot of changes in the data storage, it's not possible to 172 | update to PoWA 3.0.0. In this case, you need to drop and create the extension. 173 | 174 | Next, you will need to restart PostgreSQL in order to take account of the 175 | updated background worker. As root, run the following command : 176 | ``` 177 | service postgresql-15 restart 178 | ``` 179 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2017, DALIBO 2 | Copyright (c) 2018-2024, The PoWA-team 3 | 4 | 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. 5 | 6 | IN NO EVENT SHALL DALIBO 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 DALIBO HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 7 | 8 | DALIBO 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 DALIBO HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 9 | -------------------------------------------------------------------------------- /META.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "powa", 3 | "abstract": "An extension gathering pg_stat_statements and other plugins statistics", 4 | "version": "__VERSION__", 5 | "maintainer": "Julien Rouhaud ", 6 | "license": "postgresql", 7 | "release_status": "stable", 8 | "provides": { 9 | "powa": { 10 | "abstract": "An extension gathering pg_stat_statements and other plugins statistics", 11 | "file": "powa.sql", 12 | "docfile": "README.md", 13 | "version": "__VERSION__" 14 | } 15 | }, 16 | "resources": { 17 | "bugtracker": { 18 | "web": "http://github.com/powa-team/powa/issues/" 19 | }, 20 | "repository": { 21 | "url": "git://github.com/powa-team/powa-archivist.git", 22 | "web": "http://github.com/powa-team/powa-archivist/", 23 | "type": "git" 24 | } 25 | }, 26 | "generated_by": "Julien Rouhaud", 27 | "meta-spec": { 28 | "version": "1.0.0", 29 | "url": "http://pgxn.org/meta/spec.txt" 30 | }, 31 | "tags": [ 32 | "monitoring", 33 | "workload" 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | EXTENSION = powa 2 | EXTVERSION = $(shell grep default_version $(EXTENSION).control | sed -e "s/default_version[[:space:]]*=[[:space:]]*'\([^']*\)'/\1/") 3 | TESTS = $(sort $(wildcard sql/*.sql)) 4 | REGRESS = $(patsubst sql/%.sql,%,$(TESTS)) 5 | REGRESS_OPTS = --inputdir=test 6 | 7 | PG_CONFIG ?= pg_config 8 | 9 | MODULES = powa 10 | 11 | all: 12 | 13 | release-zip: all 14 | git archive --format zip --prefix=powa-${EXTVERSION}/ --output ./powa-${EXTVERSION}.zip HEAD 15 | unzip ./powa-$(EXTVERSION).zip 16 | rm ./powa-$(EXTVERSION).zip 17 | rm ./powa-$(EXTVERSION)/.gitignore 18 | rm ./powa-$(EXTVERSION)/reindent.sh 19 | sed -i -e "s/__VERSION__/$(EXTVERSION)/g" ./powa-$(EXTVERSION)/META.json 20 | zip -r ./powa-$(EXTVERSION).zip ./powa-$(EXTVERSION)/ 21 | rm -rf ./powa-$(EXTVERSION) 22 | 23 | DATA = $(wildcard *--*.sql) 24 | 25 | ifdef NO_PGXS 26 | subdir = contrib/$(MODULE) 27 | top_builddir = ../.. 28 | include $(top_builddir)/src/Makefile.global 29 | include $(top_srcdir)/contrib/contrib-global.mk 30 | else 31 | PGXS := $(shell $(PG_CONFIG) --pgxs) 32 | include $(PGXS) 33 | endif 34 | -------------------------------------------------------------------------------- /PL_funcs.md: -------------------------------------------------------------------------------- 1 | This is a list of all functions and what they are used for: 2 | 3 | * `powa_take_snapshot`: takes a snapshot. It means calling all the **snapshot** functions registered in the **powa_functions** table, then maybe do an **aggregate** and/or a **purge**, if conditions are met (these functions are also registered in powa_functions). 4 | * `powa_take_statements_snapshot`: takes a snapshot of pg_stat_statements. This is the included **snapshot** function. 5 | * `powa_statements_purge`: does a purge of collected data from pg_stat_statements. This is the included **purge** function. 6 | * `powa_statements_aggregate`: does an aggregate (putting individual records into arrays to save space) on collected data from pg_stat_statements. This is the included **aggregate** function. 7 | * `powa_stats_reset`: cleans-up pg_stat_staments collected data. **FIXME: Should be moved to dedicated functions, and stored in powa_functions**. 8 | * `powa_kcache_register`: Add the pg_stat_kcache snapshot, aggregate and purge functions to list of powa functions if pg_stat_kcache extension exists. 9 | * `powa_kcache_unregister`: Remove the pg_stat_kcache snapshot, aggregate and purge functions from list of powa functions. 10 | * `powa_kcache_snapshot`: Take a snapshot of pg_stat_kcache. 11 | * `powa_kcache_aggregate`: Does an aggregate on collected data from pg_stat_kcache. 12 | * `powa_kcache_purge`: Does a purge of collected data from pg_stat_kcache. 13 | * `powa_qualstats_register`: Add the pg_qualstats snapshot, aggregate and purge functions to list of powa functions if pg_qualstats extension exists. 14 | * `powa_qualstats_unregister`: Remove the pg_qualstats snapshot, aggregate and purge pg_qualstats functions from list of powa functions. 15 | * `powa_qualstats_snapshot`: Take a snapshot of pg_qualstats. 16 | * `powa_qualstats_aggregate`: Does an aggregate on collected data from pg_qualstats. 17 | * `powa_qualstats_purge`: Does a purge of collected data from pg_qualstats. 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![PostgreSQL Workload Analyzer](https://github.com/powa-team/powa/blob/master/img/powa_logo.410x161.png) 3 | 4 | PoWA Archivist 5 | ============== 6 | 7 | This project is the core extension of the [PoWA](http://powa.readthedocs.io/) 8 | project, a PostgreSQL Workload Analyzer that gathers performance stats and 9 | provides real-time charts and graphs to help monitor and tune your PostgreSQL 10 | servers. 11 | 12 | For more information, please read the [PoWA-archivist 13 | documentation](https://powa.readthedocs.io/en/latest/components/powa-archivist/index.html): 14 | 15 | https://powa.readthedocs.io/en/latest/components/powa-archivist/index.html 16 | 17 | -------------------------------------------------------------------------------- /expected/00_setup.out: -------------------------------------------------------------------------------- 1 | --Setup extension 2 | CREATE SCHEMA "PGSS"; 3 | CREATE EXTENSION pg_stat_statements WITH SCHEMA "PGSS"; 4 | CREATE EXTENSION btree_gist; 5 | CREATE SCHEMA "PoWA"; 6 | CREATE EXTENSION powa WITH SCHEMA "PoWA"; 7 | -- Test created ojects 8 | SELECT * FROM "PoWA".powa_functions ORDER BY name, operation, priority, function_name; 9 | srvid | kind | name | operation | external | function_name | query_source | query_cleanup | enabled | priority 10 | -------+-----------+----------------------------+-----------+----------+----------------------------------------+----------------------------------+---------------+---------+---------- 11 | 0 | module | pg_database | reset | f | powa_catalog_database_reset | | | t | 100 12 | 0 | module | pg_database | snapshot | f | powa_catalog_database_snapshot | powa_catalog_database_src | | t | 100 13 | 0 | module | pg_replication_slots | aggregate | f | powa_replication_slots_aggregate | | | t | 100 14 | 0 | module | pg_replication_slots | purge | f | powa_replication_slots_purge | | | t | 100 15 | 0 | module | pg_replication_slots | reset | f | powa_replication_slots_reset | | | t | 100 16 | 0 | module | pg_replication_slots | snapshot | f | powa_replication_slots_snapshot | powa_replication_slots_src | | t | 100 17 | 0 | module | pg_role | reset | f | powa_catalog_role_reset | | | t | 100 18 | 0 | module | pg_role | snapshot | f | powa_catalog_role_snapshot | powa_catalog_role_src | | t | 100 19 | 0 | module | pg_stat_activity | aggregate | f | powa_stat_activity_aggregate | | | t | 100 20 | 0 | module | pg_stat_activity | purge | f | powa_stat_activity_purge | | | t | 100 21 | 0 | module | pg_stat_activity | reset | f | powa_stat_activity_reset | | | t | 100 22 | 0 | module | pg_stat_activity | snapshot | f | powa_stat_activity_snapshot | powa_stat_activity_src | | t | 100 23 | 0 | module | pg_stat_archiver | aggregate | f | powa_stat_archiver_aggregate | | | t | 100 24 | 0 | module | pg_stat_archiver | purge | f | powa_stat_archiver_purge | | | t | 100 25 | 0 | module | pg_stat_archiver | reset | f | powa_stat_archiver_reset | | | t | 100 26 | 0 | module | pg_stat_archiver | snapshot | f | powa_stat_archiver_snapshot | powa_stat_archiver_src | | t | 100 27 | 0 | module | pg_stat_bgwriter | aggregate | f | powa_stat_bgwriter_aggregate | | | t | 100 28 | 0 | module | pg_stat_bgwriter | purge | f | powa_stat_bgwriter_purge | | | t | 100 29 | 0 | module | pg_stat_bgwriter | reset | f | powa_stat_bgwriter_reset | | | t | 100 30 | 0 | module | pg_stat_bgwriter | snapshot | f | powa_stat_bgwriter_snapshot | powa_stat_bgwriter_src | | t | 100 31 | 0 | module | pg_stat_checkpointer | aggregate | f | powa_stat_checkpointer_aggregate | | | t | 100 32 | 0 | module | pg_stat_checkpointer | purge | f | powa_stat_checkpointer_purge | | | t | 100 33 | 0 | module | pg_stat_checkpointer | reset | f | powa_stat_checkpointer_reset | | | t | 100 34 | 0 | module | pg_stat_checkpointer | snapshot | f | powa_stat_checkpointer_snapshot | powa_stat_checkpointer_src | | t | 100 35 | 0 | module | pg_stat_database | aggregate | f | powa_stat_database_aggregate | | | t | 100 36 | 0 | module | pg_stat_database | purge | f | powa_stat_database_purge | | | t | 100 37 | 0 | module | pg_stat_database | reset | f | powa_stat_database_reset | | | t | 100 38 | 0 | module | pg_stat_database | snapshot | f | powa_stat_database_snapshot | powa_stat_database_src | | t | 100 39 | 0 | module | pg_stat_database_conflicts | aggregate | f | powa_stat_database_conflicts_aggregate | | | t | 100 40 | 0 | module | pg_stat_database_conflicts | purge | f | powa_stat_database_conflicts_purge | | | t | 100 41 | 0 | module | pg_stat_database_conflicts | reset | f | powa_stat_database_conflicts_reset | | | t | 100 42 | 0 | module | pg_stat_database_conflicts | snapshot | f | powa_stat_database_conflicts_snapshot | powa_stat_database_conflicts_src | | t | 100 43 | 0 | module | pg_stat_io | aggregate | f | powa_stat_io_aggregate | | | t | 100 44 | 0 | module | pg_stat_io | purge | f | powa_stat_io_purge | | | t | 100 45 | 0 | module | pg_stat_io | reset | f | powa_stat_io_reset | | | t | 100 46 | 0 | module | pg_stat_io | snapshot | f | powa_stat_io_snapshot | powa_stat_io_src | | t | 100 47 | 0 | module | pg_stat_replication | aggregate | f | powa_stat_replication_aggregate | | | t | 100 48 | 0 | module | pg_stat_replication | purge | f | powa_stat_replication_purge | | | t | 100 49 | 0 | module | pg_stat_replication | reset | f | powa_stat_replication_reset | | | t | 100 50 | 0 | module | pg_stat_replication | snapshot | f | powa_stat_replication_snapshot | powa_stat_replication_src | | t | 100 51 | 0 | module | pg_stat_slru | aggregate | f | powa_stat_slru_aggregate | | | t | 100 52 | 0 | module | pg_stat_slru | purge | f | powa_stat_slru_purge | | | t | 100 53 | 0 | module | pg_stat_slru | reset | f | powa_stat_slru_reset | | | t | 100 54 | 0 | module | pg_stat_slru | snapshot | f | powa_stat_slru_snapshot | powa_stat_slru_src | | t | 100 55 | 0 | extension | pg_stat_statements | aggregate | f | powa_statements_aggregate | | | t | 10 56 | 0 | extension | pg_stat_statements | purge | f | powa_databases_purge | | | t | 10 57 | 0 | extension | pg_stat_statements | purge | f | powa_statements_purge | | | t | 10 58 | 0 | extension | pg_stat_statements | reset | f | powa_statements_reset | | | t | 10 59 | 0 | extension | pg_stat_statements | snapshot | f | powa_databases_snapshot | powa_databases_src | | t | -3 60 | 0 | extension | pg_stat_statements | snapshot | f | powa_statements_snapshot | powa_statements_src | | t | -2 61 | 0 | module | pg_stat_subscription | aggregate | f | powa_stat_subscription_aggregate | | | t | 100 62 | 0 | module | pg_stat_subscription | purge | f | powa_stat_subscription_purge | | | t | 100 63 | 0 | module | pg_stat_subscription | reset | f | powa_stat_subscription_reset | | | t | 100 64 | 0 | module | pg_stat_subscription | snapshot | f | powa_stat_subscription_snapshot | powa_stat_subscription_src | | t | 100 65 | 0 | module | pg_stat_subscription_stats | aggregate | f | powa_stat_subscription_stats_aggregate | | | t | 100 66 | 0 | module | pg_stat_subscription_stats | purge | f | powa_stat_subscription_stats_purge | | | t | 100 67 | 0 | module | pg_stat_subscription_stats | reset | f | powa_stat_subscription_stats_reset | | | t | 100 68 | 0 | module | pg_stat_subscription_stats | snapshot | f | powa_stat_subscription_stats_snapshot | powa_stat_subscription_stats_src | | t | 100 69 | 0 | module | pg_stat_wal | aggregate | f | powa_stat_wal_aggregate | | | t | 100 70 | 0 | module | pg_stat_wal | purge | f | powa_stat_wal_purge | | | t | 100 71 | 0 | module | pg_stat_wal | reset | f | powa_stat_wal_reset | | | t | 100 72 | 0 | module | pg_stat_wal | snapshot | f | powa_stat_wal_snapshot | powa_stat_wal_src | | t | 100 73 | 0 | module | pg_stat_wal_receiver | aggregate | f | powa_stat_wal_receiver_aggregate | | | t | 100 74 | 0 | module | pg_stat_wal_receiver | purge | f | powa_stat_wal_receiver_purge | | | t | 100 75 | 0 | module | pg_stat_wal_receiver | reset | f | powa_stat_wal_receiver_reset | | | t | 100 76 | 0 | module | pg_stat_wal_receiver | snapshot | f | powa_stat_wal_receiver_snapshot | powa_stat_wal_receiver_src | | t | 100 77 | (66 rows) 78 | 79 | -------------------------------------------------------------------------------- /expected/00_setup_1.out: -------------------------------------------------------------------------------- 1 | --Setup extension 2 | CREATE SCHEMA "PGSS"; 3 | CREATE EXTENSION pg_stat_statements WITH SCHEMA "PGSS"; 4 | CREATE EXTENSION btree_gist; 5 | CREATE SCHEMA "PoWA"; 6 | CREATE EXTENSION powa WITH SCHEMA "PoWA"; 7 | -- Test created ojects 8 | SELECT * FROM "PoWA".powa_functions ORDER BY name, operation, priority, function_name; 9 | srvid | kind | name | operation | external | function_name | query_source | query_cleanup | enabled | priority 10 | -------+-----------+----------------------------+-----------+----------+----------------------------------------+----------------------------------+---------------+---------+---------- 11 | 0 | module | pg_database | reset | f | powa_catalog_database_reset | | | t | 100 12 | 0 | module | pg_database | snapshot | f | powa_catalog_database_snapshot | powa_catalog_database_src | | t | 100 13 | 0 | module | pg_role | reset | f | powa_catalog_role_reset | | | t | 100 14 | 0 | module | pg_role | snapshot | f | powa_catalog_role_snapshot | powa_catalog_role_src | | t | 100 15 | 0 | module | pg_stat_activity | aggregate | f | powa_stat_activity_aggregate | | | t | 100 16 | 0 | module | pg_stat_activity | purge | f | powa_stat_activity_purge | | | t | 100 17 | 0 | module | pg_stat_activity | reset | f | powa_stat_activity_reset | | | t | 100 18 | 0 | module | pg_stat_activity | snapshot | f | powa_stat_activity_snapshot | powa_stat_activity_src | | t | 100 19 | 0 | module | pg_stat_archiver | aggregate | f | powa_stat_archiver_aggregate | | | t | 100 20 | 0 | module | pg_stat_archiver | purge | f | powa_stat_archiver_purge | | | t | 100 21 | 0 | module | pg_stat_archiver | reset | f | powa_stat_archiver_reset | | | t | 100 22 | 0 | module | pg_stat_archiver | snapshot | f | powa_stat_archiver_snapshot | powa_stat_archiver_src | | t | 100 23 | 0 | module | pg_stat_bgwriter | aggregate | f | powa_stat_bgwriter_aggregate | | | t | 100 24 | 0 | module | pg_stat_bgwriter | purge | f | powa_stat_bgwriter_purge | | | t | 100 25 | 0 | module | pg_stat_bgwriter | reset | f | powa_stat_bgwriter_reset | | | t | 100 26 | 0 | module | pg_stat_bgwriter | snapshot | f | powa_stat_bgwriter_snapshot | powa_stat_bgwriter_src | | t | 100 27 | 0 | module | pg_stat_checkpointer | aggregate | f | powa_stat_checkpointer_aggregate | | | t | 100 28 | 0 | module | pg_stat_checkpointer | purge | f | powa_stat_checkpointer_purge | | | t | 100 29 | 0 | module | pg_stat_checkpointer | reset | f | powa_stat_checkpointer_reset | | | t | 100 30 | 0 | module | pg_stat_checkpointer | snapshot | f | powa_stat_checkpointer_snapshot | powa_stat_checkpointer_src | | t | 100 31 | 0 | module | pg_stat_database | aggregate | f | powa_stat_database_aggregate | | | t | 100 32 | 0 | module | pg_stat_database | purge | f | powa_stat_database_purge | | | t | 100 33 | 0 | module | pg_stat_database | reset | f | powa_stat_database_reset | | | t | 100 34 | 0 | module | pg_stat_database | snapshot | f | powa_stat_database_snapshot | powa_stat_database_src | | t | 100 35 | 0 | module | pg_stat_database_conflicts | aggregate | f | powa_stat_database_conflicts_aggregate | | | t | 100 36 | 0 | module | pg_stat_database_conflicts | purge | f | powa_stat_database_conflicts_purge | | | t | 100 37 | 0 | module | pg_stat_database_conflicts | reset | f | powa_stat_database_conflicts_reset | | | t | 100 38 | 0 | module | pg_stat_database_conflicts | snapshot | f | powa_stat_database_conflicts_snapshot | powa_stat_database_conflicts_src | | t | 100 39 | 0 | module | pg_stat_io | aggregate | f | powa_stat_io_aggregate | | | t | 100 40 | 0 | module | pg_stat_io | purge | f | powa_stat_io_purge | | | t | 100 41 | 0 | module | pg_stat_io | reset | f | powa_stat_io_reset | | | t | 100 42 | 0 | module | pg_stat_io | snapshot | f | powa_stat_io_snapshot | powa_stat_io_src | | t | 100 43 | 0 | module | pg_stat_slru | aggregate | f | powa_stat_slru_aggregate | | | t | 100 44 | 0 | module | pg_stat_slru | purge | f | powa_stat_slru_purge | | | t | 100 45 | 0 | module | pg_stat_slru | reset | f | powa_stat_slru_reset | | | t | 100 46 | 0 | module | pg_stat_slru | snapshot | f | powa_stat_slru_snapshot | powa_stat_slru_src | | t | 100 47 | 0 | extension | pg_stat_statements | aggregate | f | powa_statements_aggregate | | | t | 10 48 | 0 | extension | pg_stat_statements | purge | f | powa_databases_purge | | | t | 10 49 | 0 | extension | pg_stat_statements | purge | f | powa_statements_purge | | | t | 10 50 | 0 | extension | pg_stat_statements | reset | f | powa_statements_reset | | | t | 10 51 | 0 | extension | pg_stat_statements | snapshot | f | powa_databases_snapshot | powa_databases_src | | t | -3 52 | 0 | extension | pg_stat_statements | snapshot | f | powa_statements_snapshot | powa_statements_src | | t | -2 53 | 0 | module | pg_stat_subscription_stats | aggregate | f | powa_stat_subscription_stats_aggregate | | | t | 100 54 | 0 | module | pg_stat_subscription_stats | purge | f | powa_stat_subscription_stats_purge | | | t | 100 55 | 0 | module | pg_stat_subscription_stats | reset | f | powa_stat_subscription_stats_reset | | | t | 100 56 | 0 | module | pg_stat_subscription_stats | snapshot | f | powa_stat_subscription_stats_snapshot | powa_stat_subscription_stats_src | | t | 100 57 | 0 | module | pg_stat_wal | aggregate | f | powa_stat_wal_aggregate | | | t | 100 58 | 0 | module | pg_stat_wal | purge | f | powa_stat_wal_purge | | | t | 100 59 | 0 | module | pg_stat_wal | reset | f | powa_stat_wal_reset | | | t | 100 60 | 0 | module | pg_stat_wal | snapshot | f | powa_stat_wal_snapshot | powa_stat_wal_src | | t | 100 61 | (50 rows) 62 | 63 | -------------------------------------------------------------------------------- /expected/01_general.out: -------------------------------------------------------------------------------- 1 | -- General setup 2 | \set SHOW_CONTEXT never 3 | -- Check the relations that aren't dumped 4 | -- we ignore *_src_tmp are those should never be dumped 5 | WITH ext AS ( 6 | SELECT c.oid, c.relname 7 | FROM pg_depend d 8 | JOIN pg_extension e ON d.refclassid = 'pg_extension'::regclass 9 | AND e.oid = d.refobjid 10 | AND e.extname = 'powa' 11 | JOIN pg_class c ON d.classid = 'pg_class'::regclass 12 | AND c.oid = d.objid 13 | WHERE c.relkind != 'v' 14 | ), 15 | dmp AS ( 16 | SELECT unnest(extconfig) AS oid 17 | FROM pg_extension 18 | WHERE extname = 'powa' 19 | ) 20 | SELECT ext.relname 21 | FROM ext 22 | LEFT JOIN dmp USING (oid) 23 | WHERE dmp.oid IS NULL 24 | AND ext.relname NOT LIKE '%src_tmp' 25 | ORDER BY ext.relname::text COLLATE "C"; 26 | relname 27 | -------------------------- 28 | powa_catalog_src_queries 29 | powa_catalogs 30 | powa_modules 31 | powa_roles 32 | powa_servers_id_seq 33 | (5 rows) 34 | 35 | -- Check that no *_src_tmp table are dumped 36 | WITH ext AS ( 37 | SELECT c.oid, c.relname 38 | FROM pg_depend d 39 | JOIN pg_extension e ON d.refclassid = 'pg_extension'::regclass 40 | AND e.oid = d.refobjid 41 | AND e.extname = 'powa' 42 | JOIN pg_class c ON d.classid = 'pg_class'::regclass 43 | AND c.oid = d.objid 44 | WHERE c.relkind != 'v' 45 | ), 46 | dmp AS ( 47 | SELECT unnest(extconfig) AS oid 48 | FROM pg_extension 49 | WHERE extname = 'powa' 50 | ) 51 | SELECT ext.relname 52 | FROM ext 53 | LEFT JOIN dmp USING (oid) 54 | WHERE dmp.oid IS NOT NULL 55 | AND ext.relname LIKE '%src_tmp' 56 | ORDER BY ext.relname::text COLLATE "C"; 57 | relname 58 | --------- 59 | (0 rows) 60 | 61 | -- Check for object that aren't in the "PoWA" schema 62 | WITH ext AS ( 63 | SELECT pg_describe_object(classid, objid, objsubid) AS descr 64 | FROM pg_depend d 65 | JOIN pg_extension e ON d.refclassid = 'pg_extension'::regclass 66 | AND e.oid = d.refobjid 67 | AND e.extname = 'powa' 68 | ) 69 | SELECT descr 70 | FROM ext 71 | WHERE descr NOT LIKE '%"PoWA"%' 72 | ORDER BY descr COLLATE "C"; 73 | descr 74 | --------------------------------------------- 75 | event trigger powa_check_created_extensions 76 | event trigger powa_check_dropped_extensions 77 | (2 rows) 78 | 79 | -- check (mins|maxs)_in_range columns not marked as STORAGE MAIN 80 | WITH ext AS ( 81 | SELECT c.oid, c.relname 82 | FROM pg_depend d 83 | JOIN pg_extension e ON d.refclassid = 'pg_extension'::regclass 84 | AND e.oid = d.refobjid 85 | AND e.extname = 'powa' 86 | JOIN pg_class c ON d.classid = 'pg_class'::regclass 87 | AND c.oid = d.objid 88 | WHERE c.relkind != 'v' 89 | ) 90 | SELECT ext.relname, a.attname 91 | FROM ext 92 | JOIN pg_attribute a ON a.attrelid = ext.oid 93 | WHERE a.attname ~ '^(mins|maxs)_in_range$' 94 | AND a.attstorage != 'm' 95 | ORDER BY ext.relname::text COLLATE "C", a.attname::text COLLATE "C"; 96 | relname | attname 97 | ---------+--------- 98 | (0 rows) 99 | 100 | -- Aggregate data every 5 snapshots 101 | SET powa.coalesce = 5; 102 | -- test C SRFs 103 | SELECT COUNT(*) = 0 104 | FROM pg_database, 105 | LATERAL "PoWA".powa_stat_user_functions(oid) f 106 | WHERE datname = current_database(); 107 | ?column? 108 | ---------- 109 | t 110 | (1 row) 111 | 112 | -- on pg15+ the function is a no-op, and this function will be deprecated soon 113 | -- anyway 114 | SELECT COUNT(*) >= 0 115 | FROM pg_database, 116 | LATERAL "PoWA".powa_stat_all_rel(oid) 117 | WHERE datname = current_database(); 118 | ?column? 119 | ---------- 120 | t 121 | (1 row) 122 | 123 | -- Test snapshot 124 | SELECT 1, COUNT(*) = 0 FROM "PoWA".powa_user_functions_history_current; 125 | ?column? | ?column? 126 | ----------+---------- 127 | 1 | t 128 | (1 row) 129 | 130 | SELECT 1, COUNT(*) = 0 FROM "PoWA".powa_all_tables_history_current; 131 | ?column? | ?column? 132 | ----------+---------- 133 | 1 | t 134 | (1 row) 135 | 136 | SELECT 1, COUNT(*) = 0 FROM "PoWA".powa_statements_history_current; 137 | ?column? | ?column? 138 | ----------+---------- 139 | 1 | t 140 | (1 row) 141 | 142 | SELECT 1, COUNT(*) = 0 FROM "PoWA".powa_statements_history_current_db; 143 | ?column? | ?column? 144 | ----------+---------- 145 | 1 | t 146 | (1 row) 147 | 148 | SELECT 1, COUNT(*) = 0 FROM "PoWA".powa_user_functions_history; 149 | ?column? | ?column? 150 | ----------+---------- 151 | 1 | t 152 | (1 row) 153 | 154 | SELECT 1, COUNT(*) = 0 FROM "PoWA".powa_all_tables_history; 155 | ?column? | ?column? 156 | ----------+---------- 157 | 1 | t 158 | (1 row) 159 | 160 | SELECT 1, COUNT(*) = 0 FROM "PoWA".powa_statements_history; 161 | ?column? | ?column? 162 | ----------+---------- 163 | 1 | t 164 | (1 row) 165 | 166 | SELECT 1, COUNT(*) = 0 FROM "PoWA".powa_statements_history; 167 | ?column? | ?column? 168 | ----------+---------- 169 | 1 | t 170 | (1 row) 171 | 172 | SELECT 1, count(*) = 0 FROM "PoWA".powa_stat_get_activity(0, '-infinity', 'infinity'); 173 | ?column? | ?column? 174 | ----------+---------- 175 | 1 | t 176 | (1 row) 177 | 178 | SELECT "PoWA".powa_take_snapshot(); 179 | powa_take_snapshot 180 | -------------------- 181 | 0 182 | (1 row) 183 | 184 | SELECT 2, COUNT(*) >= 0 FROM "PoWA".powa_user_functions_history_current; 185 | ?column? | ?column? 186 | ----------+---------- 187 | 2 | t 188 | (1 row) 189 | 190 | SELECT 2, COUNT(*) >= 0 FROM "PoWA".powa_all_tables_history_current; 191 | ?column? | ?column? 192 | ----------+---------- 193 | 2 | t 194 | (1 row) 195 | 196 | SELECT 2, COUNT(*) > 0 FROM "PoWA".powa_statements_history_current; 197 | ?column? | ?column? 198 | ----------+---------- 199 | 2 | t 200 | (1 row) 201 | 202 | SELECT 2, COUNT(*) > 0 FROM "PoWA".powa_statements_history_current_db; 203 | ?column? | ?column? 204 | ----------+---------- 205 | 2 | t 206 | (1 row) 207 | 208 | SELECT 2, COUNT(*) >= 0 FROM "PoWA".powa_user_functions_history; 209 | ?column? | ?column? 210 | ----------+---------- 211 | 2 | t 212 | (1 row) 213 | 214 | SELECT 2, COUNT(*) = 0 FROM "PoWA".powa_all_tables_history; 215 | ?column? | ?column? 216 | ----------+---------- 217 | 2 | t 218 | (1 row) 219 | 220 | SELECT 2, COUNT(*) = 0 FROM "PoWA".powa_statements_history; 221 | ?column? | ?column? 222 | ----------+---------- 223 | 2 | t 224 | (1 row) 225 | 226 | SELECT 2, COUNT(*) = 0 FROM "PoWA".powa_statements_history; 227 | ?column? | ?column? 228 | ----------+---------- 229 | 2 | t 230 | (1 row) 231 | 232 | SELECT 2, count(*) > 0 FROM "PoWA".powa_stat_get_activity(0, '-infinity', 'infinity'); 233 | ?column? | ?column? 234 | ----------+---------- 235 | 2 | t 236 | (1 row) 237 | 238 | SELECT 2, count(*) = 0 FROM "PoWA".powa_stat_get_activity(42, '-infinity', 'infinity'); 239 | ?column? | ?column? 240 | ----------+---------- 241 | 2 | t 242 | (1 row) 243 | 244 | SELECT "PoWA".powa_take_snapshot(); 245 | powa_take_snapshot 246 | -------------------- 247 | 0 248 | (1 row) 249 | 250 | SELECT "PoWA".powa_take_snapshot(); 251 | powa_take_snapshot 252 | -------------------- 253 | 0 254 | (1 row) 255 | 256 | SELECT "PoWA".powa_take_snapshot(); 257 | powa_take_snapshot 258 | -------------------- 259 | 0 260 | (1 row) 261 | 262 | -- This snapshot will trigger the aggregate 263 | SELECT "PoWA".powa_take_snapshot(); 264 | powa_take_snapshot 265 | -------------------- 266 | 0 267 | (1 row) 268 | 269 | SELECT 3, COUNT(*) >= 0 FROM "PoWA".powa_user_functions_history_current; 270 | ?column? | ?column? 271 | ----------+---------- 272 | 3 | t 273 | (1 row) 274 | 275 | SELECT 3, COUNT(*) >= 0 FROM "PoWA".powa_all_tables_history_current; 276 | ?column? | ?column? 277 | ----------+---------- 278 | 3 | t 279 | (1 row) 280 | 281 | SELECT 3, COUNT(*) > 0 FROM "PoWA".powa_statements_history_current; 282 | ?column? | ?column? 283 | ----------+---------- 284 | 3 | t 285 | (1 row) 286 | 287 | SELECT 3, COUNT(*) > 0 FROM "PoWA".powa_statements_history_current_db; 288 | ?column? | ?column? 289 | ----------+---------- 290 | 3 | t 291 | (1 row) 292 | 293 | SELECT 3, COUNT(*) >= 0 FROM "PoWA".powa_user_functions_history; 294 | ?column? | ?column? 295 | ----------+---------- 296 | 3 | t 297 | (1 row) 298 | 299 | SELECT 3, COUNT(*) >= 0 FROM "PoWA".powa_all_tables_history; 300 | ?column? | ?column? 301 | ----------+---------- 302 | 3 | t 303 | (1 row) 304 | 305 | SELECT 3, COUNT(*) > 0 FROM "PoWA".powa_statements_history; 306 | ?column? | ?column? 307 | ----------+---------- 308 | 3 | t 309 | (1 row) 310 | 311 | SELECT 3, COUNT(*) > 0 FROM "PoWA".powa_statements_history; 312 | ?column? | ?column? 313 | ----------+---------- 314 | 3 | t 315 | (1 row) 316 | 317 | SELECT 3, count(*) > 4 FROM "PoWA".powa_stat_get_activity(0, '-infinity', 'infinity'); 318 | ?column? | ?column? 319 | ----------+---------- 320 | 3 | t 321 | (1 row) 322 | 323 | SELECT 3, count(*) = 0 FROM "PoWA".powa_stat_get_activity(42, '-infinity', 'infinity'); 324 | ?column? | ?column? 325 | ----------+---------- 326 | 3 | t 327 | (1 row) 328 | 329 | -- Test reset function 330 | SELECT * from "PoWA".powa_reset(0); 331 | powa_reset 332 | ------------ 333 | t 334 | (1 row) 335 | 336 | SELECT 4, COUNT(*) = 0 FROM "PoWA".powa_user_functions_history_current; 337 | ?column? | ?column? 338 | ----------+---------- 339 | 4 | t 340 | (1 row) 341 | 342 | SELECT 4, COUNT(*) = 0 FROM "PoWA".powa_all_tables_history_current; 343 | ?column? | ?column? 344 | ----------+---------- 345 | 4 | t 346 | (1 row) 347 | 348 | SELECT 4, COUNT(*) = 0 FROM "PoWA".powa_statements_history_current; 349 | ?column? | ?column? 350 | ----------+---------- 351 | 4 | t 352 | (1 row) 353 | 354 | SELECT 4, COUNT(*) = 0 FROM "PoWA".powa_statements_history_current_db; 355 | ?column? | ?column? 356 | ----------+---------- 357 | 4 | t 358 | (1 row) 359 | 360 | SELECT 4, COUNT(*) = 0 FROM "PoWA".powa_user_functions_history; 361 | ?column? | ?column? 362 | ----------+---------- 363 | 4 | t 364 | (1 row) 365 | 366 | SELECT 4, COUNT(*) = 0 FROM "PoWA".powa_all_tables_history; 367 | ?column? | ?column? 368 | ----------+---------- 369 | 4 | t 370 | (1 row) 371 | 372 | SELECT 4, COUNT(*) = 0 FROM "PoWA".powa_statements_history; 373 | ?column? | ?column? 374 | ----------+---------- 375 | 4 | t 376 | (1 row) 377 | 378 | SELECT 4, COUNT(*) = 0 FROM "PoWA".powa_statements_history; 379 | ?column? | ?column? 380 | ----------+---------- 381 | 4 | t 382 | (1 row) 383 | 384 | SELECT 4, count(*) = 0 FROM "PoWA".powa_stat_get_activity(0, '-infinity', 'infinity'); 385 | ?column? | ?column? 386 | ----------+---------- 387 | 4 | t 388 | (1 row) 389 | 390 | -- Test toast_tuple_target: we shouldn't have any table belonging to powa archivist 391 | -- that has a column mins_in_range (it means it's a coalesced table) and isn't set 392 | -- for aggressive toasting 393 | WITH ext AS ( 394 | SELECT c.oid, c.relname, c.reloptions 395 | FROM pg_depend d 396 | JOIN pg_extension e ON d.refclassid = 'pg_extension'::regclass 397 | AND e.oid = d.refobjid 398 | AND e.extname = 'powa' 399 | JOIN pg_class c ON d.classid = 'pg_class'::regclass 400 | AND c.oid = d.objid 401 | WHERE c.relkind != 'v' 402 | ) 403 | SELECT ext.relname 404 | FROM ext 405 | WHERE EXISTS 406 | (SELECT 1 FROM pg_attribute a 407 | WHERE a.attrelid = ext.oid 408 | AND a.attname = 'mins_in_range' 409 | ) 410 | AND 'toast_tuple_target=128' <> ALL(coalesce(ext.reloptions,'{}')) 411 | AND current_setting('server_version_num')::int >= 110000 412 | ORDER BY ext.relname::text COLLATE "C"; 413 | relname 414 | --------- 415 | (0 rows) 416 | 417 | -------------------------------------------------------------------------------- /expected/02_remote_api.out: -------------------------------------------------------------------------------- 1 | -- General setup 2 | \set SHOW_CONTEXT never 3 | -- Check API 4 | SELECT "PoWA".powa_register_server(hostname => '127.0.0.1', 5 | extensions => '{pg_qualstats}'); 6 | powa_register_server 7 | ---------------------- 8 | t 9 | (1 row) 10 | 11 | SELECT COUNT(*) FROM "PoWA".powa_servers; 12 | count 13 | ------- 14 | 2 15 | (1 row) 16 | 17 | SELECT hostname FROM "PoWA".powa_servers WHERE id = 1; 18 | hostname 19 | ----------- 20 | 127.0.0.1 21 | (1 row) 22 | 23 | -- Check missing powa_statements FK for pg_qualstats doesn't prevent snapshot 24 | INSERT INTO "PoWA".powa_qualstats_src_tmp(srvid, ts, uniquequalnodeid, dbid, userid, 25 | qualnodeid, occurences, execution_count, nbfiltered, 26 | mean_err_estimate_ratio, mean_err_estimate_num, 27 | queryid, constvalues, quals) 28 | SELECT 1, now(), 1, 1, 1, 29 | 1, 1000, 1, 0, 30 | 0, 0, 31 | 123456789, '{}', ARRAY[(1259,1,607,'i')::"PoWA".qual_type]; 32 | SELECT count(*) FROM "PoWA".powa_qualstats_src_tmp; 33 | count 34 | ------- 35 | 1 36 | (1 row) 37 | 38 | SELECT "PoWA".powa_qualstats_snapshot(1); 39 | powa_qualstats_snapshot 40 | ------------------------- 41 | 42 | (1 row) 43 | 44 | SELECT count(*) FROM "PoWA".powa_qualstats_src_tmp; 45 | count 46 | ------- 47 | 0 48 | (1 row) 49 | 50 | SELECT count(*) FROM "PoWA".powa_qualstats_quals_history_current WHERE srvid = 1; 51 | count 52 | ------- 53 | 0 54 | (1 row) 55 | 56 | -- Check snapshot of regular quals 57 | INSERT INTO "PoWA".powa_databases(srvid, oid, datname, dropped) 58 | VALUES (1, 16384, 'postgres', NULL); 59 | INSERT INTO "PoWA".powa_statements(srvid, queryid, dbid, userid, query) 60 | VALUES(1, 123456789, 16384, 10, 'query with qual'); 61 | INSERT INTO "PoWA".powa_qualstats_src_tmp(srvid, ts, uniquequalnodeid, dbid, userid, 62 | qualnodeid, occurences, execution_count, nbfiltered, 63 | mean_err_estimate_ratio, mean_err_estimate_num, 64 | queryid, constvalues, quals) 65 | SELECT 1, now(), 1, 16384, 10, 66 | 1, 1000, 1, 0, 67 | 0, 0, 68 | 123456789, '{}', ARRAY[(1259,1,607,'i')::"PoWA".qual_type]; 69 | SELECT count(*) FROM "PoWA".powa_qualstats_src_tmp; 70 | count 71 | ------- 72 | 1 73 | (1 row) 74 | 75 | SELECT "PoWA".powa_qualstats_snapshot(1); 76 | powa_qualstats_snapshot 77 | ------------------------- 78 | 79 | (1 row) 80 | 81 | SELECT count(*) FROM "PoWA".powa_qualstats_src_tmp; 82 | count 83 | ------- 84 | 0 85 | (1 row) 86 | 87 | SELECT count(*) FROM "PoWA".powa_qualstats_quals_history_current WHERE srvid = 1; 88 | count 89 | ------- 90 | 1 91 | (1 row) 92 | 93 | -- activate / deactivate extension 94 | SELECT * FROM "PoWA".powa_functions 95 | WHERE name IN ('pg_database', 'pg_stat_statements', 'pg_stat_kcache', 'pg_qualstats', 'some_extension') 96 | ORDER BY srvid, name, operation, function_name; 97 | srvid | kind | name | operation | external | function_name | query_source | query_cleanup | enabled | priority 98 | -------+-----------+--------------------+-----------+----------+--------------------------------+---------------------------+--------------------------------------------+---------+---------- 99 | 0 | module | pg_database | reset | f | powa_catalog_database_reset | | | t | 100 100 | 0 | module | pg_database | snapshot | f | powa_catalog_database_snapshot | powa_catalog_database_src | | t | 100 101 | 0 | extension | pg_stat_statements | aggregate | f | powa_statements_aggregate | | | t | 10 102 | 0 | extension | pg_stat_statements | purge | f | powa_databases_purge | | | t | 10 103 | 0 | extension | pg_stat_statements | purge | f | powa_statements_purge | | | t | 10 104 | 0 | extension | pg_stat_statements | reset | f | powa_statements_reset | | | t | 10 105 | 0 | extension | pg_stat_statements | snapshot | f | powa_databases_snapshot | powa_databases_src | | t | -3 106 | 0 | extension | pg_stat_statements | snapshot | f | powa_statements_snapshot | powa_statements_src | | t | -2 107 | 1 | module | pg_database | reset | f | powa_catalog_database_reset | | | t | 100 108 | 1 | module | pg_database | snapshot | f | powa_catalog_database_snapshot | powa_catalog_database_src | | t | 100 109 | 1 | extension | pg_qualstats | aggregate | f | powa_qualstats_aggregate | | | t | 10 110 | 1 | extension | pg_qualstats | purge | f | powa_qualstats_purge | | | t | 10 111 | 1 | extension | pg_qualstats | reset | f | powa_qualstats_reset | | | t | 10 112 | 1 | extension | pg_qualstats | snapshot | f | powa_qualstats_snapshot | powa_qualstats_src | SELECT {pg_qualstats}.pg_qualstats_reset() | t | 10 113 | 1 | extension | pg_stat_statements | aggregate | f | powa_statements_aggregate | | | t | 10 114 | 1 | extension | pg_stat_statements | purge | f | powa_databases_purge | | | t | 10 115 | 1 | extension | pg_stat_statements | purge | f | powa_statements_purge | | | t | 10 116 | 1 | extension | pg_stat_statements | reset | f | powa_statements_reset | | | t | 10 117 | 1 | extension | pg_stat_statements | snapshot | f | powa_databases_snapshot | powa_databases_src | | t | -3 118 | 1 | extension | pg_stat_statements | snapshot | f | powa_statements_snapshot | powa_statements_src | | t | -2 119 | (20 rows) 120 | 121 | SELECT * FROM "PoWA".powa_activate_extension(1, 'pg_stat_kcache'); 122 | powa_activate_extension 123 | ------------------------- 124 | t 125 | (1 row) 126 | 127 | SELECT * FROM "PoWA".powa_activate_extension(1, 'some_extension'); 128 | WARNING: powa_activate_extension "some_extension" is not known 129 | powa_activate_extension 130 | ------------------------- 131 | f 132 | (1 row) 133 | 134 | SELECT * FROM "PoWA".powa_functions 135 | WHERE name IN ('pg_database', 'pg_stat_statements', 'pg_stat_kcache', 'pg_qualstats', 'some_extension') 136 | ORDER BY srvid, name, operation, function_name; 137 | srvid | kind | name | operation | external | function_name | query_source | query_cleanup | enabled | priority 138 | -------+-----------+--------------------+-----------+----------+--------------------------------+---------------------------+--------------------------------------------+---------+---------- 139 | 0 | module | pg_database | reset | f | powa_catalog_database_reset | | | t | 100 140 | 0 | module | pg_database | snapshot | f | powa_catalog_database_snapshot | powa_catalog_database_src | | t | 100 141 | 0 | extension | pg_stat_statements | aggregate | f | powa_statements_aggregate | | | t | 10 142 | 0 | extension | pg_stat_statements | purge | f | powa_databases_purge | | | t | 10 143 | 0 | extension | pg_stat_statements | purge | f | powa_statements_purge | | | t | 10 144 | 0 | extension | pg_stat_statements | reset | f | powa_statements_reset | | | t | 10 145 | 0 | extension | pg_stat_statements | snapshot | f | powa_databases_snapshot | powa_databases_src | | t | -3 146 | 0 | extension | pg_stat_statements | snapshot | f | powa_statements_snapshot | powa_statements_src | | t | -2 147 | 1 | module | pg_database | reset | f | powa_catalog_database_reset | | | t | 100 148 | 1 | module | pg_database | snapshot | f | powa_catalog_database_snapshot | powa_catalog_database_src | | t | 100 149 | 1 | extension | pg_qualstats | aggregate | f | powa_qualstats_aggregate | | | t | 10 150 | 1 | extension | pg_qualstats | purge | f | powa_qualstats_purge | | | t | 10 151 | 1 | extension | pg_qualstats | reset | f | powa_qualstats_reset | | | t | 10 152 | 1 | extension | pg_qualstats | snapshot | f | powa_qualstats_snapshot | powa_qualstats_src | SELECT {pg_qualstats}.pg_qualstats_reset() | t | 10 153 | 1 | extension | pg_stat_kcache | aggregate | f | powa_kcache_aggregate | | | t | 10 154 | 1 | extension | pg_stat_kcache | purge | f | powa_kcache_purge | | | t | 10 155 | 1 | extension | pg_stat_kcache | reset | f | powa_kcache_reset | | | t | 10 156 | 1 | extension | pg_stat_kcache | snapshot | f | powa_kcache_snapshot | powa_kcache_src | | t | -1 157 | 1 | extension | pg_stat_statements | aggregate | f | powa_statements_aggregate | | | t | 10 158 | 1 | extension | pg_stat_statements | purge | f | powa_databases_purge | | | t | 10 159 | 1 | extension | pg_stat_statements | purge | f | powa_statements_purge | | | t | 10 160 | 1 | extension | pg_stat_statements | reset | f | powa_statements_reset | | | t | 10 161 | 1 | extension | pg_stat_statements | snapshot | f | powa_databases_snapshot | powa_databases_src | | t | -3 162 | 1 | extension | pg_stat_statements | snapshot | f | powa_statements_snapshot | powa_statements_src | | t | -2 163 | (24 rows) 164 | 165 | SELECT * FROM "PoWA".powa_deactivate_extension(1, 'pg_stat_kcache'); 166 | powa_deactivate_extension 167 | --------------------------- 168 | t 169 | (1 row) 170 | 171 | SELECT * FROM "PoWA".powa_deactivate_extension(1, 'some_extension'); 172 | powa_deactivate_extension 173 | --------------------------- 174 | t 175 | (1 row) 176 | 177 | SELECT * FROM "PoWA".powa_functions 178 | WHERE name IN ('pg_database', 'pg_stat_statements', 'pg_stat_kcache', 'pg_qualstats', 'some_extension') 179 | ORDER BY srvid, name, operation, function_name; 180 | srvid | kind | name | operation | external | function_name | query_source | query_cleanup | enabled | priority 181 | -------+-----------+--------------------+-----------+----------+--------------------------------+---------------------------+--------------------------------------------+---------+---------- 182 | 0 | module | pg_database | reset | f | powa_catalog_database_reset | | | t | 100 183 | 0 | module | pg_database | snapshot | f | powa_catalog_database_snapshot | powa_catalog_database_src | | t | 100 184 | 0 | extension | pg_stat_statements | aggregate | f | powa_statements_aggregate | | | t | 10 185 | 0 | extension | pg_stat_statements | purge | f | powa_databases_purge | | | t | 10 186 | 0 | extension | pg_stat_statements | purge | f | powa_statements_purge | | | t | 10 187 | 0 | extension | pg_stat_statements | reset | f | powa_statements_reset | | | t | 10 188 | 0 | extension | pg_stat_statements | snapshot | f | powa_databases_snapshot | powa_databases_src | | t | -3 189 | 0 | extension | pg_stat_statements | snapshot | f | powa_statements_snapshot | powa_statements_src | | t | -2 190 | 1 | module | pg_database | reset | f | powa_catalog_database_reset | | | t | 100 191 | 1 | module | pg_database | snapshot | f | powa_catalog_database_snapshot | powa_catalog_database_src | | t | 100 192 | 1 | extension | pg_qualstats | aggregate | f | powa_qualstats_aggregate | | | t | 10 193 | 1 | extension | pg_qualstats | purge | f | powa_qualstats_purge | | | t | 10 194 | 1 | extension | pg_qualstats | reset | f | powa_qualstats_reset | | | t | 10 195 | 1 | extension | pg_qualstats | snapshot | f | powa_qualstats_snapshot | powa_qualstats_src | SELECT {pg_qualstats}.pg_qualstats_reset() | t | 10 196 | 1 | extension | pg_stat_kcache | aggregate | f | powa_kcache_aggregate | | | f | 10 197 | 1 | extension | pg_stat_kcache | purge | f | powa_kcache_purge | | | f | 10 198 | 1 | extension | pg_stat_kcache | reset | f | powa_kcache_reset | | | f | 10 199 | 1 | extension | pg_stat_kcache | snapshot | f | powa_kcache_snapshot | powa_kcache_src | | f | -1 200 | 1 | extension | pg_stat_statements | aggregate | f | powa_statements_aggregate | | | t | 10 201 | 1 | extension | pg_stat_statements | purge | f | powa_databases_purge | | | t | 10 202 | 1 | extension | pg_stat_statements | purge | f | powa_statements_purge | | | t | 10 203 | 1 | extension | pg_stat_statements | reset | f | powa_statements_reset | | | t | 10 204 | 1 | extension | pg_stat_statements | snapshot | f | powa_databases_snapshot | powa_databases_src | | t | -3 205 | 1 | extension | pg_stat_statements | snapshot | f | powa_statements_snapshot | powa_statements_src | | t | -2 206 | (24 rows) 207 | 208 | SELECT alias FROM "PoWA".powa_servers WHERE id = 1; 209 | alias 210 | ------- 211 | 212 | (1 row) 213 | 214 | SELECT * FROM "PoWA".powa_configure_server(0, '{"somekey": "someval"}'); 215 | ERROR: Local server cannot be configured 216 | SELECT * FROM "PoWA".powa_configure_server(1, '{"somekey": "someval"}'); 217 | ERROR: Unknown field: somekey 218 | SELECT * FROM "PoWA".powa_configure_server(1, '{"alias": "test server"}'); 219 | powa_configure_server 220 | ----------------------- 221 | t 222 | (1 row) 223 | 224 | SELECT alias FROM "PoWA".powa_servers WHERE id = 1; 225 | alias 226 | ------------- 227 | test server 228 | (1 row) 229 | 230 | -- Test reset function 231 | SELECT * from "PoWA".powa_reset(1); 232 | powa_reset 233 | ------------ 234 | t 235 | (1 row) 236 | 237 | -- Test remove server removal 238 | BEGIN; 239 | SELECT * from "PoWA".powa_delete_and_purge_server(1); 240 | powa_delete_and_purge_server 241 | ------------------------------ 242 | t 243 | (1 row) 244 | 245 | -- and rollback it as we later test the content of tables with a registered 246 | -- remote server 247 | ROLLBACK; 248 | -------------------------------------------------------------------------------- /expected/03_db_module.out: -------------------------------------------------------------------------------- 1 | -- General setup 2 | \set SHOW_CONTEXT never 3 | -- registering a remote server should have registered all default db modules 4 | SELECT * FROM "PoWA".powa_db_module_config 5 | ORDER BY srvid, db_module COLLATE "C"; 6 | srvid | db_module | dbnames | enabled 7 | -------+------------------------+---------+--------- 8 | 1 | pg_stat_all_indexes | | t 9 | 1 | pg_stat_all_tables | | t 10 | 1 | pg_stat_user_functions | | t 11 | (3 rows) 12 | 13 | -- Can't deactivate a specific db on an "all databases" config 14 | SELECT * FROM "PoWA".powa_deactivate_db_module(1, 'pg_stat_user_functions', ARRAY['test']); 15 | ERROR: cannot deactivate a db module for a specific database if no specific database is configured 16 | SELECT enabled, dbnames FROM "PoWA".powa_db_module_config WHERE srvid = 1 AND db_module = 'pg_stat_user_functions'; 17 | enabled | dbnames 18 | ---------+--------- 19 | t | 20 | (1 row) 21 | 22 | -- Activating a specifc db on an "all databases" config switch to that db only 23 | SELECT * FROM "PoWA".powa_activate_db_module(1, 'pg_stat_user_functions', ARRAY['d1']); 24 | powa_activate_db_module 25 | ------------------------- 26 | t 27 | (1 row) 28 | 29 | SELECT enabled, dbnames FROM "PoWA".powa_db_module_config WHERE srvid = 1 AND db_module = 'pg_stat_user_functions'; 30 | enabled | dbnames 31 | ---------+--------- 32 | t | {d1} 33 | (1 row) 34 | 35 | -- Activating a specifc db on an specific db config replace that database 36 | SELECT * FROM "PoWA".powa_activate_db_module(1, 'pg_stat_user_functions', ARRAY['d2']); 37 | powa_activate_db_module 38 | ------------------------- 39 | t 40 | (1 row) 41 | 42 | SELECT enabled, dbnames FROM "PoWA".powa_db_module_config WHERE srvid = 1 AND db_module = 'pg_stat_user_functions'; 43 | enabled | dbnames 44 | ---------+--------- 45 | t | {d2} 46 | (1 row) 47 | 48 | -- Deactivating without specific database switches back to "all db", and mark it as disabled 49 | SELECT * FROM "PoWA".powa_deactivate_db_module(1, 'pg_stat_user_functions'); 50 | powa_deactivate_db_module 51 | --------------------------- 52 | t 53 | (1 row) 54 | 55 | SELECT enabled, dbnames FROM "PoWA".powa_db_module_config WHERE srvid = 1 AND db_module = 'pg_stat_user_functions'; 56 | enabled | dbnames 57 | ---------+--------- 58 | f | 59 | (1 row) 60 | 61 | -- Activating with multiple db switches back to enabled and setup the datbases 62 | SELECT * FROM "PoWA".powa_activate_db_module(1, 'pg_stat_user_functions', ARRAY['d1', 'd3', 'd4']); 63 | powa_activate_db_module 64 | ------------------------- 65 | t 66 | (1 row) 67 | 68 | SELECT enabled, dbnames FROM "PoWA".powa_db_module_config WHERE srvid = 1 AND db_module = 'pg_stat_user_functions'; 69 | enabled | dbnames 70 | ---------+------------ 71 | t | {d1,d3,d4} 72 | (1 row) 73 | 74 | -- Deactivating a specific db will just remove that db 75 | SELECT * FROM "PoWA".powa_deactivate_db_module(1, 'pg_stat_user_functions', ARRAY['d3']); 76 | powa_deactivate_db_module 77 | --------------------------- 78 | t 79 | (1 row) 80 | 81 | SELECT enabled, dbnames FROM "PoWA".powa_db_module_config WHERE srvid = 1 AND db_module = 'pg_stat_user_functions'; 82 | enabled | dbnames 83 | ---------+--------- 84 | t | {d1,d4} 85 | (1 row) 86 | 87 | -- Can't deactivate a non existing specific db 88 | SELECT * FROM "PoWA".powa_deactivate_db_module(1, 'pg_stat_user_functions', ARRAY['d3']); 89 | ERROR: cannot deactivate a db module for a specific database if not already activated on that database 90 | SELECT enabled, dbnames FROM "PoWA".powa_db_module_config WHERE srvid = 1 AND db_module = 'pg_stat_user_functions'; 91 | enabled | dbnames 92 | ---------+--------- 93 | t | {d1,d4} 94 | (1 row) 95 | 96 | -- Deactivating all remaining db will switch back to "all db", and mark it as disabled 97 | SELECT * FROM "PoWA".powa_deactivate_db_module(1, 'pg_stat_user_functions', ARRAY['d1', 'd4']); 98 | powa_deactivate_db_module 99 | --------------------------- 100 | t 101 | (1 row) 102 | 103 | SELECT enabled, dbnames FROM "PoWA".powa_db_module_config WHERE srvid = 1 AND db_module = 'pg_stat_user_functions'; 104 | enabled | dbnames 105 | ---------+--------- 106 | f | 107 | (1 row) 108 | 109 | -- Deactivating a deactivated db module is a noop 110 | SELECT * FROM "PoWA".powa_deactivate_db_module(1, 'pg_stat_user_functions', ARRAY['d1', 'd4']); 111 | powa_deactivate_db_module 112 | --------------------------- 113 | t 114 | (1 row) 115 | 116 | SELECT enabled, dbnames FROM "PoWA".powa_db_module_config WHERE srvid = 1 AND db_module = 'pg_stat_user_functions'; 117 | enabled | dbnames 118 | ---------+--------- 119 | f | 120 | (1 row) 121 | 122 | -- Deactivating a known but not configured db module isn't supported 123 | DELETE FROM "PoWA".powa_db_module_config WHERE srvid = 1 AND db_module = 'pg_stat_all_indexes'; 124 | SELECT * FROM "PoWA".powa_deactivate_db_module(1, 'pg_stat_all_indexes'); 125 | ERROR: db module "pg_stat_all_indexes" is not configured 126 | ----------------------------------------------------------- 127 | -- Test the query source API, with different major versions 128 | ----------------------------------------------------------- 129 | -- pg 13.1 should see n_ins_since_vacuum but not last_seq_scan and other fields 130 | -- introduced in pg16 131 | SELECT * FROM "PoWA".powa_db_functions(1, 130001) 132 | ORDER BY db_module COLLATE "C", operation COLLATE "C"; 133 | srvid | db_module | operation | function_name | dbnames | query_source | tmp_table | enabled | priority 134 | -------+------------------------+-----------+-------------------------------+---------+---------------------------------------------------------------------------------+------------------------------------+---------+---------- 135 | 1 | pg_stat_all_tables | aggregate | powa_all_tables_aggregate | | | | t | 10 136 | 1 | pg_stat_all_tables | purge | powa_all_tables_purge | | | | t | 10 137 | 1 | pg_stat_all_tables | reset | powa_all_tables_reset | | | | t | 10 138 | 1 | pg_stat_all_tables | snapshot | powa_all_tables_snapshot | | SELECT relid, pg_table_size(relid) AS tbl_size, +| "PoWA".powa_all_tables_src_tmp | t | 10 139 | | | | | | seq_scan, NULL AS last_seq_scan, seq_tup_read, +| | | 140 | | | | | | idx_scan, NULL AS last_idx_scan, idx_tup_fetch, +| | | 141 | | | | | | n_tup_ins, n_tup_upd, n_tup_del, n_tup_hot_upd, 0 AS n_tup_newpage_upd,+| | | 142 | | | | | | n_live_tup, n_dead_tup, n_mod_since_analyze, n_ins_since_vacuum, +| | | 143 | | | | | | last_vacuum, last_autovacuum, last_analyze, last_autoanalyze, +| | | 144 | | | | | | vacuum_count, autovacuum_count, analyze_count, autoanalyze_count, +| | | 145 | | | | | | heap_blks_read, heap_blks_hit, idx_blks_read, idx_blks_hit, +| | | 146 | | | | | | toast_blks_read, toast_blks_hit, tidx_blks_read, tidx_blks_hit +| | | 147 | | | | | | FROM pg_catalog.pg_stat_all_tables st +| | | 148 | | | | | | JOIN pg_catalog.pg_statio_all_tables sit USING (relid) | | | 149 | 1 | pg_stat_user_functions | aggregate | powa_user_functions_aggregate | | | | f | 10 150 | 1 | pg_stat_user_functions | purge | powa_user_functions_purge | | | | f | 10 151 | 1 | pg_stat_user_functions | reset | powa_user_functions_reset | | | | f | 10 152 | 1 | pg_stat_user_functions | snapshot | powa_user_functions_snapshot | | SELECT funcid, calls, total_time, self_time +| "PoWA".powa_user_functions_src_tmp | f | 10 153 | | | | | | FROM pg_catalog.pg_stat_user_functions | | | 154 | (8 rows) 155 | 156 | -- Check that we don't see n_ins_since_vacuum on pg13- 157 | SELECT query_source FROM "PoWA".powa_db_functions(1, 120012) 158 | WHERE db_module = 'pg_stat_all_tables' AND operation = 'snapshot'; 159 | query_source 160 | --------------------------------------------------------------------------------- 161 | SELECT relid, pg_table_size(relid) AS tbl_size, + 162 | seq_scan, NULL AS last_seq_scan, seq_tup_read, + 163 | idx_scan, NULL AS last_idx_scan, idx_tup_fetch, + 164 | n_tup_ins, n_tup_upd, n_tup_del, n_tup_hot_upd, 0 AS n_tup_newpage_upd,+ 165 | n_live_tup, n_dead_tup, n_mod_since_analyze, 0 AS n_ins_since_vacuum, + 166 | last_vacuum, last_autovacuum, last_analyze, last_autoanalyze, + 167 | vacuum_count, autovacuum_count, analyze_count, autoanalyze_count, + 168 | heap_blks_read, heap_blks_hit, idx_blks_read, idx_blks_hit, + 169 | toast_blks_read, toast_blks_hit, tidx_blks_read, tidx_blks_hit + 170 | FROM pg_catalog.pg_stat_all_tables st + 171 | JOIN pg_catalog.pg_statio_all_tables sit USING (relid) 172 | (1 row) 173 | 174 | -------------------------------------------------------------------------------- /expected/04_catalog.out: -------------------------------------------------------------------------------- 1 | -- General setup 2 | \set SHOW_CONTEXT never 3 | -- Check the source query retrieval 4 | SELECT * FROM "PoWA".powa_catalog_src_query('pg_class', 90600); 5 | powa_catalog_src_query 6 | -------------------------------------------------------------------------- 7 | SELECT oid, relname::text AS relname, relnamespace, relpages, reltuples,+ 8 | reltoastrelid, relisshared, relpersistence, relkind, relnatts, + 9 | relrowsecurity, relforcerowsecurity, relreplident, + 10 | false AS relispartition, + 11 | reloptions, + 12 | NULL::text AS relpartbound + 13 | FROM pg_catalog.pg_class + 14 | WHERE relpersistence != 't' 15 | (1 row) 16 | 17 | -- check pg_database catalog snapshot. We just insert 2 databases to make 18 | -- things simpler 19 | INSERT INTO "PoWA".powa_catalog_database_src_tmp 20 | SELECT 1, * 21 | FROM "PoWA".powa_catalog_database_src(0) src 22 | WHERE src.datname = current_database(); 23 | INSERT INTO "PoWA".powa_catalog_database_src_tmp 24 | SELECT 1, * 25 | FROM "PoWA".powa_catalog_database_src(0) src 26 | WHERE src.datname != current_database() 27 | AND src.datname != 'test' 28 | AND src.oid != 1 29 | LIMIT 1; 30 | SELECT "PoWA".powa_catalog_database_snapshot(1); 31 | powa_catalog_database_snapshot 32 | -------------------------------- 33 | 34 | (1 row) 35 | 36 | -- there shouldn't be a refresh time 37 | SELECT count(*) FILTER (WHERE last_refresh IS NULL) = 2 AS ok, 38 | count(*) FILTER (WHERE last_refresh IS NOT NULL) AS nb_with_refresh 39 | FROM "PoWA".powa_catalog_databases 40 | WHERE srvid = 1; 41 | ok | nb_with_refresh 42 | ----+----------------- 43 | t | 0 44 | (1 row) 45 | 46 | -- check the rest of the per-database catalog snapshot 47 | DO $_$ 48 | DECLARE 49 | v_ok boolean; 50 | v_num integer; 51 | v_nb_rec integer; 52 | v_nb_rec2 integer; 53 | v_catname text; 54 | v_prefix text; 55 | v_src_tmp text; 56 | v_query text; 57 | BEGIN 58 | SELECT setting INTO v_num 59 | FROM pg_settings 60 | WHERE name = 'server_version_num'; 61 | 62 | FOR v_catname IN SELECT catname FROM "PoWA".powa_catalogs ORDER BY priority 63 | LOOP 64 | -- get the necessary object name 65 | SELECT 'powa_catalog_' || replace(v_catname, 'pg_', '') INTO v_prefix; 66 | SELECT v_prefix || '_src_tmp' 67 | INTO v_src_tmp; 68 | SELECT "PoWA".powa_catalog_src_query(v_catname, v_num) 69 | INTO v_query; 70 | 71 | -- there shouldn't be any stored data for this catalog 72 | EXECUTE format('SELECT count(*) = 0, count(*) FROM "PoWA".%I', v_prefix) 73 | INTO v_ok, v_nb_rec; 74 | 75 | IF NOT v_ok THEN 76 | RAISE WARNING 'catalog % already has stored data (% rows) in %', 77 | v_catname, v_nb_rec, v_prefix; 78 | END IF; 79 | 80 | -- manually insert some data for this catalog for 2 different 81 | -- databases, with the same content 82 | EXECUTE format('INSERT INTO "PoWA".%I 83 | SELECT 1 AS srvid, d.oid AS dbid, src.* 84 | FROM (%s) src 85 | CROSS JOIN "PoWA".powa_catalog_databases d', 86 | v_src_tmp, v_query); 87 | 88 | -- snapshot the given catalog 89 | PERFORM "PoWA".powa_catalog_generic_snapshot(1, v_catname); 90 | 91 | -- there should now be stored data for this catalog 92 | EXECUTE format('SELECT count(*) > 0, count(*) FROM "PoWA".%I', v_prefix) 93 | INTO v_ok, v_nb_rec; 94 | 95 | IF NOT v_ok THEN 96 | RAISE WARNING 'catalog % does not have stored data in %', 97 | v_catname, v_prefix; 98 | END IF; 99 | 100 | -- source table should now be empty 101 | EXECUTE format('SELECT count(*) = 0, count(*) FROM "PoWA".%I', v_src_tmp) 102 | INTO v_ok, v_nb_rec; 103 | 104 | IF NOT v_ok THEN 105 | RAISE WARNING 'source table % for catalog % still has % rows', 106 | v_src_tmp, v_catname, v_nb_rec; 107 | END IF; 108 | 109 | -- There should be records for 2 databases 110 | EXECUTE format('SELECT count(DISTINCT dbid) = 2, count(DISTINCT dbid) 111 | FROM "PoWA".%I', v_prefix) 112 | INTO v_ok, v_nb_rec; 113 | 114 | IF NOT v_ok THEN 115 | RAISE WARNING 'table % for catalog % does not have record for 2 databases (found %)', 116 | v_prefix, v_catname, v_nb_rec; 117 | END IF; 118 | 119 | -- both databases should have the same number of records 120 | EXECUTE format('SELECT ( 121 | SELECT count(*) FROM "PoWA".%1$I c 122 | JOIN pg_database d ON c.dbid = d.oid 123 | WHERE srvid = 1 AND d.datname = current_database() 124 | ) = ( 125 | SELECT count(*) FROM "PoWA".%1$I c 126 | JOIN pg_database d ON c.dbid = d.oid 127 | WHERE srvid = 1 AND d.datname != current_database() 128 | ),( 129 | SELECT count(*) FROM "PoWA".%1$I c 130 | JOIN pg_database d ON c.dbid = d.oid 131 | WHERE srvid = 1 AND d.datname = current_database() 132 | ), ( 133 | SELECT count(*) FROM "PoWA".%1$I c 134 | JOIN pg_database d ON c.dbid = d.oid 135 | WHERE srvid = 1 AND d.datname != current_database() 136 | ) 137 | ', v_prefix, v_nb_rec, v_nb_rec2) INTO v_ok; 138 | 139 | IF NOT v_ok THEN 140 | RAISE WARNING 'table % for catalog % does not have the same number of records for the 2 databases: % vs %', 141 | v_prefix, v_catname, v_nb_rec, v_nb_rec2; 142 | END IF; 143 | 144 | -- the refresh time should have been saved only if this is pg_class 145 | -- catalog 146 | SELECT count(*) INTO v_nb_rec 147 | FROM "PoWA".powa_catalog_databases 148 | WHERE srvid = 1 149 | AND last_refresh IS NULL; 150 | IF v_catname = 'pg_class' THEN 151 | IF v_nb_rec != 0 THEN 152 | RAISE WARNING 'last_refresh was not saved when processing pg_class, % records without a refresh time', 153 | v_nb_rec; 154 | END IF; 155 | ELSE 156 | IF v_nb_rec = 0 THEN 157 | RAISE WARNING 'last_refresh was saved when processing %, % records without a refresh time', 158 | v_catname, v_nb_rec; 159 | END IF; 160 | END IF; 161 | 162 | -- snapshot the given catalog again without source data, nothing should 163 | -- happen 164 | PERFORM "PoWA".powa_catalog_generic_snapshot(1, v_catname); 165 | 166 | -- there should still be stored data for this catalog 167 | EXECUTE format('SELECT count(*) > 0, count(*) FROM "PoWA".%I', v_prefix) 168 | INTO v_ok, v_nb_rec; 169 | 170 | IF NOT v_ok THEN 171 | RAISE WARNING 'catalog % does not have stored data in %', 172 | v_catname, v_prefix; 173 | END IF; 174 | 175 | -- re-add some data in the src table, but for 1 db only, and snapshot 176 | -- again 177 | EXECUTE format('INSERT INTO "PoWA".%I 178 | SELECT 1 AS srvid, d.oid AS dbid, src.* 179 | FROM (%s) src 180 | JOIN "PoWA".powa_catalog_databases d 181 | ON d.datname = current_database()', 182 | v_src_tmp, v_query); 183 | PERFORM "PoWA".powa_catalog_generic_snapshot(1, v_catname); 184 | 185 | -- both databases should still have the same number of records 186 | EXECUTE format('SELECT ( 187 | SELECT count(*) FROM "PoWA".%1$I c 188 | JOIN pg_database d ON c.dbid = d.oid 189 | WHERE srvid = 1 AND d.datname = current_database() 190 | ) = ( 191 | SELECT count(*) FROM "PoWA".%1$I c 192 | JOIN pg_database d ON c.dbid = d.oid 193 | WHERE srvid = 1 AND d.datname != current_database() 194 | ),( 195 | SELECT count(*) FROM "PoWA".%1$I c 196 | JOIN pg_database d ON c.dbid = d.oid 197 | WHERE srvid = 1 AND d.datname = current_database() 198 | ), ( 199 | SELECT count(*) FROM "PoWA".%1$I c 200 | JOIN pg_database d ON c.dbid = d.oid 201 | WHERE srvid = 1 AND d.datname != current_database() 202 | ) 203 | ', v_prefix, v_nb_rec, v_nb_rec2) INTO v_ok; 204 | 205 | IF NOT v_ok THEN 206 | RAISE WARNING 'table % for catalog % does not have the same number of records for the 2 databases: % vs %', 207 | v_prefix, v_catname, v_nb_rec, v_nb_rec2; 208 | END IF; 209 | 210 | -- re-add some data in the src table to later test the reset 211 | EXECUTE format('INSERT INTO "PoWA".%I 212 | SELECT 1 AS srvid, d.oid AS dbid, src.* 213 | FROM (%s) src 214 | CROSS JOIN "PoWA".powa_catalog_databases d', 215 | v_src_tmp, v_query); 216 | END LOOP; 217 | END; 218 | $_$ LANGUAGE plpgsql; 219 | SELECT catname, substr(query_source, 1, 12) AS query_source, tmp_table, 220 | array_upper(excluded_dbnames, 1) AS nb_excluded 221 | FROM "PoWA".powa_catalog_functions(1, 150000) 222 | WHERE catname = 'pg_class'; 223 | catname | query_source | tmp_table | nb_excluded 224 | ----------+--------------+-----------------------------------+------------- 225 | pg_class | SELECT oid, | "PoWA".powa_catalog_class_src_tmp | 2 226 | (1 row) 227 | 228 | -- Check the refresh interval filtering 229 | INSERT INTO "PoWA".powa_catalog_databases(srvid, oid, datname, last_refresh) 230 | VALUES (1, 1, 'test', now() - interval '1 month'); 231 | -- default interval should exclude test database 232 | WITH e AS (SELECT DISTINCT unnest(excluded_dbnames) AS excluded 233 | FROM "PoWA".powa_catalog_functions(1, 150000)) 234 | SELECT coalesce(array_agg(excluded), '{}') AS excluded_dbnames 235 | FROM e 236 | WHERE excluded = 'test'; 237 | excluded_dbnames 238 | ------------------ 239 | {test} 240 | (1 row) 241 | 242 | -- 15 days interval should not exclude test database 243 | WITH e AS (SELECT DISTINCT unnest(excluded_dbnames) AS excluded 244 | FROM "PoWA".powa_catalog_functions(1, 150000, '15 days')) 245 | SELECT coalesce(array_agg(excluded), '{}') AS excluded_dbnames 246 | FROM e 247 | WHERE excluded = 'test'; 248 | excluded_dbnames 249 | ------------------ 250 | {} 251 | (1 row) 252 | 253 | -------------------------------------------------------------------------------- /expected/05_module.out: -------------------------------------------------------------------------------- 1 | -- we can't test API unless there are module config for remote servers 2 | SELECT count(distinct srvid) > 0 AS ok FROM "PoWA".powa_module_config 3 | WHERE srvid != 0; 4 | ok 5 | ---- 6 | t 7 | (1 row) 8 | 9 | -- check for missing module config 10 | SELECT pm.module 11 | FROM "PoWA".powa_modules pm 12 | LEFT JOIN "PoWA".powa_module_config pmc USING (module) 13 | LEFT JOIN "PoWA".powa_servers ps ON ps.id = pmc.srvid 14 | WHERE ps.id IS NULL OR pmc.module IS NULL; 15 | module 16 | -------- 17 | (0 rows) 18 | 19 | -- check that all declared functions have been defined 20 | SELECT pmf.module, pmf.function_name 21 | FROM "PoWA".powa_module_functions pmf 22 | LEFT JOIN pg_proc p ON p.proname = pmf.function_name 23 | LEFT JOIN pg_namespace n ON n.oid = p.pronamespace 24 | WHERE p.proname IS NULL; 25 | module | function_name 26 | --------+--------------- 27 | (0 rows) 28 | 29 | -- same for src function 30 | SELECT pmf.module, pmf.query_source 31 | FROM "PoWA".powa_module_functions pmf 32 | LEFT JOIN pg_proc p ON p.proname = pmf.query_source 33 | LEFT JOIN pg_namespace n ON n.oid = p.pronamespace 34 | WHERE pmf.query_source IS NOT NULL AND p.proname IS NULL; 35 | module | query_source 36 | --------+-------------- 37 | (0 rows) 38 | 39 | -------------------------------------------------------------------------------- /expected/10_acl.out: -------------------------------------------------------------------------------- 1 | -- Check the relations for which powa_admin is missing ACL 2 | CREATE FUNCTION has_table_or_seq_privilege(relkind "char", rolname text, 3 | relid oid, priv text) 4 | RETURNS bool 5 | AS $$ 6 | BEGIN 7 | IF relkind = 'S' THEN 8 | RETURN has_sequence_privilege(rolname, relid, priv); 9 | ELSE 10 | RETURN has_table_privilege(rolname, relid, priv); 11 | END IF; 12 | END; 13 | $$ LANGUAGE plpgsql; 14 | CREATE FUNCTION check_has_privilege(rolname text, 15 | tbl_priv text[], seq_priv text[]) 16 | RETURNS TABLE (powa_role text, relname name, relkind "char", priv text) 17 | AS $$ 18 | WITH ext AS ( 19 | SELECT c.oid, c.relname, c.relkind 20 | FROM pg_depend d 21 | JOIN pg_extension e ON d.refclassid = 'pg_extension'::regclass 22 | AND e.oid = d.refobjid 23 | AND e.extname = 'powa' 24 | JOIN pg_class c ON d.classid = 'pg_class'::regclass 25 | AND c.oid = d.objid 26 | ), 27 | acls(priv, isseq) AS ( 28 | SELECT unnest(tbl_priv), false 29 | UNION ALL 30 | SELECT unnest(seq_priv), true 31 | ) 32 | SELECT rolname AS powa_role, relname, relkind, priv 33 | FROM ext 34 | JOIN acls ON acls.isseq = (ext.relkind = 'S') 35 | WHERE NOT has_table_or_seq_privilege(relkind, rolname, ext.oid, priv) 36 | ORDER BY relname, priv; 37 | $$ LANGUAGE sql; 38 | CREATE FUNCTION check_has_not_privilege(rolname text, 39 | tbl_priv text[], seq_priv text[]) 40 | RETURNS TABLE (powa_role text, relname name, relkind "char", priv text) 41 | AS $$ 42 | WITH ext AS ( 43 | SELECT c.oid, c.relname, c.relkind 44 | FROM pg_depend d 45 | JOIN pg_extension e ON d.refclassid = 'pg_extension'::regclass 46 | AND e.oid = d.refobjid 47 | AND e.extname = 'powa' 48 | JOIN pg_class c ON d.classid = 'pg_class'::regclass 49 | AND c.oid = d.objid 50 | ), 51 | acls(priv, isseq) AS ( 52 | SELECT unnest(tbl_priv), false 53 | UNION ALL 54 | SELECT unnest(seq_priv), true 55 | ) 56 | SELECT rolname AS powa_role, relname, relkind, priv 57 | FROM ext 58 | JOIN acls ON acls.isseq = (ext.relkind = 'S') 59 | WHERE has_table_or_seq_privilege(relkind, rolname, ext.oid, priv) 60 | ORDER BY relname, priv; 61 | $$ LANGUAGE sql; 62 | -- powa_admin should have all privileges on all relations 63 | SELECT powa_role, relname, priv 64 | FROM check_has_privilege('powa_admin', 65 | array ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 66 | 'TRIGGER'], 67 | array ['USAGE', 'SELECT', 'UPDATE']); 68 | powa_role | relname | priv 69 | -----------+---------+------ 70 | (0 rows) 71 | 72 | -- powa_read_all_data should have SELECT privilege on all relation except 73 | -- *_src_tmp tables and sequences 74 | SELECT powa_role, relname, priv 75 | FROM check_has_privilege('powa_read_all_data', 76 | array ['SELECT'], 77 | array []::text[]); 78 | powa_role | relname | priv 79 | --------------------+--------------------------------------+-------- 80 | powa_read_all_data | powa_all_indexes_src_tmp | SELECT 81 | powa_read_all_data | powa_all_tables_src_tmp | SELECT 82 | powa_read_all_data | powa_catalog_attribute_src_tmp | SELECT 83 | powa_read_all_data | powa_catalog_class_src_tmp | SELECT 84 | powa_read_all_data | powa_catalog_collation_src_tmp | SELECT 85 | powa_read_all_data | powa_catalog_database_src_tmp | SELECT 86 | powa_read_all_data | powa_catalog_language_src_tmp | SELECT 87 | powa_read_all_data | powa_catalog_namespace_src_tmp | SELECT 88 | powa_read_all_data | powa_catalog_proc_src_tmp | SELECT 89 | powa_read_all_data | powa_catalog_role_src_tmp | SELECT 90 | powa_read_all_data | powa_catalog_type_src_tmp | SELECT 91 | powa_read_all_data | powa_databases_src_tmp | SELECT 92 | powa_read_all_data | powa_kcache_src_tmp | SELECT 93 | powa_read_all_data | powa_qualstats_src_tmp | SELECT 94 | powa_read_all_data | powa_replication_slots_src_tmp | SELECT 95 | powa_read_all_data | powa_stat_activity_src_tmp | SELECT 96 | powa_read_all_data | powa_stat_archiver_src_tmp | SELECT 97 | powa_read_all_data | powa_stat_bgwriter_src_tmp | SELECT 98 | powa_read_all_data | powa_stat_checkpointer_src_tmp | SELECT 99 | powa_read_all_data | powa_stat_database_conflicts_src_tmp | SELECT 100 | powa_read_all_data | powa_stat_database_src_tmp | SELECT 101 | powa_read_all_data | powa_stat_io_src_tmp | SELECT 102 | powa_read_all_data | powa_stat_replication_src_tmp | SELECT 103 | powa_read_all_data | powa_stat_slru_src_tmp | SELECT 104 | powa_read_all_data | powa_stat_subscription_src_tmp | SELECT 105 | powa_read_all_data | powa_stat_subscription_stats_src_tmp | SELECT 106 | powa_read_all_data | powa_stat_wal_receiver_src_tmp | SELECT 107 | powa_read_all_data | powa_stat_wal_src_tmp | SELECT 108 | powa_read_all_data | powa_statements_src_tmp | SELECT 109 | powa_read_all_data | powa_user_functions_src_tmp | SELECT 110 | powa_read_all_data | powa_wait_sampling_src_tmp | SELECT 111 | (31 rows) 112 | 113 | -- powa_read_all_data should not have non-SELECT privilege on any table, and no 114 | -- privilege on sequences 115 | SELECT powa_role, relname, priv 116 | FROM check_has_not_privilege('powa_read_all_data', 117 | array ['INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 118 | array ['USAGE', 'SELECT', 'UPDATE']); 119 | powa_role | relname | priv 120 | -----------+---------+------ 121 | (0 rows) 122 | 123 | -- powa_read_all_metrics should be the same as powa_read_all_data except that 124 | -- it can't acceess any pg_qualstats related table 125 | SELECT powa_role, relname, priv 126 | FROM check_has_privilege('powa_read_all_metrics', 127 | array ['SELECT'], 128 | array []::text[]); 129 | powa_role | relname | priv 130 | -----------------------+--------------------------------------------+-------- 131 | powa_read_all_metrics | powa_all_indexes_src_tmp | SELECT 132 | powa_read_all_metrics | powa_all_tables_src_tmp | SELECT 133 | powa_read_all_metrics | powa_catalog_attribute_src_tmp | SELECT 134 | powa_read_all_metrics | powa_catalog_class_src_tmp | SELECT 135 | powa_read_all_metrics | powa_catalog_collation_src_tmp | SELECT 136 | powa_read_all_metrics | powa_catalog_database_src_tmp | SELECT 137 | powa_read_all_metrics | powa_catalog_language_src_tmp | SELECT 138 | powa_read_all_metrics | powa_catalog_namespace_src_tmp | SELECT 139 | powa_read_all_metrics | powa_catalog_proc_src_tmp | SELECT 140 | powa_read_all_metrics | powa_catalog_role_src_tmp | SELECT 141 | powa_read_all_metrics | powa_catalog_type_src_tmp | SELECT 142 | powa_read_all_metrics | powa_databases_src_tmp | SELECT 143 | powa_read_all_metrics | powa_kcache_src_tmp | SELECT 144 | powa_read_all_metrics | powa_qualstats_constvalues_history | SELECT 145 | powa_read_all_metrics | powa_qualstats_constvalues_history_current | SELECT 146 | powa_read_all_metrics | powa_qualstats_src_tmp | SELECT 147 | powa_read_all_metrics | powa_replication_slots_src_tmp | SELECT 148 | powa_read_all_metrics | powa_stat_activity_src_tmp | SELECT 149 | powa_read_all_metrics | powa_stat_archiver_src_tmp | SELECT 150 | powa_read_all_metrics | powa_stat_bgwriter_src_tmp | SELECT 151 | powa_read_all_metrics | powa_stat_checkpointer_src_tmp | SELECT 152 | powa_read_all_metrics | powa_stat_database_conflicts_src_tmp | SELECT 153 | powa_read_all_metrics | powa_stat_database_src_tmp | SELECT 154 | powa_read_all_metrics | powa_stat_io_src_tmp | SELECT 155 | powa_read_all_metrics | powa_stat_replication_src_tmp | SELECT 156 | powa_read_all_metrics | powa_stat_slru_src_tmp | SELECT 157 | powa_read_all_metrics | powa_stat_subscription_src_tmp | SELECT 158 | powa_read_all_metrics | powa_stat_subscription_stats_src_tmp | SELECT 159 | powa_read_all_metrics | powa_stat_wal_receiver_src_tmp | SELECT 160 | powa_read_all_metrics | powa_stat_wal_src_tmp | SELECT 161 | powa_read_all_metrics | powa_statements_src_tmp | SELECT 162 | powa_read_all_metrics | powa_user_functions_src_tmp | SELECT 163 | powa_read_all_metrics | powa_wait_sampling_src_tmp | SELECT 164 | (33 rows) 165 | 166 | SELECT powa_role, relname, priv 167 | FROM check_has_not_privilege('powa_read_all_metrics', 168 | array ['INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 169 | array ['USAGE', 'SELECT', 'UPDATE']); 170 | powa_role | relname | priv 171 | -----------+---------+------ 172 | (0 rows) 173 | 174 | -- powa_write_all_data should have SELECT/INSERT/UPDATE/DELETE/TRUNCATE 175 | -- privileges on all relations (and all privileges on sequences) 176 | SELECT powa_role, relname, priv 177 | FROM check_has_privilege('powa_write_all_data', 178 | array ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE'], 179 | array ['USAGE', 'SELECT', 'UPDATE']); 180 | powa_role | relname | priv 181 | -----------+---------+------ 182 | (0 rows) 183 | 184 | -- powa_write_all_data should not have TRIGGER/REFERENCES privileges on any 185 | -- relations 186 | SELECT powa_role, relname, priv 187 | FROM check_has_not_privilege('powa_write_all_data', 188 | array ['TRIGGER', 'REFERENCES'], 189 | array []::text[]); 190 | powa_role | relname | priv 191 | -----------+---------+------ 192 | (0 rows) 193 | 194 | -- powa_snapshot should have SELECT/INSERT/UPDATE/DELETE/TRUNCATE 195 | -- privileges on all metric-related relations (and all privileges on sequences) 196 | -- only 197 | SELECT powa_role, relname, relkind, array_agg(priv) 198 | FROM check_has_privilege('powa_snapshot', 199 | array ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE'], 200 | array ['USAGE', 'SELECT', 'UPDATE']) 201 | GROUP BY 1, 2, 3 202 | ORDER BY 1, 2, 3; 203 | powa_role | relname | relkind | array_agg 204 | ---------------+----------------------------+---------+--------------------------------- 205 | powa_snapshot | powa_all_functions | v | {DELETE,INSERT,TRUNCATE,UPDATE} 206 | powa_snapshot | powa_catalog_src_queries | r | {DELETE,INSERT,TRUNCATE,UPDATE} 207 | powa_snapshot | powa_catalogs | r | {DELETE,INSERT,TRUNCATE,UPDATE} 208 | powa_snapshot | powa_db_module_config | r | {DELETE,INSERT,TRUNCATE,UPDATE} 209 | powa_snapshot | powa_db_module_functions | r | {DELETE,INSERT,TRUNCATE,UPDATE} 210 | powa_snapshot | powa_db_module_src_queries | r | {DELETE,INSERT,TRUNCATE,UPDATE} 211 | powa_snapshot | powa_db_modules | r | {DELETE,INSERT,TRUNCATE,UPDATE} 212 | powa_snapshot | powa_extension_config | r | {DELETE,INSERT,TRUNCATE,UPDATE} 213 | powa_snapshot | powa_extension_functions | r | {DELETE,INSERT,TRUNCATE,UPDATE} 214 | powa_snapshot | powa_extensions | r | {DELETE,INSERT,TRUNCATE,UPDATE} 215 | powa_snapshot | powa_functions | v | {DELETE,INSERT,TRUNCATE,UPDATE} 216 | powa_snapshot | powa_module_config | r | {DELETE,INSERT,TRUNCATE,UPDATE} 217 | powa_snapshot | powa_module_functions | r | {DELETE,INSERT,TRUNCATE,UPDATE} 218 | powa_snapshot | powa_modules | r | {DELETE,INSERT,TRUNCATE,UPDATE} 219 | powa_snapshot | powa_roles | r | {DELETE,INSERT,TRUNCATE,UPDATE} 220 | powa_snapshot | powa_servers | r | {DELETE,INSERT,TRUNCATE,UPDATE} 221 | powa_snapshot | powa_servers_id_seq | S | {SELECT,UPDATE,USAGE} 222 | (17 rows) 223 | 224 | -- powa_snapshot should not have TRIGGER/REFERENCES privileges on any relations 225 | SELECT powa_role, relname, priv 226 | FROM check_has_not_privilege('powa_snapshot', 227 | array ['TRIGGER', 'REFERENCES'], 228 | array []::text[]); 229 | powa_role | relname | priv 230 | -----------+---------+------ 231 | (0 rows) 232 | 233 | -- and try to detect any unexpected GRANT on powa_snapshot, as any newly 234 | -- created table will have too many privileges granted unless explicitly 235 | -- handled 236 | SELECT DISTINCT powa_role, relname 237 | FROM check_has_not_privilege('powa_snapshot', 238 | array ['INSERT', 'UPDATE', 'DELETE', 'TRUNCATE'], 239 | array ['USAGE', 'SELECT', 'UPDATE']) 240 | WHERE relkind != 'v' 241 | AND relname NOT LIKE '%history' 242 | AND relname NOT LIKE '%history\_db' 243 | AND relname NOT LIKE '%history\_current' 244 | AND relname NOT LIKE '%history\_current\_db' 245 | AND relname NOT LIKE '%src\_tmp' 246 | AND relname NOT LIKE 'powa\_catalog\_%' 247 | AND relname NOT LIKE '%qualstats%' 248 | AND relname NOT LIKE '%kcache%' 249 | AND relname NOT IN ('powa_databases', 'powa_snapshot_metas', 'powa_statements'); 250 | powa_role | relname 251 | -----------+--------- 252 | (0 rows) 253 | 254 | -- powa_signal_backend should not have any privilege on any relation 255 | SELECT powa_role, relname, priv 256 | FROM check_has_not_privilege('powa_signal_backend', 257 | array ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 258 | 'TRIGGER'], 259 | array ['USAGE', 'SELECT', 'UPDATE']); 260 | powa_role | relname | priv 261 | -----------+---------+------ 262 | (0 rows) 263 | 264 | -------------------------------------------------------------------------------- /expected/99_cleanup.out: -------------------------------------------------------------------------------- 1 | -- General setup 2 | \set SHOW_CONTEXT never 3 | -- Check for local server reset 4 | SELECT "PoWA".powa_reset(0); 5 | powa_reset 6 | ------------ 7 | t 8 | (1 row) 9 | 10 | -- check catalog FK 11 | DO 12 | $_$ 13 | DECLARE 14 | v_dbid oid; 15 | v_nb integer; 16 | v_catname text; 17 | v_prefix text; 18 | BEGIN 19 | SELECT oid INTO v_dbid FROM "PoWA".powa_catalog_databases 20 | WHERE srvid = 1 AND datname = current_database(); 21 | 22 | DELETE FROM "PoWA".powa_catalog_databases 23 | WHERE srvid = 1 AND datname = current_database(); 24 | FOR v_catname IN SELECT catname FROM "PoWA".powa_catalogs 25 | LOOP 26 | -- get the necessary object name 27 | SELECT 'powa_catalog_' || replace(v_catname, 'pg_', '') INTO v_prefix; 28 | 29 | -- There shouldn't be any row left for that databa in any catalog 30 | EXECUTE format('SELECT count(*) FROM "PoWA".%I WHERE dbid = %s', 31 | v_prefix, v_dbid) INTO v_nb; 32 | IF v_nb != 0 THEN 33 | RAISE WARNING 'table "PoWA".% for catalog % has % rows', 34 | v_prefix, v_catname, v_nb; 35 | END IF; 36 | 37 | -- but there should be record in the src_tmp tables 38 | EXECUTE format('SELECT count(*) FROM "PoWA".%I', v_prefix) INTO v_nb; 39 | IF v_nb = 0 THEN 40 | RAISE WARNING 'table "PoWA".% for catalog % has % rows', 41 | v_prefix || '_src_tmp', v_catname, v_nb; 42 | END IF; 43 | END LOOP; 44 | END; 45 | $_$ LANGUAGE plpgsql; 46 | SELECT "PoWA".powa_reset(1); 47 | powa_reset 48 | ------------ 49 | t 50 | (1 row) 51 | 52 | -- There shouldn't be any row left for that server in any catalog 53 | DO 54 | $_$ 55 | DECLARE 56 | v_nb integer; 57 | v_catname text; 58 | v_prefix text; 59 | BEGIN 60 | FOR v_catname IN SELECT catname FROM "PoWA".powa_catalogs 61 | LOOP 62 | -- get the necessary object name 63 | SELECT 'powa_catalog_' || replace(v_catname, 'pg_', '') INTO v_prefix; 64 | 65 | EXECUTE format('SELECT count(*) FROM "PoWA".%I', v_prefix) INTO v_nb; 66 | IF v_nb != 0 THEN 67 | RAISE WARNING 'table "PoWA".% for catalog % has % rows', 68 | v_prefix, v_catname, v_nb; 69 | END IF; 70 | 71 | EXECUTE format('SELECT count(*) FROM "PoWA".%I', v_prefix) INTO v_nb; 72 | IF v_nb != 0 THEN 73 | RAISE WARNING 'table "PoWA".% for catalog % has % rows', 74 | v_prefix || '_src_tmp', v_catname, v_nb; 75 | END IF; 76 | END LOOP; 77 | END; 78 | $_$ LANGUAGE plpgsql; 79 | -- Check remote server removal 80 | DELETE FROM "PoWA".powa_servers WHERE id = 1; 81 | -- dropping powa will leave the role initially created, and we should be able 82 | -- to reinstall powa again 83 | DROP EXTENSION powa; 84 | CREATE EXTENSION powa WITH SCHEMA "PoWA"; 85 | -- we shouldn't assigned the existing role to our pseudo predefined role, even 86 | -- if they exist with the default name 87 | SELECT * FROM "PoWA".powa_roles WHERE rolname IS NOT NULL; 88 | powa_role | rolname 89 | -----------+--------- 90 | (0 rows) 91 | 92 | -- and also shouldn't put back any ACL on the previous roles 93 | SELECT has_table_privilege('powa_admin', '"PoWA".powa_servers', 'SELECT'); 94 | has_table_privilege 95 | --------------------- 96 | f 97 | (1 row) 98 | 99 | -- We shouldn't be able to grant or revoke privileges in that situation 100 | SELECT "PoWA".powa_revoke(); 101 | ERROR: powa_role powa_admin is NULL 102 | SELECT "PoWA".powa_grant(); 103 | ERROR: powa_role powa_admin is NULL 104 | -- we should be able to reuse the initially created roles 105 | SELECT "PoWA".setup_powa_roles(true); 106 | setup_powa_roles 107 | ------------------ 108 | 109 | (1 row) 110 | 111 | SELECT has_table_privilege('powa_admin', '"PoWA".powa_servers', 'SELECT'); 112 | has_table_privilege 113 | --------------------- 114 | t 115 | (1 row) 116 | 117 | -- cleanup ACL and remove the powa pseudo predefined roles 118 | SELECT "PoWA".powa_revoke(); 119 | powa_revoke 120 | ------------- 121 | 122 | (1 row) 123 | 124 | SELECT has_table_privilege('powa_admin', '"PoWA".powa_servers', 'SELECT'); 125 | has_table_privilege 126 | --------------------- 127 | f 128 | (1 row) 129 | 130 | DROP ROLE powa_admin; 131 | DROP ROLE powa_read_all_data; 132 | DROP ROLE powa_read_all_metrics; 133 | DROP ROLE powa_write_all_data; 134 | DROP ROLE powa_snapshot; 135 | DROP ROLE powa_signal_backend; 136 | -------------------------------------------------------------------------------- /install_all.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE powa; 2 | \c powa 3 | CREATE EXTENSION btree_gist; 4 | CREATE EXTENSION pg_stat_statements; 5 | CREATE EXTENSION pg_qualstats; 6 | CREATE EXTENSION pg_stat_kcache; 7 | CREATE EXTENSION powa; 8 | -------------------------------------------------------------------------------- /powa--5.0.0--5.0.1.sql: -------------------------------------------------------------------------------- 1 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 2 | \echo Use "ALTER EXTENSION powa" to load this file. \quit 3 | 4 | ALTER TABLE @extschema@.powa_module_config DROP COLUMN added_manually; 5 | 6 | SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_module_config',''); 7 | SELECT pg_catalog.pg_extension_config_dump('@extschema@.powa_module_functions',''); 8 | 9 | CREATE OR REPLACE FUNCTION @extschema@.powa_activate_module(_srvid int, _module text) RETURNS boolean 10 | AS $_$ 11 | DECLARE 12 | v_res bool; 13 | BEGIN 14 | IF (_srvid IS NULL) THEN 15 | RAISE EXCEPTION 'powa_activate_module: no server id provided'; 16 | END IF; 17 | 18 | IF (_module IS NULL) THEN 19 | RAISE EXCEPTION 'powa_activate_module: no module provided'; 20 | END IF; 21 | 22 | -- Check that the module is known. 23 | SELECT COUNT(*) = 1 INTO v_res 24 | FROM @extschema@.powa_modules 25 | WHERE module = _module; 26 | 27 | IF (NOT v_res) THEN 28 | RAISE EXCEPTION 'Module "%" is not known', _module; 29 | END IF; 30 | 31 | -- The record may already be present, but the enabled flag could be off. 32 | -- If so simply enable it. Otherwise, add the needed record. 33 | SELECT COUNT(*) > 0 INTO v_res 34 | FROM @extschema@.powa_module_config 35 | WHERE module = _module 36 | AND srvid = _srvid; 37 | 38 | IF (v_res) THEN 39 | UPDATE @extschema@.powa_module_config 40 | SET enabled = true 41 | WHERE enabled = false 42 | AND srvid = _srvid 43 | AND module = _module; 44 | ELSE 45 | INSERT INTO @extschema@.powa_module_config 46 | (srvid, module) 47 | VALUES 48 | (_srvid, _module); 49 | END IF; 50 | 51 | RETURN true; 52 | END; 53 | $_$ LANGUAGE plpgsql 54 | SET search_path = pg_catalog; /* end of powa_activate_module */ 55 | 56 | CREATE OR REPLACE FUNCTION @extschema@.powa_delete_and_purge_server(_srvid integer) RETURNS boolean 57 | AS $_$ 58 | DECLARE 59 | v_rowcount bigint; 60 | v_extnsp text; 61 | BEGIN 62 | IF (_srvid = 0) THEN 63 | RAISE EXCEPTION 'Local server cannot be deleted'; 64 | END IF; 65 | 66 | DELETE FROM @extschema@.powa_servers WHERE id = _srvid; 67 | 68 | GET DIAGNOSTICS v_rowcount = ROW_COUNT; 69 | 70 | -- pg_track_settings is an autonomous extension, so it doesn't have a FK to 71 | -- powa_servers. It therefore needs to be processed manually 72 | SELECT COUNT(*), nspname 73 | FROM pg_extension e 74 | LEFT JOIN pg_namespace n ON n.oid = e.extnamespace 75 | WHERE extname = 'pg_track_settings' 76 | GROUP BY nspname 77 | INTO v_rowcount, v_extnsp; 78 | IF (v_rowcount = 1) THEN 79 | EXECUTE format('DELETE FROM %I.pg_track_settings_list WHERE srvid = %s', 80 | v_extnsp, 81 | _srvid); 82 | EXECUTE format('DELETE FROM %I.pg_track_settings_history WHERE srvid = %s', 83 | v_extnsp, 84 | _srvid); 85 | EXECUTE format('DELETE FROM %I.pg_track_db_role_settings_list WHERE srvid = %s', 86 | v_extnsp, 87 | _srvid); 88 | EXECUTE format('DELETE FROM %I.pg_track_db_role_settings_history WHERE srvid = %s', 89 | v_extnsp, 90 | _srvid); 91 | EXECUTE format('DELETE FROM %I.pg_reboot WHERE srvid = %s', 92 | v_extnsp, 93 | _srvid); 94 | END IF; 95 | 96 | RETURN v_rowcount = 1; 97 | END; 98 | $_$ LANGUAGE plpgsql 99 | SET search_path = pg_catalog; /* powa_delete_and_purge_server */ 100 | -------------------------------------------------------------------------------- /powa--5.0.1--5.0.2.sql: -------------------------------------------------------------------------------- 1 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 2 | \echo Use "ALTER EXTENSION powa" to load this file. \quit 3 | 4 | CREATE OR REPLACE FUNCTION @extschema@.powa_take_snapshot(_srvid integer = 0) RETURNS integer 5 | AS $PROC$ 6 | DECLARE 7 | purgets timestamp with time zone; 8 | purge_seq bigint; 9 | r record; 10 | v_state text; 11 | v_msg text; 12 | v_detail text; 13 | v_hint text; 14 | v_context text; 15 | v_title text = 'PoWA - '; 16 | v_rowcount bigint; 17 | v_nb_err int = 0; 18 | v_errs text[] = '{}'; 19 | v_pattern text = '@extschema@.powa_take_snapshot(%s): function %s.%I failed: 20 | state : %s 21 | message: %s 22 | detail : %s 23 | hint : %s 24 | context: %s'; 25 | v_pattern_simple text = '@extschema@.powa_take_snapshot(%s): function %s.%I failed: %s'; 26 | 27 | v_pattern_cat text = '@extschema@.powa_take_snapshot(%s): function @extschema@.powa_catalog_generic_snapshot for catalog %s failed: 28 | state : %s 29 | message: %s 30 | detail : %s 31 | hint : %s 32 | context: %s'; 33 | v_pattern_cat_simple text = '@extschema@.powa_take_snapshot(%s): function @extschema@.powa_catalog_generic_snapshot for catalog %s failed: %s'; 34 | v_coalesce bigint; 35 | v_catname text; 36 | BEGIN 37 | PERFORM set_config('application_name', 38 | v_title || ' snapshot database list', 39 | false); 40 | PERFORM @extschema@.powa_log('start of powa_take_snapshot(' || _srvid || ')'); 41 | 42 | PERFORM @extschema@.powa_prevent_concurrent_snapshot(_srvid); 43 | 44 | UPDATE @extschema@.powa_snapshot_metas 45 | SET coalesce_seq = coalesce_seq + 1, 46 | errors = NULL, 47 | snapts = now() 48 | WHERE srvid = _srvid 49 | RETURNING coalesce_seq INTO purge_seq; 50 | 51 | PERFORM @extschema@.powa_log(format('coalesce_seq(%s): %s', _srvid, purge_seq)); 52 | 53 | IF (_srvid = 0) THEN 54 | SELECT current_setting('powa.coalesce') INTO v_coalesce; 55 | ELSE 56 | SELECT powa_coalesce 57 | FROM @extschema@.powa_servers 58 | WHERE id = _srvid 59 | INTO v_coalesce; 60 | END IF; 61 | 62 | -- For all enabled snapshot functions in the powa_functions table, execute 63 | FOR r IN SELECT CASE external 64 | WHEN true THEN quote_ident(nsp.nspname) 65 | ELSE '@extschema@' 66 | END AS schema, function_name AS funcname 67 | FROM @extschema@.powa_all_functions AS pf 68 | LEFT JOIN pg_extension AS ext ON pf.kind = 'extension' 69 | AND ext.extname = pf.name 70 | LEFT JOIN pg_namespace AS nsp ON nsp.oid = ext.extnamespace 71 | WHERE operation='snapshot' 72 | AND enabled 73 | AND srvid = _srvid 74 | ORDER BY priority, name 75 | LOOP 76 | -- Call all of them, for the current srvid 77 | BEGIN 78 | PERFORM @extschema@.powa_log(format('calling snapshot function: %s.%I', 79 | r.schema, r.funcname)); 80 | PERFORM set_config('application_name', 81 | v_title || quote_ident(r.funcname) || '(' || _srvid || ')', false); 82 | 83 | EXECUTE format('SELECT %s.%I(%s)', r.schema, r.funcname, _srvid); 84 | EXCEPTION 85 | WHEN OTHERS THEN 86 | GET STACKED DIAGNOSTICS 87 | v_state = RETURNED_SQLSTATE, 88 | v_msg = MESSAGE_TEXT, 89 | v_detail = PG_EXCEPTION_DETAIL, 90 | v_hint = PG_EXCEPTION_HINT, 91 | v_context = PG_EXCEPTION_CONTEXT; 92 | 93 | RAISE warning '%', format(v_pattern, _srvid, r.schema, r.funcname, 94 | v_state, v_msg, v_detail, v_hint, v_context); 95 | 96 | v_errs := array_append(v_errs, format(v_pattern_simple, _srvid, 97 | r.schema, r.funcname, v_msg)); 98 | 99 | v_nb_err = v_nb_err + 1; 100 | END; 101 | END LOOP; 102 | 103 | -- Coalesce datas if needed. The _srvid % 20 is there to avoid having all coalesces run at once 104 | IF ( ((purge_seq + (_srvid % 20) ) % v_coalesce ) = 0 ) 105 | THEN 106 | PERFORM @extschema@.powa_log( 107 | format('coalesce needed, srvid: %s - seq: %s - coalesce seq: %s', 108 | _srvid, purge_seq, v_coalesce )); 109 | 110 | FOR r IN SELECT CASE external 111 | WHEN true THEN quote_ident(nsp.nspname) 112 | ELSE '@extschema@' 113 | END AS schema, function_name AS funcname 114 | FROM @extschema@.powa_all_functions AS pf 115 | LEFT JOIN pg_extension AS ext ON pf.kind = 'extension' 116 | AND ext.extname = pf.name 117 | LEFT JOIN pg_namespace AS nsp ON nsp.oid = ext.extnamespace 118 | WHERE operation='aggregate' 119 | AND enabled 120 | AND srvid = _srvid 121 | ORDER BY priority, name 122 | LOOP 123 | -- Call all of them, for the current srvid 124 | BEGIN 125 | PERFORM @extschema@.powa_log(format('calling aggregate function: %s.%I(%s)', 126 | r.schema, r.funcname, _srvid)); 127 | 128 | PERFORM set_config('application_name', 129 | v_title || quote_ident(r.funcname) || '(' || _srvid || ')', 130 | false); 131 | 132 | EXECUTE format('SELECT %s.%I(%s)', r.schema, r.funcname, _srvid); 133 | EXCEPTION 134 | WHEN OTHERS THEN 135 | GET STACKED DIAGNOSTICS 136 | v_state = RETURNED_SQLSTATE, 137 | v_msg = MESSAGE_TEXT, 138 | v_detail = PG_EXCEPTION_DETAIL, 139 | v_hint = PG_EXCEPTION_HINT, 140 | v_context = PG_EXCEPTION_CONTEXT; 141 | 142 | RAISE warning '%', format(v_pattern, _srvid, r.schema, r.funcname, 143 | v_state, v_msg, v_detail, v_hint, v_context); 144 | 145 | v_errs := array_append(v_errs, format(v_pattern_simple, _srvid, 146 | r.schema, r.funcname, v_msg)); 147 | 148 | v_nb_err = v_nb_err + 1; 149 | END; 150 | END LOOP; 151 | 152 | PERFORM set_config('application_name', 153 | v_title || 'UPDATE powa_snapshot_metas.aggets', 154 | false); 155 | UPDATE @extschema@.powa_snapshot_metas 156 | SET aggts = now() 157 | WHERE srvid = _srvid; 158 | END IF; 159 | 160 | -- We also purge, at the pass after the coalesce 161 | -- The _srvid % 20 is there to avoid having all purges run at once 162 | IF ( ((purge_seq + (_srvid % 20)) % v_coalesce) = 1 ) 163 | THEN 164 | PERFORM @extschema@.powa_log( 165 | format('purge needed, srvid: %s - seq: %s coalesce seq: %s', 166 | _srvid, purge_seq, v_coalesce)); 167 | 168 | FOR r IN SELECT CASE external 169 | WHEN true THEN quote_ident(nsp.nspname) 170 | ELSE '@extschema@' 171 | END AS schema, function_name AS funcname 172 | FROM @extschema@.powa_all_functions AS pf 173 | LEFT JOIN pg_extension AS ext ON pf.kind = 'extension' 174 | AND ext.extname = pf.name 175 | LEFT JOIN pg_namespace AS nsp ON nsp.oid = ext.extnamespace 176 | WHERE operation='purge' 177 | AND enabled 178 | AND srvid = _srvid 179 | ORDER BY priority, name 180 | LOOP 181 | -- Call all of them, for the current srvid 182 | BEGIN 183 | PERFORM @extschema@.powa_log(format('calling purge function: %s.%I(%s)', 184 | r.schema, r.funcname, _srvid)); 185 | PERFORM set_config('application_name', 186 | v_title || quote_ident(r.funcname) || '(' || _srvid || ')', 187 | false); 188 | 189 | EXECUTE format('SELECT %s.%I(%s)', r.schema, r.funcname, _srvid); 190 | EXCEPTION 191 | WHEN OTHERS THEN 192 | GET STACKED DIAGNOSTICS 193 | v_state = RETURNED_SQLSTATE, 194 | v_msg = MESSAGE_TEXT, 195 | v_detail = PG_EXCEPTION_DETAIL, 196 | v_hint = PG_EXCEPTION_HINT, 197 | v_context = PG_EXCEPTION_CONTEXT; 198 | 199 | RAISE warning '%', format(v_pattern, _srvid, r.schema, r.funcname, 200 | v_state, v_msg, v_detail, v_hint, v_context); 201 | 202 | v_errs := array_append(v_errs, format(v_pattern_simple, _srvid, 203 | r.schema, r.funcname, v_msg)); 204 | 205 | v_nb_err = v_nb_err + 1; 206 | END; 207 | END LOOP; 208 | 209 | PERFORM set_config('application_name', 210 | v_title || 'UPDATE powa_snapshot_metas.purgets', 211 | false); 212 | UPDATE @extschema@.powa_snapshot_metas 213 | SET purgets = now() 214 | WHERE srvid = _srvid; 215 | END IF; 216 | 217 | -- and finally we call the snapshot function for the per-db catalog import, 218 | -- if this is a remote server 219 | IF (_srvid != 0) THEN 220 | FOR v_catname IN SELECT catname FROM @extschema@.powa_catalogs ORDER BY priority 221 | LOOP 222 | PERFORM @extschema@.powa_log(format('calling catalog function: %s.%I(%s, %s)', 223 | '@extschema@', 'powa_catalog_generic_snapshot', _srvid, v_catname)); 224 | PERFORM set_config('application_name', 225 | v_title || quote_ident('powa_catalog_generic_snapshot') 226 | || '(' || _srvid || ', ' || v_catname || ')', false); 227 | 228 | BEGIN 229 | PERFORM @extschema@.powa_catalog_generic_snapshot(_srvid, v_catname); 230 | EXCEPTION 231 | WHEN OTHERS THEN 232 | GET STACKED DIAGNOSTICS 233 | v_state = RETURNED_SQLSTATE, 234 | v_msg = MESSAGE_TEXT, 235 | v_detail = PG_EXCEPTION_DETAIL, 236 | v_hint = PG_EXCEPTION_HINT, 237 | v_context = PG_EXCEPTION_CONTEXT; 238 | 239 | RAISE warning '%', format(v_pattern_cat, _srvid, v_catname, 240 | v_state, v_msg, v_detail, v_hint, v_context); 241 | 242 | v_errs := array_append(v_errs, format(v_pattern_cat_simple, _srvid, 243 | v_catname, v_msg)); 244 | 245 | v_nb_err = v_nb_err + 1; 246 | END; 247 | END LOOP; 248 | END IF; 249 | 250 | IF (v_nb_err > 0) THEN 251 | UPDATE @extschema@.powa_snapshot_metas 252 | SET errors = v_errs 253 | WHERE srvid = _srvid; 254 | END IF; 255 | 256 | PERFORM @extschema@.powa_log('end of powa_take_snapshot(' || _srvid || ')'); 257 | PERFORM set_config('application_name', 258 | v_title || 'snapshot finished', 259 | false); 260 | 261 | return v_nb_err; 262 | END; 263 | $PROC$ LANGUAGE plpgsql 264 | SET search_path = pg_catalog; /* end of powa_take_snapshot(int) */ 265 | 266 | CREATE OR REPLACE FUNCTION @extschema@.powa_check_created_extensions() 267 | RETURNS event_trigger 268 | LANGUAGE plpgsql 269 | AS $_$ 270 | DECLARE 271 | v_extname text; 272 | v_res bool; 273 | BEGIN 274 | SELECT extname INTO v_extname 275 | FROM pg_event_trigger_ddl_commands() d 276 | JOIN pg_extension e ON d.classid = 'pg_extension'::regclass 277 | AND d.objid = e.oid 278 | JOIN @extschema@.powa_extensions p USING (extname) 279 | WHERE d.object_type = 'extension'; 280 | 281 | -- Bail out if this isn't a known extension 282 | IF (v_extname IS NULL) THEN 283 | RETURN; 284 | END IF; 285 | 286 | RAISE LOG 'powa: automatically activing extension %', v_extname; 287 | SELECT @extschema@.powa_activate_extension(0, v_extname) INTO v_res; 288 | 289 | IF (NOT v_res) THEN 290 | RAISE WARNING 'Could not automatically activate extension "%"', v_extname; 291 | END IF; 292 | END; 293 | $_$ 294 | SET search_path = pg_catalog; /* end of powa_check_created_extensions */ 295 | 296 | CREATE OR REPLACE FUNCTION @extschema@.powa_check_dropped_extensions() 297 | RETURNS event_trigger 298 | LANGUAGE plpgsql 299 | AS $_$ 300 | DECLARE 301 | v_extname text; 302 | v_state text; 303 | v_msg text; 304 | v_detail text; 305 | v_hint text; 306 | v_context text; 307 | BEGIN 308 | -- We unregister extensions regardless the "enabled" field 309 | FOR v_extname IN SELECT pe.extname 310 | FROM pg_event_trigger_dropped_objects() d 311 | LEFT JOIN @extschema@.powa_extensions pe ON pe.extname = d.object_name 312 | WHERE d.object_type = 'extension' 313 | LOOP 314 | BEGIN 315 | RAISE LOG 'powa: automatically deactiving extension %', v_extname; 316 | PERFORM @extschema@.powa_deactivate_extension(0, v_extname); 317 | EXCEPTION 318 | WHEN OTHERS THEN 319 | GET STACKED DIAGNOSTICS 320 | v_state = RETURNED_SQLSTATE, 321 | v_msg = MESSAGE_TEXT, 322 | v_detail = PG_EXCEPTION_DETAIL, 323 | v_hint = PG_EXCEPTION_HINT, 324 | v_context = PG_EXCEPTION_CONTEXT; 325 | RAISE WARNING 'Could not deactivate extension %:" 326 | state : % 327 | message: % 328 | detail : % 329 | hint : % 330 | context: %', v_extname, v_state, v_msg, v_detail, v_hint, 331 | v_context; 332 | END; 333 | END LOOP; 334 | END; 335 | $_$ 336 | SET search_path = pg_catalog; /* end of powa_check_dropped_extensions */ 337 | 338 | CREATE OR REPLACE FUNCTION @extschema@.powa_user_functions_aggregate(_srvid integer) 339 | RETURNS void AS $PROC$ 340 | DECLARE 341 | v_funcname text := format('@extschema@.%I(%s)', 342 | 'powa_user_functions_aggregate', _srvid); 343 | v_rowcount bigint; 344 | BEGIN 345 | PERFORM @extschema@.powa_log('running powa_user_functions_aggregate(' || _srvid ||')'); 346 | 347 | PERFORM @extschema@.powa_prevent_concurrent_snapshot(_srvid); 348 | 349 | -- aggregate user_functions table 350 | INSERT INTO @extschema@.powa_user_functions_history 351 | (srvid, dbid, funcid, coalesce_range, records, 352 | mins_in_range, maxs_in_range) 353 | SELECT srvid, dbid, funcid, 354 | tstzrange(min((record).ts), max((record).ts),'[]'), 355 | array_agg(record), 356 | ROW(min((record).ts), min((record).calls),min((record).total_time), 357 | min((record).self_time))::@extschema@.powa_user_functions_history_record, 358 | ROW(max((record).ts), max((record).calls),max((record).total_time), 359 | max((record).self_time))::@extschema@.powa_user_functions_history_record 360 | FROM @extschema@.powa_user_functions_history_current 361 | WHERE srvid = _srvid 362 | GROUP BY srvid, dbid, funcid; 363 | 364 | GET DIAGNOSTICS v_rowcount = ROW_COUNT; 365 | PERFORM @extschema@.powa_log(format('%s - (powa_user_functions_history_current) rowcount: %s', 366 | v_funcname, v_rowcount)); 367 | 368 | DELETE FROM @extschema@.powa_user_functions_history_current WHERE srvid = _srvid; 369 | 370 | -- aggregate user_functions_db table 371 | INSERT INTO @extschema@.powa_user_functions_history_db 372 | (srvid, dbid, coalesce_range, records, mins_in_range, maxs_in_range) 373 | SELECT srvid, dbid, 374 | tstzrange(min((record).ts), max((record).ts),'[]'), 375 | array_agg(record), 376 | ROW(min((record).ts), min((record).calls),min((record).total_time), 377 | min((record).self_time))::@extschema@.powa_user_functions_history_record, 378 | ROW(max((record).ts), max((record).calls),max((record).total_time), 379 | max((record).self_time))::@extschema@.powa_user_functions_history_record 380 | FROM @extschema@.powa_user_functions_history_current_db 381 | WHERE srvid = _srvid 382 | GROUP BY srvid, dbid; 383 | 384 | GET DIAGNOSTICS v_rowcount = ROW_COUNT; 385 | PERFORM @extschema@.powa_log(format('%s - (powa_user_functions_history_current_db) rowcount: %s', 386 | v_funcname, v_rowcount)); 387 | 388 | DELETE FROM @extschema@.powa_user_functions_history_current_db WHERE srvid = _srvid; 389 | END; 390 | $PROC$ LANGUAGE plpgsql 391 | SET search_path = pg_catalog; /* end of powa_user_functions_aggregate */ 392 | 393 | CREATE OR REPLACE FUNCTION @extschema@.powa_stat_replication_src(IN _srvid integer, 394 | OUT ts timestamp with time zone, 395 | OUT current_lsn pg_lsn, 396 | OUT pid integer, 397 | OUT usename text, 398 | OUT application_name text, 399 | OUT client_addr inet, 400 | OUT backend_start timestamp with time zone, 401 | OUT backend_xmin xid, 402 | OUT state text, 403 | OUT sent_lsn pg_lsn, 404 | OUT write_lsn pg_lsn, 405 | OUT flush_lsn pg_lsn, 406 | OUT replay_lsn pg_lsn, 407 | OUT write_lag interval, 408 | OUT flush_lag interval, 409 | OUT replay_lag interval, 410 | OUT sync_priority integer, 411 | OUT sync_state text, 412 | OUT reply_time timestamp with time zone 413 | ) RETURNS SETOF record STABLE AS $PROC$ 414 | DECLARE 415 | v_current_lsn pg_lsn; 416 | v_pg_version_num int; 417 | BEGIN 418 | IF (_srvid = 0) THEN 419 | v_pg_version_num := current_setting('server_version_num')::int; 420 | 421 | IF v_pg_version_num < 100000 THEN 422 | IF pg_is_in_recovery() THEN 423 | v_current_lsn := pg_last_xlog_receive_location(); 424 | ELSE 425 | v_current_lsn := pg_current_xlog_location(); 426 | END IF; 427 | ELSE 428 | IF pg_is_in_recovery() THEN 429 | v_current_lsn := pg_last_wal_receive_lsn(); 430 | ELSE 431 | v_current_lsn := pg_current_wal_lsn(); 432 | END IF; 433 | END IF; 434 | 435 | -- We use a LEFT JOIN on the pg_stat_replication view to make sure that 436 | -- we always return at least one (all-NULL) row, so client apps can 437 | -- detect when all the replication connections are down. 438 | -- 439 | -- We handle older versions compatibility even if we don't actually 440 | -- enable them for pg 12 and below, are there are no aggregate 441 | -- functions for pg_lsn datatype before pg13. This way if the 442 | -- repository server is on pg13+ and a remote is on pg12-, we can still 443 | -- support that datasource. 444 | 445 | -- pg12+, reply_time is added 446 | IF v_pg_version_num >= 120000 THEN 447 | RETURN QUERY SELECT now, 448 | v_current_lsn, 449 | s.pid, s.usename::text AS usename, s.application_name, s.client_addr, 450 | s.backend_start, s.backend_xmin, s.state, s.sent_lsn, s.write_lsn, 451 | s.flush_lsn, s.replay_lsn, s.write_lag, s.flush_lag, s.replay_lag, 452 | s.sync_priority, s.sync_state, s.reply_time 453 | FROM (SELECT now() AS now) n 454 | LEFT JOIN pg_catalog.pg_stat_replication AS s ON true; 455 | -- pg10+, *_location fields renamed to *_lsn, and *_lag fields added 456 | ELSIF v_pg_version_num >= 100000 THEN 457 | RETURN QUERY SELECT now, 458 | v_current_lsn, 459 | s.pid, s.usename::text AS usename, s.application_name, s.client_addr, 460 | s.backend_start, s.backend_xmin, s.state, s.sent_lsn, s.write_lsn, 461 | s.flush_lsn, s.replay_lsn, s.write_lag, s.flush_lag, s.replay_lag, 462 | s.sync_priority, s.sync_state, NULL::timestamptz AS reply_time 463 | FROM (SELECT now() AS now) n 464 | LEFT JOIN pg_catalog.pg_stat_replication AS s ON true; 465 | -- pg9.4+ definition 466 | ELSE 467 | RETURN QUERY SELECT now, 468 | v_current_lsn, 469 | s.pid, s.usename::text AS usename, s.application_name, s.client_addr, 470 | s.backend_start, s.backend_xmin, s.state, 471 | s.sent_location AS sent_lsn, s.write_location AS write_lsn, 472 | s.flush_location AS flush_lsn, s.replay_location AS replay_lsn, 473 | NULL::interval AS write_lag, NULL::interval AS flush_lag, 474 | NULL::interval AS replay_lag, 475 | s.sync_priority, s.sync_state, NULL::timestamptz AS reply_time 476 | FROM (SELECT now() AS now) n 477 | LEFT JOIN pg_catalog.pg_stat_replication AS s ON true; 478 | END IF; 479 | ELSE 480 | RETURN QUERY SELECT s.ts, 481 | s.current_lsn, 482 | s.pid, s.usename, s.application_name, s.client_addr, 483 | s.backend_start, s.backend_xmin, s.state, s.sent_lsn, s.write_lsn, 484 | s.flush_lsn, s.replay_lsn, s.write_lag, s.flush_lag, s.replay_lag, 485 | s.sync_priority, s.sync_state, s.reply_time 486 | FROM @extschema@.powa_stat_replication_src_tmp AS s 487 | WHERE s.srvid = _srvid; 488 | END IF; 489 | END; 490 | $PROC$ LANGUAGE plpgsql; /* end of powa_stat_replication_src */ 491 | 492 | CREATE OR REPLACE FUNCTION @extschema@.powa_delete_and_purge_server(_srvid integer) RETURNS boolean 493 | AS $_$ 494 | DECLARE 495 | v_deleted bool; 496 | v_rowcount bigint; 497 | v_src_tmp text; 498 | v_extnsp text; 499 | BEGIN 500 | IF (_srvid = 0) THEN 501 | RAISE EXCEPTION 'Local server cannot be deleted'; 502 | END IF; 503 | 504 | DELETE FROM @extschema@.powa_servers WHERE id = _srvid; 505 | 506 | -- Remember if we removed a remote server 507 | GET DIAGNOSTICS v_rowcount = ROW_COUNT; 508 | v_deleted := (v_rowcount = 1); 509 | 510 | -- The powa_db_modules stores fully qualified (and quoted) src table name, 511 | -- so we have to return the same everywhere to keep the code consistent. 512 | -- 513 | -- Note also that there is no guarantee that all the source tables in the 514 | -- powa_extension_functions exist. At the very least the pg_track_settings 515 | -- source table could be missing if that extension is not enabled on any of 516 | -- the remove or even local servers, so we have to double check for their 517 | -- existence first. 518 | FOR v_src_tmp IN 519 | SELECT '@extschema@' || '.' || quote_ident(tmp_table) 520 | FROM @extschema@.powa_catalogs 521 | UNION ALL 522 | SELECT tmp_table 523 | FROM @extschema@.powa_db_modules 524 | UNION ALL 525 | SELECT '@extschema@' || '.' || quote_ident(query_source || '_tmp') 526 | FROM @extschema@.powa_extension_functions pef 527 | JOIN pg_catalog.pg_class c ON c.relname = (query_source || '_tmp') 528 | JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace 529 | AND quote_ident(nspname) = '@extschema@' 530 | WHERE query_source IS NOT NULL 531 | UNION ALL 532 | SELECT '@extschema@' || '.' || quote_ident(query_source || '_tmp') 533 | FROM @extschema@.powa_module_functions 534 | WHERE query_source IS NOT NULL 535 | LOOP 536 | EXECUTE format('DELETE FROM %s WHERE srvid = %s', 537 | v_src_tmp, _srvid); 538 | END LOOP; 539 | 540 | -- pg_track_settings is an autonomous extension, so it doesn't have a FK to 541 | -- powa_servers. It therefore needs to be processed manually 542 | SELECT COUNT(*), nspname 543 | FROM pg_extension e 544 | LEFT JOIN pg_namespace n ON n.oid = e.extnamespace 545 | WHERE extname = 'pg_track_settings' 546 | GROUP BY nspname 547 | INTO v_rowcount, v_extnsp; 548 | IF (v_rowcount = 1) THEN 549 | EXECUTE format('DELETE FROM %I.pg_track_settings_list WHERE srvid = %s', 550 | v_extnsp, 551 | _srvid); 552 | EXECUTE format('DELETE FROM %I.pg_track_settings_history WHERE srvid = %s', 553 | v_extnsp, 554 | _srvid); 555 | EXECUTE format('DELETE FROM %I.pg_track_db_role_settings_list WHERE srvid = %s', 556 | v_extnsp, 557 | _srvid); 558 | EXECUTE format('DELETE FROM %I.pg_track_db_role_settings_history WHERE srvid = %s', 559 | v_extnsp, 560 | _srvid); 561 | EXECUTE format('DELETE FROM %I.pg_reboot WHERE srvid = %s', 562 | v_extnsp, 563 | _srvid); 564 | END IF; 565 | 566 | RETURN v_deleted; 567 | END; 568 | $_$ LANGUAGE plpgsql 569 | SET search_path = pg_catalog; /* powa_delete_and_purge_server */ 570 | -------------------------------------------------------------------------------- /powa--5.0.2--5.0.3.sql: -------------------------------------------------------------------------------- 1 | -- complain if script is sourced in psql, rather than via CREATE EXTENSION 2 | \echo Use "ALTER EXTENSION powa" to load this file. \quit 3 | 4 | CREATE OR REPLACE FUNCTION @extschema@.powa_replication_slots_src(IN _srvid integer, 5 | OUT ts timestamp with time zone, 6 | OUT slot_name text, 7 | OUT plugin text, 8 | OUT slot_type text, 9 | OUT datoid oid, 10 | OUT temporary boolean, 11 | OUT cur_txid xid, 12 | OUT current_lsn pg_lsn, 13 | OUT active bool, 14 | OUT active_pid int, 15 | OUT slot_xmin xid, 16 | OUT catalog_xmin xid, 17 | OUT restart_lsn pg_lsn, 18 | OUT confirmed_flush_lsn pg_lsn, 19 | OUT wal_status text, 20 | OUT safe_wal_size bigint, 21 | OUT two_phase boolean, 22 | OUT conflicting boolean 23 | ) RETURNS SETOF record STABLE AS $PROC$ 24 | DECLARE 25 | v_txid xid; 26 | v_current_lsn pg_lsn; 27 | v_server_version int; 28 | BEGIN 29 | IF (_srvid = 0) THEN 30 | v_server_version := current_setting('server_version_num')::int; 31 | 32 | IF pg_catalog.pg_is_in_recovery() THEN 33 | v_txid = NULL; 34 | ELSE 35 | -- xid() was introduced in pg13 36 | IF v_server_version >= 130000 THEN 37 | v_txid = pg_catalog.xid(pg_catalog.pg_current_xact_id()); 38 | ELSE 39 | v_txid = (txid_current()::bigint - (txid_current()::bigint >> 32 << 32))::text::xid; 40 | END IF; 41 | END IF; 42 | 43 | IF v_server_version < 100000 THEN 44 | IF pg_is_in_recovery() THEN 45 | v_current_lsn := pg_last_xlog_receive_location(); 46 | ELSE 47 | v_current_lsn := pg_current_xlog_location(); 48 | END IF; 49 | ELSE 50 | IF pg_is_in_recovery() THEN 51 | v_current_lsn := pg_last_wal_receive_lsn(); 52 | ELSE 53 | v_current_lsn := pg_current_wal_lsn(); 54 | END IF; 55 | END IF; 56 | 57 | -- We want to always return a row, even if no replication slots is 58 | -- found, so the UI can properly graph that no slot exists. 59 | 60 | -- conflicting added in pg16 61 | IF v_server_version >= 160000 THEN 62 | RETURN QUERY SELECT n.now, 63 | s.slot_name::text AS slot_name, s.plugin::text AS plugin, 64 | s.slot_type, s.datoid, s.temporary, 65 | v_txid, v_current_lsn, 66 | s.active, 67 | s.active_pid, s.xmin AS slot_xmin, s.catalog_xmin, 68 | s.restart_lsn, s.confirmed_flush_lsn, s.wal_status, 69 | s.safe_wal_size, s.two_phase, s.conflicting 70 | FROM (SELECT now() AS now) n 71 | LEFT JOIN pg_catalog.pg_replication_slots AS s ON true; 72 | -- two_phase added in pg14 73 | ELSIF v_server_version >= 140000 THEN 74 | RETURN QUERY SELECT n.now, 75 | s.slot_name::text AS slot_name, s.plugin::text AS plugin, 76 | s.slot_type, s.datoid, s.temporary, 77 | v_txid, v_current_lsn, 78 | s.active, 79 | s.active_pid, s.xmin AS slot_xmin, s.catalog_xmin, 80 | s.restart_lsn, s.confirmed_flush_lsn, s.wal_status, 81 | s.safe_wal_size, s.two_phase, false AS conflicting 82 | FROM (SELECT now() AS now) n 83 | LEFT JOIN pg_catalog.pg_replication_slots AS s ON true; 84 | -- wal_status and safe_wal_size added in pg13 85 | ELSIF v_server_version >= 130000 THEN 86 | RETURN QUERY SELECT n.now, 87 | s.slot_name::text AS slot_name, s.plugin::text AS plugin, 88 | s.slot_type, s.datoid, s.temporary, 89 | v_txid, v_current_lsn, 90 | s.active, 91 | s.active_pid, s.xmin AS slot_xmin, s.catalog_xmin, 92 | s.restart_lsn, s.confirmed_flush_lsn, s.wal_status, 93 | s.safe_wal_size, false AS two_phase, false AS conflicting 94 | FROM (SELECT now() AS now) n 95 | LEFT JOIN pg_catalog.pg_replication_slots AS s ON true; 96 | -- temporary added in pg10 97 | ELSIF v_server_version >= 100000 THEN 98 | RETURN QUERY SELECT n.now, 99 | s.slot_name::text AS slot_name, s.plugin::text AS plugin, 100 | s.slot_type, s.datoid, s.temporary, 101 | v_txid, v_current_lsn, 102 | s.active, 103 | s.active_pid, s.xmin AS slot_xmin, s.catalog_xmin, 104 | s.restart_lsn, s.confirmed_flush_lsn, 105 | NULL::text as wal_status, 106 | NULL::bigint as safe_wal_size, 107 | false AS two_phase, false AS conflicting 108 | FROM (SELECT now() AS now) n 109 | LEFT JOIN pg_catalog.pg_replication_slots AS s ON true; 110 | -- confirmed_flush_lsn added in pg9.6 111 | ELSIF v_server_version >= 90600 THEN 112 | RETURN QUERY SELECT n.now, 113 | s.slot_name::text AS slot_name, s.plugin::text AS plugin, 114 | s.slot_type, s.datoid, false AS temporary, 115 | v_txid, v_current_lsn, 116 | s.active, 117 | s.active_pid, s.xmin AS slot_xmin, s.catalog_xmin, 118 | s.restart_lsn, s.confirmed_flush_lsn, 119 | NULL::text as wal_status, 120 | NULL::bigint as safe_wal_size, 121 | false AS two_phase, false AS conflicting 122 | FROM (SELECT now() AS now) n 123 | LEFT JOIN pg_catalog.pg_replication_slots AS s ON true; 124 | -- active_pid added in pg9.5 125 | ELSIF v_server_version >= 90500 THEN 126 | RETURN QUERY SELECT n.now, 127 | s.slot_name::text AS slot_name, s.plugin::text AS plugin, 128 | s.slot_type, s.datoid, false AS temporary, 129 | v_txid, v_current_lsn, 130 | s.active, 131 | s.active_pid, s.xmin AS slot_xmin, s.catalog_xmin, 132 | s.restart_lsn, NULL::pg_lsn AS confirmed_flush_lsn, 133 | NULL::text as wal_status, 134 | NULL::bigint as safe_wal_size, 135 | false AS two_phase, false AS conflicting 136 | FROM (SELECT now() AS now) n 137 | LEFT JOIN pg_catalog.pg_replication_slots AS s ON true; 138 | ELSE 139 | RETURN QUERY SELECT n.now, 140 | s.slot_name::text AS slot_name, s.plugin::text AS plugin, 141 | s.slot_type, s.datoid, false AS temporary, 142 | v_txid, v_current_lsn, 143 | s.active, 144 | NULL::int AS active_pid, s.xmin AS slot_xmin, s.catalog_xmin, 145 | s.restart_lsn, NULL::pg_lsn AS confirmed_flush_lsn, 146 | NULL::text as wal_status, 147 | NULL::bigint as safe_wal_size, 148 | false AS two_phase, false AS conflicting 149 | FROM (SELECT now() AS now) n 150 | LEFT JOIN pg_catalog.pg_replication_slots AS s ON true; 151 | END IF; 152 | ELSE 153 | RETURN QUERY SELECT s.ts, 154 | s.slot_name, s.plugin, 155 | s.slot_type, s.datoid, s.temporary, 156 | s.cur_txid, s.current_lsn, 157 | s.active, 158 | s.active_pid, s.xmin AS slot_xmin, s.catalog_xmin, 159 | s.restart_lsn, s.confirmed_flush_lsn, s.wal_status, 160 | s.safe_wal_size, s.two_phase, s.conflicting 161 | FROM @extschema@.powa_replication_slots_src_tmp AS s 162 | WHERE s.srvid = _srvid; 163 | END IF; 164 | END; 165 | $PROC$ LANGUAGE plpgsql 166 | SET search_path = pg_catalog; /* end of powa_replication_slots_src */ 167 | 168 | CREATE OR REPLACE FUNCTION @extschema@.powa_stat_activity_src(IN _srvid integer, 169 | OUT ts timestamp with time zone, 170 | OUT cur_txid xid, 171 | OUT datid oid, 172 | OUT pid integer, 173 | OUT leader_pid integer, 174 | OUT usesysid oid, 175 | OUT application_name text, 176 | OUT client_addr inet, 177 | OUT backend_start timestamp with time zone, 178 | OUT xact_start timestamp with time zone, 179 | OUT query_start timestamp with time zone, 180 | OUT state_change timestamp with time zone, 181 | OUT state text, 182 | OUT backend_xid xid, 183 | OUT backend_xmin xid, 184 | OUT query_id bigint, 185 | OUT backend_type text 186 | ) RETURNS SETOF record STABLE AS $PROC$ 187 | DECLARE 188 | txid xid; 189 | v_server_version int; 190 | BEGIN 191 | IF (_srvid = 0) THEN 192 | v_server_version := current_setting('server_version_num')::int; 193 | 194 | IF pg_catalog.pg_is_in_recovery() THEN 195 | txid = NULL; 196 | ELSE 197 | -- xid() was introduced in pg13 198 | IF v_server_version >= 130000 THEN 199 | txid = pg_catalog.xid(pg_catalog.pg_current_xact_id()); 200 | ELSE 201 | txid = (txid_current()::bigint - (txid_current()::bigint >> 32 << 32))::text::xid; 202 | END IF; 203 | END IF; 204 | 205 | -- query_id added in pg14 206 | IF v_server_version >= 140000 THEN 207 | RETURN QUERY SELECT now(), 208 | txid, 209 | s.datid, s.pid, s.leader_pid, s.usesysid, 210 | s.application_name, s.client_addr, s.backend_start, 211 | s.xact_start, 212 | s.query_start, s.state_change, s.state, s.backend_xid, 213 | s.backend_xmin, s.query_id, s.backend_type 214 | FROM pg_catalog.pg_stat_activity AS s; 215 | -- leader_pid added in pg13+ 216 | ELSIF v_server_version >= 130000 THEN 217 | RETURN QUERY SELECT now(), 218 | txid, 219 | s.datid, s.pid, s.leader_pid, s.usesysid, 220 | s.application_name, s.client_addr, s.backend_start, 221 | s.xact_start, 222 | s.query_start, s.state_change, s.state, s.backend_xid, 223 | s.backend_xmin, NULL::bigint AS query_id, s.backend_type 224 | FROM pg_catalog.pg_stat_activity AS s; 225 | -- backend_type added in pg10+ 226 | ELSIF v_server_version >= 100000 THEN 227 | RETURN QUERY SELECT now(), 228 | txid, 229 | s.datid, s.pid, NULL::integer AS leader_pid, s.usesysid, 230 | s.application_name, s.client_addr, s.backend_start, 231 | s.xact_start, 232 | s.query_start, s.state_change, s.state, s.backend_xid, 233 | s.backend_xmin, NULL::bigint AS query_id, s.backend_type 234 | FROM pg_catalog.pg_stat_activity AS s; 235 | ELSE 236 | RETURN QUERY SELECT now(), 237 | txid, 238 | s.datid, s.pid, NULL::integer AS leader_pid, s.usesysid, 239 | s.application_name, s.client_addr, s.backend_start, 240 | s.xact_start, 241 | s.query_start, s.state_change, s.state, s.backend_xid, 242 | s.backend_xmin, NULL::bigint AS query_id, 243 | NULL::text AS backend_type 244 | FROM pg_catalog.pg_stat_activity AS s; 245 | END IF; 246 | ELSE 247 | RETURN QUERY SELECT s.ts, 248 | s.cur_txid, 249 | s.datid, s.pid, s.leader_pid, s.usesysid, 250 | s.application_name, s.client_addr, s.backend_start, 251 | s.xact_start, 252 | s.query_start, s.state_change, s.state, s.backend_xid, 253 | s.backend_xmin, s.query_id, s.backend_type 254 | FROM @extschema@.powa_stat_activity_src_tmp AS s 255 | WHERE s.srvid = _srvid; 256 | END IF; 257 | END; 258 | $PROC$ LANGUAGE plpgsql 259 | SET search_path = pg_catalog; /* end of powa_stat_activity_src */ 260 | 261 | 262 | CREATE FUNCTION @extschema@.powa_fix_toast_tuple_target() RETURNS void 263 | LANGUAGE plpgsql AS 264 | $$ 265 | DECLARE curr_table regclass; 266 | BEGIN 267 | IF current_setting('server_version_num')::int >= 110000 THEN 268 | 269 | FOR curr_table IN 270 | WITH ext AS ( 271 | SELECT c.oid, c.relname, c.reloptions 272 | FROM pg_depend d 273 | JOIN pg_extension e ON d.refclassid = 'pg_extension'::regclass 274 | AND e.oid = d.refobjid 275 | AND e.extname = 'powa' 276 | JOIN pg_class c ON d.classid = 'pg_class'::regclass 277 | AND c.oid = d.objid 278 | WHERE c.relkind != 'v' 279 | ) 280 | SELECT ext.oid::regclass::text 281 | FROM ext 282 | WHERE EXISTS 283 | (SELECT 1 FROM pg_attribute a 284 | WHERE a.attrelid = ext.oid 285 | AND a.attname = 'mins_in_range' 286 | ) 287 | AND 'toast_tuple_target=128' <> ALL(coalesce(ext.reloptions,'{}')) 288 | LOOP 289 | EXECUTE 'ALTER TABLE ' || curr_table::text || ' SET (TOAST_TUPLE_TARGET=128)'; 290 | END LOOP; 291 | END IF; 292 | END 293 | $$; /* end of powa_fix_toast_tuple_target */ 294 | 295 | CREATE FUNCTION @extschema@.powa_stat_get_activity( 296 | _srvid integer, 297 | _from timestamp with time zone, 298 | _to timestamp with time zone 299 | ) 300 | RETURNS SETOF @extschema@.powa_stat_activity_history_record 301 | AS 302 | $$ 303 | BEGIN 304 | RETURN QUERY 305 | SELECT (record).* 306 | FROM @extschema@.powa_stat_activity_history_current 307 | WHERE srvid = _srvid 308 | AND (record).ts >= _from 309 | AND (record).ts <= _to 310 | UNION ALL 311 | SELECT (record).* 312 | FROM ( 313 | SELECT unnest(records) AS record 314 | FROM @extschema@.powa_stat_activity_history 315 | WHERE srvid = _srvid 316 | AND coalesce_range && tstzrange(_from, _to, '[]') 317 | ) unnested 318 | WHERE (unnested.record).ts >= _from 319 | AND (unnested.record).ts <= _to; 320 | END; 321 | $$ 322 | LANGUAGE plpgsql; /* end of powa_stat_get_activity */ 323 | 324 | SELECT @extschema@.powa_fix_toast_tuple_target(); 325 | -------------------------------------------------------------------------------- /powa.control: -------------------------------------------------------------------------------- 1 | # powa extension 2 | comment = 'PostgreSQL Workload Analyser-core' 3 | default_version = '5.0.3' 4 | module_pathname = '$libdir/powa' 5 | requires = 'plpgsql, pg_stat_statements, btree_gist' 6 | superuser = true 7 | relocatable = false 8 | -------------------------------------------------------------------------------- /reindent.sh: -------------------------------------------------------------------------------- 1 | #/bin/sh 2 | 3 | # This reindents all C files in the directory following PG rules (ported to gnu indent) 4 | # as some of us have tab=8 spaces, and others tab=4 spaces 5 | 6 | indent -bad -bap -bbo -bbb -bc -bl -brs -c33 -cd33 -cdb -nce -ci4 -cli0 \ 7 | -cp33 -cs -d0 -di12 -nfc1 -nfca -nfc1 -i4 -nip -l79 -lp -nip -npcs \ 8 | -nprs -npsl -saf -sai -saw -nsc -nsob -nss -nut *.c 9 | -------------------------------------------------------------------------------- /sql/00_setup.sql: -------------------------------------------------------------------------------- 1 | --Setup extension 2 | CREATE SCHEMA "PGSS"; 3 | CREATE EXTENSION pg_stat_statements WITH SCHEMA "PGSS"; 4 | CREATE EXTENSION btree_gist; 5 | CREATE SCHEMA "PoWA"; 6 | CREATE EXTENSION powa WITH SCHEMA "PoWA"; 7 | 8 | -- Test created ojects 9 | SELECT * FROM "PoWA".powa_functions ORDER BY name, operation, priority, function_name; 10 | -------------------------------------------------------------------------------- /sql/01_general.sql: -------------------------------------------------------------------------------- 1 | -- General setup 2 | \set SHOW_CONTEXT never 3 | 4 | -- Check the relations that aren't dumped 5 | -- we ignore *_src_tmp are those should never be dumped 6 | WITH ext AS ( 7 | SELECT c.oid, c.relname 8 | FROM pg_depend d 9 | JOIN pg_extension e ON d.refclassid = 'pg_extension'::regclass 10 | AND e.oid = d.refobjid 11 | AND e.extname = 'powa' 12 | JOIN pg_class c ON d.classid = 'pg_class'::regclass 13 | AND c.oid = d.objid 14 | WHERE c.relkind != 'v' 15 | ), 16 | dmp AS ( 17 | SELECT unnest(extconfig) AS oid 18 | FROM pg_extension 19 | WHERE extname = 'powa' 20 | ) 21 | SELECT ext.relname 22 | FROM ext 23 | LEFT JOIN dmp USING (oid) 24 | WHERE dmp.oid IS NULL 25 | AND ext.relname NOT LIKE '%src_tmp' 26 | ORDER BY ext.relname::text COLLATE "C"; 27 | 28 | -- Check that no *_src_tmp table are dumped 29 | WITH ext AS ( 30 | SELECT c.oid, c.relname 31 | FROM pg_depend d 32 | JOIN pg_extension e ON d.refclassid = 'pg_extension'::regclass 33 | AND e.oid = d.refobjid 34 | AND e.extname = 'powa' 35 | JOIN pg_class c ON d.classid = 'pg_class'::regclass 36 | AND c.oid = d.objid 37 | WHERE c.relkind != 'v' 38 | ), 39 | dmp AS ( 40 | SELECT unnest(extconfig) AS oid 41 | FROM pg_extension 42 | WHERE extname = 'powa' 43 | ) 44 | SELECT ext.relname 45 | FROM ext 46 | LEFT JOIN dmp USING (oid) 47 | WHERE dmp.oid IS NOT NULL 48 | AND ext.relname LIKE '%src_tmp' 49 | ORDER BY ext.relname::text COLLATE "C"; 50 | 51 | -- Check for object that aren't in the "PoWA" schema 52 | WITH ext AS ( 53 | SELECT pg_describe_object(classid, objid, objsubid) AS descr 54 | FROM pg_depend d 55 | JOIN pg_extension e ON d.refclassid = 'pg_extension'::regclass 56 | AND e.oid = d.refobjid 57 | AND e.extname = 'powa' 58 | ) 59 | SELECT descr 60 | FROM ext 61 | WHERE descr NOT LIKE '%"PoWA"%' 62 | ORDER BY descr COLLATE "C"; 63 | 64 | -- check (mins|maxs)_in_range columns not marked as STORAGE MAIN 65 | WITH ext AS ( 66 | SELECT c.oid, c.relname 67 | FROM pg_depend d 68 | JOIN pg_extension e ON d.refclassid = 'pg_extension'::regclass 69 | AND e.oid = d.refobjid 70 | AND e.extname = 'powa' 71 | JOIN pg_class c ON d.classid = 'pg_class'::regclass 72 | AND c.oid = d.objid 73 | WHERE c.relkind != 'v' 74 | ) 75 | SELECT ext.relname, a.attname 76 | FROM ext 77 | JOIN pg_attribute a ON a.attrelid = ext.oid 78 | WHERE a.attname ~ '^(mins|maxs)_in_range$' 79 | AND a.attstorage != 'm' 80 | ORDER BY ext.relname::text COLLATE "C", a.attname::text COLLATE "C"; 81 | 82 | -- Aggregate data every 5 snapshots 83 | SET powa.coalesce = 5; 84 | 85 | -- test C SRFs 86 | SELECT COUNT(*) = 0 87 | FROM pg_database, 88 | LATERAL "PoWA".powa_stat_user_functions(oid) f 89 | WHERE datname = current_database(); 90 | 91 | -- on pg15+ the function is a no-op, and this function will be deprecated soon 92 | -- anyway 93 | SELECT COUNT(*) >= 0 94 | FROM pg_database, 95 | LATERAL "PoWA".powa_stat_all_rel(oid) 96 | WHERE datname = current_database(); 97 | 98 | -- Test snapshot 99 | SELECT 1, COUNT(*) = 0 FROM "PoWA".powa_user_functions_history_current; 100 | SELECT 1, COUNT(*) = 0 FROM "PoWA".powa_all_tables_history_current; 101 | SELECT 1, COUNT(*) = 0 FROM "PoWA".powa_statements_history_current; 102 | SELECT 1, COUNT(*) = 0 FROM "PoWA".powa_statements_history_current_db; 103 | SELECT 1, COUNT(*) = 0 FROM "PoWA".powa_user_functions_history; 104 | SELECT 1, COUNT(*) = 0 FROM "PoWA".powa_all_tables_history; 105 | SELECT 1, COUNT(*) = 0 FROM "PoWA".powa_statements_history; 106 | SELECT 1, COUNT(*) = 0 FROM "PoWA".powa_statements_history; 107 | SELECT 1, count(*) = 0 FROM "PoWA".powa_stat_get_activity(0, '-infinity', 'infinity'); 108 | 109 | SELECT "PoWA".powa_take_snapshot(); 110 | 111 | SELECT 2, COUNT(*) >= 0 FROM "PoWA".powa_user_functions_history_current; 112 | SELECT 2, COUNT(*) >= 0 FROM "PoWA".powa_all_tables_history_current; 113 | SELECT 2, COUNT(*) > 0 FROM "PoWA".powa_statements_history_current; 114 | SELECT 2, COUNT(*) > 0 FROM "PoWA".powa_statements_history_current_db; 115 | SELECT 2, COUNT(*) >= 0 FROM "PoWA".powa_user_functions_history; 116 | SELECT 2, COUNT(*) = 0 FROM "PoWA".powa_all_tables_history; 117 | SELECT 2, COUNT(*) = 0 FROM "PoWA".powa_statements_history; 118 | SELECT 2, COUNT(*) = 0 FROM "PoWA".powa_statements_history; 119 | SELECT 2, count(*) > 0 FROM "PoWA".powa_stat_get_activity(0, '-infinity', 'infinity'); 120 | SELECT 2, count(*) = 0 FROM "PoWA".powa_stat_get_activity(42, '-infinity', 'infinity'); 121 | 122 | SELECT "PoWA".powa_take_snapshot(); 123 | SELECT "PoWA".powa_take_snapshot(); 124 | SELECT "PoWA".powa_take_snapshot(); 125 | -- This snapshot will trigger the aggregate 126 | SELECT "PoWA".powa_take_snapshot(); 127 | 128 | SELECT 3, COUNT(*) >= 0 FROM "PoWA".powa_user_functions_history_current; 129 | SELECT 3, COUNT(*) >= 0 FROM "PoWA".powa_all_tables_history_current; 130 | SELECT 3, COUNT(*) > 0 FROM "PoWA".powa_statements_history_current; 131 | SELECT 3, COUNT(*) > 0 FROM "PoWA".powa_statements_history_current_db; 132 | SELECT 3, COUNT(*) >= 0 FROM "PoWA".powa_user_functions_history; 133 | SELECT 3, COUNT(*) >= 0 FROM "PoWA".powa_all_tables_history; 134 | SELECT 3, COUNT(*) > 0 FROM "PoWA".powa_statements_history; 135 | SELECT 3, COUNT(*) > 0 FROM "PoWA".powa_statements_history; 136 | SELECT 3, count(*) > 4 FROM "PoWA".powa_stat_get_activity(0, '-infinity', 'infinity'); 137 | SELECT 3, count(*) = 0 FROM "PoWA".powa_stat_get_activity(42, '-infinity', 'infinity'); 138 | 139 | -- Test reset function 140 | SELECT * from "PoWA".powa_reset(0); 141 | 142 | SELECT 4, COUNT(*) = 0 FROM "PoWA".powa_user_functions_history_current; 143 | SELECT 4, COUNT(*) = 0 FROM "PoWA".powa_all_tables_history_current; 144 | SELECT 4, COUNT(*) = 0 FROM "PoWA".powa_statements_history_current; 145 | SELECT 4, COUNT(*) = 0 FROM "PoWA".powa_statements_history_current_db; 146 | SELECT 4, COUNT(*) = 0 FROM "PoWA".powa_user_functions_history; 147 | SELECT 4, COUNT(*) = 0 FROM "PoWA".powa_all_tables_history; 148 | SELECT 4, COUNT(*) = 0 FROM "PoWA".powa_statements_history; 149 | SELECT 4, COUNT(*) = 0 FROM "PoWA".powa_statements_history; 150 | SELECT 4, count(*) = 0 FROM "PoWA".powa_stat_get_activity(0, '-infinity', 'infinity'); 151 | 152 | -- Test toast_tuple_target: we shouldn't have any table belonging to powa archivist 153 | -- that has a column mins_in_range (it means it's a coalesced table) and isn't set 154 | -- for aggressive toasting 155 | WITH ext AS ( 156 | SELECT c.oid, c.relname, c.reloptions 157 | FROM pg_depend d 158 | JOIN pg_extension e ON d.refclassid = 'pg_extension'::regclass 159 | AND e.oid = d.refobjid 160 | AND e.extname = 'powa' 161 | JOIN pg_class c ON d.classid = 'pg_class'::regclass 162 | AND c.oid = d.objid 163 | WHERE c.relkind != 'v' 164 | ) 165 | SELECT ext.relname 166 | FROM ext 167 | WHERE EXISTS 168 | (SELECT 1 FROM pg_attribute a 169 | WHERE a.attrelid = ext.oid 170 | AND a.attname = 'mins_in_range' 171 | ) 172 | AND 'toast_tuple_target=128' <> ALL(coalesce(ext.reloptions,'{}')) 173 | AND current_setting('server_version_num')::int >= 110000 174 | ORDER BY ext.relname::text COLLATE "C"; 175 | -------------------------------------------------------------------------------- /sql/02_remote_api.sql: -------------------------------------------------------------------------------- 1 | -- General setup 2 | \set SHOW_CONTEXT never 3 | 4 | -- Check API 5 | SELECT "PoWA".powa_register_server(hostname => '127.0.0.1', 6 | extensions => '{pg_qualstats}'); 7 | SELECT COUNT(*) FROM "PoWA".powa_servers; 8 | SELECT hostname FROM "PoWA".powa_servers WHERE id = 1; 9 | 10 | -- Check missing powa_statements FK for pg_qualstats doesn't prevent snapshot 11 | INSERT INTO "PoWA".powa_qualstats_src_tmp(srvid, ts, uniquequalnodeid, dbid, userid, 12 | qualnodeid, occurences, execution_count, nbfiltered, 13 | mean_err_estimate_ratio, mean_err_estimate_num, 14 | queryid, constvalues, quals) 15 | SELECT 1, now(), 1, 1, 1, 16 | 1, 1000, 1, 0, 17 | 0, 0, 18 | 123456789, '{}', ARRAY[(1259,1,607,'i')::"PoWA".qual_type]; 19 | SELECT count(*) FROM "PoWA".powa_qualstats_src_tmp; 20 | SELECT "PoWA".powa_qualstats_snapshot(1); 21 | SELECT count(*) FROM "PoWA".powa_qualstats_src_tmp; 22 | SELECT count(*) FROM "PoWA".powa_qualstats_quals_history_current WHERE srvid = 1; 23 | 24 | -- Check snapshot of regular quals 25 | INSERT INTO "PoWA".powa_databases(srvid, oid, datname, dropped) 26 | VALUES (1, 16384, 'postgres', NULL); 27 | INSERT INTO "PoWA".powa_statements(srvid, queryid, dbid, userid, query) 28 | VALUES(1, 123456789, 16384, 10, 'query with qual'); 29 | INSERT INTO "PoWA".powa_qualstats_src_tmp(srvid, ts, uniquequalnodeid, dbid, userid, 30 | qualnodeid, occurences, execution_count, nbfiltered, 31 | mean_err_estimate_ratio, mean_err_estimate_num, 32 | queryid, constvalues, quals) 33 | SELECT 1, now(), 1, 16384, 10, 34 | 1, 1000, 1, 0, 35 | 0, 0, 36 | 123456789, '{}', ARRAY[(1259,1,607,'i')::"PoWA".qual_type]; 37 | SELECT count(*) FROM "PoWA".powa_qualstats_src_tmp; 38 | SELECT "PoWA".powa_qualstats_snapshot(1); 39 | SELECT count(*) FROM "PoWA".powa_qualstats_src_tmp; 40 | SELECT count(*) FROM "PoWA".powa_qualstats_quals_history_current WHERE srvid = 1; 41 | 42 | -- activate / deactivate extension 43 | SELECT * FROM "PoWA".powa_functions 44 | WHERE name IN ('pg_database', 'pg_stat_statements', 'pg_stat_kcache', 'pg_qualstats', 'some_extension') 45 | ORDER BY srvid, name, operation, function_name; 46 | SELECT * FROM "PoWA".powa_activate_extension(1, 'pg_stat_kcache'); 47 | SELECT * FROM "PoWA".powa_activate_extension(1, 'some_extension'); 48 | SELECT * FROM "PoWA".powa_functions 49 | WHERE name IN ('pg_database', 'pg_stat_statements', 'pg_stat_kcache', 'pg_qualstats', 'some_extension') 50 | ORDER BY srvid, name, operation, function_name; 51 | SELECT * FROM "PoWA".powa_deactivate_extension(1, 'pg_stat_kcache'); 52 | SELECT * FROM "PoWA".powa_deactivate_extension(1, 'some_extension'); 53 | SELECT * FROM "PoWA".powa_functions 54 | WHERE name IN ('pg_database', 'pg_stat_statements', 'pg_stat_kcache', 'pg_qualstats', 'some_extension') 55 | ORDER BY srvid, name, operation, function_name; 56 | 57 | SELECT alias FROM "PoWA".powa_servers WHERE id = 1; 58 | SELECT * FROM "PoWA".powa_configure_server(0, '{"somekey": "someval"}'); 59 | SELECT * FROM "PoWA".powa_configure_server(1, '{"somekey": "someval"}'); 60 | SELECT * FROM "PoWA".powa_configure_server(1, '{"alias": "test server"}'); 61 | 62 | SELECT alias FROM "PoWA".powa_servers WHERE id = 1; 63 | 64 | -- Test reset function 65 | SELECT * from "PoWA".powa_reset(1); 66 | 67 | -- Test remove server removal 68 | BEGIN; 69 | SELECT * from "PoWA".powa_delete_and_purge_server(1); 70 | 71 | -- and rollback it as we later test the content of tables with a registered 72 | -- remote server 73 | ROLLBACK; 74 | -------------------------------------------------------------------------------- /sql/03_db_module.sql: -------------------------------------------------------------------------------- 1 | -- General setup 2 | \set SHOW_CONTEXT never 3 | 4 | -- registering a remote server should have registered all default db modules 5 | SELECT * FROM "PoWA".powa_db_module_config 6 | ORDER BY srvid, db_module COLLATE "C"; 7 | 8 | -- Can't deactivate a specific db on an "all databases" config 9 | SELECT * FROM "PoWA".powa_deactivate_db_module(1, 'pg_stat_user_functions', ARRAY['test']); 10 | SELECT enabled, dbnames FROM "PoWA".powa_db_module_config WHERE srvid = 1 AND db_module = 'pg_stat_user_functions'; 11 | 12 | -- Activating a specifc db on an "all databases" config switch to that db only 13 | SELECT * FROM "PoWA".powa_activate_db_module(1, 'pg_stat_user_functions', ARRAY['d1']); 14 | SELECT enabled, dbnames FROM "PoWA".powa_db_module_config WHERE srvid = 1 AND db_module = 'pg_stat_user_functions'; 15 | 16 | -- Activating a specifc db on an specific db config replace that database 17 | SELECT * FROM "PoWA".powa_activate_db_module(1, 'pg_stat_user_functions', ARRAY['d2']); 18 | SELECT enabled, dbnames FROM "PoWA".powa_db_module_config WHERE srvid = 1 AND db_module = 'pg_stat_user_functions'; 19 | 20 | -- Deactivating without specific database switches back to "all db", and mark it as disabled 21 | SELECT * FROM "PoWA".powa_deactivate_db_module(1, 'pg_stat_user_functions'); 22 | SELECT enabled, dbnames FROM "PoWA".powa_db_module_config WHERE srvid = 1 AND db_module = 'pg_stat_user_functions'; 23 | 24 | -- Activating with multiple db switches back to enabled and setup the datbases 25 | SELECT * FROM "PoWA".powa_activate_db_module(1, 'pg_stat_user_functions', ARRAY['d1', 'd3', 'd4']); 26 | SELECT enabled, dbnames FROM "PoWA".powa_db_module_config WHERE srvid = 1 AND db_module = 'pg_stat_user_functions'; 27 | 28 | -- Deactivating a specific db will just remove that db 29 | SELECT * FROM "PoWA".powa_deactivate_db_module(1, 'pg_stat_user_functions', ARRAY['d3']); 30 | SELECT enabled, dbnames FROM "PoWA".powa_db_module_config WHERE srvid = 1 AND db_module = 'pg_stat_user_functions'; 31 | 32 | -- Can't deactivate a non existing specific db 33 | SELECT * FROM "PoWA".powa_deactivate_db_module(1, 'pg_stat_user_functions', ARRAY['d3']); 34 | SELECT enabled, dbnames FROM "PoWA".powa_db_module_config WHERE srvid = 1 AND db_module = 'pg_stat_user_functions'; 35 | 36 | -- Deactivating all remaining db will switch back to "all db", and mark it as disabled 37 | SELECT * FROM "PoWA".powa_deactivate_db_module(1, 'pg_stat_user_functions', ARRAY['d1', 'd4']); 38 | SELECT enabled, dbnames FROM "PoWA".powa_db_module_config WHERE srvid = 1 AND db_module = 'pg_stat_user_functions'; 39 | 40 | -- Deactivating a deactivated db module is a noop 41 | SELECT * FROM "PoWA".powa_deactivate_db_module(1, 'pg_stat_user_functions', ARRAY['d1', 'd4']); 42 | SELECT enabled, dbnames FROM "PoWA".powa_db_module_config WHERE srvid = 1 AND db_module = 'pg_stat_user_functions'; 43 | 44 | -- Deactivating a known but not configured db module isn't supported 45 | DELETE FROM "PoWA".powa_db_module_config WHERE srvid = 1 AND db_module = 'pg_stat_all_indexes'; 46 | SELECT * FROM "PoWA".powa_deactivate_db_module(1, 'pg_stat_all_indexes'); 47 | 48 | ----------------------------------------------------------- 49 | -- Test the query source API, with different major versions 50 | ----------------------------------------------------------- 51 | 52 | -- pg 13.1 should see n_ins_since_vacuum but not last_seq_scan and other fields 53 | -- introduced in pg16 54 | SELECT * FROM "PoWA".powa_db_functions(1, 130001) 55 | ORDER BY db_module COLLATE "C", operation COLLATE "C"; 56 | 57 | -- Check that we don't see n_ins_since_vacuum on pg13- 58 | SELECT query_source FROM "PoWA".powa_db_functions(1, 120012) 59 | WHERE db_module = 'pg_stat_all_tables' AND operation = 'snapshot'; 60 | -------------------------------------------------------------------------------- /sql/04_catalog.sql: -------------------------------------------------------------------------------- 1 | -- General setup 2 | \set SHOW_CONTEXT never 3 | 4 | -- Check the source query retrieval 5 | SELECT * FROM "PoWA".powa_catalog_src_query('pg_class', 90600); 6 | 7 | -- check pg_database catalog snapshot. We just insert 2 databases to make 8 | -- things simpler 9 | INSERT INTO "PoWA".powa_catalog_database_src_tmp 10 | SELECT 1, * 11 | FROM "PoWA".powa_catalog_database_src(0) src 12 | WHERE src.datname = current_database(); 13 | INSERT INTO "PoWA".powa_catalog_database_src_tmp 14 | SELECT 1, * 15 | FROM "PoWA".powa_catalog_database_src(0) src 16 | WHERE src.datname != current_database() 17 | AND src.datname != 'test' 18 | AND src.oid != 1 19 | LIMIT 1; 20 | SELECT "PoWA".powa_catalog_database_snapshot(1); 21 | 22 | -- there shouldn't be a refresh time 23 | SELECT count(*) FILTER (WHERE last_refresh IS NULL) = 2 AS ok, 24 | count(*) FILTER (WHERE last_refresh IS NOT NULL) AS nb_with_refresh 25 | FROM "PoWA".powa_catalog_databases 26 | WHERE srvid = 1; 27 | 28 | -- check the rest of the per-database catalog snapshot 29 | DO $_$ 30 | DECLARE 31 | v_ok boolean; 32 | v_num integer; 33 | v_nb_rec integer; 34 | v_nb_rec2 integer; 35 | v_catname text; 36 | v_prefix text; 37 | v_src_tmp text; 38 | v_query text; 39 | BEGIN 40 | SELECT setting INTO v_num 41 | FROM pg_settings 42 | WHERE name = 'server_version_num'; 43 | 44 | FOR v_catname IN SELECT catname FROM "PoWA".powa_catalogs ORDER BY priority 45 | LOOP 46 | -- get the necessary object name 47 | SELECT 'powa_catalog_' || replace(v_catname, 'pg_', '') INTO v_prefix; 48 | SELECT v_prefix || '_src_tmp' 49 | INTO v_src_tmp; 50 | SELECT "PoWA".powa_catalog_src_query(v_catname, v_num) 51 | INTO v_query; 52 | 53 | -- there shouldn't be any stored data for this catalog 54 | EXECUTE format('SELECT count(*) = 0, count(*) FROM "PoWA".%I', v_prefix) 55 | INTO v_ok, v_nb_rec; 56 | 57 | IF NOT v_ok THEN 58 | RAISE WARNING 'catalog % already has stored data (% rows) in %', 59 | v_catname, v_nb_rec, v_prefix; 60 | END IF; 61 | 62 | -- manually insert some data for this catalog for 2 different 63 | -- databases, with the same content 64 | EXECUTE format('INSERT INTO "PoWA".%I 65 | SELECT 1 AS srvid, d.oid AS dbid, src.* 66 | FROM (%s) src 67 | CROSS JOIN "PoWA".powa_catalog_databases d', 68 | v_src_tmp, v_query); 69 | 70 | -- snapshot the given catalog 71 | PERFORM "PoWA".powa_catalog_generic_snapshot(1, v_catname); 72 | 73 | -- there should now be stored data for this catalog 74 | EXECUTE format('SELECT count(*) > 0, count(*) FROM "PoWA".%I', v_prefix) 75 | INTO v_ok, v_nb_rec; 76 | 77 | IF NOT v_ok THEN 78 | RAISE WARNING 'catalog % does not have stored data in %', 79 | v_catname, v_prefix; 80 | END IF; 81 | 82 | -- source table should now be empty 83 | EXECUTE format('SELECT count(*) = 0, count(*) FROM "PoWA".%I', v_src_tmp) 84 | INTO v_ok, v_nb_rec; 85 | 86 | IF NOT v_ok THEN 87 | RAISE WARNING 'source table % for catalog % still has % rows', 88 | v_src_tmp, v_catname, v_nb_rec; 89 | END IF; 90 | 91 | -- There should be records for 2 databases 92 | EXECUTE format('SELECT count(DISTINCT dbid) = 2, count(DISTINCT dbid) 93 | FROM "PoWA".%I', v_prefix) 94 | INTO v_ok, v_nb_rec; 95 | 96 | IF NOT v_ok THEN 97 | RAISE WARNING 'table % for catalog % does not have record for 2 databases (found %)', 98 | v_prefix, v_catname, v_nb_rec; 99 | END IF; 100 | 101 | -- both databases should have the same number of records 102 | EXECUTE format('SELECT ( 103 | SELECT count(*) FROM "PoWA".%1$I c 104 | JOIN pg_database d ON c.dbid = d.oid 105 | WHERE srvid = 1 AND d.datname = current_database() 106 | ) = ( 107 | SELECT count(*) FROM "PoWA".%1$I c 108 | JOIN pg_database d ON c.dbid = d.oid 109 | WHERE srvid = 1 AND d.datname != current_database() 110 | ),( 111 | SELECT count(*) FROM "PoWA".%1$I c 112 | JOIN pg_database d ON c.dbid = d.oid 113 | WHERE srvid = 1 AND d.datname = current_database() 114 | ), ( 115 | SELECT count(*) FROM "PoWA".%1$I c 116 | JOIN pg_database d ON c.dbid = d.oid 117 | WHERE srvid = 1 AND d.datname != current_database() 118 | ) 119 | ', v_prefix, v_nb_rec, v_nb_rec2) INTO v_ok; 120 | 121 | IF NOT v_ok THEN 122 | RAISE WARNING 'table % for catalog % does not have the same number of records for the 2 databases: % vs %', 123 | v_prefix, v_catname, v_nb_rec, v_nb_rec2; 124 | END IF; 125 | 126 | -- the refresh time should have been saved only if this is pg_class 127 | -- catalog 128 | SELECT count(*) INTO v_nb_rec 129 | FROM "PoWA".powa_catalog_databases 130 | WHERE srvid = 1 131 | AND last_refresh IS NULL; 132 | IF v_catname = 'pg_class' THEN 133 | IF v_nb_rec != 0 THEN 134 | RAISE WARNING 'last_refresh was not saved when processing pg_class, % records without a refresh time', 135 | v_nb_rec; 136 | END IF; 137 | ELSE 138 | IF v_nb_rec = 0 THEN 139 | RAISE WARNING 'last_refresh was saved when processing %, % records without a refresh time', 140 | v_catname, v_nb_rec; 141 | END IF; 142 | END IF; 143 | 144 | -- snapshot the given catalog again without source data, nothing should 145 | -- happen 146 | PERFORM "PoWA".powa_catalog_generic_snapshot(1, v_catname); 147 | 148 | -- there should still be stored data for this catalog 149 | EXECUTE format('SELECT count(*) > 0, count(*) FROM "PoWA".%I', v_prefix) 150 | INTO v_ok, v_nb_rec; 151 | 152 | IF NOT v_ok THEN 153 | RAISE WARNING 'catalog % does not have stored data in %', 154 | v_catname, v_prefix; 155 | END IF; 156 | 157 | -- re-add some data in the src table, but for 1 db only, and snapshot 158 | -- again 159 | EXECUTE format('INSERT INTO "PoWA".%I 160 | SELECT 1 AS srvid, d.oid AS dbid, src.* 161 | FROM (%s) src 162 | JOIN "PoWA".powa_catalog_databases d 163 | ON d.datname = current_database()', 164 | v_src_tmp, v_query); 165 | PERFORM "PoWA".powa_catalog_generic_snapshot(1, v_catname); 166 | 167 | -- both databases should still have the same number of records 168 | EXECUTE format('SELECT ( 169 | SELECT count(*) FROM "PoWA".%1$I c 170 | JOIN pg_database d ON c.dbid = d.oid 171 | WHERE srvid = 1 AND d.datname = current_database() 172 | ) = ( 173 | SELECT count(*) FROM "PoWA".%1$I c 174 | JOIN pg_database d ON c.dbid = d.oid 175 | WHERE srvid = 1 AND d.datname != current_database() 176 | ),( 177 | SELECT count(*) FROM "PoWA".%1$I c 178 | JOIN pg_database d ON c.dbid = d.oid 179 | WHERE srvid = 1 AND d.datname = current_database() 180 | ), ( 181 | SELECT count(*) FROM "PoWA".%1$I c 182 | JOIN pg_database d ON c.dbid = d.oid 183 | WHERE srvid = 1 AND d.datname != current_database() 184 | ) 185 | ', v_prefix, v_nb_rec, v_nb_rec2) INTO v_ok; 186 | 187 | IF NOT v_ok THEN 188 | RAISE WARNING 'table % for catalog % does not have the same number of records for the 2 databases: % vs %', 189 | v_prefix, v_catname, v_nb_rec, v_nb_rec2; 190 | END IF; 191 | 192 | -- re-add some data in the src table to later test the reset 193 | EXECUTE format('INSERT INTO "PoWA".%I 194 | SELECT 1 AS srvid, d.oid AS dbid, src.* 195 | FROM (%s) src 196 | CROSS JOIN "PoWA".powa_catalog_databases d', 197 | v_src_tmp, v_query); 198 | END LOOP; 199 | END; 200 | $_$ LANGUAGE plpgsql; 201 | 202 | SELECT catname, substr(query_source, 1, 12) AS query_source, tmp_table, 203 | array_upper(excluded_dbnames, 1) AS nb_excluded 204 | FROM "PoWA".powa_catalog_functions(1, 150000) 205 | WHERE catname = 'pg_class'; 206 | 207 | -- Check the refresh interval filtering 208 | INSERT INTO "PoWA".powa_catalog_databases(srvid, oid, datname, last_refresh) 209 | VALUES (1, 1, 'test', now() - interval '1 month'); 210 | 211 | -- default interval should exclude test database 212 | WITH e AS (SELECT DISTINCT unnest(excluded_dbnames) AS excluded 213 | FROM "PoWA".powa_catalog_functions(1, 150000)) 214 | SELECT coalesce(array_agg(excluded), '{}') AS excluded_dbnames 215 | FROM e 216 | WHERE excluded = 'test'; 217 | -- 15 days interval should not exclude test database 218 | WITH e AS (SELECT DISTINCT unnest(excluded_dbnames) AS excluded 219 | FROM "PoWA".powa_catalog_functions(1, 150000, '15 days')) 220 | SELECT coalesce(array_agg(excluded), '{}') AS excluded_dbnames 221 | FROM e 222 | WHERE excluded = 'test'; 223 | -------------------------------------------------------------------------------- /sql/05_module.sql: -------------------------------------------------------------------------------- 1 | -- we can't test API unless there are module config for remote servers 2 | SELECT count(distinct srvid) > 0 AS ok FROM "PoWA".powa_module_config 3 | WHERE srvid != 0; 4 | 5 | -- check for missing module config 6 | SELECT pm.module 7 | FROM "PoWA".powa_modules pm 8 | LEFT JOIN "PoWA".powa_module_config pmc USING (module) 9 | LEFT JOIN "PoWA".powa_servers ps ON ps.id = pmc.srvid 10 | WHERE ps.id IS NULL OR pmc.module IS NULL; 11 | 12 | -- check that all declared functions have been defined 13 | SELECT pmf.module, pmf.function_name 14 | FROM "PoWA".powa_module_functions pmf 15 | LEFT JOIN pg_proc p ON p.proname = pmf.function_name 16 | LEFT JOIN pg_namespace n ON n.oid = p.pronamespace 17 | WHERE p.proname IS NULL; 18 | 19 | -- same for src function 20 | SELECT pmf.module, pmf.query_source 21 | FROM "PoWA".powa_module_functions pmf 22 | LEFT JOIN pg_proc p ON p.proname = pmf.query_source 23 | LEFT JOIN pg_namespace n ON n.oid = p.pronamespace 24 | WHERE pmf.query_source IS NOT NULL AND p.proname IS NULL; 25 | -------------------------------------------------------------------------------- /sql/10_acl.sql: -------------------------------------------------------------------------------- 1 | -- Check the relations for which powa_admin is missing ACL 2 | 3 | CREATE FUNCTION has_table_or_seq_privilege(relkind "char", rolname text, 4 | relid oid, priv text) 5 | RETURNS bool 6 | AS $$ 7 | BEGIN 8 | IF relkind = 'S' THEN 9 | RETURN has_sequence_privilege(rolname, relid, priv); 10 | ELSE 11 | RETURN has_table_privilege(rolname, relid, priv); 12 | END IF; 13 | END; 14 | $$ LANGUAGE plpgsql; 15 | 16 | CREATE FUNCTION check_has_privilege(rolname text, 17 | tbl_priv text[], seq_priv text[]) 18 | RETURNS TABLE (powa_role text, relname name, relkind "char", priv text) 19 | AS $$ 20 | WITH ext AS ( 21 | SELECT c.oid, c.relname, c.relkind 22 | FROM pg_depend d 23 | JOIN pg_extension e ON d.refclassid = 'pg_extension'::regclass 24 | AND e.oid = d.refobjid 25 | AND e.extname = 'powa' 26 | JOIN pg_class c ON d.classid = 'pg_class'::regclass 27 | AND c.oid = d.objid 28 | ), 29 | acls(priv, isseq) AS ( 30 | SELECT unnest(tbl_priv), false 31 | UNION ALL 32 | SELECT unnest(seq_priv), true 33 | ) 34 | SELECT rolname AS powa_role, relname, relkind, priv 35 | FROM ext 36 | JOIN acls ON acls.isseq = (ext.relkind = 'S') 37 | WHERE NOT has_table_or_seq_privilege(relkind, rolname, ext.oid, priv) 38 | ORDER BY relname, priv; 39 | $$ LANGUAGE sql; 40 | 41 | CREATE FUNCTION check_has_not_privilege(rolname text, 42 | tbl_priv text[], seq_priv text[]) 43 | RETURNS TABLE (powa_role text, relname name, relkind "char", priv text) 44 | AS $$ 45 | WITH ext AS ( 46 | SELECT c.oid, c.relname, c.relkind 47 | FROM pg_depend d 48 | JOIN pg_extension e ON d.refclassid = 'pg_extension'::regclass 49 | AND e.oid = d.refobjid 50 | AND e.extname = 'powa' 51 | JOIN pg_class c ON d.classid = 'pg_class'::regclass 52 | AND c.oid = d.objid 53 | ), 54 | acls(priv, isseq) AS ( 55 | SELECT unnest(tbl_priv), false 56 | UNION ALL 57 | SELECT unnest(seq_priv), true 58 | ) 59 | SELECT rolname AS powa_role, relname, relkind, priv 60 | FROM ext 61 | JOIN acls ON acls.isseq = (ext.relkind = 'S') 62 | WHERE has_table_or_seq_privilege(relkind, rolname, ext.oid, priv) 63 | ORDER BY relname, priv; 64 | $$ LANGUAGE sql; 65 | 66 | 67 | -- powa_admin should have all privileges on all relations 68 | SELECT powa_role, relname, priv 69 | FROM check_has_privilege('powa_admin', 70 | array ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 71 | 'TRIGGER'], 72 | array ['USAGE', 'SELECT', 'UPDATE']); 73 | 74 | -- powa_read_all_data should have SELECT privilege on all relation except 75 | -- *_src_tmp tables and sequences 76 | SELECT powa_role, relname, priv 77 | FROM check_has_privilege('powa_read_all_data', 78 | array ['SELECT'], 79 | array []::text[]); 80 | 81 | -- powa_read_all_data should not have non-SELECT privilege on any table, and no 82 | -- privilege on sequences 83 | SELECT powa_role, relname, priv 84 | FROM check_has_not_privilege('powa_read_all_data', 85 | array ['INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 86 | array ['USAGE', 'SELECT', 'UPDATE']); 87 | 88 | -- powa_read_all_metrics should be the same as powa_read_all_data except that 89 | -- it can't acceess any pg_qualstats related table 90 | SELECT powa_role, relname, priv 91 | FROM check_has_privilege('powa_read_all_metrics', 92 | array ['SELECT'], 93 | array []::text[]); 94 | 95 | SELECT powa_role, relname, priv 96 | FROM check_has_not_privilege('powa_read_all_metrics', 97 | array ['INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER'], 98 | array ['USAGE', 'SELECT', 'UPDATE']); 99 | 100 | -- powa_write_all_data should have SELECT/INSERT/UPDATE/DELETE/TRUNCATE 101 | -- privileges on all relations (and all privileges on sequences) 102 | SELECT powa_role, relname, priv 103 | FROM check_has_privilege('powa_write_all_data', 104 | array ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE'], 105 | array ['USAGE', 'SELECT', 'UPDATE']); 106 | 107 | -- powa_write_all_data should not have TRIGGER/REFERENCES privileges on any 108 | -- relations 109 | SELECT powa_role, relname, priv 110 | FROM check_has_not_privilege('powa_write_all_data', 111 | array ['TRIGGER', 'REFERENCES'], 112 | array []::text[]); 113 | 114 | -- powa_snapshot should have SELECT/INSERT/UPDATE/DELETE/TRUNCATE 115 | -- privileges on all metric-related relations (and all privileges on sequences) 116 | -- only 117 | SELECT powa_role, relname, relkind, array_agg(priv) 118 | FROM check_has_privilege('powa_snapshot', 119 | array ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE'], 120 | array ['USAGE', 'SELECT', 'UPDATE']) 121 | GROUP BY 1, 2, 3 122 | ORDER BY 1, 2, 3; 123 | 124 | -- powa_snapshot should not have TRIGGER/REFERENCES privileges on any relations 125 | SELECT powa_role, relname, priv 126 | FROM check_has_not_privilege('powa_snapshot', 127 | array ['TRIGGER', 'REFERENCES'], 128 | array []::text[]); 129 | 130 | -- and try to detect any unexpected GRANT on powa_snapshot, as any newly 131 | -- created table will have too many privileges granted unless explicitly 132 | -- handled 133 | SELECT DISTINCT powa_role, relname 134 | FROM check_has_not_privilege('powa_snapshot', 135 | array ['INSERT', 'UPDATE', 'DELETE', 'TRUNCATE'], 136 | array ['USAGE', 'SELECT', 'UPDATE']) 137 | WHERE relkind != 'v' 138 | AND relname NOT LIKE '%history' 139 | AND relname NOT LIKE '%history\_db' 140 | AND relname NOT LIKE '%history\_current' 141 | AND relname NOT LIKE '%history\_current\_db' 142 | AND relname NOT LIKE '%src\_tmp' 143 | AND relname NOT LIKE 'powa\_catalog\_%' 144 | AND relname NOT LIKE '%qualstats%' 145 | AND relname NOT LIKE '%kcache%' 146 | AND relname NOT IN ('powa_databases', 'powa_snapshot_metas', 'powa_statements'); 147 | 148 | -- powa_signal_backend should not have any privilege on any relation 149 | SELECT powa_role, relname, priv 150 | FROM check_has_not_privilege('powa_signal_backend', 151 | array ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 152 | 'TRIGGER'], 153 | array ['USAGE', 'SELECT', 'UPDATE']); 154 | -------------------------------------------------------------------------------- /sql/99_cleanup.sql: -------------------------------------------------------------------------------- 1 | -- General setup 2 | \set SHOW_CONTEXT never 3 | 4 | -- Check for local server reset 5 | SELECT "PoWA".powa_reset(0); 6 | 7 | -- check catalog FK 8 | DO 9 | $_$ 10 | DECLARE 11 | v_dbid oid; 12 | v_nb integer; 13 | v_catname text; 14 | v_prefix text; 15 | BEGIN 16 | SELECT oid INTO v_dbid FROM "PoWA".powa_catalog_databases 17 | WHERE srvid = 1 AND datname = current_database(); 18 | 19 | DELETE FROM "PoWA".powa_catalog_databases 20 | WHERE srvid = 1 AND datname = current_database(); 21 | FOR v_catname IN SELECT catname FROM "PoWA".powa_catalogs 22 | LOOP 23 | -- get the necessary object name 24 | SELECT 'powa_catalog_' || replace(v_catname, 'pg_', '') INTO v_prefix; 25 | 26 | -- There shouldn't be any row left for that databa in any catalog 27 | EXECUTE format('SELECT count(*) FROM "PoWA".%I WHERE dbid = %s', 28 | v_prefix, v_dbid) INTO v_nb; 29 | IF v_nb != 0 THEN 30 | RAISE WARNING 'table "PoWA".% for catalog % has % rows', 31 | v_prefix, v_catname, v_nb; 32 | END IF; 33 | 34 | -- but there should be record in the src_tmp tables 35 | EXECUTE format('SELECT count(*) FROM "PoWA".%I', v_prefix) INTO v_nb; 36 | IF v_nb = 0 THEN 37 | RAISE WARNING 'table "PoWA".% for catalog % has % rows', 38 | v_prefix || '_src_tmp', v_catname, v_nb; 39 | END IF; 40 | END LOOP; 41 | END; 42 | $_$ LANGUAGE plpgsql; 43 | 44 | SELECT "PoWA".powa_reset(1); 45 | 46 | -- There shouldn't be any row left for that server in any catalog 47 | DO 48 | $_$ 49 | DECLARE 50 | v_nb integer; 51 | v_catname text; 52 | v_prefix text; 53 | BEGIN 54 | FOR v_catname IN SELECT catname FROM "PoWA".powa_catalogs 55 | LOOP 56 | -- get the necessary object name 57 | SELECT 'powa_catalog_' || replace(v_catname, 'pg_', '') INTO v_prefix; 58 | 59 | EXECUTE format('SELECT count(*) FROM "PoWA".%I', v_prefix) INTO v_nb; 60 | IF v_nb != 0 THEN 61 | RAISE WARNING 'table "PoWA".% for catalog % has % rows', 62 | v_prefix, v_catname, v_nb; 63 | END IF; 64 | 65 | EXECUTE format('SELECT count(*) FROM "PoWA".%I', v_prefix) INTO v_nb; 66 | IF v_nb != 0 THEN 67 | RAISE WARNING 'table "PoWA".% for catalog % has % rows', 68 | v_prefix || '_src_tmp', v_catname, v_nb; 69 | END IF; 70 | END LOOP; 71 | END; 72 | $_$ LANGUAGE plpgsql; 73 | 74 | -- Check remote server removal 75 | DELETE FROM "PoWA".powa_servers WHERE id = 1; 76 | 77 | -- dropping powa will leave the role initially created, and we should be able 78 | -- to reinstall powa again 79 | DROP EXTENSION powa; 80 | CREATE EXTENSION powa WITH SCHEMA "PoWA"; 81 | 82 | -- we shouldn't assigned the existing role to our pseudo predefined role, even 83 | -- if they exist with the default name 84 | SELECT * FROM "PoWA".powa_roles WHERE rolname IS NOT NULL; 85 | 86 | -- and also shouldn't put back any ACL on the previous roles 87 | SELECT has_table_privilege('powa_admin', '"PoWA".powa_servers', 'SELECT'); 88 | 89 | -- We shouldn't be able to grant or revoke privileges in that situation 90 | SELECT "PoWA".powa_revoke(); 91 | SELECT "PoWA".powa_grant(); 92 | 93 | -- we should be able to reuse the initially created roles 94 | SELECT "PoWA".setup_powa_roles(true); 95 | SELECT has_table_privilege('powa_admin', '"PoWA".powa_servers', 'SELECT'); 96 | 97 | -- cleanup ACL and remove the powa pseudo predefined roles 98 | SELECT "PoWA".powa_revoke(); 99 | SELECT has_table_privilege('powa_admin', '"PoWA".powa_servers', 'SELECT'); 100 | DROP ROLE powa_admin; 101 | DROP ROLE powa_read_all_data; 102 | DROP ROLE powa_read_all_metrics; 103 | DROP ROLE powa_write_all_data; 104 | DROP ROLE powa_snapshot; 105 | DROP ROLE powa_signal_backend; 106 | --------------------------------------------------------------------------------