Skip to content

Commit e771628

Browse files
committed
Fix JSON serialization for MySQL Decimal/datetime/bytes types
Executor now coerces DB-specific types (Decimal→float, datetime→ISO string, bytes→str) before returning rows, preventing 500 errors on MySQL queries.
1 parent 3a5ec0c commit e771628

1 file changed

Lines changed: 16 additions & 1 deletion

File tree

app/sql/executor.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import datetime
12
import time
3+
from decimal import Decimal
24
from typing import Any, Callable, Dict, List, Tuple
35

46
from sqlalchemy import create_engine, text
@@ -8,6 +10,17 @@
810
from app.sql.policy import enforce_limit, is_allowed, is_read_query
911

1012

13+
def _coerce_value(val: Any) -> Any:
14+
"""Convert DB-specific types to JSON-safe Python primitives."""
15+
if isinstance(val, Decimal):
16+
return float(val)
17+
if isinstance(val, (datetime.date, datetime.datetime)):
18+
return val.isoformat()
19+
if isinstance(val, bytes):
20+
return val.decode("utf-8", errors="replace")
21+
return val
22+
23+
1124
class SQLExecutor:
1225
def __init__(self, db_url: str) -> None:
1326
self.engine: Engine = create_engine(db_url)
@@ -36,7 +49,9 @@ def cleanup() -> None:
3649
if result.returns_rows:
3750
rows = result.fetchall()
3851
columns = list(result.keys())
39-
parsed = [dict(zip(columns, row)) for row in rows]
52+
parsed = [
53+
{col: _coerce_value(val) for col, val in zip(columns, row)} for row in rows
54+
]
4055
return parsed, columns, elapsed_ms, None
4156
except SQLAlchemyError as exc:
4257
elapsed_ms = int((time.time() - start) * 1000)

0 commit comments

Comments
 (0)