fix(replicas): parse Prisma DATABASE_URL and dump tables by name

Passwords with @ broke libpq URIs; convert to keyword conninfo.
pg_dump -t public."table" produced empty dumps so replicas stayed at 0.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-18 12:55:39 +03:30
co-authored by Cursor
parent 26630cecf6
commit 1cc5e09b5f
2 changed files with 60 additions and 20 deletions
+57 -16
View File
@@ -45,8 +45,24 @@ if [[ -z "${DATABASE_URL:-}" && -f "$API_DIR/.env" ]]; then
set -a && source "$API_DIR/.env" && set +a
fi
# Prisma DATABASE_URL often has @ in the password (e.g. Admin@12345) which breaks
# libpq URI parsing → host "Montime@127.0.0.1". Use an explicit publisher DSN.
# Prisma DATABASE_URL often has @ in the password (09128081392@Montime).
# libpq URI parsing then treats host as "Montime@127.0.0.1". Convert to keyword form.
uri_to_conninfo() {
local u="${1%%\?*}"
if [[ "$u" != postgresql://* && "$u" != postgres://* ]]; then
echo "$u"
return 0
fi
local rest="${u#*://}"
if [[ "$rest" =~ ^([^:]+):(.+)@([^:/]+):([0-9]+)/([^/?]+) ]]; then
printf 'host=%s port=%s dbname=%s user=%s password=%s' \
"${BASH_REMATCH[3]}" "${BASH_REMATCH[4]}" "${BASH_REMATCH[5]}" \
"${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}"
return 0
fi
echo "$u"
}
resolve_publisher_dsn() {
if [[ -n "${PUBLISHER_DSN:-}" ]]; then
echo "$PUBLISHER_DSN"
@@ -57,14 +73,10 @@ resolve_publisher_dsn() {
return 0
fi
if [[ -n "${DATABASE_URL:-}" ]]; then
local url="${DATABASE_URL%%\?*}"
if [[ "$url" == postgresql://* || "$url" == postgres://* ]]; then
echo "$url"
return 0
fi
echo "$DATABASE_URL"
return 0
fi
echo "Set PUBLISHER_DSN in deploy/replicas.env (postgresql://…@127.0.0.1:5432/superadmin)." >&2
echo "Prisma DATABASE_URL alone is not reliable for pg_dump when the password contains @." >&2
echo "Set DATABASE_URL or PUBLISHER_DSN (publisher Postgres)." >&2
return 1
}
@@ -79,8 +91,7 @@ run_psql() {
}
libpq_dsn() {
local u="$1"
echo "${u%%\?*}"
uri_to_conninfo "$1"
}
label() {
@@ -125,17 +136,45 @@ grant_app_user() {
" 2>/dev/null || true
}
grant_replica_writer() {
local dsn="$1"
local user=""
if [[ "$dsn" == *"://"* ]]; then
user="$(sed -E 's#^[a-z]+://([^:/]+).*#\1#' <<< "$dsn")"
else
user="$(sed -nE 's/.*user=([^ ]+).*/\1/p' <<< "$dsn")"
fi
[[ -n "$user" ]] || return 0
echo " grant write to $user"
run_psql "$(libpq_dsn "$dsn")" -v ON_ERROR_STOP=1 -q -c "
GRANT USAGE ON SCHEMA public TO ${user};
GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE ON ALL TABLES IN SCHEMA public TO ${user};
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO ${user};
" || true
}
copy_table_from_publisher() {
local replica_dsn="$1" table="$2"
local pub_dsn
pub_dsn="$(libpq_dsn "$PUBLISHER")"
pg_dump "$pub_dsn" \
-t "public.\"$table\"" \
local dump
dump="$(mktemp)"
pg_dump "$(libpq_dsn "$PUBLISHER")" \
-t "$table" \
--data-only \
--no-owner \
--no-privileges \
--column-inserts \
--disable-triggers \
| run_psql "$(libpq_dsn "$replica_dsn")" -v ON_ERROR_STOP=1 -q
>"$dump"
local bytes
bytes="$(wc -c <"$dump" | tr -d ' ')"
echo " dumped $table (${bytes} bytes)"
if [[ "${bytes:-0}" -lt 20 ]]; then
echo " ! pg_dump produced empty output for $table" >&2
rm -f "$dump"
return 1
fi
run_psql "$(libpq_dsn "$replica_dsn")" -v ON_ERROR_STOP=1 -f "$dump"
rm -f "$dump"
}
repair_replica() {
@@ -176,6 +215,8 @@ repair_replica() {
-c "ALTER SUBSCRIPTION \"${sub}\" DISABLE"
done
grant_replica_writer "$replica_dsn"
for table in "${drifted[@]}"; do
echo " truncating + copying $table"
run_psql "$(libpq_dsn "$replica_dsn")" -v ON_ERROR_STOP=1 -q \