Hi hackers,

Attached is a patch that adds --exclude-database (-D) to vacuumdb,
allowing users to skip specific databases when using --all.

Motivation
----------

"vacuumdb --all" vacuums every connectable database without exception.
In production environments, operators often need to skip certain databases
during maintenance — for example, test databases, large inactive databases
with historical data.
The current workaround is to manually vacuum each database
individually or write wrapper scripts.

Usage
-----

    vacuumdb --all --exclude-database=test_db
    vacuumdb --all -D db1 -D db2 -D db3

The option requires --all

Testing
-------

The patch passes all existing vacuumdb TAP tests (100_vacuumdb,
101_vacuumdb_all, 102_vacuumdb_stages) and includes 4 new TAP tests
in 101_vacuumdb_all.pl covering exclusion, multiple exclusions, and
error cases.

I've also attached a standalone test script (test_exclude_database.sh)
that exercises the feature with 11 test scenarios:

  1. Single database exclusion
  2. Multiple database exclusions (-D db1 -D db2)
  3. Exclude all databases
  4. Exclude non-existent database (silently ignored)
  5. Exclude maintenance database (postgres)
  6. Case sensitivity (exact case excludes, wrong case does not)
  7. --exclude-database without --all (error)
  8. --exclude-database with -d (error)
  9. SQL injection protection
 10. --help output
 11. -d with -D and --all (conflicting options error)

All tests pass. The test script is portable — it uses standard libpq
environment variables and auto-detects binaries. Test output is also
attached (test_exclude_database_results.txt).

-- 
Mohamed Ali
AWS RDS
============================================================
VACUUMDB --exclude-database FEATURE TEST SUITE
============================================================
Date: Thu May 14 22:48:56 PDT 2026
PostgreSQL: PostgreSQL 19devel on aarch64-darwin, compiled by clang-21.0.0, 
64-bit
vacuumdb: vacuumdb (PostgreSQL) 19devel

Available databases:
 postgres
 template1
 test_db1
 test_db2
 test_db3



================================================================
TEST 1: Single database exclusion
================================================================
Description: Exclude test_db1 from vacuum --all. Expected: exit 0, test_db1 
skipped, other dbs vacuumed.
Command: /Users/mmohali/Kiro_projects/vacuumdb/pghome/bin/vacuumdb --all 
--exclude-database=test_db1
Expected: pass
---
Exit Code: 0
Output:
vacuumdb: vacuuming database "postgres"
vacuumdb: vacuuming database "template1"
vacuumdb: vacuuming database "test_db2"
vacuumdb: vacuuming database "test_db3"
---
RESULT: ✅ PASS
  ✅ 'vacuuming database "test_db1"' correctly absent from output
  ✅ All other databases vacuumed (4/4)

================================================================
TEST 2: Multiple database exclusions (-D db1 -D db2)
================================================================
Description: Exclude test_db1 and test_db2. Expected: exit 0, both excluded, 
test_db3 still vacuumed.
Command: /Users/mmohali/Kiro_projects/vacuumdb/pghome/bin/vacuumdb --all -D 
test_db1 -D test_db2
Expected: pass
---
Exit Code: 0
Output:
vacuumdb: vacuuming database "postgres"
vacuumdb: vacuuming database "template1"
vacuumdb: vacuuming database "test_db3"
---
RESULT: ✅ PASS
  ✅ 'vacuuming database "test_db1"' correctly absent from output
  ✅ 'vacuuming database "test_db2"' correctly absent from output
  ✅ 'vacuuming database "test_db3"' found in output

================================================================
TEST 3: Exclude ALL databases (edge case)
================================================================
Description: Exclude every connectable database. Expected: exit 0, no vacuum 
ops, graceful no-op.
Command: /Users/mmohali/Kiro_projects/vacuumdb/pghome/bin/vacuumdb --all  -D 
postgres -D test_db1 -D template1 -D test_db2 -D test_db3
Expected: pass
---
Exit Code: 0
Output:

---
RESULT: ✅ PASS

================================================================
TEST 4: Exclude non-existent database
================================================================
Description: Exclude a name that doesn't exist. Expected: exit 0, silently 
ignored, all real dbs vacuumed.
Command: /Users/mmohali/Kiro_projects/vacuumdb/pghome/bin/vacuumdb --all 
--exclude-database=this_db_does_not_exist_xyz
Expected: pass
---
Exit Code: 0
Output:
vacuumdb: vacuuming database "postgres"
vacuumdb: vacuuming database "template1"
vacuumdb: vacuuming database "test_db1"
vacuumdb: vacuuming database "test_db2"
vacuumdb: vacuuming database "test_db3"
---
RESULT: ✅ PASS
  ✅ 'vacuuming database "postgres"' found in output

================================================================
TEST 5: Exclude maintenance database (postgres)
================================================================
Description: Exclude postgres (used for listing). Expected: exit 0, postgres 
skipped, others vacuumed.
Command: /Users/mmohali/Kiro_projects/vacuumdb/pghome/bin/vacuumdb --all 
--exclude-database=postgres
Expected: pass
---
Exit Code: 0
Output:
vacuumdb: vacuuming database "template1"
vacuumdb: vacuuming database "test_db1"
vacuumdb: vacuuming database "test_db2"
vacuumdb: vacuuming database "test_db3"
---
RESULT: ✅ PASS
  ✅ 'vacuuming database "postgres"' correctly absent from output
  ✅ All other databases vacuumed (4/4)

================================================================
TEST 6: Case sensitivity - exact case excludes MixedCaseDB
================================================================
Description: TEST 6.1: Exclude MixedCaseDB with exact case. Expected: exit 0, 
MixedCaseDB NOT vacuumed.
Command: /Users/mmohali/Kiro_projects/vacuumdb/pghome/bin/vacuumdb --all 
--exclude-database=MixedCaseDB
Expected: pass
---
Exit Code: 0
Output:
vacuumdb: vacuuming database "postgres"
vacuumdb: vacuuming database "template1"
vacuumdb: vacuuming database "test_db1"
vacuumdb: vacuuming database "test_db2"
vacuumdb: vacuuming database "test_db3"
---
RESULT: ✅ PASS
  ✅ 'vacuuming database "MixedCaseDB"' correctly absent from output

----------------------------------------------------------------
TEST 6.2: Case sensitivity - wrong case does NOT exclude
----------------------------------------------------------------
Description: Exclude with wrong case 'mixedcasedb'. Expected: MixedCaseDB IS 
vacuumed.
Command: /Users/mmohali/Kiro_projects/vacuumdb/pghome/bin/vacuumdb --all 
--exclude-database=mixedcasedb
---
Exit Code: 0
Output:
vacuumdb: vacuuming database "MixedCaseDB"
vacuumdb: vacuuming database "postgres"
vacuumdb: vacuuming database "template1"
vacuumdb: vacuuming database "test_db1"
vacuumdb: vacuuming database "test_db2"
vacuumdb: vacuuming database "test_db3"
---
RESULT: ✅ PASS (MixedCaseDB was vacuumed — wrong case did not exclude it)

================================================================
TEST 7: --exclude-database without --all (must fail)
================================================================
Description: Use -D without --all. Expected: non-zero exit, error: cannot use 
--exclude-database without --all.
Command: /Users/mmohali/Kiro_projects/vacuumdb/pghome/bin/vacuumdb -D test_db1
Expected: fail
---
Exit Code: 1
Output:
vacuumdb: error: cannot use --exclude-database without --all option
---
RESULT: ✅ PASS (correctly failed)
  ✅ Correct error message: 'cannot use --exclude-database without --all'

================================================================
TEST 8: --exclude-database with -d (must fail)
================================================================
Description: Use -D with -d. Expected: non-zero exit, -D requires --all but -d 
conflicts with --all.
Command: /Users/mmohali/Kiro_projects/vacuumdb/pghome/bin/vacuumdb -d postgres 
-D test_db1
Expected: fail
---
Exit Code: 1
Output:
vacuumdb: error: cannot use --exclude-database without --all option
---
RESULT: ✅ PASS (correctly failed)

================================================================
TEST 9: SQL injection attempt in database name
================================================================
Description: Inject SQL via -D value. Expected: exit 0, safely escaped, no 
injection, all dbs vacuumed.
Command: /Users/mmohali/Kiro_projects/vacuumdb/pghome/bin/vacuumdb --all 
--exclude-database="'; DROP TABLE pg_class; --"
Expected: pass
---
Exit Code: 0
Output:
vacuumdb: vacuuming database "MixedCaseDB"
vacuumdb: vacuuming database "postgres"
vacuumdb: vacuuming database "template1"
vacuumdb: vacuuming database "test_db1"
vacuumdb: vacuuming database "test_db2"
vacuumdb: vacuuming database "test_db3"
---
RESULT: ✅ PASS

================================================================
TEST 10: --help shows --exclude-database
================================================================
Description: Run vacuumdb --help and check output.
Expected: Output contains '-D, --exclude-database=DBNAME'
---
Output:
vacuumdb cleans and analyzes a PostgreSQL database.

Usage:
  vacuumdb [OPTION]... [DBNAME]

Options:
  -a, --all                       vacuum all databases
      --buffer-usage-limit=SIZE   size of ring buffer used for vacuum
  -d, --dbname=DBNAME             database to vacuum
  -D, --exclude-database=DBNAME   exclude database from --all operation
      --disable-page-skipping     disable all page-skipping behavior
      --dry-run                   show the commands that would be sent to the 
server
  -e, --echo                      show the commands being sent to the server
  -f, --full                      do full vacuuming
  -F, --freeze                    freeze row transaction information
      --force-index-cleanup       always remove index entries that point to 
dead tuples
  -j, --jobs=NUM                  use this many concurrent connections to vacuum
      --min-mxid-age=MXID_AGE     minimum multixact ID age of tables to vacuum
      --min-xid-age=XID_AGE       minimum transaction ID age of tables to vacuum
      --missing-stats-only        only analyze relations with missing statistics
      --no-index-cleanup          don't remove index entries that point to dead 
tuples
      --no-process-main           skip the main relation
      --no-process-toast          skip the TOAST table associated with the 
table to vacuum
      --no-truncate               don't truncate empty pages at the end of the 
table
  -n, --schema=SCHEMA             vacuum tables in the specified schema(s) only
  -N, --exclude-schema=SCHEMA     do not vacuum tables in the specified 
schema(s)
  -P, --parallel=PARALLEL_WORKERS use this many background workers for vacuum, 
if available
  -q, --quiet                     don't write any messages
      --skip-locked               skip relations that cannot be immediately 
locked
  -t, --table='TABLE[(COLUMNS)]'  vacuum specific table(s) only
  -v, --verbose                   write a lot of output
  -V, --version                   output version information, then exit
  -z, --analyze                   update optimizer statistics
  -Z, --analyze-only              only update optimizer statistics; no vacuum
      --analyze-in-stages         only update optimizer statistics, in multiple
                                  stages for faster results; no vacuum
  -?, --help                      show this help, then exit

Connection options:
  -h, --host=HOSTNAME       database server host or socket directory
  -p, --port=PORT           database server port
  -U, --username=USERNAME   user name to connect as
  -w, --no-password         never prompt for password
  -W, --password            force password prompt
  --maintenance-db=DBNAME   alternate maintenance database

Read the description of the SQL command VACUUM for details.

Report bugs to <[email protected]>.
PostgreSQL home page: <https://www.postgresql.org/>
---
RESULT: ✅ PASS

================================================================
TEST 11: -d with -D and --all (must fail)
================================================================
Description: Use -d postgres -D test_db1 --all. Expected: non-zero exit, -d and 
--all conflict.
Command: /Users/mmohali/Kiro_projects/vacuumdb/pghome/bin/vacuumdb -d postgres 
-D test_db1 --all
Expected: fail
---
Exit Code: 1
Output:
vacuumdb: error: cannot vacuum all databases and a specific one at the same time
---
RESULT: ✅ PASS (correctly failed)

============================================================
TEST SUMMARY
============================================================
Total checks: 22
Passed:       22
Failed:       0

ALL TESTS PASSED

Results saved to: ./test_exclude_database_results.txt
#!/bin/bash
#
# Test script for vacuumdb --exclude-database feature
# Patch: "Add --exclude-database option to vacuumdb"
#
# Prerequisites:
#   - PostgreSQL built with the --exclude-database patch applied
#   - A running PostgreSQL instance (initdb + pg_ctl start)
#   - vacuumdb and psql in PATH (or specify with PGBIN)
#
# Usage:
#   ./test_exclude_database.sh                     # uses standard PG env vars
#   PGBIN=/path/to/bin ./test_exclude_database.sh  # custom binary path
#
# The script uses standard libpq environment variables (PGPORT, PGUSER,
# PGHOST, etc.) — same as psql, vacuumdb, and other PG tools.
# Set them before running if your instance uses non-default values.
#
# Optional:
#   PGBIN   - Path to PostgreSQL bin directory (default: uses PATH)
#   OUTFILE - Output file path (default: ./test_exclude_database_results.txt)
#
# The script will:
#   1. Create test databases (test_db1, test_db2, test_db3) if they don't exist
#   2. Run 11 tests covering the --exclude-database feature
#   3. Clean up (drops MixedCaseDB if created, keeps test_db1/2/3)
#   4. Write detailed results to OUTFILE
#
# ============================================================
# TEST PLAN — Description & Expected Results
# ============================================================
#
# TEST 1: Single database exclusion
#   Description: Run "vacuumdb --all --exclude-database=test_db1".
#                Verifies the basic functionality of excluding one
#                database from a vacuum --all operation.
#   Expected:    Exit code 0. test_db1 is NOT vacuumed.
#                All other connectable databases ARE vacuumed.
#
# TEST 2: Multiple database exclusions
#   Description: Run "vacuumdb --all -D test_db1 -D test_db2".
#                Verifies that the -D option can be specified multiple
#                times to exclude more than one database.
#   Expected:    Exit code 0. test_db1 and test_db2 are NOT vacuumed.
#                test_db3 and other databases ARE vacuumed.
#
# TEST 3: Exclude ALL databases (edge case)
#   Description: Run "vacuumdb --all -D <every connectable database>".
#                Tests the edge case where every database is excluded,
#                leaving nothing to vacuum.
#   Expected:    Exit code 0. No vacuum operations occur.
#                The command handles this gracefully without crashing.
#
# TEST 4: Exclude non-existent database
#   Description: Run "vacuumdb --all --exclude-database=nonexistent_db".
#                Tests that specifying a database name that doesn't exist
#                in pg_database is silently ignored.
#   Expected:    Exit code 0. All real databases are vacuumed normally.
#
# TEST 5: Exclude maintenance database (postgres)
#   Description: Run "vacuumdb --all --exclude-database=postgres".
#                Tests that the maintenance database itself can be excluded.
#                vacuumdb connects to postgres to retrieve the database list,
#                but should then skip it during the vacuum phase.
#   Expected:    Exit code 0. postgres is NOT vacuumed.
#                Other databases ARE vacuumed.
#
# TEST 6.1: Case sensitivity - exact case excludes
#   Description: Create "MixedCaseDB", then exclude with exact case.
#   Expected:    Exit code 0. MixedCaseDB is NOT vacuumed.
#
# TEST 6.2: Case sensitivity - wrong case does NOT exclude
#   Description: Exclude with wrong case "mixedcasedb".
#   Expected:    Exit code 0. MixedCaseDB IS vacuumed (case mismatch).
#
# TEST 7: --exclude-database without --all (error case)
#   Description: Run "vacuumdb -D test_db1" without --all.
#   Expected:    Non-zero exit code. Error message:
#                "cannot use --exclude-database without --all option"
#
# TEST 8: --exclude-database with -d (error case)
#   Description: Run "vacuumdb -d postgres -D test_db1".
#   Expected:    Non-zero exit code (conflicting options).
#
# TEST 9: SQL injection protection
#   Description: Pass a SQL injection attempt as the database name.
#   Expected:    Exit code 0. Safely escaped, all databases vacuumed.
#
# TEST 10: --help shows the new option
#   Description: Run "vacuumdb --help" and check the output.
#   Expected:    Output contains "-D, --exclude-database=DBNAME".
#
# TEST 11: -d with -D and --all (conflicting options)
#   Description: Run "vacuumdb -d postgres -D test_db1 --all".
#   Expected:    Non-zero exit code (-d and --all conflict).
#
# ============================================================

set -o pipefail

# ============================================================
# Configuration
# ============================================================
OUTFILE="${OUTFILE:-./test_exclude_database_results.txt}"

# Determine binary paths
if [ -n "$PGBIN" ]; then
    VACUUMDB="$PGBIN/vacuumdb"
    PSQL="$PGBIN/psql"
else
    VACUUMDB="$(command -v vacuumdb)"
    PSQL="$(command -v psql)"
fi

# Validate binaries exist
if [ ! -x "$VACUUMDB" ]; then
    echo "ERROR: vacuumdb not found. Set PGBIN or add to PATH."
    echo "  PGBIN=/path/to/pg/bin ./test_exclude_database.sh"
    exit 1
fi
if [ ! -x "$PSQL" ]; then
    echo "ERROR: psql not found. Set PGBIN or add to PATH."
    exit 1
fi

# Verify connection works
if ! $PSQL -d postgres -c "SELECT 1" >/dev/null 2>&1; then
    echo "ERROR: Cannot connect to PostgreSQL."
    echo "  Make sure PostgreSQL is running and standard PG env vars are set."
    echo "  Example: PGPORT=5433 PGUSER=myuser ./test_exclude_database.sh"
    exit 1
fi

# Verify --exclude-database option exists
if ! $VACUUMDB --help 2>&1 | grep -q "exclude-database"; then
    echo "ERROR: vacuumdb does not support --exclude-database."
    echo "  Make sure the patch is applied and vacuumdb is rebuilt."
    exit 1
fi

# ============================================================
# Test framework
# ============================================================
PASS_COUNT=0
FAIL_COUNT=0
TOTAL_COUNT=0
TEST_NUM=0

log() {
    echo "$1" | tee -a "$OUTFILE"
}

run_test() {
    local test_name="$1"
    local expected_result="$2"  # "pass" or "fail"
    local description="$3"
    shift 3
    local cmd="$@"

    TEST_NUM=$((TEST_NUM + 1))
    TOTAL_COUNT=$((TOTAL_COUNT + 1))
    log ""
    log "================================================================"
    log "TEST $TEST_NUM: $test_name"
    log "================================================================"
    log "Description: $description"
    log "Command: $cmd"
    log "Expected: $expected_result"
    log "---"

    LAST_OUTPUT=$(eval "$cmd" 2>&1)
    LAST_EXIT_CODE=$?

    log "Exit Code: $LAST_EXIT_CODE"
    log "Output:"
    log "$LAST_OUTPUT"
    log "---"

    if [ "$expected_result" = "pass" ]; then
        if [ $LAST_EXIT_CODE -eq 0 ]; then
            log "RESULT: ✅ PASS"
            PASS_COUNT=$((PASS_COUNT + 1))
            echo -e "${GREEN}✅ PASS${NC}: $test_name"
        else
            log "RESULT: ❌ FAIL (expected exit 0, got $LAST_EXIT_CODE)"
            FAIL_COUNT=$((FAIL_COUNT + 1))
            echo -e "${RED}❌ FAIL${NC}: $test_name"
        fi
    elif [ "$expected_result" = "fail" ]; then
        if [ $LAST_EXIT_CODE -ne 0 ]; then
            log "RESULT: ✅ PASS (correctly failed)"
            PASS_COUNT=$((PASS_COUNT + 1))
            echo -e "${GREEN}✅ PASS${NC}: $test_name"
        else
            log "RESULT: ❌ FAIL (expected non-zero exit, got 0)"
            FAIL_COUNT=$((FAIL_COUNT + 1))
            echo -e "${RED}❌ FAIL${NC}: $test_name"
        fi
    fi
}

check_output_contains() {
    local test_name="$1"
    local pattern="$2"
    local output="$3"

    TOTAL_COUNT=$((TOTAL_COUNT + 1))
    if echo "$output" | grep -q "$pattern"; then
        log "  ✅ '$pattern' found in output"
        PASS_COUNT=$((PASS_COUNT + 1))
        echo -e "${GREEN}  ✅${NC}: $test_name"
    else
        log "  ❌ '$pattern' NOT found in output"
        FAIL_COUNT=$((FAIL_COUNT + 1))
        echo -e "${RED}  ❌${NC}: $test_name"
    fi
}

check_output_not_contains() {
    local test_name="$1"
    local pattern="$2"
    local output="$3"

    TOTAL_COUNT=$((TOTAL_COUNT + 1))
    if echo "$output" | grep -q "$pattern"; then
        log "  ❌ '$pattern' found in output (should NOT be)"
        FAIL_COUNT=$((FAIL_COUNT + 1))
        echo -e "${RED}  ❌${NC}: $test_name"
    else
        log "  ✅ '$pattern' correctly absent from output"
        PASS_COUNT=$((PASS_COUNT + 1))
        echo -e "${GREEN}  ✅${NC}: $test_name"
    fi
}

# Colors (disabled if not a terminal)
if [ -t 1 ]; then
    RED='\033[0;31m'
    GREEN='\033[0;32m'
    YELLOW='\033[1;33m'
    NC='\033[0m'
else
    RED=''
    GREEN=''
    YELLOW=''
    NC=''
fi

# ============================================================
# SETUP
# ============================================================

> "$OUTFILE"

log "============================================================"
log "VACUUMDB --exclude-database FEATURE TEST SUITE"
log "============================================================"
log "Date: $(date)"
log "PostgreSQL: $($PSQL -d postgres -t -c 'SELECT version();' | head -1 | xargs)"
log "vacuumdb: $($VACUUMDB --version)"
log ""

# Create test databases if they don't exist
for db in test_db1 test_db2 test_db3; do
    $PSQL -d postgres -t -c \
        "SELECT 1 FROM pg_database WHERE datname='$db'" | grep -q 1 || \
        $PSQL -d postgres -c "CREATE DATABASE $db;" 2>/dev/null
done

log "Available databases:"
$PSQL -d postgres -t -c \
    "SELECT datname FROM pg_database WHERE datallowconn AND datconnlimit <> -2 ORDER BY 1;" | tee -a "$OUTFILE"
log ""

# ============================================================
# TEST 1: Single database exclusion
# ============================================================
run_test "Single database exclusion" "pass" \
    "Exclude test_db1 from vacuum --all. Expected: exit 0, test_db1 skipped, other dbs vacuumed." \
    "$VACUUMDB --all --exclude-database=test_db1"

check_output_not_contains "1.1: test_db1 NOT vacuumed" 'vacuuming database "test_db1"' "$LAST_OUTPUT"

# Verify all other databases were vacuumed
TOTAL_COUNT=$((TOTAL_COUNT + 1))
VACUUMED_COUNT=$(echo "$LAST_OUTPUT" | grep -c 'vacuuming database' || true)
TOTAL_DBS=$($PSQL -d postgres -t -c \
    "SELECT count(*) FROM pg_database WHERE datallowconn AND datconnlimit <> -2;" | tr -d ' ')
EXPECTED_COUNT=$((TOTAL_DBS - 1))
if [ "$VACUUMED_COUNT" -eq "$EXPECTED_COUNT" ]; then
    log "  ✅ All other databases vacuumed ($VACUUMED_COUNT/$EXPECTED_COUNT)"
    PASS_COUNT=$((PASS_COUNT + 1))
    echo -e "${GREEN}  ✅${NC}: 1.2: All other databases vacuumed ($VACUUMED_COUNT/$EXPECTED_COUNT)"
else
    log "  ❌ Expected $EXPECTED_COUNT databases vacuumed, got $VACUUMED_COUNT"
    FAIL_COUNT=$((FAIL_COUNT + 1))
    echo -e "${RED}  ❌${NC}: 1.2: Expected $EXPECTED_COUNT databases vacuumed, got $VACUUMED_COUNT"
fi

# ============================================================
# TEST 2: Multiple database exclusions
# ============================================================
run_test "Multiple database exclusions (-D db1 -D db2)" "pass" \
    "Exclude test_db1 and test_db2. Expected: exit 0, both excluded, test_db3 still vacuumed." \
    "$VACUUMDB --all -D test_db1 -D test_db2"

check_output_not_contains "2.1: test_db1 excluded" 'vacuuming database "test_db1"' "$LAST_OUTPUT"
check_output_not_contains "2.2: test_db2 excluded" 'vacuuming database "test_db2"' "$LAST_OUTPUT"
check_output_contains "2.3: test_db3 still vacuumed" 'vacuuming database "test_db3"' "$LAST_OUTPUT"

# ============================================================
# TEST 3: Exclude all databases (edge case)
# ============================================================
ALL_DBS=$($PSQL -d postgres -t -c \
    "SELECT datname FROM pg_database WHERE datallowconn AND datconnlimit <> -2;" | tr -d ' ' | grep -v '^$')
EXCLUDE_ARGS=""
for db in $ALL_DBS; do
    EXCLUDE_ARGS="$EXCLUDE_ARGS -D $db"
done

run_test "Exclude ALL databases (edge case)" "pass" \
    "Exclude every connectable database. Expected: exit 0, no vacuum ops, graceful no-op." \
    "$VACUUMDB --all $EXCLUDE_ARGS"

# ============================================================
# TEST 4: Exclude non-existent database
# ============================================================
run_test "Exclude non-existent database" "pass" \
    "Exclude a name that doesn't exist. Expected: exit 0, silently ignored, all real dbs vacuumed." \
    "$VACUUMDB --all --exclude-database=this_db_does_not_exist_xyz"

check_output_contains "4.1: postgres still vacuumed" 'vacuuming database "postgres"' "$LAST_OUTPUT"

# ============================================================
# TEST 5: Exclude maintenance database (postgres)
# ============================================================
run_test "Exclude maintenance database (postgres)" "pass" \
    "Exclude postgres (used for listing). Expected: exit 0, postgres skipped, others vacuumed." \
    "$VACUUMDB --all --exclude-database=postgres"

check_output_not_contains "5.1: postgres NOT vacuumed" 'vacuuming database "postgres"' "$LAST_OUTPUT"

# Verify all other databases were vacuumed
TOTAL_COUNT=$((TOTAL_COUNT + 1))
VACUUMED_COUNT=$(echo "$LAST_OUTPUT" | grep -c 'vacuuming database' || true)
EXPECTED_COUNT=$((TOTAL_DBS - 1))
if [ "$VACUUMED_COUNT" -eq "$EXPECTED_COUNT" ]; then
    log "  ✅ All other databases vacuumed ($VACUUMED_COUNT/$EXPECTED_COUNT)"
    PASS_COUNT=$((PASS_COUNT + 1))
    echo -e "${GREEN}  ✅${NC}: 5.2: All other databases vacuumed ($VACUUMED_COUNT/$EXPECTED_COUNT)"
else
    log "  ❌ Expected $EXPECTED_COUNT databases vacuumed, got $VACUUMED_COUNT"
    FAIL_COUNT=$((FAIL_COUNT + 1))
    echo -e "${RED}  ❌${NC}: 5.2: Expected $EXPECTED_COUNT databases vacuumed, got $VACUUMED_COUNT"
fi

# ============================================================
# TEST 6: Case sensitivity
# ============================================================
$PSQL -d postgres -c 'CREATE DATABASE "MixedCaseDB";' 2>/dev/null

# --- TEST 6.1: Exact case should exclude ---
run_test "Case sensitivity - exact case excludes MixedCaseDB" "pass" \
    "TEST 6.1: Exclude MixedCaseDB with exact case. Expected: exit 0, MixedCaseDB NOT vacuumed." \
    "$VACUUMDB --all --exclude-database=MixedCaseDB"

check_output_not_contains "6.1: MixedCaseDB excluded (exact case)" 'vacuuming database "MixedCaseDB"' "$LAST_OUTPUT"

# --- TEST 6.2: Wrong case should NOT exclude ---
log ""
log "----------------------------------------------------------------"
log "TEST 6.2: Case sensitivity - wrong case does NOT exclude"
log "----------------------------------------------------------------"
log "Description: Exclude with wrong case 'mixedcasedb'. Expected: MixedCaseDB IS vacuumed."
log "Command: $VACUUMDB --all --exclude-database=mixedcasedb"
log "---"

OUTPUT=$($VACUUMDB --all --exclude-database=mixedcasedb 2>&1)
log "Exit Code: $?"
log "Output:"
log "$OUTPUT"
log "---"

TOTAL_COUNT=$((TOTAL_COUNT + 1))
if echo "$OUTPUT" | grep -q 'vacuuming database "MixedCaseDB"'; then
    log "RESULT: ✅ PASS (MixedCaseDB was vacuumed — wrong case did not exclude it)"
    PASS_COUNT=$((PASS_COUNT + 1))
    echo -e "${GREEN}✅ PASS${NC}: 6.2: MixedCaseDB IS vacuumed (wrong case doesn't match)"
else
    log "RESULT: ❌ FAIL (MixedCaseDB was excluded by wrong case — should not happen)"
    FAIL_COUNT=$((FAIL_COUNT + 1))
    echo -e "${RED}❌ FAIL${NC}: 6.2: MixedCaseDB was incorrectly excluded by wrong case"
fi

# ============================================================
# TEST 7: --exclude-database without --all (error case)
# ============================================================
run_test "--exclude-database without --all (must fail)" "fail" \
    "Use -D without --all. Expected: non-zero exit, error: cannot use --exclude-database without --all." \
    "$VACUUMDB -D test_db1"

# Verify error message
TOTAL_COUNT=$((TOTAL_COUNT + 1))
if echo "$LAST_OUTPUT" | grep -q "cannot use --exclude-database without --all"; then
    log "  ✅ Correct error message: 'cannot use --exclude-database without --all'"
    PASS_COUNT=$((PASS_COUNT + 1))
    echo -e "${GREEN}  ✅${NC}: 7.1: Correct error message"
else
    log "  ❌ Error message missing or wrong"
    log "  Got: $LAST_OUTPUT"
    FAIL_COUNT=$((FAIL_COUNT + 1))
    echo -e "${RED}  ❌${NC}: 7.1: Error message missing or wrong"
fi

# ============================================================
# TEST 8: --exclude-database with -d (error case)
# ============================================================
run_test "--exclude-database with -d (must fail)" "fail" \
    "Use -D with -d. Expected: non-zero exit, -D requires --all but -d conflicts with --all." \
    "$VACUUMDB -d postgres -D test_db1"

# ============================================================
# TEST 9: SQL injection protection
# ============================================================
run_test "SQL injection attempt in database name" "pass" \
    "Inject SQL via -D value. Expected: exit 0, safely escaped, no injection, all dbs vacuumed." \
    "$VACUUMDB --all --exclude-database=\"'; DROP TABLE pg_class; --\""

# ============================================================
# TEST 10: --help shows the new option
# ============================================================
OUTPUT=$($VACUUMDB --help 2>&1)
TEST_NUM=$((TEST_NUM + 1))
TOTAL_COUNT=$((TOTAL_COUNT + 1))
log ""
log "================================================================"
log "TEST $TEST_NUM: --help shows --exclude-database"
log "================================================================"
log "Description: Run vacuumdb --help and check output."
log "Expected: Output contains '-D, --exclude-database=DBNAME'"
log "---"
log "Output:"
log "$OUTPUT"
log "---"
if echo "$OUTPUT" | grep -q "\-D.*--exclude-database"; then
    log "RESULT: ✅ PASS"
    PASS_COUNT=$((PASS_COUNT + 1))
    echo -e "${GREEN}✅ PASS${NC}: --help shows --exclude-database"
else
    log "RESULT: ❌ FAIL"
    FAIL_COUNT=$((FAIL_COUNT + 1))
    echo -e "${RED}❌ FAIL${NC}: --help shows --exclude-database"
fi

# ============================================================
# TEST 11: -d with -D and --all (conflicting options)
# ============================================================
run_test "-d with -D and --all (must fail)" "fail" \
    "Use -d postgres -D test_db1 --all. Expected: non-zero exit, -d and --all conflict." \
    "$VACUUMDB -d postgres -D test_db1 --all"

# ============================================================
# CLEANUP
# ============================================================
$PSQL -d postgres -c 'DROP DATABASE IF EXISTS "MixedCaseDB";' 2>/dev/null

# ============================================================
# SUMMARY
# ============================================================
log ""
log "============================================================"
log "TEST SUMMARY"
log "============================================================"
log "Total checks: $TOTAL_COUNT"
log "Passed:       $PASS_COUNT"
log "Failed:       $FAIL_COUNT"
log ""

if [ $FAIL_COUNT -eq 0 ]; then
    log "ALL TESTS PASSED"
    echo -e "\n${GREEN}ALL TESTS PASSED ($PASS_COUNT/$TOTAL_COUNT)${NC}"
else
    log "SOME TESTS FAILED"
    echo -e "\n${YELLOW}$FAIL_COUNT TESTS FAILED ($PASS_COUNT/$TOTAL_COUNT passed)${NC}"
fi

log ""
log "Results saved to: $OUTFILE"
echo ""
echo "Full results saved to: $OUTFILE"

Attachment: v1-0001-vacuumdb-Add-exclude-database-option-to-skip-data.patch
Description: Binary data

Reply via email to