Skip to content

Commit c5e0154

Browse files
authored
fix(#532): align window total schema mapping (#541)
## Summary Closes #532. - Route `select_with_total(count_with_window=True)` window rows through the normal schema conversion path in both sync and async drivers. - Keep `_add_count_over_column()` and `_extract_total_from_rows()` unchanged; the generated SQL was already correct. - Add sync SQLite and async aiosqlite regressions for `msgspec.Struct` schemas that omit an extra selected column. ## Root Cause The two-query path used `SQLResult.get_data(schema_type=...)`, but the window-count path stripped `_total_count` and then constructed schema objects directly with `schema_type(**row)`. Direct `msgspec.Struct` construction rejects unmapped columns, while the normal SQLSpec conversion path tolerates them.
1 parent 77aafaa commit c5e0154

3 files changed

Lines changed: 77 additions & 2 deletions

File tree

sqlspec/driver/_async.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1154,7 +1154,7 @@ async def select_with_total(
11541154
data, total = self._extract_total_from_rows(rows)
11551155

11561156
if schema_type is not None:
1157-
return ([schema_type(**r) for r in data], total)
1157+
return (cast("list[SchemaT]", self.to_schema(data, schema_type=schema_type)), total)
11581158
return (data, total)
11591159

11601160
count_result = await self.dispatch_statement_execution(self._create_count_query(sql_statement), self.connection)

sqlspec/driver/_sync.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1143,7 +1143,7 @@ def select_with_total(
11431143
data, total = self._extract_total_from_rows(rows)
11441144

11451145
if schema_type is not None:
1146-
return ([schema_type(**r) for r in data], total)
1146+
return (cast("list[SchemaT]", self.to_schema(data, schema_type=schema_type)), total)
11471147
return (data, total)
11481148

11491149
count_result = self.dispatch_statement_execution(self._create_count_query(sql_statement), self.connection)

tests/unit/driver/test_count_query.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from types import SimpleNamespace
99
from typing import Any
1010

11+
import msgspec
1112
import pytest
1213

1314
from sqlspec import SQL
@@ -656,6 +657,80 @@ def test_select_with_total_window_count_handles_repeated_named_parameters(sqlite
656657
assert [row["id"] for row in rows] == [1, 2]
657658

658659

660+
def test_select_with_total_window_count_uses_schema_conversion_for_extra_columns(sqlite_driver: "SqliteDriver") -> None:
661+
"""Window-count pagination should map rows through the normal schema converter."""
662+
663+
class Item(msgspec.Struct):
664+
id: int
665+
name: str
666+
667+
sqlite_driver.connection.executescript("""
668+
CREATE TABLE window_schema_items (
669+
id INTEGER PRIMARY KEY,
670+
name TEXT,
671+
extra_col TEXT
672+
);
673+
INSERT INTO window_schema_items (id, name, extra_col) VALUES (1, 'a', 'x');
674+
INSERT INTO window_schema_items (id, name, extra_col) VALUES (2, 'b', 'x');
675+
INSERT INTO window_schema_items (id, name, extra_col) VALUES (3, 'c', 'y');
676+
""")
677+
678+
rows, total = sqlite_driver.select_with_total(
679+
"""
680+
SELECT id, name, extra_col
681+
FROM window_schema_items
682+
ORDER BY id
683+
LIMIT 2
684+
""",
685+
schema_type=Item,
686+
count_with_window=True,
687+
)
688+
689+
assert total == 3
690+
assert rows == [Item(id=1, name="a"), Item(id=2, name="b")]
691+
692+
693+
@pytest.mark.anyio
694+
async def test_async_select_with_total_window_count_uses_schema_conversion_for_extra_columns() -> None:
695+
"""Async window-count pagination should map rows through the normal schema converter."""
696+
697+
from sqlspec.adapters.aiosqlite import AiosqliteConfig
698+
699+
class Item(msgspec.Struct):
700+
id: int
701+
name: str
702+
703+
config = AiosqliteConfig()
704+
try:
705+
async with config.provide_session() as driver:
706+
await driver.execute_script("""
707+
CREATE TABLE async_window_schema_items (
708+
id INTEGER PRIMARY KEY,
709+
name TEXT,
710+
extra_col TEXT
711+
);
712+
INSERT INTO async_window_schema_items (id, name, extra_col) VALUES (1, 'a', 'x');
713+
INSERT INTO async_window_schema_items (id, name, extra_col) VALUES (2, 'b', 'x');
714+
INSERT INTO async_window_schema_items (id, name, extra_col) VALUES (3, 'c', 'y');
715+
""")
716+
717+
rows, total = await driver.select_with_total(
718+
"""
719+
SELECT id, name, extra_col
720+
FROM async_window_schema_items
721+
ORDER BY id
722+
LIMIT 2
723+
""",
724+
schema_type=Item,
725+
count_with_window=True,
726+
)
727+
728+
assert total == 3
729+
assert rows == [Item(id=1, name="a"), Item(id=2, name="b")]
730+
finally:
731+
await config.close_pool()
732+
733+
659734
# =============================================================================
660735
# Bug Fix: Pagination Parameters Excluded from Count Query
661736
# =============================================================================

0 commit comments

Comments
 (0)