Skip to content

Commit 1e9a2b1

Browse files
committed
fix(live-db): Fix three connectivity bugs in Live DB Mode
1. Replace quote_plus with quote(safe='') for URL-encoding credentials in the connection string. SQLAlchemy decodes with urllib.parse.unquote (not unquote_plus), so quote_plus-encoded spaces ('+') were passed literally to psycopg2 and caused authentication failures. 2. Use a single engine.connect() context in get_external_schema() and inspect(conn) instead of inspect(engine). In SQLAlchemy 2.0, inspect(engine) opens a new DB connection for every reflection method call (get_table_names, get_columns per table), leading to N+1 connection attempts. Using a single connection context eliminates this overhead and gives a well-defined connection lifecycle. 3. Remove pool_pre_ping=True from both get_external_schema() and execute_sql_on_external(). These engines are created, used once, and disposed — pool_pre_ping adds a redundant SELECT 1 round-trip before every use. On cold Neon instances this extra round-trip consumed part of the 10-second connect_timeout budget, causing spurious timeouts. https://claude.ai/code/session_016qjAVBXoVuhQD9MNdHQ6Fd
1 parent f73ead2 commit 1e9a2b1

3 files changed

Lines changed: 14 additions & 11 deletions

File tree

backend/app/queries/router.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import json
2-
from urllib.parse import quote_plus
2+
from urllib.parse import quote
33
from fastapi import APIRouter, Depends, HTTPException
44

55
from app.auth.utils import CurrentUser, get_current_user, require_role
@@ -155,10 +155,12 @@ def run_live_query(
155155
):
156156
sb = get_supabase()
157157

158-
# URL-encode user + password so special chars (@, :, #, +, etc.) don't break the URI
158+
# URL-encode user, password, and db_name so special chars don't break the URI.
159+
# Use quote() with safe='' (not quote_plus) because SQLAlchemy decodes with
160+
# urllib.parse.unquote, which does NOT convert '+' back to space.
159161
conn_str = (
160-
f"postgresql+psycopg2://{quote_plus(payload.db_user)}:{quote_plus(payload.db_password)}"
161-
f"@{payload.db_host}:{payload.db_port}/{payload.db_name}"
162+
f"postgresql+psycopg2://{quote(payload.db_user, safe='')}:{quote(payload.db_password, safe='')}"
163+
f"@{payload.db_host}:{payload.db_port}/{quote(payload.db_name, safe='')}"
162164
)
163165

164166
schema_text = get_external_schema(conn_str, ssl_required=payload.ssl_required)

backend/app/queries/sql_executor.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,6 @@ def execute_sql_on_external(
113113
try:
114114
engine = create_engine(
115115
connection_string,
116-
pool_pre_ping=True,
117116
connect_args={"connect_timeout": 10, "sslmode": sslmode},
118117
)
119118
with engine.connect() as conn:

backend/app/schema_service/service.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,17 @@ def get_external_schema(connection_string: str, ssl_required: bool = True) -> st
4242
try:
4343
engine = create_engine(
4444
connection_string,
45-
pool_pre_ping=True,
4645
connect_args={"connect_timeout": 10, "sslmode": sslmode},
4746
)
48-
inspector = inspect(engine)
4947
parts = []
50-
for table_name in inspector.get_table_names(schema="public"):
51-
cols = inspector.get_columns(table_name, schema="public")
52-
col_defs = [f" {c['name']} {c['type']}" for c in cols]
53-
parts.append(f"Table: {table_name}\nColumns:\n" + "\n".join(col_defs))
48+
# Use a single connection for all reflection calls to avoid the overhead
49+
# of opening a new connection per method when using inspect(engine).
50+
with engine.connect() as conn:
51+
inspector = inspect(conn)
52+
for table_name in inspector.get_table_names(schema="public"):
53+
cols = inspector.get_columns(table_name, schema="public")
54+
col_defs = [f" {c['name']} {c['type']}" for c in cols]
55+
parts.append(f"Table: {table_name}\nColumns:\n" + "\n".join(col_defs))
5456
return "\n\n".join(parts)
5557
except Exception as exc:
5658
msg = str(exc)

0 commit comments

Comments
 (0)