From 2b4493ce83de17a0569679afd45cb14f9d1d6125 Mon Sep 17 00:00:00 2001 From: samo-agent <280144521+samo-agent@users.noreply.github.com> Date: Thu, 13 Aug 2026 16:57:44 +0000 Subject: [PATCH 1/7] feat: support PostgreSQL 19 beta 2 Add the exact beta image to the compatibility matrix and make report failures observable in CI. Exercise both display modes, preload pg_stat_statements, and test interactive role reports.\n\nRelates to #96 --- .github/workflows/test.yml | 350 ++++++++++++++++++++++++++----------- CLAUDE.md | 2 +- README.md | 4 +- RELEASE_NOTES.md | 81 ++------- 4 files changed, 266 insertions(+), 171 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 20f1de4..e95305d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,28 +12,24 @@ jobs: strategy: matrix: - postgres-version: ['13', '14', '15', '16', '17', '18'] + postgres-version: ['13', '14', '15', '16', '17', '18', '19beta2'] fail-fast: false - services: - postgres: - image: postgres:${{ matrix.postgres-version }} - env: - POSTGRES_PASSWORD: postgres - POSTGRES_DB: test - POSTGRES_HOST_AUTH_METHOD: trust - POSTGRES_INITDB_ARGS: --auth-host=trust --auth-local=trust - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - ports: - - 5432:5432 - steps: - name: Checkout code uses: actions/checkout@v4 + + - name: Start PostgreSQL + run: | + docker run --detach \ + --name postgres-dba-test \ + --env POSTGRES_PASSWORD=postgres \ + --env POSTGRES_DB=test \ + --env POSTGRES_HOST_AUTH_METHOD=trust \ + --env POSTGRES_INITDB_ARGS='--auth-host=trust --auth-local=trust' \ + --publish 5432:5432 \ + postgres:${{ matrix.postgres-version }} \ + -c shared_preload_libraries=pg_stat_statements - name: Install PostgreSQL client run: | @@ -43,123 +39,271 @@ jobs: - name: Prepare test database run: | - until pg_isready -h localhost -p 5432 -U postgres; do + ready=false + for _ in {1..30}; do + if pg_isready --host=localhost --port=5432 --username=postgres; then + ready=true + break + fi echo "Waiting for postgres..." sleep 2 done - - psql -h localhost -U postgres -d test -c 'SELECT version();' - + if [[ "${ready}" != true ]]; then + docker logs postgres-dba-test + exit 1 + fi + + server_version=$(PAGER='cat' psql \ + --no-psqlrc \ + --host=localhost \ + --username=postgres \ + --dbname=test \ + --tuples-only \ + --no-align \ + --command='show server_version;') + echo "PostgreSQL ${server_version}" + if [[ '${{ matrix.postgres-version }}' == '19beta2' \ + && "${server_version}" != 19beta2* ]]; then + echo "Expected PostgreSQL 19beta2, got ${server_version}" >&2 + exit 1 + fi + # Extensions - psql -h localhost -U postgres -d test -c 'CREATE EXTENSION IF NOT EXISTS pg_stat_statements;' || echo "Warning: pg_stat_statements not available" - psql -h localhost -U postgres -d test -c 'CREATE EXTENSION IF NOT EXISTS pgstattuple;' - psql -h localhost -U postgres -d test -c 'CREATE EXTENSION IF NOT EXISTS intarray;' - psql -h localhost -U postgres -d test -c 'CREATE EXTENSION IF NOT EXISTS pg_buffercache;' - psql -h localhost -U postgres -d test -c 'CREATE EXTENSION IF NOT EXISTS amcheck;' - # amcheck needs execute privileges for non-superusers - psql -h localhost -U postgres -d test -c 'GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO PUBLIC;' - - # Minimal privilege user - psql -h localhost -U postgres -d test -c "CREATE USER dba_user;" - psql -h localhost -U postgres -d test -c "GRANT pg_monitor TO dba_user;" - psql -h localhost -U postgres -d test -c "GRANT CONNECT ON DATABASE test TO dba_user;" - psql -h localhost -U postgres -d test -c "GRANT USAGE ON SCHEMA public TO dba_user;" - - psql -h localhost -U postgres -d test -c 'SELECT extname FROM pg_extension ORDER BY extname;' - - # Test tables for alignment (p1) - psql -h localhost -U postgres -d test -c "CREATE TABLE align1 AS SELECT 1::int4, 2::int8, 3::int4 AS more FROM generate_series(1, 100000) _(i);" - psql -h localhost -U postgres -d test -c "CREATE TABLE align2 AS SELECT 1::int4, 3::int4 AS more, 2::int8 FROM generate_series(1, 100000) _(i);" - - # Test tables for foreign key check (i3) — with intarray to catch operator ambiguity - psql -h localhost -U postgres -d test -c "CREATE TABLE fk_parent (id int PRIMARY KEY, data text);" - psql -h localhost -U postgres -d test -c "CREATE TABLE fk_child (id int PRIMARY KEY, parent_id int, data text, CONSTRAINT fk_test FOREIGN KEY (parent_id) REFERENCES fk_parent(id));" - psql -h localhost -U postgres -d test -c "INSERT INTO fk_parent SELECT i, 'data_' || i FROM generate_series(1, 100000) i;" - psql -h localhost -U postgres -d test -c "INSERT INTO fk_child SELECT i, (i % 100000) + 1, 'data_' || i FROM generate_series(1, 200000) i;" - psql -h localhost -U postgres -d test -c "ANALYZE;" - - # Grant access - psql -h localhost -U postgres -d test -c "GRANT SELECT ON ALL TABLES IN SCHEMA public TO dba_user;" - psql -h localhost -U dba_user -d test -c 'SELECT current_user, session_user;' + PAGER='cat' psql \ + --no-psqlrc \ + --set=ON_ERROR_STOP=1 \ + --host=localhost \ + --username=postgres \ + --dbname=test <<'SQL' + create extension if not exists pg_stat_statements; + create extension if not exists pgstattuple; + create extension if not exists intarray; + create extension if not exists pg_buffercache; + create extension if not exists amcheck; + -- amcheck needs execute privileges for non-superusers + grant execute on all functions in schema public to public; + + -- Minimal privilege user + create user dba_user; + grant pg_monitor to dba_user; + grant connect on database test to dba_user; + grant usage on schema public to dba_user; + + -- Test tables for alignment (p1) + create table align1 as + select 1::int4, 2::int8, 3::int4 as more + from generate_series(1, 100000) _(i); + create table align2 as + select 1::int4, 3::int4 as more, 2::int8 + from generate_series(1, 100000) _(i); + + -- Test tables for foreign key check (i3) — with intarray to catch operator ambiguity + create table fk_parent (id int primary key, data text); + create table fk_child ( + id int primary key, + parent_id int, + data text, + constraint fk_test foreign key (parent_id) references fk_parent(id) + ); + insert into fk_parent + select i, 'data_' || i from generate_series(1, 100000) i; + insert into fk_child + select i, (i % 100000) + 1, 'data_' || i + from generate_series(1, 200000) i; + analyze; + + -- Grant access + grant select on all tables in schema public to dba_user; + SQL + + PAGER='cat' psql \ + --no-psqlrc \ + --set=ON_ERROR_STOP=1 \ + --host=localhost \ + --username=dba_user \ + --dbname=test \ + --command='select current_user, session_user;' - name: Test wide mode run: | - echo "\set postgres_dba_wide true" > ~/.psqlrc - echo "\set postgres_dba_interactive_mode false" >> ~/.psqlrc echo "Testing all SQL files in wide mode with minimal privileges..." - for f in sql/*; do - echo " Testing $f..." - if ! PAGER=cat psql -h localhost -U dba_user -d test --no-psqlrc -f warmup.psql -f "$f" > /dev/null 2>&1; then - echo "❌ FAILED: $f in wide mode" - echo "Error output:" - PAGER=cat psql -h localhost -U dba_user -d test --no-psqlrc -f warmup.psql -f "$f" + for report in ./sql/*.sql; do + if [[ "${report}" == ./sql/r1_* || "${report}" == ./sql/r2_* ]]; then + continue + fi + echo " Testing ${report}..." + if ! output=$(PGOPTIONS='-c postgres_dba.wide=on' PAGER='cat' psql \ + --no-psqlrc \ + --set=ON_ERROR_STOP=1 \ + --set=postgres_dba_interactive_mode=false \ + --host=localhost \ + --username=dba_user \ + --dbname=test \ + --file=warmup.psql \ + --file="${report}" 2>&1); then + echo "FAILED: ${report} in wide mode" >&2 + echo "${output}" >&2 exit 1 fi done - echo "✅ All tests passed in wide mode" + echo "All tests passed in wide mode" - name: Test normal mode run: | - echo "\set postgres_dba_wide false" > ~/.psqlrc - echo "\set postgres_dba_interactive_mode false" >> ~/.psqlrc echo "Testing all SQL files in normal mode with minimal privileges..." - for f in sql/*; do - echo " Testing $f..." - if ! PAGER=cat psql -h localhost -U dba_user -d test --no-psqlrc -f warmup.psql -f "$f" > /dev/null 2>&1; then - echo "❌ FAILED: $f in normal mode" - echo "Error output:" - PAGER=cat psql -h localhost -U dba_user -d test --no-psqlrc -f warmup.psql -f "$f" + for report in ./sql/*.sql; do + if [[ "${report}" == ./sql/r1_* || "${report}" == ./sql/r2_* ]]; then + continue + fi + echo " Testing ${report}..." + if ! output=$(PGOPTIONS='-c postgres_dba.wide=off' PAGER='cat' psql \ + --no-psqlrc \ + --set=ON_ERROR_STOP=1 \ + --set=postgres_dba_interactive_mode=false \ + --host=localhost \ + --username=dba_user \ + --dbname=test \ + --file=warmup.psql \ + --file="${report}" 2>&1); then + echo "FAILED: ${report} in normal mode" >&2 + echo "${output}" >&2 exit 1 fi done - echo "✅ All tests passed in normal mode" + echo "All tests passed in normal mode" + + - name: Test interactive role reports + run: | + if ! output=$(printf 'ci_test_role\n0\n1\n' | PAGER='cat' psql \ + --no-psqlrc \ + --set=ON_ERROR_STOP=1 \ + --host=localhost \ + --username=postgres \ + --dbname=test \ + --file=sql/r1_create_user_with_random_password.sql 2>&1); then + echo "${output}" | sed -E 's/(password: ).*/\1[redacted]/' >&2 + exit 1 + fi + + role_state=$(PAGER='cat' psql \ + --no-psqlrc \ + --set=ON_ERROR_STOP=1 \ + --host=localhost \ + --username=postgres \ + --dbname=test \ + --tuples-only \ + --no-align \ + --command="select rolsuper, rolcanlogin, rolpassword is not null + from pg_authid + where rolname = 'ci_test_role';") + if [[ "${role_state}" != 'f|t|t' ]]; then + echo "Unexpected created role state: ${role_state}" >&2 + exit 1 + fi + + old_password=$(PAGER='cat' psql \ + --no-psqlrc \ + --set=ON_ERROR_STOP=1 \ + --host=localhost \ + --username=postgres \ + --dbname=test \ + --tuples-only \ + --no-align \ + --command="select rolpassword from pg_authid + where rolname = 'ci_test_role';") + + if ! output=$(printf 'ci_test_role\n0\n1\n' | PAGER='cat' psql \ + --no-psqlrc \ + --set=ON_ERROR_STOP=1 \ + --host=localhost \ + --username=postgres \ + --dbname=test \ + --file=sql/r2_alter_user_with_random_password.sql 2>&1); then + echo "${output}" | sed -E 's/(password: ).*/\1[redacted]/' >&2 + exit 1 + fi + + new_password=$(PAGER='cat' psql \ + --no-psqlrc \ + --set=ON_ERROR_STOP=1 \ + --host=localhost \ + --username=postgres \ + --dbname=test \ + --tuples-only \ + --no-align \ + --command="select rolpassword from pg_authid + where rolname = 'ci_test_role';") + if [[ -z "${old_password}" \ + || -z "${new_password}" \ + || "${old_password}" == "${new_password}" ]]; then + echo 'Alter-role report did not rotate the password' >&2 + exit 1 + fi - name: Run regression tests run: | - echo "\set postgres_dba_wide false" > ~/.psqlrc - echo "\set postgres_dba_interactive_mode false" >> ~/.psqlrc - echo "Running regression tests with minimal privileges..." - + echo " Testing 0_node.sql..." - OUTPUT=$(PAGER=cat psql -h localhost -U dba_user -d test --no-psqlrc -f warmup.psql -f sql/0_node.sql | grep Role) - if [[ "$OUTPUT" == *"Primary"* ]]; then - echo " ✓ Role test passed" - else - echo " ✗ Role test failed: $OUTPUT" + output=$(PGOPTIONS='-c postgres_dba.wide=off' PAGER='cat' psql \ + --no-psqlrc \ + --set=ON_ERROR_STOP=1 \ + --set=postgres_dba_interactive_mode=false \ + --host=localhost \ + --username=dba_user \ + --dbname=test \ + --file=warmup.psql \ + --file=sql/0_node.sql) + if [[ "${output}" != *Primary* ]]; then + echo "Role test failed: ${output}" >&2 exit 1 fi - + echo " Testing x1_alignment_padding.sql..." - OUTPUT=$(PAGER=cat psql -h localhost -U dba_user -d test --no-psqlrc -f warmup.psql -f sql/x1_alignment_padding.sql | grep align) - if [[ "$OUTPUT" == *"align1"* && "$OUTPUT" == *"align2"* && "$OUTPUT" == *"int4, more, int8"* ]]; then - echo " ✓ Alignment padding test passed" - else - echo " ✗ Alignment padding test failed: $OUTPUT" + output=$(PGOPTIONS='-c postgres_dba.wide=off' PAGER='cat' psql \ + --no-psqlrc \ + --set=ON_ERROR_STOP=1 \ + --set=postgres_dba_interactive_mode=false \ + --host=localhost \ + --username=dba_user \ + --dbname=test \ + --file=warmup.psql \ + --file=sql/x1_alignment_padding.sql) + if [[ "${output}" != *align1* \ + || "${output}" != *align2* \ + || "${output}" != *'int4, more, int8'* ]]; then + echo "Alignment padding test failed: ${output}" >&2 exit 1 fi - + echo " Testing a1_activity.sql..." - OUTPUT=$(PAGER=cat psql -h localhost -U dba_user -d test --no-psqlrc -f warmup.psql -f sql/a1_activity.sql | grep User) - if [[ "$OUTPUT" == *"User"* ]]; then - echo " ✓ Activity test passed" - else - echo " ✗ Activity test failed: $OUTPUT" + output=$(PGOPTIONS='-c postgres_dba.wide=off' PAGER='cat' psql \ + --no-psqlrc \ + --set=ON_ERROR_STOP=1 \ + --set=postgres_dba_interactive_mode=false \ + --host=localhost \ + --username=dba_user \ + --dbname=test \ + --file=warmup.psql \ + --file=sql/a1_activity.sql) + if [[ "${output}" != *User* ]]; then + echo "Activity test failed: ${output}" >&2 exit 1 fi - + echo " Testing i3_non_indexed_fks.sql (with intarray extension)..." - OUTPUT=$(PAGER=cat psql -h localhost -U dba_user -d test --no-psqlrc -f warmup.psql -f sql/i3_non_indexed_fks.sql 2>&1) - if [[ "$OUTPUT" == *"ERROR"* ]]; then - echo " ✗ i3 test failed with error:" - echo "$OUTPUT" - exit 1 - elif [[ "$OUTPUT" == *"fk_child"* && "$OUTPUT" == *"fk_test"* ]]; then - echo " ✓ i3 foreign key test passed (found missing index on FK)" - else - echo " ✗ i3 test failed: unexpected output" - echo "$OUTPUT" + output=$(PGOPTIONS='-c postgres_dba.wide=off' PAGER='cat' psql \ + --no-psqlrc \ + --set=ON_ERROR_STOP=1 \ + --set=postgres_dba_interactive_mode=false \ + --host=localhost \ + --username=dba_user \ + --dbname=test \ + --file=warmup.psql \ + --file=sql/i3_non_indexed_fks.sql 2>&1) + if [[ "${output}" != *fk_child* || "${output}" != *fk_test* ]]; then + echo "i3 test failed: ${output}" >&2 exit 1 fi - - echo "✅ All regression tests passed with minimal privileges" + + echo "All regression tests passed with minimal privileges" diff --git a/CLAUDE.md b/CLAUDE.md index 00ed25c..13bb2f8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,7 +11,7 @@ Follow the rules at https://gitlab.com/postgres-ai/rules/-/tree/main/rules — a ## CI -GitHub Actions (`test.yml`): runs on push and PRs — tests across PostgreSQL 13, 14, 15, 16, 17, 18. +GitHub Actions (`test.yml`): runs on push and PRs — tests across PostgreSQL 13, 14, 15, 16, 17, 18, and 19 beta 2. ## Code Review diff --git a/README.md b/README.md index f0a31ff..1dd7764 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # 🐘 postgres_dba [![CI](https://github.com/NikolayS/postgres_dba/actions/workflows/test.yml/badge.svg)](https://github.com/NikolayS/postgres_dba/actions) -[![PostgreSQL 13–18](https://img.shields.io/badge/PostgreSQL-13--18-336791?logo=postgresql&logoColor=white)](https://www.postgresql.org/) +[![PostgreSQL 13–19beta2](https://img.shields.io/badge/PostgreSQL-13--19beta2-336791?logo=postgresql&logoColor=white)](https://www.postgresql.org/) [![License: BSD-3](https://img.shields.io/badge/License-BSD--3-blue.svg)](LICENSE) **34 diagnostic reports for PostgreSQL, right inside `psql`.** No agents, no daemons, no external dependencies — just SQL. @@ -111,7 +111,7 @@ Some reports benefit from additional extensions: ## Compatibility -Tested on **PostgreSQL 13 through 18** via CI on every commit. Older versions (9.6–12) may work but are not actively tested. +Tested on **PostgreSQL 13 through 18 and PostgreSQL 19 beta 2** via CI on every commit. Older versions (9.6–12) may work but are not actively tested. Works with the `pg_monitor` role — superuser is not required for most reports (corruption checks need superuser or explicit `GRANT EXECUTE`). diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 1b7027e..9c4a779 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,70 +1,21 @@ -# postgres_dba 7.0 +# postgres_dba 2026.8.1 -**34 reports** | Tested on **PostgreSQL 13–18** | Works with `pg_monitor` role +**34 reports** | Tested on **PostgreSQL 13–19beta2** | Works with `pg_monitor` role -## New Reports +## PostgreSQL 19 Beta 2 -### Corruption checks (c1–c4) — powered by `amcheck` +- Added PostgreSQL 19 beta 2 to the compatibility matrix. +- Verified all non-interactive reports in normal and wide modes with a + `pg_monitor`-only user. +- Verified extension-backed reports with `amcheck`, `intarray`, + `pg_buffercache`, `pg_stat_statements`, and `pgstattuple` installed. -Four levels of integrity checking, from quick production-safe to full paranoia: +## CI Reliability -| Report | Lock | What it checks | When to use | -|--------|------|----------------|-------------| -| **c1** | AccessShareLock | B-tree pages, GIN indexes (PG18+) | **Production** — fast, safe, non-blocking | -| **c2** | AccessShareLock | c1 + heap/TOAST integrity (PG14+) | **Production** — safe but reads all data | -| **c3** | ShareLock | B-tree parent-child ordering, sibling pointers, rootdescend, checkunique (PG14+) | **Clones** — detects glibc/collation corruption | -| **c4** | ShareLock | Everything in c3 + heapallindexed + verify_heapam with full TOAST | **Clones only** — proves every heap tuple is indexed, slow | - -All four check system catalog indexes (`pg_catalog`, `pg_toast`). - -Requires `CREATE EXTENSION amcheck`. Graceful handling when extension is missing or user lacks privileges. Version-conditional function signatures for PG11–18. GIN support via `gin_index_check()` on PG18+. - -### m1 — Buffer cache contents -What's in `shared_buffers`: cached size vs total, % of cache per object, dirty buffer counts. Includes system catalogs. Requires `pg_buffercache`. - -### s3 — Workload profile by query type -Groups `pg_stat_statements` by first SQL keyword (SELECT, INSERT, UPDATE, DELETE, etc.). Handles leading block comments (`/* ... */`) and line comments (`-- ...`). - -### t2 — Objects with custom storage parameters -Tables, indexes, and materialized views with non-default `reloptions`. Flags: disabled autovacuum on large tables, low fillfactor, aggressive vacuum scale factors. - -### Report 0 — WAL and replication slot info -Node information now includes WAL position, file count, total WAL size, and replication slot status. - -## Report Renames - -| Old | New | Reason | -|-----|-----|--------| -| b6 | **m1** | Buffer cache → **m** (memory) category | -| c1 | **p1** | Index creation progress → **p** (progress) category | -| p1 | **x1** | Alignment padding → **x** (experimental) category | - -v1/v2 descriptions clarified: v1 is "running operations (detailed progress)", v2 is "autovacuum queue and pending tables". - -## Bug Fixes - -- **s1, s2**: Fixed `blk_read_time does not exist` on PG17+ (renamed to `shared_blk_read_time` in pg_stat_statements 1.11) -- **s3**: Fixed `function round(double precision, integer) does not exist` — added `::numeric` casts -- **i3**: Fixed `operator is not unique` error when `intarray` extension is installed -- **m1**: Include system catalogs in buffer cache report (was showing empty on small databases) -- **i2**: Removed dead code (`redundant_indexes_grouped` CTE) -- **s1**: Removed duplicate `sum(calls)` in pre-PG13 code path - -## Terminology - -`Master` → `Primary` across all reports and CI. - -## Other Improvements - -- Modernized README with badges, individual credits, optional extensions table -- Fixed Quick Start psqlrc escaping -- Fixed menu spacing for new reports -- `alt_shits` → `alt_shifts` (p1) -- Various typo fixes across b1, b2, b3, b4, l1, s2, v2 - -## CI - -- All 34 reports tested on PG 13, 14, 15, 16, 17, 18 -- Added `amcheck`, `intarray`, `pg_buffercache` extensions to test matrix -- Added i3 regression test with `intarray` installed -- Added `PAGER=cat` to prevent pager hangs +- Made SQL errors fail the test job with `ON_ERROR_STOP`. +- Enabled `pg_stat_statements` in `shared_preload_libraries`, so its reports + execute instead of returning a masked error. +- Fixed wide-mode coverage to set `postgres_dba.wide` for the tested session. +- Added scripted functional tests for the interactive create-role and + alter-role reports, including role-state and password-rotation assertions. +- Added an explicit assertion that the PG19 job is running PostgreSQL 19 beta 2. From fd42084fe17a105f447b68025c2e92dcdd9a7130 Mon Sep 17 00:00:00 2001 From: samo-agent <280144521+samo-agent@users.noreply.github.com> Date: Fri, 14 Aug 2026 03:23:44 +0000 Subject: [PATCH 2/7] fix: harden PostgreSQL compatibility CI Address actionable samorev findings: preserve release history, remove duplicate mode coverage, restrict and clean up the test database, validate every matrix version, avoid password material in logs, and improve regression diagnostics.\n\nRelates to #96 --- .github/workflows/test.yml | 106 +++++++++++++++++-------------------- RELEASE_NOTES.md | 85 +++++++++++++++++++++++++++-- 2 files changed, 131 insertions(+), 60 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e95305d..4de5885 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,7 +27,7 @@ jobs: --env POSTGRES_DB=test \ --env POSTGRES_HOST_AUTH_METHOD=trust \ --env POSTGRES_INITDB_ARGS='--auth-host=trust --auth-local=trust' \ - --publish 5432:5432 \ + --publish 127.0.0.1:5432:5432 \ postgres:${{ matrix.postgres-version }} \ -c shared_preload_libraries=pg_stat_statements @@ -62,9 +62,8 @@ jobs: --no-align \ --command='show server_version;') echo "PostgreSQL ${server_version}" - if [[ '${{ matrix.postgres-version }}' == '19beta2' \ - && "${server_version}" != 19beta2* ]]; then - echo "Expected PostgreSQL 19beta2, got ${server_version}" >&2 + if [[ "${server_version}" != '${{ matrix.postgres-version }}'* ]]; then + echo "Expected PostgreSQL ${{ matrix.postgres-version }}, got ${server_version}" >&2 exit 1 fi @@ -124,15 +123,15 @@ jobs: --dbname=test \ --command='select current_user, session_user;' - - name: Test wide mode + - name: Test non-interactive reports run: | - echo "Testing all SQL files in wide mode with minimal privileges..." + echo "Testing all non-interactive reports with minimal privileges..." for report in ./sql/*.sql; do if [[ "${report}" == ./sql/r1_* || "${report}" == ./sql/r2_* ]]; then continue fi echo " Testing ${report}..." - if ! output=$(PGOPTIONS='-c postgres_dba.wide=on' PAGER='cat' psql \ + if ! output=$(PAGER='cat' psql \ --no-psqlrc \ --set=ON_ERROR_STOP=1 \ --set=postgres_dba_interactive_mode=false \ @@ -141,47 +140,24 @@ jobs: --dbname=test \ --file=warmup.psql \ --file="${report}" 2>&1); then - echo "FAILED: ${report} in wide mode" >&2 + echo "FAILED: ${report}" >&2 echo "${output}" >&2 exit 1 fi done - echo "All tests passed in wide mode" - - - name: Test normal mode - run: | - echo "Testing all SQL files in normal mode with minimal privileges..." - for report in ./sql/*.sql; do - if [[ "${report}" == ./sql/r1_* || "${report}" == ./sql/r2_* ]]; then - continue - fi - echo " Testing ${report}..." - if ! output=$(PGOPTIONS='-c postgres_dba.wide=off' PAGER='cat' psql \ - --no-psqlrc \ - --set=ON_ERROR_STOP=1 \ - --set=postgres_dba_interactive_mode=false \ - --host=localhost \ - --username=dba_user \ - --dbname=test \ - --file=warmup.psql \ - --file="${report}" 2>&1); then - echo "FAILED: ${report} in normal mode" >&2 - echo "${output}" >&2 - exit 1 - fi - done - echo "All tests passed in normal mode" + echo "All non-interactive reports passed" - name: Test interactive role reports run: | - if ! output=$(printf 'ci_test_role\n0\n1\n' | PAGER='cat' psql \ + if ! printf 'ci_test_role\n0\n1\n' | PAGER='cat' psql \ --no-psqlrc \ --set=ON_ERROR_STOP=1 \ --host=localhost \ --username=postgres \ --dbname=test \ - --file=sql/r1_create_user_with_random_password.sql 2>&1); then - echo "${output}" | sed -E 's/(password: ).*/\1[redacted]/' >&2 + --file=sql/r1_create_user_with_random_password.sql \ + >/dev/null 2>&1; then + echo 'Create-role report failed; output suppressed because it contains a password' >&2 exit 1 fi @@ -201,7 +177,7 @@ jobs: exit 1 fi - old_password=$(PAGER='cat' psql \ + old_password_digest=$(PAGER='cat' psql \ --no-psqlrc \ --set=ON_ERROR_STOP=1 \ --host=localhost \ @@ -209,21 +185,22 @@ jobs: --dbname=test \ --tuples-only \ --no-align \ - --command="select rolpassword from pg_authid + --command="select md5(rolpassword) from pg_authid where rolname = 'ci_test_role';") - if ! output=$(printf 'ci_test_role\n0\n1\n' | PAGER='cat' psql \ + if ! printf 'ci_test_role\n0\n1\n' | PAGER='cat' psql \ --no-psqlrc \ --set=ON_ERROR_STOP=1 \ --host=localhost \ --username=postgres \ --dbname=test \ - --file=sql/r2_alter_user_with_random_password.sql 2>&1); then - echo "${output}" | sed -E 's/(password: ).*/\1[redacted]/' >&2 + --file=sql/r2_alter_user_with_random_password.sql \ + >/dev/null 2>&1; then + echo 'Alter-role report failed; output suppressed because it may contain a password' >&2 exit 1 fi - new_password=$(PAGER='cat' psql \ + new_password_digest=$(PAGER='cat' psql \ --no-psqlrc \ --set=ON_ERROR_STOP=1 \ --host=localhost \ @@ -231,11 +208,11 @@ jobs: --dbname=test \ --tuples-only \ --no-align \ - --command="select rolpassword from pg_authid + --command="select md5(rolpassword) from pg_authid where rolname = 'ci_test_role';") - if [[ -z "${old_password}" \ - || -z "${new_password}" \ - || "${old_password}" == "${new_password}" ]]; then + if [[ -z "${old_password_digest}" \ + || -z "${new_password_digest}" \ + || "${old_password_digest}" == "${new_password_digest}" ]]; then echo 'Alter-role report did not rotate the password' >&2 exit 1 fi @@ -245,7 +222,7 @@ jobs: echo "Running regression tests with minimal privileges..." echo " Testing 0_node.sql..." - output=$(PGOPTIONS='-c postgres_dba.wide=off' PAGER='cat' psql \ + if ! output=$(PAGER='cat' psql \ --no-psqlrc \ --set=ON_ERROR_STOP=1 \ --set=postgres_dba_interactive_mode=false \ @@ -253,14 +230,18 @@ jobs: --username=dba_user \ --dbname=test \ --file=warmup.psql \ - --file=sql/0_node.sql) - if [[ "${output}" != *Primary* ]]; then + --file=sql/0_node.sql 2>&1); then + echo "${output}" >&2 + exit 1 + fi + role_line=$(printf '%s\n' "${output}" | grep -E '^[[:space:]]*Role[[:space:]]*\|') + if [[ "${role_line}" != *Primary* ]]; then echo "Role test failed: ${output}" >&2 exit 1 fi echo " Testing x1_alignment_padding.sql..." - output=$(PGOPTIONS='-c postgres_dba.wide=off' PAGER='cat' psql \ + if ! output=$(PAGER='cat' psql \ --no-psqlrc \ --set=ON_ERROR_STOP=1 \ --set=postgres_dba_interactive_mode=false \ @@ -268,7 +249,10 @@ jobs: --username=dba_user \ --dbname=test \ --file=warmup.psql \ - --file=sql/x1_alignment_padding.sql) + --file=sql/x1_alignment_padding.sql 2>&1); then + echo "${output}" >&2 + exit 1 + fi if [[ "${output}" != *align1* \ || "${output}" != *align2* \ || "${output}" != *'int4, more, int8'* ]]; then @@ -277,7 +261,7 @@ jobs: fi echo " Testing a1_activity.sql..." - output=$(PGOPTIONS='-c postgres_dba.wide=off' PAGER='cat' psql \ + if ! output=$(PAGER='cat' psql \ --no-psqlrc \ --set=ON_ERROR_STOP=1 \ --set=postgres_dba_interactive_mode=false \ @@ -285,14 +269,17 @@ jobs: --username=dba_user \ --dbname=test \ --file=warmup.psql \ - --file=sql/a1_activity.sql) - if [[ "${output}" != *User* ]]; then + --file=sql/a1_activity.sql 2>&1); then + echo "${output}" >&2 + exit 1 + fi + if [[ "${output}" != *dba_user* ]]; then echo "Activity test failed: ${output}" >&2 exit 1 fi echo " Testing i3_non_indexed_fks.sql (with intarray extension)..." - output=$(PGOPTIONS='-c postgres_dba.wide=off' PAGER='cat' psql \ + if ! output=$(PAGER='cat' psql \ --no-psqlrc \ --set=ON_ERROR_STOP=1 \ --set=postgres_dba_interactive_mode=false \ @@ -300,10 +287,17 @@ jobs: --username=dba_user \ --dbname=test \ --file=warmup.psql \ - --file=sql/i3_non_indexed_fks.sql 2>&1) + --file=sql/i3_non_indexed_fks.sql 2>&1); then + echo "${output}" >&2 + exit 1 + fi if [[ "${output}" != *fk_child* || "${output}" != *fk_test* ]]; then echo "i3 test failed: ${output}" >&2 exit 1 fi echo "All regression tests passed with minimal privileges" + + - name: Stop PostgreSQL + if: always() + run: docker rm --force postgres-dba-test diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 9c4a779..6059bac 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -5,8 +5,7 @@ ## PostgreSQL 19 Beta 2 - Added PostgreSQL 19 beta 2 to the compatibility matrix. -- Verified all non-interactive reports in normal and wide modes with a - `pg_monitor`-only user. +- Verified all non-interactive reports with a `pg_monitor`-only user. - Verified extension-backed reports with `amcheck`, `intarray`, `pg_buffercache`, `pg_stat_statements`, and `pgstattuple` installed. @@ -15,7 +14,85 @@ - Made SQL errors fail the test job with `ON_ERROR_STOP`. - Enabled `pg_stat_statements` in `shared_preload_libraries`, so its reports execute instead of returning a masked error. -- Fixed wide-mode coverage to set `postgres_dba.wide` for the tested session. - Added scripted functional tests for the interactive create-role and alter-role reports, including role-state and password-rotation assertions. -- Added an explicit assertion that the PG19 job is running PostgreSQL 19 beta 2. +- Added assertions that every job is running its expected PostgreSQL version. +- Restricted the test database to loopback and added unconditional cleanup. + +## Versioning + +Starting with this release, postgres_dba uses calendar versions in +`YYYY.M.patch` format. + +--- + +# postgres_dba 7.0 + +**34 reports** | Tested on **PostgreSQL 13–18** | Works with `pg_monitor` role + +## New Reports + +### Corruption checks (c1–c4) — powered by `amcheck` + +Four levels of integrity checking, from quick production-safe to full paranoia: + +| Report | Lock | What it checks | When to use | +|--------|------|----------------|-------------| +| **c1** | AccessShareLock | B-tree pages, GIN indexes (PG18+) | **Production** — fast, safe, non-blocking | +| **c2** | AccessShareLock | c1 + heap/TOAST integrity (PG14+) | **Production** — safe but reads all data | +| **c3** | ShareLock | B-tree parent-child ordering, sibling pointers, rootdescend, checkunique (PG14+) | **Clones** — detects glibc/collation corruption | +| **c4** | ShareLock | Everything in c3 + heapallindexed + verify_heapam with full TOAST | **Clones only** — proves every heap tuple is indexed, slow | + +All four check system catalog indexes (`pg_catalog`, `pg_toast`). + +Requires `CREATE EXTENSION amcheck`. Graceful handling when extension is missing or user lacks privileges. Version-conditional function signatures for PG11–18. GIN support via `gin_index_check()` on PG18+. + +### m1 — Buffer cache contents +What's in `shared_buffers`: cached size vs total, % of cache per object, dirty buffer counts. Includes system catalogs. Requires `pg_buffercache`. + +### s3 — Workload profile by query type +Groups `pg_stat_statements` by first SQL keyword (SELECT, INSERT, UPDATE, DELETE, etc.). Handles leading block comments (`/* ... */`) and line comments (`-- ...`). + +### t2 — Objects with custom storage parameters +Tables, indexes, and materialized views with non-default `reloptions`. Flags: disabled autovacuum on large tables, low fillfactor, aggressive vacuum scale factors. + +### Report 0 — WAL and replication slot info +Node information now includes WAL position, file count, total WAL size, and replication slot status. + +## Report Renames + +| Old | New | Reason | +|-----|-----|--------| +| b6 | **m1** | Buffer cache → **m** (memory) category | +| c1 | **p1** | Index creation progress → **p** (progress) category | +| p1 | **x1** | Alignment padding → **x** (experimental) category | + +v1/v2 descriptions clarified: v1 is "running operations (detailed progress)", v2 is "autovacuum queue and pending tables". + +## Bug Fixes + +- **s1, s2**: Fixed `blk_read_time does not exist` on PG17+ (renamed to `shared_blk_read_time` in pg_stat_statements 1.11) +- **s3**: Fixed `function round(double precision, integer) does not exist` — added `::numeric` casts +- **i3**: Fixed `operator is not unique` error when `intarray` extension is installed +- **m1**: Include system catalogs in buffer cache report (was showing empty on small databases) +- **i2**: Removed dead code (`redundant_indexes_grouped` CTE) +- **s1**: Removed duplicate `sum(calls)` in pre-PG13 code path + +## Terminology + +`Master` → `Primary` across all reports and CI. + +## Other Improvements + +- Modernized README with badges, individual credits, optional extensions table +- Fixed Quick Start psqlrc escaping +- Fixed menu spacing for new reports +- `alt_shits` → `alt_shifts` (p1) +- Various typo fixes across b1, b2, b3, b4, l1, s2, v2 + +## CI + +- All 34 reports tested on PG 13, 14, 15, 16, 17, 18 +- Added `amcheck`, `intarray`, `pg_buffercache` extensions to test matrix +- Added i3 regression test with `intarray` installed +- Added `PAGER=cat` to prevent pager hangs From b47e2a7b8454eaa99bd313bc5c08f02b54a05206 Mon Sep 17 00:00:00 2001 From: samo-agent <280144521+samo-agent@users.noreply.github.com> Date: Fri, 14 Aug 2026 03:35:59 +0000 Subject: [PATCH 3/7] test: verify generated role passwords Require SCRAM authentication for the generated test role, verify both created and rotated passwords, and reject the previous password after rotation. Keep password-bearing report output out of CI logs.\n\nRelates to #96 --- .github/workflows/test.yml | 76 ++++++++++++++++++++++++++++++++++++-- RELEASE_NOTES.md | 3 +- 2 files changed, 74 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4de5885..862887d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -149,18 +149,27 @@ jobs: - name: Test interactive role reports run: | - if ! printf 'ci_test_role\n0\n1\n' | PAGER='cat' psql \ + if ! output=$(printf 'ci_test_role\n0\n1\n' | PAGER='cat' psql \ --no-psqlrc \ --set=ON_ERROR_STOP=1 \ --host=localhost \ --username=postgres \ --dbname=test \ --file=sql/r1_create_user_with_random_password.sql \ - >/dev/null 2>&1; then + 2>&1); then echo 'Create-role report failed; output suppressed because it contains a password' >&2 exit 1 fi + created_password=$(printf '%s\n' "${output}" \ + | sed -nE \ + 's/^.*INFO:[[:space:]]+User ci_test_role created, password: ([[:alnum:]]+)$/\1/p') + if [[ -z "${created_password}" ]]; then + echo 'Create-role report did not return a parseable password' >&2 + exit 1 + fi + echo "::add-mask::${created_password}" + role_state=$(PAGER='cat' psql \ --no-psqlrc \ --set=ON_ERROR_STOP=1 \ @@ -177,6 +186,32 @@ jobs: exit 1 fi + docker exec postgres-dba-test sh -c ' + hba="${PGDATA}/pg_hba.conf" + { + printf "%s\n" \ + "host all ci_test_role all scram-sha-256" + cat "${hba}" + } > "${hba}.new" + mv "${hba}.new" "${hba}" + ' + PAGER='cat' psql \ + --no-psqlrc \ + --set=ON_ERROR_STOP=1 \ + --host=localhost \ + --username=postgres \ + --dbname=test \ + --command='select pg_reload_conf();' + + PGPASSWORD="${created_password}" PAGER='cat' psql \ + --no-psqlrc \ + --set=ON_ERROR_STOP=1 \ + --host=localhost \ + --username=ci_test_role \ + --dbname=test \ + --command='select 1;' \ + >/dev/null + old_password_digest=$(PAGER='cat' psql \ --no-psqlrc \ --set=ON_ERROR_STOP=1 \ @@ -188,18 +223,32 @@ jobs: --command="select md5(rolpassword) from pg_authid where rolname = 'ci_test_role';") - if ! printf 'ci_test_role\n0\n1\n' | PAGER='cat' psql \ + if ! output=$(printf 'ci_test_role\n0\n1\n' \ + | PGOPTIONS='-c client_min_messages=debug' PAGER='cat' psql \ --no-psqlrc \ --set=ON_ERROR_STOP=1 \ --host=localhost \ --username=postgres \ --dbname=test \ --file=sql/r2_alter_user_with_random_password.sql \ - >/dev/null 2>&1; then + 2>&1); then echo 'Alter-role report failed; output suppressed because it may contain a password' >&2 exit 1 fi + altered_password=$(printf '%s\n' "${output}" \ + | sed -nE \ + 's/^.*DEBUG:[[:space:]]+User ci_test_role altered, password: ([[:alnum:]]+)$/\1/p') + if [[ -z "${altered_password}" ]]; then + echo 'Alter-role report did not return a parseable password' >&2 + exit 1 + fi + echo "::add-mask::${altered_password}" + if [[ "${created_password}" == "${altered_password}" ]]; then + echo 'Alter-role report reused the previous password' >&2 + exit 1 + fi + new_password_digest=$(PAGER='cat' psql \ --no-psqlrc \ --set=ON_ERROR_STOP=1 \ @@ -216,6 +265,25 @@ jobs: echo 'Alter-role report did not rotate the password' >&2 exit 1 fi + + PGPASSWORD="${altered_password}" PAGER='cat' psql \ + --no-psqlrc \ + --set=ON_ERROR_STOP=1 \ + --host=localhost \ + --username=ci_test_role \ + --dbname=test \ + --command='select 1;' \ + >/dev/null + if PGPASSWORD="${created_password}" PAGER='cat' psql \ + --no-psqlrc \ + --host=localhost \ + --username=ci_test_role \ + --dbname=test \ + --command='select 1;' \ + >/dev/null 2>&1; then + echo 'Previous password still authenticates after rotation' >&2 + exit 1 + fi - name: Run regression tests run: | diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 6059bac..8ab11f2 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -15,7 +15,8 @@ - Enabled `pg_stat_statements` in `shared_preload_libraries`, so its reports execute instead of returning a masked error. - Added scripted functional tests for the interactive create-role and - alter-role reports, including role-state and password-rotation assertions. + alter-role reports, including successful authentication, password rotation, + and rejection of the previous password. - Added assertions that every job is running its expected PostgreSQL version. - Restricted the test database to loopback and added unconditional cleanup. From e7517b86e0cdb0ff1c716fae70d0ffa5f4305cbc Mon Sep 17 00:00:00 2001 From: samo-agent <280144521+samo-agent@users.noreply.github.com> Date: Fri, 14 Aug 2026 03:38:36 +0000 Subject: [PATCH 4/7] test: support PG13 password auth Use the version-compatible md5 HBA method. PostgreSQL 13 authenticates its MD5 verifier, while newer releases negotiate SCRAM when the stored verifier is SCRAM. Relates to #96 --- .github/workflows/test.yml | 2 +- RELEASE_NOTES.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 862887d..f0c80af 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -190,7 +190,7 @@ jobs: hba="${PGDATA}/pg_hba.conf" { printf "%s\n" \ - "host all ci_test_role all scram-sha-256" + "host all ci_test_role all md5" cat "${hba}" } > "${hba}.new" mv "${hba}.new" "${hba}" diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 8ab11f2..79dc1e5 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -15,8 +15,8 @@ - Enabled `pg_stat_statements` in `shared_preload_libraries`, so its reports execute instead of returning a masked error. - Added scripted functional tests for the interactive create-role and - alter-role reports, including successful authentication, password rotation, - and rejection of the previous password. + alter-role reports, including successful password authentication, password + rotation, and rejection of the previous password. - Added assertions that every job is running its expected PostgreSQL version. - Restricted the test database to loopback and added unconditional cleanup. From a2bba1edc36ef9b88a56dc1f446eded1d82f2f78 Mon Sep 17 00:00:00 2001 From: samo-agent <280144521+samo-agent@users.noreply.github.com> Date: Fri, 14 Aug 2026 04:02:11 +0000 Subject: [PATCH 5/7] test: preserve CI failure diagnostics Guard the remaining grep assertion, require the expected authentication failure, retain server logs on failed jobs, and harden the version, pg_stat_statements, HBA, and role-state checks. Relates to #96 --- .github/workflows/test.yml | 86 +++++++++++++++++++++++++++++++++++--- RELEASE_NOTES.md | 3 +- 2 files changed, 82 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f0c80af..d8361a3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -62,7 +62,13 @@ jobs: --no-align \ --command='show server_version;') echo "PostgreSQL ${server_version}" - if [[ "${server_version}" != '${{ matrix.postgres-version }}'* ]]; then + expected_version='${{ matrix.postgres-version }}' + if [[ "${expected_version}" =~ ^[0-9]+$ ]]; then + expected_prefix="${expected_version}." + else + expected_prefix="${expected_version} " + fi + if [[ "${server_version}" != "${expected_prefix}"* ]]; then echo "Expected PostgreSQL ${{ matrix.postgres-version }}, got ${server_version}" >&2 exit 1 fi @@ -105,7 +111,8 @@ jobs: constraint fk_test foreign key (parent_id) references fk_parent(id) ); insert into fk_parent - select i, 'data_' || i from generate_series(1, 100000) i; + select i, 'data_' || i + from generate_series(1, 100000) i; insert into fk_child select i, (i % 100000) + 1, 'data_' || i from generate_series(1, 200000) i; @@ -122,11 +129,26 @@ jobs: --username=dba_user \ --dbname=test \ --command='select current_user, session_user;' + + pgss_rows=$(PAGER='cat' psql \ + --no-psqlrc \ + --set=ON_ERROR_STOP=1 \ + --host=localhost \ + --username=dba_user \ + --dbname=test \ + --tuples-only \ + --no-align \ + --command='select count(*) from pg_stat_statements;') + if (( pgss_rows == 0 )); then + echo 'pg_stat_statements did not capture any statements' >&2 + exit 1 + fi - name: Test non-interactive reports run: | echo "Testing all non-interactive reports with minimal privileges..." for report in ./sql/*.sql; do + # Interactive reports are covered with scripted input below. if [[ "${report}" == ./sql/r1_* || "${report}" == ./sql/r2_* ]]; then continue fi @@ -186,13 +208,16 @@ jobs: exit 1 fi + # md5 works with PG13 verifiers and negotiates SCRAM for newer verifiers. docker exec postgres-dba-test sh -c ' + set -eu hba="${PGDATA}/pg_hba.conf" { printf "%s\n" \ "host all ci_test_role all md5" cat "${hba}" } > "${hba}.new" + test -s "${hba}.new" mv "${hba}.new" "${hba}" ' PAGER='cat' psql \ @@ -203,6 +228,27 @@ jobs: --dbname=test \ --command='select pg_reload_conf();' + auth_rule_ready=false + for _ in {1..30}; do + if auth_error=$(PGPASSWORD='deliberately-wrong' PAGER='cat' psql \ + --no-psqlrc \ + --host=localhost \ + --username=ci_test_role \ + --dbname=test \ + --command='select 1;' 2>&1); then + : + elif [[ "${auth_error}" == \ + *'password authentication failed for user "ci_test_role"'* ]]; then + auth_rule_ready=true + break + fi + sleep 0.1 + done + if [[ "${auth_rule_ready}" != true ]]; then + echo 'Password-authentication rule did not become active' >&2 + exit 1 + fi + PGPASSWORD="${created_password}" PAGER='cat' psql \ --no-psqlrc \ --set=ON_ERROR_STOP=1 \ @@ -223,6 +269,7 @@ jobs: --command="select md5(rolpassword) from pg_authid where rolname = 'ci_test_role';") + # r2 emits its generated password at DEBUG level. if ! output=$(printf 'ci_test_role\n0\n1\n' \ | PGOPTIONS='-c client_min_messages=debug' PAGER='cat' psql \ --no-psqlrc \ @@ -249,6 +296,22 @@ jobs: exit 1 fi + altered_role_state=$(PAGER='cat' psql \ + --no-psqlrc \ + --set=ON_ERROR_STOP=1 \ + --host=localhost \ + --username=postgres \ + --dbname=test \ + --tuples-only \ + --no-align \ + --command="select rolsuper, rolcanlogin + from pg_authid + where rolname = 'ci_test_role';") + if [[ "${altered_role_state}" != 'f|t' ]]; then + echo "Unexpected altered role state: ${altered_role_state}" >&2 + exit 1 + fi + new_password_digest=$(PAGER='cat' psql \ --no-psqlrc \ --set=ON_ERROR_STOP=1 \ @@ -274,16 +337,21 @@ jobs: --dbname=test \ --command='select 1;' \ >/dev/null - if PGPASSWORD="${created_password}" PAGER='cat' psql \ + if old_password_error=$(PGPASSWORD="${created_password}" PAGER='cat' psql \ --no-psqlrc \ --host=localhost \ --username=ci_test_role \ --dbname=test \ --command='select 1;' \ - >/dev/null 2>&1; then + 2>&1); then echo 'Previous password still authenticates after rotation' >&2 exit 1 fi + if [[ "${old_password_error}" != \ + *'password authentication failed for user "ci_test_role"'* ]]; then + echo "Unexpected old-password failure: ${old_password_error}" >&2 + exit 1 + fi - name: Run regression tests run: | @@ -302,8 +370,10 @@ jobs: echo "${output}" >&2 exit 1 fi - role_line=$(printf '%s\n' "${output}" | grep -E '^[[:space:]]*Role[[:space:]]*\|') - if [[ "${role_line}" != *Primary* ]]; then + role_line=$(printf '%s\n' "${output}" \ + | grep -E '^[[:space:]]*Role[[:space:]]*\|' \ + || true) + if [[ -z "${role_line}" || "${role_line}" != *Primary* ]]; then echo "Role test failed: ${output}" >&2 exit 1 fi @@ -366,6 +436,10 @@ jobs: echo "All regression tests passed with minimal privileges" + - name: Print PostgreSQL logs on failure + if: failure() + run: docker logs postgres-dba-test || true + - name: Stop PostgreSQL if: always() run: docker rm --force postgres-dba-test diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 79dc1e5..20e10e7 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -5,7 +5,8 @@ ## PostgreSQL 19 Beta 2 - Added PostgreSQL 19 beta 2 to the compatibility matrix. -- Verified all non-interactive reports with a `pg_monitor`-only user. +- Verified all non-interactive reports with a non-superuser `pg_monitor` member + holding the minimal database and object grants the reports require. - Verified extension-backed reports with `amcheck`, `intarray`, `pg_buffercache`, `pg_stat_statements`, and `pgstattuple` installed. From d684f3848838ad0e9404a8ead12af1b91deecc9d Mon Sep 17 00:00:00 2001 From: samo-agent <280144521+samo-agent@users.noreply.github.com> Date: Fri, 14 Aug 2026 04:11:23 +0000 Subject: [PATCH 6/7] test: report auth readiness failures Print the last psql result when the password-authentication rule does not become active. Relates to #96 --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d8361a3..5077932 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -245,7 +245,7 @@ jobs: sleep 0.1 done if [[ "${auth_rule_ready}" != true ]]; then - echo 'Password-authentication rule did not become active' >&2 + echo "Password-authentication rule did not become active: ${auth_error}" >&2 exit 1 fi From 0fe3531fe42461cdb06dfe1c63df0430699df6f7 Mon Sep 17 00:00:00 2001 From: samo-agent <280144521+samo-agent@users.noreply.github.com> Date: Tue, 18 Aug 2026 15:56:43 +0000 Subject: [PATCH 7/7] ci: cover wide reports and fail on pgss errors --- .github/workflows/test.yml | 48 +++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5077932..53016c7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -130,7 +130,7 @@ jobs: --dbname=test \ --command='select current_user, session_user;' - pgss_rows=$(PAGER='cat' psql \ + if ! pgss_rows=$(PAGER='cat' psql \ --no-psqlrc \ --set=ON_ERROR_STOP=1 \ --host=localhost \ @@ -138,7 +138,10 @@ jobs: --dbname=test \ --tuples-only \ --no-align \ - --command='select count(*) from pg_stat_statements;') + --command='select count(*) from pg_stat_statements;'); then + echo 'pg_stat_statements query failed' >&2 + exit 1 + fi if (( pgss_rows == 0 )); then echo 'pg_stat_statements did not capture any statements' >&2 exit 1 @@ -147,25 +150,28 @@ jobs: - name: Test non-interactive reports run: | echo "Testing all non-interactive reports with minimal privileges..." - for report in ./sql/*.sql; do - # Interactive reports are covered with scripted input below. - if [[ "${report}" == ./sql/r1_* || "${report}" == ./sql/r2_* ]]; then - continue - fi - echo " Testing ${report}..." - if ! output=$(PAGER='cat' psql \ - --no-psqlrc \ - --set=ON_ERROR_STOP=1 \ - --set=postgres_dba_interactive_mode=false \ - --host=localhost \ - --username=dba_user \ - --dbname=test \ - --file=warmup.psql \ - --file="${report}" 2>&1); then - echo "FAILED: ${report}" >&2 - echo "${output}" >&2 - exit 1 - fi + for mode in off on; do + echo "Testing reports with postgres_dba.wide=${mode}..." + for report in ./sql/*.sql; do + # Interactive reports are covered with scripted input below. + if [[ "${report}" == ./sql/r1_* || "${report}" == ./sql/r2_* ]]; then + continue + fi + echo " Testing ${report}..." + if ! output=$(PGOPTIONS="-c postgres_dba.wide=${mode}" PAGER='cat' psql \ + --no-psqlrc \ + --set=ON_ERROR_STOP=1 \ + --set=postgres_dba_interactive_mode=false \ + --host=localhost \ + --username=dba_user \ + --dbname=test \ + --file=warmup.psql \ + --file="${report}" 2>&1); then + echo "FAILED (${mode}): ${report}" >&2 + echo "${output}" >&2 + exit 1 + fi + done done echo "All non-interactive reports passed"