Skip to content

Commit 74376a6

Browse files
committed
fix: resolve ADBC nullable timestamp and Spanner cache hit issues
- ADBC store: Use separate SQL path when expires_at is None to avoid Arrow 'na' type mapping error (Arrow can't infer type from Python None) - Cache: Preserve list type for execute_many parameters on cache hit (Spanner driver requires list, not tuple)
1 parent 4c54416 commit 74376a6

File tree

2 files changed

+30
-9
lines changed

2 files changed

+30
-9
lines changed

sqlspec/adapters/adbc/litestar/store.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -311,18 +311,41 @@ def _set(self, key: str, value: "str | bytes", expires_in: "int | timedelta | No
311311
VALUES ({p1}, {p2}, {p3})
312312
"""
313313
elif dialect in {"postgres", "postgresql"}:
314-
# Use explicit CAST for nullable timestamp to avoid Arrow 'na' type mapping issues
314+
# ADBC Arrow driver cannot infer type from Python None, causing 'na' type error.
315+
# Use separate SQL statements for NULL vs non-NULL expires_at to avoid the issue.
316+
if expires_at is None:
317+
sql = f"""
318+
INSERT INTO {self._table_name} (session_id, data, expires_at)
319+
VALUES ({p1}, {p2}, NULL)
320+
ON CONFLICT (session_id) DO UPDATE
321+
SET data = EXCLUDED.data, expires_at = NULL
322+
"""
323+
with self._config.provide_session() as driver:
324+
driver.execute(sql, key, data)
325+
driver.commit()
326+
return
315327
sql = f"""
316328
INSERT INTO {self._table_name} (session_id, data, expires_at)
317-
VALUES ({p1}, {p2}, CAST({p3} AS TIMESTAMPTZ))
329+
VALUES ({p1}, {p2}, {p3})
318330
ON CONFLICT (session_id) DO UPDATE
319331
SET data = EXCLUDED.data, expires_at = EXCLUDED.expires_at
320332
"""
321333
else:
322-
# DuckDB: Use explicit CAST for nullable timestamp to avoid Arrow 'na' type mapping issues
334+
# DuckDB: Same issue with Arrow 'na' type for None values
335+
if expires_at is None:
336+
sql = f"""
337+
INSERT INTO {self._table_name} (session_id, data, expires_at)
338+
VALUES ({p1}, {p2}, NULL)
339+
ON CONFLICT (session_id) DO UPDATE
340+
SET data = EXCLUDED.data, expires_at = NULL
341+
"""
342+
with self._config.provide_session() as driver:
343+
driver.execute(sql, key, data)
344+
driver.commit()
345+
return
323346
sql = f"""
324347
INSERT INTO {self._table_name} (session_id, data, expires_at)
325-
VALUES ({p1}, {p2}, CAST({p3} AS TIMESTAMP))
348+
VALUES ({p1}, {p2}, {p3})
326349
ON CONFLICT (session_id) DO UPDATE
327350
SET data = EXCLUDED.data, expires_at = EXCLUDED.expires_at
328351
"""

sqlspec/driver/_common.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1390,15 +1390,13 @@ def _get_compiled_statement(
13901390
prepared_statement=prepared_statement,
13911391
)
13921392
# Return cached SQL metadata but with newly processed parameters
1393-
cached_parameters = (
1394-
tuple(prepared_parameters) if isinstance(prepared_parameters, list) else prepared_parameters
1395-
)
1393+
# Preserve list type for execute_many operations (some drivers require list, not tuple)
13961394
updated_cached = CachedStatement(
13971395
compiled_sql=cached_result.compiled_sql,
1398-
parameters=cached_parameters,
1396+
parameters=prepared_parameters,
13991397
expression=cached_result.expression,
14001398
)
1401-
return updated_cached, cached_parameters
1399+
return updated_cached, prepared_parameters
14021400

14031401
prepared_statement = self.prepare_statement(statement, statement_config=statement_config)
14041402
compiled_sql, execution_parameters = prepared_statement.compile()

0 commit comments

Comments
 (0)