Skip to content

Feat/rep 1379 - additional PG-18 container with switch command to go switch between postgresql 14 and 18 (bidirectional switch)#6

Open
buildwithhp wants to merge 2 commits into
masterfrom
feat/REP-1379
Open

Feat/rep 1379 - additional PG-18 container with switch command to go switch between postgresql 14 and 18 (bidirectional switch)#6
buildwithhp wants to merge 2 commits into
masterfrom
feat/REP-1379

Conversation

@buildwithhp

@buildwithhp buildwithhp commented Mar 31, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

Release Notes

  • Documentation

    • Added PostgreSQL 14 → 18 compatibility and upgrade guide with pre/during/post migration checklist
    • Added DDEV workflow documentation for managing PostgreSQL version switching
  • New Features

    • Added PostgreSQL 18 support with logical replication capabilities
    • Added database backup, import, and validation tools for PostgreSQL version upgrades and migrations

@buildwithhp buildwithhp changed the title Feat/rep 1379 - additional PG-18 container Feat/rep 1379 - additional PG-18 container with switch command to go switch between postgresql 14 and 18 (bidirectional switch) Mar 31, 2026
@coderabbitai

coderabbitai Bot commented Mar 31, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Infrastructure and documentation for PostgreSQL 18 parallel deployment and migration are added, including Docker compose setup, shell utilities for backup/restore/validation, configuration switching scripts, and database initialization, along with a comprehensive migration guide and DDEV workflow documentation.

Changes

Cohort / File(s) Summary
Documentation & Guides
POSTGRESQL_18_CHANGES.md, commands/PG-SWITCH-README.md
Structured compatibility checklist for PG14→18 migration with version history, change categories, actionable upgrade items (reindexing gin_trgm_ops, GENERATED ALWAYS AS IDENTITY verification, VACUUM/ANALYZE behavior), and DDEV workflow guide for switching between parallel PostgreSQL instances.
Migration & Validation Scripts
commands/host/backup-pg14, commands/host/import-db18, commands/host/validate-pg18-migration, commands/host/switch-pg-version
New shell utilities for PostgreSQL version management: backup-pg14 creates timestamped PG14 dumps; import-db18 restores SQL/gzip dumps into PG18; validate-pg18-migration compares PG14/PG18 instances for schema consistency and reports reindexing needs; switch-pg-version swaps versioned config files between PG14 and PG18.
Docker & Infrastructure
docker-compose.postgres18.yaml, pg18-pglogical-build/Dockerfile
Docker Compose sidecar for PG18 (port 5433) with pglogical and extensions; custom Dockerfile extends postgis/postgis:18-3.6 to build and install pglogical, pg_background, pg_cron, and pgl_ddl_deploy extensions.
Database Initialization
postgres18-init/01-init-roles.sql
SQL initialization script for PG18 that conditionally creates fdsu role, sets database ownership, grants privileges, and creates an ICU-based en_natural collation for numeric-aware sorting.
Configuration
config.yaml
Xdebug enabled (xdebug_enabled: true).

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Config as Config Manager
    participant PG14 as PostgreSQL 14
    participant Backup as backup-pg14
    participant PG18 as PostgreSQL 18
    participant Import as import-db18
    participant Validate as validate-pg18-migration
    participant Switch as switch-pg-version
    participant Cache as Cache System

    User->>Backup: Request backup
    Backup->>PG14: Query DB size
    Backup->>Backup: Generate timestamped dump
    Backup->>User: Return dump path

    User->>Import: Import dump to PG18
    Import->>Import: Detect compression (.gz)
    Import->>PG18: Stream SQL or decompress & stream
    PG18->>User: Confirm import

    User->>Validate: Validate migration
    Validate->>PG14: Fetch version, schemas, tables, extensions
    Validate->>PG18: Fetch version, schemas, tables, extensions
    Validate->>Validate: Compare counts & check gin_trgm_ops indexes
    Validate->>User: Report differences & reindex needs

    User->>Switch: Switch to PostgreSQL 18
    Switch->>Config: Verify versioned config files exist
    Switch->>Config: Backup current active config (if unversioned)
    Switch->>Config: Copy pg18 config to active location
    Switch->>User: Confirm config switched

    User->>Cache: Clear application cache
    Cache->>User: Cache cleared

    User->>User: Restart DDEV & verify
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 PostgreSQL hops from fourteen to eighteen so fine,
With backups and switches, migrations divine!
pglogical extensions in Docker we brew,
A sidecar on five-four-three-three, shiny and new!
Validate, restore, and switch without care,
The database roadmap is now in the air! 🐇✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main objective of the changeset: adding a PostgreSQL 18 container with bidirectional switching capability between PostgreSQL 14 and 18.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/REP-1379

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (1)
pg18-pglogical-build/Dockerfile (1)

39-40: Pin source repos to immutable commits/tags for reproducible builds.

pg_background and pgl_ddl_deploy are currently floating refs, so builds can drift over time unexpectedly.

Also applies to: 47-47, 56-56

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pg18-pglogical-build/Dockerfile` around lines 39 - 40, The Dockerfile clones
external repos with floating refs; update the RUN steps that clone
https://github.com/vibhorkum/pg_background into /tmp/pg_background and the
pgl_ddl_deploy clone(s) to pin each git clone to an immutable tag or commit hash
(e.g., use git clone ... && cd /tmp/pg_background && git checkout
<COMMIT_OR_TAG>) so builds are reproducible; ensure you replace the floating
clone commands for both pg_background and pgl_ddl_deploy with clones that
checkout a specific commit or tag and document the chosen commit/tag in the
Dockerfile comments.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@commands/host/backup-pg14`:
- Around line 23-31: The pipeline currently assigns EXIT_CODE=$? which captures
tee's exit status, not pg_dump's; after running the pg_dump ... 2>&1 | tee -a
"$LOG_FILE" pipeline, set EXIT_CODE from the bash PIPESTATUS array (e.g.
EXIT_CODE=${PIPESTATUS[0]}) so you record pg_dump's exit code (ensure the script
runs under bash where PIPESTATUS exists); reference the pg_dump invocation and
the variables OUTPUT_FILE, LOG_FILE and EXIT_CODE when making this change.

In `@commands/host/import-db18`:
- Around line 22-33: The script can report success despite decompression or SQL
errors because piped commands don't propagate failures and psql isn't set to
stop on SQL errors; enable bash's pipefail (set -o pipefail) and export or pass
ON_ERROR_STOP=1 to psql invocations so any SQL error causes non-zero exit, and
replace the current "$?" check with a direct test of the full pipeline/command
exit status (e.g. check "$?" immediately after the command or use if ...; then)
for both the gunzip | psql branch and the direct psql branch to ensure failures
in gunzip or psql are detected; update references to FILE, gunzip, and psql
invocation to include ON_ERROR_STOP=1 and rely on pipefail for the piped case.

In `@commands/host/switch-pg-version`:
- Around line 43-57: The replacement flow currently deletes $active_file before
confirming the new config was staged, risking a missing active config if cp
fails; instead, stage the selected source ($pg18_file or $pg14_file based on
$VERSION) into a temporary file (e.g., using mktemp), verify the copy succeeded,
then atomically move (mv) the temp into place to replace $active_file and remove
the old symlink/file only as part of the atomic rename; ensure the backup step
for $active_file still runs before staging, and update the logic around [ -f
"$active_file" ] / [ -L "$active_file" ] so they are not removed until after the
temp-to-active mv succeeds.
- Around line 65-77: The success summary is printed unconditionally even if some
switch_config calls fail; update the block that calls switch_config "db_write",
"db_read", "db_read_report", and "common" to check each call's return code,
accumulate a failure flag (or count), and only print the "✓ Successfully
switched to PostgreSQL $VERSION" and the success list when all calls succeed; if
any fail, print a clear error/partial-failure summary and exit with a non-zero
status (e.g., exit 1) instead of the current unconditional echo block.

In `@commands/host/validate-pg18-migration`:
- Around line 11-26: The connection checks reuse $? incorrectly causing the
PG14/PG18 results to be misreported; fix by capturing and testing each command's
exit status immediately after its psql invocation (either run each psql in its
own if statement, e.g. if PGPASSWORD=... psql ...; then echo success/exit on
failure, or store the exit code into distinct variables like PG14_EXIT=$? and
PG18_EXIT=$? right after assigning PG14_CONN and PG18_CONN) and use those
specific statuses when printing the "PostgreSQL 14/18: Connected/Connection
failed" messages; reference the PG14_CONN and PG18_CONN assignments and the two
if blocks to locate where to check $? or store PG14_EXIT/PG18_EXIT so each DB is
validated independently.

In `@config.yaml`:
- Line 6: The config change permanently enabling Xdebug (xdebug_enabled) is
unrelated and can hurt developer performance; revert it by restoring
xdebug_enabled to false or removing the override in config.yaml so developers
use ddev xdebug / ddev xdebug off as intended, or if this was intentional update
the same file to include a clear comment explaining why Xdebug must be enabled
globally and confirm the decision in the PR; locate the xdebug_enabled entry in
config.yaml to apply the change.

In `@postgres18-init/01-init-roles.sql`:
- Around line 37-40: The CREATE COLLATION statement for app.en_natural can fail
if the app schema doesn’t exist; before creating the collation (CREATE COLLATION
app.en_natural ...) add an idempotent step to ensure the schema exists (for
example run CREATE SCHEMA IF NOT EXISTS app) so the collation creation won’t
error on first-run; update the migration around the CREATE COLLATION
app.en_natural to create the app schema first and keep the existing IF NOT
EXISTS/existence checks to remain idempotent.

In `@POSTGRESQL_18_CHANGES.md`:
- Around line 62-72: The query is selecting non-existent columns schemaname and
tablename from pg_namespace/pg_class; update the SELECT and any WHERE/filters to
use n.nspname and c.relname (or alias them, e.g., n.nspname AS schemaname and
c.relname AS tablename) so the query in the block referencing pg_attribute a,
pg_class c and pg_namespace n returns correct schema and table names; keep the
attidentity = 'a' filter and the schemaname IN ('app','rrule') condition but
apply it to n.nspname (or your chosen alias) instead of schemaname.
- Around line 218-222: The query incorrectly calls pg_get_expr(c.reloptions,
c.oid) — reloptions in pg_class is a text[] of keyword=value strings, not a
pg_node_tree, so pg_get_expr will fail; replace that expression by either
selecting c.reloptions directly (or unnesting it) to read options, and if you
need inheritance info join pg_inherits (using inhparent/inhrelid) to retrieve
parent/child relationships; update references to pg_get_expr -> reloptions (or
unnest(reloptions)) and add a join to pg_inherits when querying inheritance.

---

Nitpick comments:
In `@pg18-pglogical-build/Dockerfile`:
- Around line 39-40: The Dockerfile clones external repos with floating refs;
update the RUN steps that clone https://github.com/vibhorkum/pg_background into
/tmp/pg_background and the pgl_ddl_deploy clone(s) to pin each git clone to an
immutable tag or commit hash (e.g., use git clone ... && cd /tmp/pg_background
&& git checkout <COMMIT_OR_TAG>) so builds are reproducible; ensure you replace
the floating clone commands for both pg_background and pgl_ddl_deploy with
clones that checkout a specific commit or tag and document the chosen commit/tag
in the Dockerfile comments.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b7649b38-4966-4703-86a1-43cf33b9679e

📥 Commits

Reviewing files that changed from the base of the PR and between 9983011 and 91a52c8.

📒 Files selected for processing (10)
  • POSTGRESQL_18_CHANGES.md
  • commands/PG-SWITCH-README.md
  • commands/host/backup-pg14
  • commands/host/import-db18
  • commands/host/switch-pg-version
  • commands/host/validate-pg18-migration
  • config.yaml
  • docker-compose.postgres18.yaml
  • pg18-pglogical-build/Dockerfile
  • postgres18-init/01-init-roles.sql

Comment thread commands/host/backup-pg14
Comment on lines +23 to +31
pg_dump -h localhost -p 5432 -U db -d db \
--format=custom \
--verbose \
--no-owner \
--no-acl \
--file="$OUTPUT_FILE" \
2>&1 | tee -a "$LOG_FILE"

EXIT_CODE=$?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Quick static check for pipeline exit-code handling in this script.
rg -n -C2 'pg_dump|PIPESTATUS|set -o pipefail|EXIT_CODE=\$\?' commands/host/backup-pg14

Repository: sandeepfd/fdsu_ddev

Length of output: 268


🏁 Script executed:

cat -n commands/host/backup-pg14

Repository: sandeepfd/fdsu_ddev

Length of output: 1754


Fix pipeline exit-code capture for pg_dump command.

The current code captures tee's exit code (via EXIT_CODE=$?), not pg_dump's. A failed backup can be silently misreported as successful while the script exits with code 0.

Proposed fix
 #!/bin/bash
+set -o pipefail
@@
 pg_dump -h localhost -p 5432 -U db -d db \
@@
     --file="$OUTPUT_FILE" \
     2>&1 | tee -a "$LOG_FILE"
 
-EXIT_CODE=$?
+EXIT_CODE=${PIPESTATUS[0]}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@commands/host/backup-pg14` around lines 23 - 31, The pipeline currently
assigns EXIT_CODE=$? which captures tee's exit status, not pg_dump's; after
running the pg_dump ... 2>&1 | tee -a "$LOG_FILE" pipeline, set EXIT_CODE from
the bash PIPESTATUS array (e.g. EXIT_CODE=${PIPESTATUS[0]}) so you record
pg_dump's exit code (ensure the script runs under bash where PIPESTATUS exists);
reference the pg_dump invocation and the variables OUTPUT_FILE, LOG_FILE and
EXIT_CODE when making this change.

Comment thread commands/host/import-db18
Comment on lines +22 to +33
if [[ "$FILE" == *.gz ]]; then
gunzip -c "$FILE" | PGPASSWORD=db psql -h 127.0.0.1 -p 5433 -U db -d db
else
PGPASSWORD=db psql -h 127.0.0.1 -p 5433 -U db -d db < "$FILE"
fi

if [ $? -eq 0 ]; then
echo "✓ Successfully imported into PostgreSQL 18"
else
echo "✗ Import failed"
exit 1
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Static verification of import error-handling flags/status capture.
rg -n -C2 'ON_ERROR_STOP|pipefail|PIPESTATUS|if \[ \$\? -eq 0 \]' commands/host/import-db18

Repository: sandeepfd/fdsu_ddev

Length of output: 162


🏁 Script executed:

cat -n commands/host/import-db18

Repository: sandeepfd/fdsu_ddev

Length of output: 1034


Import can report success on partial/failed SQL execution.

The piped gunzip command lacks pipefail error propagation, and both psql invocations lack ON_ERROR_STOP=1, allowing SQL errors and decompression failures to be silently ignored. Line 28 uses $? which only captures the exit code of psql in the pipe, not gunzip.

Proposed fix
 #!/bin/bash
+set -o pipefail
 
 ## Description: Import database dump into PostgreSQL 18
 ## Usage: import-db18 [file]
@@ -20,14 +21,18 @@
 echo "Importing $FILE into PostgreSQL 18..."
 
 if [[ "$FILE" == *.gz ]]; then
-    gunzip -c "$FILE" | PGPASSWORD=db psql -h 127.0.0.1 -p 5433 -U db -d db
+    gunzip -c "$FILE" | PGPASSWORD=db psql -v ON_ERROR_STOP=1 -h 127.0.0.1 -p 5433 -U db -d db
+    IMPORT_EXIT=${PIPESTATUS[1]}
 else
-    PGPASSWORD=db psql -h 127.0.0.1 -p 5433 -U db -d db < "$FILE"
+    PGPASSWORD=db psql -v ON_ERROR_STOP=1 -h 127.0.0.1 -p 5433 -U db -d db < "$FILE"
+    IMPORT_EXIT=$?
 fi
 
-if [ $? -eq 0 ]; then
+if [ $IMPORT_EXIT -eq 0 ]; then
     echo "✓ Successfully imported into PostgreSQL 18"
 else
     echo "✗ Import failed"
     exit 1
 fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@commands/host/import-db18` around lines 22 - 33, The script can report
success despite decompression or SQL errors because piped commands don't
propagate failures and psql isn't set to stop on SQL errors; enable bash's
pipefail (set -o pipefail) and export or pass ON_ERROR_STOP=1 to psql
invocations so any SQL error causes non-zero exit, and replace the current "$?"
check with a direct test of the full pipeline/command exit status (e.g. check
"$?" immediately after the command or use if ...; then) for both the gunzip |
psql branch and the direct psql branch to ensure failures in gunzip or psql are
detected; update references to FILE, gunzip, and psql invocation to include
ON_ERROR_STOP=1 and rely on pipefail for the piped case.

Comment on lines +43 to +57
mv "$active_file" "${active_file}.backup.$(date +%Y%m%d_%H%M%S)"
echo " Backed up existing $base_name-local.php"
fi
fi

# Remove existing active file (if it's a symlink or regular file)
[ -f "$active_file" ] && rm -f "$active_file"
[ -L "$active_file" ] && rm -f "$active_file"

# Copy the appropriate versioned file to the active file
if [ "$VERSION" == "18" ]; then
cp "$pg18_file" "$active_file"
else
cp "$pg14_file" "$active_file"
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Non-atomic replacement can leave configs missing on copy failure.

On Line 43 and Lines 49-57, the current flow removes the active file before confirming the new file is safely staged. If cp fails, the app can be left without an active config.

Suggested safer replacement flow
-            mv "$active_file" "${active_file}.backup.$(date +%Y%m%d_%H%M%S)"
+            cp "$active_file" "${active_file}.backup.$(date +%Y%m%d_%H%M%S)"
             echo "  Backed up existing $base_name-local.php"
         fi
     fi
-    
-    # Remove existing active file (if it's a symlink or regular file)
-    [ -f "$active_file" ] && rm -f "$active_file"
-    [ -L "$active_file" ] && rm -f "$active_file"
-    
-    # Copy the appropriate versioned file to the active file
-    if [ "$VERSION" == "18" ]; then
-        cp "$pg18_file" "$active_file"
-    else
-        cp "$pg14_file" "$active_file"
-    fi
+
+    local source_file tmp_file
+    if [ "$VERSION" == "18" ]; then
+        source_file="$pg18_file"
+    else
+        source_file="$pg14_file"
+    fi
+    tmp_file="${active_file}.tmp.$$"
+
+    if ! cp "$source_file" "$tmp_file"; then
+        echo "✗ Failed to stage $source_file"
+        rm -f "$tmp_file"
+        return 1
+    fi
+    if ! mv -f "$tmp_file" "$active_file"; then
+        echo "✗ Failed to activate $active_file"
+        rm -f "$tmp_file"
+        return 1
+    fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@commands/host/switch-pg-version` around lines 43 - 57, The replacement flow
currently deletes $active_file before confirming the new config was staged,
risking a missing active config if cp fails; instead, stage the selected source
($pg18_file or $pg14_file based on $VERSION) into a temporary file (e.g., using
mktemp), verify the copy succeeded, then atomically move (mv) the temp into
place to replace $active_file and remove the old symlink/file only as part of
the atomic rename; ensure the backup step for $active_file still runs before
staging, and update the logic around [ -f "$active_file" ] / [ -L "$active_file"
] so they are not removed until after the temp-to-active mv succeeds.

Comment on lines +65 to +77
switch_config "db_write"
switch_config "db_read"
switch_config "db_read_report"
switch_config "common"

echo ""
echo "✓ Successfully switched to PostgreSQL $VERSION"
echo ""
echo "Current active configuration:"
echo " - db_write-local.php → PostgreSQL $VERSION"
echo " - db_read-local.php → PostgreSQL $VERSION"
echo " - db_read_report-local.php → PostgreSQL $VERSION"
echo " - common-local.php → PostgreSQL $VERSION"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Final success summary is printed even when one or more switches fail.

switch_config return codes are ignored on Lines 65-68, then Line 71 reports success unconditionally. This can mislead operators during partial failures.

Track failures and exit non-zero on partial/total failure
-# Switch all config files
-echo ""
-switch_config "db_write"
-switch_config "db_read"
-switch_config "db_read_report"
-switch_config "common"
+echo ""
+failures=0
+for base in db_write db_read db_read_report common; do
+    if ! switch_config "$base"; then
+        failures=$((failures + 1))
+    fi
+done
+
+if [ "$failures" -gt 0 ]; then
+    echo ""
+    echo "✗ Switch completed with $failures failure(s)"
+    exit 1
+fi
 
 echo ""
 echo "✓ Successfully switched to PostgreSQL $VERSION"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@commands/host/switch-pg-version` around lines 65 - 77, The success summary is
printed unconditionally even if some switch_config calls fail; update the block
that calls switch_config "db_write", "db_read", "db_read_report", and "common"
to check each call's return code, accumulate a failure flag (or count), and only
print the "✓ Successfully switched to PostgreSQL $VERSION" and the success list
when all calls succeed; if any fail, print a clear error/partial-failure summary
and exit with a non-zero status (e.g., exit 1) instead of the current
unconditional echo block.

Comment on lines +11 to +26
PG14_CONN=$(PGPASSWORD=db psql -h localhost -p 5432 -U db -d db -t -c "SELECT 1;" 2>&1)
PG18_CONN=$(PGPASSWORD=db psql -h localhost -p 5433 -U db -d db -t -c "SELECT 1;" 2>&1)

if [ $? -eq 0 ]; then
echo " ✓ PostgreSQL 14: Connected"
else
echo " ✗ PostgreSQL 14: Connection failed"
exit 1
fi

if [ $? -eq 0 ]; then
echo " ✓ PostgreSQL 18: Connected"
else
echo " ✗ PostgreSQL 18: Connection failed"
exit 1
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Static verification of fragile `$?` usage in connection checks.
rg -n -C2 '\$\?|PG14_CONN|PG18_CONN|SELECT 1' commands/host/validate-pg18-migration

Repository: sandeepfd/fdsu_ddev

Length of output: 473


Connection validation logic is broken due to $? reuse — PostgreSQL 14 is never tested and PostgreSQL 18 status is ignored.

After assigning both variables on lines 11–12, the first if at line 14 checks the exit status of the second assignment (PG18), not the first (PG14). The second if at line 21 then checks the exit status of the first if statement itself, not the PG18 assignment. This means the script reports PG18 connectivity status as "PostgreSQL 14" and never validates PG18 at all.

Proposed fix
-PG14_CONN=$(PGPASSWORD=db psql -h localhost -p 5432 -U db -d db -t -c "SELECT 1;" 2>&1)
-PG18_CONN=$(PGPASSWORD=db psql -h localhost -p 5433 -U db -d db -t -c "SELECT 1;" 2>&1)
-
-if [ $? -eq 0 ]; then
+if PGPASSWORD=db psql -h localhost -p 5432 -U db -d db -t -c "SELECT 1;" >/dev/null 2>&1; then
     echo "   ✓ PostgreSQL 14: Connected"
 else
     echo "   ✗ PostgreSQL 14: Connection failed"
     exit 1
 fi
 
-if [ $? -eq 0 ]; then
+if PGPASSWORD=db psql -h localhost -p 5433 -U db -d db -t -c "SELECT 1;" >/dev/null 2>&1; then
     echo "   ✓ PostgreSQL 18: Connected"
 else
     echo "   ✗ PostgreSQL 18: Connection failed"
     exit 1
 fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@commands/host/validate-pg18-migration` around lines 11 - 26, The connection
checks reuse $? incorrectly causing the PG14/PG18 results to be misreported; fix
by capturing and testing each command's exit status immediately after its psql
invocation (either run each psql in its own if statement, e.g. if PGPASSWORD=...
psql ...; then echo success/exit on failure, or store the exit code into
distinct variables like PG14_EXIT=$? and PG18_EXIT=$? right after assigning
PG14_CONN and PG18_CONN) and use those specific statuses when printing the
"PostgreSQL 14/18: Connected/Connection failed" messages; reference the
PG14_CONN and PG18_CONN assignments and the two if blocks to locate where to
check $? or store PG14_EXIT/PG18_EXIT so each DB is validated independently.

Comment thread config.yaml
php_version: "8.0"
webserver_type: nginx-fpm
xdebug_enabled: false
xdebug_enabled: true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Unrelated change: Xdebug permanently enabled may degrade performance.

This change enables Xdebug permanently but appears unrelated to the PR objective (PostgreSQL 18 container). The config file's own comments (lines 57-60) warn that "leaving Xdebug enabled all the time is a big performance hit" and recommend using ddev xdebug / ddev xdebug off commands instead.

If this was intentional, please confirm. Otherwise, consider reverting to avoid impacting all developers using this config.

Suggested revert if unintentional
-xdebug_enabled: true
+xdebug_enabled: false
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
xdebug_enabled: true
xdebug_enabled: false
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@config.yaml` at line 6, The config change permanently enabling Xdebug
(xdebug_enabled) is unrelated and can hurt developer performance; revert it by
restoring xdebug_enabled to false or removing the override in config.yaml so
developers use ddev xdebug / ddev xdebug off as intended, or if this was
intentional update the same file to include a clear comment explaining why
Xdebug must be enabled globally and confirm the decision in the PR; locate the
xdebug_enabled entry in config.yaml to apply the change.

Comment on lines +37 to +40
CREATE COLLATION app.en_natural (
LOCALE = 'en-US-u-kn-true',
PROVIDER = icu
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether any init script creates schema `app` before this file runs.
fd -a . postgres18-init
rg -n -C2 'CREATE[[:space:]]+SCHEMA[[:space:]]+app|CREATE[[:space:]]+COLLATION[[:space:]]+app\.en_natural' postgres18-init

Repository: sandeepfd/fdsu_ddev

Length of output: 484


🏁 Script executed:

cat -n postgres18-init/01-init-roles.sql

Repository: sandeepfd/fdsu_ddev

Length of output: 1798


🏁 Script executed:

# Check if there are other init scripts and if any create schema app
ls -la postgres18-init/
rg 'CREATE[[:space:]]+SCHEMA[[:space:]]+app' postgres18-init/

Repository: sandeepfd/fdsu_ddev

Length of output: 243


🏁 Script executed:

# Search entire repository for CREATE SCHEMA app to see if it's created elsewhere
rg 'CREATE[[:space:]]+SCHEMA[[:space:]]+app' --type sql

Repository: sandeepfd/fdsu_ddev

Length of output: 45


🏁 Script executed:

# Final check: verify app schema doesn't exist in any initialization context
find . -name "*.sql" -o -name "*init*" -o -name "*docker*" | head -20
rg 'CREATE.*SCHEMA' --type sql | grep -v '.git'

Repository: sandeepfd/fdsu_ddev

Length of output: 241


Ensure schema app exists before creating app.en_natural collation.

The collation creation at lines 37-40 will fail with "schema app does not exist" if the schema hasn't been created yet. The current error handling only checks for collation existence, not schema existence. This will break container initialization on first boot.

Proposed fix
 DO $$
 BEGIN
+    IF NOT EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = 'app') THEN
+        EXECUTE 'CREATE SCHEMA app';
+    END IF;
+
     -- Check if en_natural collation exists in app schema
     IF NOT EXISTS (
         SELECT 1 FROM pg_collation c
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
CREATE COLLATION app.en_natural (
LOCALE = 'en-US-u-kn-true',
PROVIDER = icu
);
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = 'app') THEN
EXECUTE 'CREATE SCHEMA app';
END IF;
-- Check if en_natural collation exists in app schema
IF NOT EXISTS (
SELECT 1 FROM pg_collation c
WHERE c.collname = 'en_natural'
AND n.nspname = 'app'
) THEN
CREATE COLLATION app.en_natural (
LOCALE = 'en-US-u-kn-true',
PROVIDER = icu
);
END IF;
END $$;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@postgres18-init/01-init-roles.sql` around lines 37 - 40, The CREATE COLLATION
statement for app.en_natural can fail if the app schema doesn’t exist; before
creating the collation (CREATE COLLATION app.en_natural ...) add an idempotent
step to ensure the schema exists (for example run CREATE SCHEMA IF NOT EXISTS
app) so the collation creation won’t error on first-run; update the migration
around the CREATE COLLATION app.en_natural to create the app schema first and
keep the existing IF NOT EXISTS/existence checks to remain idempotent.

Comment thread POSTGRESQL_18_CHANGES.md
Comment on lines +62 to +72
SELECT
schemaname,
tablename,
attname,
attidentity
FROM pg_attribute a
JOIN pg_class c ON a.attrelid = c.oid
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE attidentity = 'a' -- 'a' = ALWAYS
AND schemaname IN ('app', 'rrule');
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Validate corrected query compiles on PG18.
PGPASSWORD=db psql -h localhost -p 5433 -U db -d db -v ON_ERROR_STOP=1 <<'SQL'
SELECT 
  n.nspname AS schemaname,
  c.relname AS tablename,
  a.attname,
  a.attidentity
FROM pg_attribute a
JOIN pg_class c ON a.attrelid = c.oid
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE a.attidentity = 'a'
  AND n.nspname IN ('app', 'rrule')
LIMIT 5;
SQL

Repository: sandeepfd/fdsu_ddev

Length of output: 107


🏁 Script executed:

git ls-files | head -20

Repository: sandeepfd/fdsu_ddev

Length of output: 582


🏁 Script executed:

fd -i postgresql

Repository: sandeepfd/fdsu_ddev

Length of output: 87


🏁 Script executed:

cat -n POSTGRESQL_18_CHANGES.md | sed -n '60,75p'

Repository: sandeepfd/fdsu_ddev

Length of output: 568


🌐 Web query:

PostgreSQL 18 pg_attribute pg_class pg_namespace catalog columns nspname relname attname

💡 Result:

In PostgreSQL 18, the system catalogs pg_namespace, pg_class, and pg_attribute store metadata about schemas, tables/relations, and columns/attributes, respectively. The specified columns are: - pg_namespace.nspname (name): Name of the namespace (schema). - pg_class.relname (name): Name of the table, index, view, etc. (relation). - pg_attribute.attname (name): The column name (attribute). These catalogs are linked as follows: - pg_class.relnamespace (oid) references pg_namespace.oid - pg_attribute.attrelid (oid) references pg_class.oid A common query to list schema name, table name, and column name for user tables is: SELECT n.nspname AS schema_name, c.relname AS table_name, a.attname AS column_name FROM pg_namespace n JOIN pg_class c ON c.relnamespace = n.oid JOIN pg_attribute a ON a.attrelid = c.oid WHERE c.relkind = 'r' -- ordinary tables AND a.attnum > 0 -- user columns only AND NOT a.attisdropped ORDER BY n.nspname, c.relname, a.attnum; This joins the catalogs using the specified columns to retrieve schema.table.column information from the PostgreSQL 18 system catalogs.

Citations:


The catalog query uses non-existent columns (schemaname, tablename).

This snippet will fail as written; use n.nspname and c.relname when reading from pg_namespace/pg_class.

Proposed fix
 SELECT 
-    schemaname,
-    tablename,
+    n.nspname AS schemaname,
+    c.relname AS tablename,
     attname,
     attidentity
 FROM pg_attribute a
 JOIN pg_class c ON a.attrelid = c.oid
 JOIN pg_namespace n ON c.relnamespace = n.oid
 WHERE attidentity = 'a'  -- 'a' = ALWAYS
-AND schemaname IN ('app', 'rrule');
+AND n.nspname IN ('app', 'rrule');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
SELECT
schemaname,
tablename,
attname,
attidentity
FROM pg_attribute a
JOIN pg_class c ON a.attrelid = c.oid
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE attidentity = 'a' -- 'a' = ALWAYS
AND schemaname IN ('app', 'rrule');
```
SELECT
n.nspname AS schemaname,
c.relname AS tablename,
attname,
attidentity
FROM pg_attribute a
JOIN pg_class c ON a.attrelid = c.oid
JOIN pg_namespace n ON c.relnamespace = n.oid
WHERE attidentity = 'a' -- 'a' = ALWAYS
AND n.nspname IN ('app', 'rrule');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@POSTGRESQL_18_CHANGES.md` around lines 62 - 72, The query is selecting
non-existent columns schemaname and tablename from pg_namespace/pg_class; update
the SELECT and any WHERE/filters to use n.nspname and c.relname (or alias them,
e.g., n.nspname AS schemaname and c.relname AS tablename) so the query in the
block referencing pg_attribute a, pg_class c and pg_namespace n returns correct
schema and table names; keep the attidentity = 'a' filter and the schemaname IN
('app','rrule') condition but apply it to n.nspname (or your chosen alias)
instead of schemaname.

Comment thread POSTGRESQL_18_CHANGES.md
Comment on lines +218 to +222
SELECT
n.nspname as schema,
c.relname as table_name,
pg_get_expr(c.reloptions, c.oid) as options
FROM pg_class c

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Validate corrected inheritance query compiles on PG18.
PGPASSWORD=db psql -h localhost -p 5433 -U db -d db -v ON_ERROR_STOP=1 <<'SQL'
SELECT 
  child_ns.nspname AS child_schema,
  child.relname AS child_table,
  parent_ns.nspname AS parent_schema,
  parent.relname AS parent_table
FROM pg_inherits i
JOIN pg_class child ON child.oid = i.inhrelid
JOIN pg_namespace child_ns ON child_ns.oid = child.relnamespace
JOIN pg_class parent ON parent.oid = i.inhparent
JOIN pg_namespace parent_ns ON parent_ns.oid = parent.relnamespace
LIMIT 5;
SQL

Repository: sandeepfd/fdsu_ddev

Length of output: 107


🏁 Script executed:

git ls-files | grep -i postgresql_18_changes

Repository: sandeepfd/fdsu_ddev

Length of output: 87


🏁 Script executed:

find . -name "*POSTGRESQL_18_CHANGES*" -o -name "*postgresql_18_changes*"

Repository: sandeepfd/fdsu_ddev

Length of output: 89


🌐 Web query:

PostgreSQL pg_get_expr() function behavior with reloptions

💡 Result:

The pg_get_expr function decompiles a pg_node_tree (binary expression representation) into readable SQL text, using the provided relation OID as context for resolving variable references (Vars). It is commonly used with fields like pg_attrdef.adbin for default expressions (pg_get_expr(adbin, adrelid)) or pg_constraint.conbin for check constraints. pg_class.reloptions is a text[] column storing access-method-specific options as "keyword=value" strings (e.g., autovacuum_enabled=false). This is not a pg_node_tree; it is a simple array of text values stored directly as text[]. pg_get_expr is not designed for or used with reloptions. Attempting pg_get_expr(reloptions, oid) would likely error because reloptions lacks the expected pg_node_tree format. To display reloptions, use array_to_string(reloptions, ', ') or similar; no decompilation is needed as it is already human-readable text. No official documentation, source code snippets, or discussions indicate special behavior of pg_get_expr with reloptions. The function's purpose is strictly for node tree expressions, not text arrays.

Citations:


🌐 Web query:

PostgreSQL reloptions column pg_class what is it

💡 Result:

The reloptions column in PostgreSQL's pg_class system catalog is a text[] (array of text) that stores access-method-specific options for relations (tables, indexes, etc.) as "keyword=value" strings. These options include settings like fillfactor for heap tables, autovacuum parameters, or index-specific configurations such as buffer usage for B-tree indexes. They are set via CREATE TABLE/INDEX or ALTER TABLE SET options and parsed by access methods.

Citations:


🏁 Script executed:

head -n 230 POSTGRESQL_18_CHANGES.md | tail -n 20

Repository: sandeepfd/fdsu_ddev

Length of output: 660


pg_get_expr(c.reloptions, c.oid) is invalid—reloptions is a text array, not a parse tree expression.

reloptions in pg_class stores access-method-specific options as simple text[] (keyword=value strings), not as pg_node_tree binary expressions. The pg_get_expr() function decompiles only pg_node_tree structures (used for defaults, constraints, etc.) and will error or produce incorrect results when applied to reloptions. For querying table inheritance relationships, directly join pg_inherits instead.

Corrected query
 SELECT 
-    n.nspname as schema,
-    c.relname as table_name,
-    pg_get_expr(c.reloptions, c.oid) as options
-FROM pg_class c
-JOIN pg_namespace n ON c.relnamespace = n.oid
-WHERE c.relkind = 'r' 
-  AND c.relispartition = false
-  AND EXISTS (
-      SELECT 1 FROM pg_inherits 
-      WHERE inhrelid = c.oid
-  );
+    child_ns.nspname AS child_schema,
+    child.relname AS child_table,
+    parent_ns.nspname AS parent_schema,
+    parent.relname AS parent_table
+FROM pg_inherits i
+JOIN pg_class child ON child.oid = i.inhrelid
+JOIN pg_namespace child_ns ON child_ns.oid = child.relnamespace
+JOIN pg_class parent ON parent.oid = i.inhparent
+JOIN pg_namespace parent_ns ON parent_ns.oid = parent.relnamespace;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@POSTGRESQL_18_CHANGES.md` around lines 218 - 222, The query incorrectly calls
pg_get_expr(c.reloptions, c.oid) — reloptions in pg_class is a text[] of
keyword=value strings, not a pg_node_tree, so pg_get_expr will fail; replace
that expression by either selecting c.reloptions directly (or unnesting it) to
read options, and if you need inheritance info join pg_inherits (using
inhparent/inhrelid) to retrieve parent/child relationships; update references to
pg_get_expr -> reloptions (or unnest(reloptions)) and add a join to pg_inherits
when querying inheritance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants