sqlspec 0.14.1__py3-none-any.whl → 0.15.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sqlspec might be problematic. Click here for more details.
- sqlspec/__init__.py +50 -25
- sqlspec/__main__.py +1 -1
- sqlspec/__metadata__.py +1 -3
- sqlspec/_serialization.py +1 -2
- sqlspec/_sql.py +256 -120
- sqlspec/_typing.py +278 -142
- sqlspec/adapters/adbc/__init__.py +4 -3
- sqlspec/adapters/adbc/_types.py +12 -0
- sqlspec/adapters/adbc/config.py +115 -260
- sqlspec/adapters/adbc/driver.py +462 -367
- sqlspec/adapters/aiosqlite/__init__.py +18 -3
- sqlspec/adapters/aiosqlite/_types.py +13 -0
- sqlspec/adapters/aiosqlite/config.py +199 -129
- sqlspec/adapters/aiosqlite/driver.py +230 -269
- sqlspec/adapters/asyncmy/__init__.py +18 -3
- sqlspec/adapters/asyncmy/_types.py +12 -0
- sqlspec/adapters/asyncmy/config.py +80 -168
- sqlspec/adapters/asyncmy/driver.py +260 -225
- sqlspec/adapters/asyncpg/__init__.py +19 -4
- sqlspec/adapters/asyncpg/_types.py +17 -0
- sqlspec/adapters/asyncpg/config.py +82 -181
- sqlspec/adapters/asyncpg/driver.py +285 -383
- sqlspec/adapters/bigquery/__init__.py +17 -3
- sqlspec/adapters/bigquery/_types.py +12 -0
- sqlspec/adapters/bigquery/config.py +191 -258
- sqlspec/adapters/bigquery/driver.py +474 -646
- sqlspec/adapters/duckdb/__init__.py +14 -3
- sqlspec/adapters/duckdb/_types.py +12 -0
- sqlspec/adapters/duckdb/config.py +415 -351
- sqlspec/adapters/duckdb/driver.py +343 -413
- sqlspec/adapters/oracledb/__init__.py +19 -5
- sqlspec/adapters/oracledb/_types.py +14 -0
- sqlspec/adapters/oracledb/config.py +123 -379
- sqlspec/adapters/oracledb/driver.py +507 -560
- sqlspec/adapters/psqlpy/__init__.py +13 -3
- sqlspec/adapters/psqlpy/_types.py +11 -0
- sqlspec/adapters/psqlpy/config.py +93 -254
- sqlspec/adapters/psqlpy/driver.py +505 -234
- sqlspec/adapters/psycopg/__init__.py +19 -5
- sqlspec/adapters/psycopg/_types.py +17 -0
- sqlspec/adapters/psycopg/config.py +143 -403
- sqlspec/adapters/psycopg/driver.py +706 -872
- sqlspec/adapters/sqlite/__init__.py +14 -3
- sqlspec/adapters/sqlite/_types.py +11 -0
- sqlspec/adapters/sqlite/config.py +202 -118
- sqlspec/adapters/sqlite/driver.py +264 -303
- sqlspec/base.py +105 -9
- sqlspec/{statement/builder → builder}/__init__.py +12 -14
- sqlspec/{statement/builder → builder}/_base.py +120 -55
- sqlspec/{statement/builder → builder}/_column.py +17 -6
- sqlspec/{statement/builder → builder}/_ddl.py +46 -79
- sqlspec/{statement/builder → builder}/_ddl_utils.py +5 -10
- sqlspec/{statement/builder → builder}/_delete.py +6 -25
- sqlspec/{statement/builder → builder}/_insert.py +6 -64
- sqlspec/builder/_merge.py +56 -0
- sqlspec/{statement/builder → builder}/_parsing_utils.py +3 -10
- sqlspec/{statement/builder → builder}/_select.py +11 -56
- sqlspec/{statement/builder → builder}/_update.py +12 -18
- sqlspec/{statement/builder → builder}/mixins/__init__.py +10 -14
- sqlspec/{statement/builder → builder}/mixins/_cte_and_set_ops.py +48 -59
- sqlspec/{statement/builder → builder}/mixins/_insert_operations.py +22 -16
- sqlspec/{statement/builder → builder}/mixins/_join_operations.py +1 -3
- sqlspec/{statement/builder → builder}/mixins/_merge_operations.py +3 -5
- sqlspec/{statement/builder → builder}/mixins/_order_limit_operations.py +3 -3
- sqlspec/{statement/builder → builder}/mixins/_pivot_operations.py +4 -8
- sqlspec/{statement/builder → builder}/mixins/_select_operations.py +21 -36
- sqlspec/{statement/builder → builder}/mixins/_update_operations.py +3 -14
- sqlspec/{statement/builder → builder}/mixins/_where_clause.py +52 -79
- sqlspec/cli.py +4 -5
- sqlspec/config.py +180 -133
- sqlspec/core/__init__.py +63 -0
- sqlspec/core/cache.py +873 -0
- sqlspec/core/compiler.py +396 -0
- sqlspec/core/filters.py +828 -0
- sqlspec/core/hashing.py +310 -0
- sqlspec/core/parameters.py +1209 -0
- sqlspec/core/result.py +664 -0
- sqlspec/{statement → core}/splitter.py +321 -191
- sqlspec/core/statement.py +651 -0
- sqlspec/driver/__init__.py +7 -10
- sqlspec/driver/_async.py +387 -176
- sqlspec/driver/_common.py +527 -289
- sqlspec/driver/_sync.py +390 -172
- sqlspec/driver/mixins/__init__.py +2 -19
- sqlspec/driver/mixins/_result_tools.py +168 -0
- sqlspec/driver/mixins/_sql_translator.py +6 -3
- sqlspec/exceptions.py +5 -252
- sqlspec/extensions/aiosql/adapter.py +93 -96
- sqlspec/extensions/litestar/config.py +0 -1
- sqlspec/extensions/litestar/handlers.py +15 -26
- sqlspec/extensions/litestar/plugin.py +16 -14
- sqlspec/extensions/litestar/providers.py +17 -52
- sqlspec/loader.py +424 -105
- sqlspec/migrations/__init__.py +12 -0
- sqlspec/migrations/base.py +92 -68
- sqlspec/migrations/commands.py +24 -106
- sqlspec/migrations/loaders.py +402 -0
- sqlspec/migrations/runner.py +49 -51
- sqlspec/migrations/tracker.py +31 -44
- sqlspec/migrations/utils.py +64 -24
- sqlspec/protocols.py +7 -183
- sqlspec/storage/__init__.py +1 -1
- sqlspec/storage/backends/base.py +37 -40
- sqlspec/storage/backends/fsspec.py +136 -112
- sqlspec/storage/backends/obstore.py +138 -160
- sqlspec/storage/capabilities.py +5 -4
- sqlspec/storage/registry.py +57 -106
- sqlspec/typing.py +136 -115
- sqlspec/utils/__init__.py +2 -3
- sqlspec/utils/correlation.py +0 -3
- sqlspec/utils/deprecation.py +6 -6
- sqlspec/utils/fixtures.py +6 -6
- sqlspec/utils/logging.py +0 -2
- sqlspec/utils/module_loader.py +7 -12
- sqlspec/utils/singleton.py +0 -1
- sqlspec/utils/sync_tools.py +16 -37
- sqlspec/utils/text.py +12 -51
- sqlspec/utils/type_guards.py +443 -232
- {sqlspec-0.14.1.dist-info → sqlspec-0.15.0.dist-info}/METADATA +7 -2
- sqlspec-0.15.0.dist-info/RECORD +134 -0
- sqlspec/adapters/adbc/transformers.py +0 -108
- sqlspec/driver/connection.py +0 -207
- sqlspec/driver/mixins/_cache.py +0 -114
- sqlspec/driver/mixins/_csv_writer.py +0 -91
- sqlspec/driver/mixins/_pipeline.py +0 -508
- sqlspec/driver/mixins/_query_tools.py +0 -796
- sqlspec/driver/mixins/_result_utils.py +0 -138
- sqlspec/driver/mixins/_storage.py +0 -912
- sqlspec/driver/mixins/_type_coercion.py +0 -128
- sqlspec/driver/parameters.py +0 -138
- sqlspec/statement/__init__.py +0 -21
- sqlspec/statement/builder/_merge.py +0 -95
- sqlspec/statement/cache.py +0 -50
- sqlspec/statement/filters.py +0 -625
- sqlspec/statement/parameters.py +0 -956
- sqlspec/statement/pipelines/__init__.py +0 -210
- sqlspec/statement/pipelines/analyzers/__init__.py +0 -9
- sqlspec/statement/pipelines/analyzers/_analyzer.py +0 -646
- sqlspec/statement/pipelines/context.py +0 -109
- sqlspec/statement/pipelines/transformers/__init__.py +0 -7
- sqlspec/statement/pipelines/transformers/_expression_simplifier.py +0 -88
- sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +0 -1247
- sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +0 -76
- sqlspec/statement/pipelines/validators/__init__.py +0 -23
- sqlspec/statement/pipelines/validators/_dml_safety.py +0 -290
- sqlspec/statement/pipelines/validators/_parameter_style.py +0 -370
- sqlspec/statement/pipelines/validators/_performance.py +0 -714
- sqlspec/statement/pipelines/validators/_security.py +0 -967
- sqlspec/statement/result.py +0 -435
- sqlspec/statement/sql.py +0 -1774
- sqlspec/utils/cached_property.py +0 -25
- sqlspec/utils/statement_hashing.py +0 -203
- sqlspec-0.14.1.dist-info/RECORD +0 -145
- /sqlspec/{statement/builder → builder}/mixins/_delete_operations.py +0 -0
- {sqlspec-0.14.1.dist-info → sqlspec-0.15.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.14.1.dist-info → sqlspec-0.15.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.14.1.dist-info → sqlspec-0.15.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.14.1.dist-info → sqlspec-0.15.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,796 +0,0 @@
|
|
|
1
|
-
# pyright: reportCallIssue=false, reportAttributeAccessIssue=false, reportArgumentType=false
|
|
2
|
-
import logging
|
|
3
|
-
from abc import ABC
|
|
4
|
-
from typing import TYPE_CHECKING, Any, Generic, Optional, Union, overload
|
|
5
|
-
|
|
6
|
-
from sqlglot import exp, parse_one
|
|
7
|
-
from typing_extensions import Self
|
|
8
|
-
|
|
9
|
-
from sqlspec.exceptions import NotFoundError
|
|
10
|
-
from sqlspec.statement.filters import LimitOffsetFilter, OffsetPagination
|
|
11
|
-
from sqlspec.statement.sql import SQL
|
|
12
|
-
from sqlspec.typing import ConnectionT, ModelDTOT, RowT
|
|
13
|
-
from sqlspec.utils.type_guards import (
|
|
14
|
-
is_dict_row,
|
|
15
|
-
is_indexable_row,
|
|
16
|
-
is_limit_offset_filter,
|
|
17
|
-
is_select_builder,
|
|
18
|
-
is_statement_filter,
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
if TYPE_CHECKING:
|
|
22
|
-
from sqlglot.dialects.dialect import DialectType
|
|
23
|
-
|
|
24
|
-
from sqlspec.statement import Statement, StatementFilter
|
|
25
|
-
from sqlspec.statement.builder import Select
|
|
26
|
-
from sqlspec.statement.sql import SQLConfig
|
|
27
|
-
from sqlspec.typing import StatementParameters
|
|
28
|
-
|
|
29
|
-
__all__ = ("AsyncQueryMixin", "SyncQueryMixin")
|
|
30
|
-
|
|
31
|
-
logger = logging.getLogger(__name__)
|
|
32
|
-
|
|
33
|
-
WINDOWS_PATH_MIN_LENGTH = 3
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class QueryBase(ABC, Generic[ConnectionT]):
|
|
37
|
-
"""Base class with common query functionality."""
|
|
38
|
-
|
|
39
|
-
config: Any
|
|
40
|
-
_connection: Any
|
|
41
|
-
dialect: "DialectType"
|
|
42
|
-
|
|
43
|
-
@property
|
|
44
|
-
def connection(self) -> "ConnectionT":
|
|
45
|
-
"""Get the connection instance."""
|
|
46
|
-
return self._connection # type: ignore[no-any-return]
|
|
47
|
-
|
|
48
|
-
@classmethod
|
|
49
|
-
def new(cls, connection: "ConnectionT") -> Self:
|
|
50
|
-
return cls(connection) # type: ignore[call-arg]
|
|
51
|
-
|
|
52
|
-
def _transform_to_sql(
|
|
53
|
-
self,
|
|
54
|
-
statement: "Union[Statement, Select]",
|
|
55
|
-
params: "Optional[dict[str, Any]]" = None,
|
|
56
|
-
config: "Optional[SQLConfig]" = None,
|
|
57
|
-
) -> "SQL":
|
|
58
|
-
"""Normalize a statement of any supported type into a SQL object.
|
|
59
|
-
|
|
60
|
-
Args:
|
|
61
|
-
statement: The statement to normalize (str, Expression, SQL, or Select)
|
|
62
|
-
params: Optional parameters (ignored for Select and SQL objects)
|
|
63
|
-
config: Optional SQL configuration
|
|
64
|
-
|
|
65
|
-
Returns:
|
|
66
|
-
A converted SQL object
|
|
67
|
-
"""
|
|
68
|
-
|
|
69
|
-
if is_select_builder(statement):
|
|
70
|
-
# Select has its own parameters via build(), ignore external params
|
|
71
|
-
safe_query = statement.build()
|
|
72
|
-
return SQL(safe_query.sql, parameters=safe_query.parameters, config=config)
|
|
73
|
-
|
|
74
|
-
if isinstance(statement, SQL):
|
|
75
|
-
# SQL object is already complete, ignore external params
|
|
76
|
-
return statement
|
|
77
|
-
|
|
78
|
-
if isinstance(statement, (str, exp.Expression)):
|
|
79
|
-
return SQL(statement, parameters=params, config=config)
|
|
80
|
-
|
|
81
|
-
# Fallback for type safety
|
|
82
|
-
msg = f"Unsupported statement type: {type(statement).__name__}"
|
|
83
|
-
raise TypeError(msg)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
class SyncQueryMixin(QueryBase[ConnectionT]):
|
|
87
|
-
"""Unified storage operations for synchronous drivers."""
|
|
88
|
-
|
|
89
|
-
@overload
|
|
90
|
-
def select_one(
|
|
91
|
-
self,
|
|
92
|
-
statement: "Union[Statement, Select]",
|
|
93
|
-
/,
|
|
94
|
-
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
95
|
-
schema_type: "type[ModelDTOT]",
|
|
96
|
-
_connection: "Optional[ConnectionT]" = None,
|
|
97
|
-
_config: "Optional[SQLConfig]" = None,
|
|
98
|
-
**kwargs: Any,
|
|
99
|
-
) -> "ModelDTOT": ...
|
|
100
|
-
|
|
101
|
-
@overload
|
|
102
|
-
def select_one(
|
|
103
|
-
self,
|
|
104
|
-
statement: "Union[Statement, Select]",
|
|
105
|
-
/,
|
|
106
|
-
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
107
|
-
schema_type: None = None,
|
|
108
|
-
_connection: "Optional[ConnectionT]" = None,
|
|
109
|
-
_config: "Optional[SQLConfig]" = None,
|
|
110
|
-
**kwargs: Any,
|
|
111
|
-
) -> "RowT": ... # type: ignore[type-var]
|
|
112
|
-
|
|
113
|
-
def select_one(
|
|
114
|
-
self,
|
|
115
|
-
statement: "Union[Statement, Select]",
|
|
116
|
-
/,
|
|
117
|
-
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
118
|
-
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
119
|
-
_connection: "Optional[ConnectionT]" = None,
|
|
120
|
-
_config: "Optional[SQLConfig]" = None,
|
|
121
|
-
**kwargs: Any,
|
|
122
|
-
) -> "Union[RowT, ModelDTOT]":
|
|
123
|
-
"""Execute a select statement and return exactly one row.
|
|
124
|
-
|
|
125
|
-
Raises an exception if no rows or more than one row is returned.
|
|
126
|
-
"""
|
|
127
|
-
result = self.execute( # type: ignore[attr-defined]
|
|
128
|
-
statement, *parameters, schema_type=schema_type, _connection=_connection, _config=_config, **kwargs
|
|
129
|
-
)
|
|
130
|
-
data = result.get_data()
|
|
131
|
-
if not isinstance(data, list):
|
|
132
|
-
msg = "Expected list result from select operation"
|
|
133
|
-
raise TypeError(msg)
|
|
134
|
-
if not data:
|
|
135
|
-
msg = "No rows found"
|
|
136
|
-
raise NotFoundError(msg)
|
|
137
|
-
if len(data) > 1:
|
|
138
|
-
msg = f"Expected exactly one row, found {len(data)}"
|
|
139
|
-
raise ValueError(msg)
|
|
140
|
-
return data[0] # type: ignore[no-any-return]
|
|
141
|
-
|
|
142
|
-
@overload
|
|
143
|
-
def select_one_or_none(
|
|
144
|
-
self,
|
|
145
|
-
statement: "Union[Statement, Select]",
|
|
146
|
-
/,
|
|
147
|
-
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
148
|
-
schema_type: "type[ModelDTOT]",
|
|
149
|
-
_connection: "Optional[ConnectionT]" = None,
|
|
150
|
-
_config: "Optional[SQLConfig]" = None,
|
|
151
|
-
**kwargs: Any,
|
|
152
|
-
) -> "Optional[ModelDTOT]": ...
|
|
153
|
-
|
|
154
|
-
@overload
|
|
155
|
-
def select_one_or_none(
|
|
156
|
-
self,
|
|
157
|
-
statement: "Union[Statement, Select]",
|
|
158
|
-
/,
|
|
159
|
-
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
160
|
-
schema_type: None = None,
|
|
161
|
-
_connection: "Optional[ConnectionT]" = None,
|
|
162
|
-
_config: "Optional[SQLConfig]" = None,
|
|
163
|
-
**kwargs: Any,
|
|
164
|
-
) -> "Optional[RowT]": ...
|
|
165
|
-
|
|
166
|
-
def select_one_or_none(
|
|
167
|
-
self,
|
|
168
|
-
statement: "Union[Statement, Select]",
|
|
169
|
-
/,
|
|
170
|
-
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
171
|
-
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
172
|
-
_connection: "Optional[ConnectionT]" = None,
|
|
173
|
-
_config: "Optional[SQLConfig]" = None,
|
|
174
|
-
**kwargs: "Optional[Union[RowT, ModelDTOT]]",
|
|
175
|
-
) -> Any:
|
|
176
|
-
"""Execute a select statement and return at most one row.
|
|
177
|
-
|
|
178
|
-
Returns None if no rows are found.
|
|
179
|
-
Raises an exception if more than one row is returned.
|
|
180
|
-
"""
|
|
181
|
-
result = self.execute( # type: ignore[attr-defined]
|
|
182
|
-
statement, *parameters, schema_type=schema_type, _connection=_connection, _config=_config, **kwargs
|
|
183
|
-
)
|
|
184
|
-
data = result.get_data()
|
|
185
|
-
# For select operations, data should be a list
|
|
186
|
-
if not isinstance(data, list):
|
|
187
|
-
msg = "Expected list result from select operation"
|
|
188
|
-
raise TypeError(msg)
|
|
189
|
-
if not data:
|
|
190
|
-
return None
|
|
191
|
-
if len(data) > 1:
|
|
192
|
-
msg = f"Expected at most one row, found {len(data)}"
|
|
193
|
-
raise ValueError(msg)
|
|
194
|
-
return data[0]
|
|
195
|
-
|
|
196
|
-
@overload
|
|
197
|
-
def select(
|
|
198
|
-
self,
|
|
199
|
-
statement: "Union[Statement, Select]",
|
|
200
|
-
/,
|
|
201
|
-
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
202
|
-
schema_type: "type[ModelDTOT]",
|
|
203
|
-
_connection: "Optional[ConnectionT]" = None,
|
|
204
|
-
_config: "Optional[SQLConfig]" = None,
|
|
205
|
-
**kwargs: Any,
|
|
206
|
-
) -> "list[ModelDTOT]": ...
|
|
207
|
-
|
|
208
|
-
@overload
|
|
209
|
-
def select(
|
|
210
|
-
self,
|
|
211
|
-
statement: "Union[Statement, Select]",
|
|
212
|
-
/,
|
|
213
|
-
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
214
|
-
schema_type: None = None,
|
|
215
|
-
_connection: "Optional[ConnectionT]" = None,
|
|
216
|
-
_config: "Optional[SQLConfig]" = None,
|
|
217
|
-
**kwargs: Any,
|
|
218
|
-
) -> "list[RowT]": ...
|
|
219
|
-
|
|
220
|
-
def select(
|
|
221
|
-
self,
|
|
222
|
-
statement: "Union[Statement, Select]",
|
|
223
|
-
/,
|
|
224
|
-
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
225
|
-
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
226
|
-
_connection: "Optional[ConnectionT]" = None,
|
|
227
|
-
_config: "Optional[SQLConfig]" = None,
|
|
228
|
-
**kwargs: Any,
|
|
229
|
-
) -> Union[list[RowT], list[ModelDTOT]]:
|
|
230
|
-
"""Execute a select statement and return all rows."""
|
|
231
|
-
result = self.execute( # type: ignore[attr-defined]
|
|
232
|
-
statement, *parameters, schema_type=schema_type, _connection=_connection, _config=_config, **kwargs
|
|
233
|
-
)
|
|
234
|
-
data = result.get_data()
|
|
235
|
-
# For select operations, data should be a list
|
|
236
|
-
if not isinstance(data, list):
|
|
237
|
-
msg = "Expected list result from select operation"
|
|
238
|
-
raise TypeError(msg)
|
|
239
|
-
return data
|
|
240
|
-
|
|
241
|
-
def select_value(
|
|
242
|
-
self,
|
|
243
|
-
statement: "Union[Statement, Select]",
|
|
244
|
-
/,
|
|
245
|
-
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
246
|
-
_connection: "Optional[ConnectionT]" = None,
|
|
247
|
-
_config: "Optional[SQLConfig]" = None,
|
|
248
|
-
**kwargs: Any,
|
|
249
|
-
) -> Any:
|
|
250
|
-
"""Execute a select statement and return a single scalar value.
|
|
251
|
-
|
|
252
|
-
Expects exactly one row with one column.
|
|
253
|
-
Raises an exception if no rows or more than one row/column is returned.
|
|
254
|
-
"""
|
|
255
|
-
result = self.execute(statement, *parameters, _connection=_connection, _config=_config, **kwargs) # type: ignore[attr-defined]
|
|
256
|
-
data = result.get_data()
|
|
257
|
-
# For select operations, data should be a list
|
|
258
|
-
if not isinstance(data, list):
|
|
259
|
-
msg = "Expected list result from select operation"
|
|
260
|
-
raise TypeError(msg)
|
|
261
|
-
if not data:
|
|
262
|
-
msg = "No rows found"
|
|
263
|
-
raise NotFoundError(msg)
|
|
264
|
-
if len(data) > 1:
|
|
265
|
-
msg = f"Expected exactly one row, found {len(data)}"
|
|
266
|
-
raise ValueError(msg)
|
|
267
|
-
row = data[0]
|
|
268
|
-
if is_dict_row(row):
|
|
269
|
-
if not row:
|
|
270
|
-
msg = "Row has no columns"
|
|
271
|
-
raise ValueError(msg)
|
|
272
|
-
return next(iter(row.values()))
|
|
273
|
-
if is_indexable_row(row):
|
|
274
|
-
if not row:
|
|
275
|
-
msg = "Row has no columns"
|
|
276
|
-
raise ValueError(msg)
|
|
277
|
-
return row[0]
|
|
278
|
-
msg = f"Unexpected row type: {type(row)}"
|
|
279
|
-
raise ValueError(msg)
|
|
280
|
-
|
|
281
|
-
def select_value_or_none(
|
|
282
|
-
self,
|
|
283
|
-
statement: "Union[Statement, Select]",
|
|
284
|
-
/,
|
|
285
|
-
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
286
|
-
_connection: "Optional[ConnectionT]" = None,
|
|
287
|
-
_config: "Optional[SQLConfig]" = None,
|
|
288
|
-
**kwargs: Any,
|
|
289
|
-
) -> Any:
|
|
290
|
-
"""Execute a select statement and return a single scalar value or None.
|
|
291
|
-
|
|
292
|
-
Returns None if no rows are found.
|
|
293
|
-
Expects at most one row with one column.
|
|
294
|
-
Raises an exception if more than one row is returned.
|
|
295
|
-
"""
|
|
296
|
-
result = self.execute(statement, *parameters, _connection=_connection, _config=_config, **kwargs) # type: ignore[attr-defined]
|
|
297
|
-
data = result.get_data()
|
|
298
|
-
# For select operations, data should be a list
|
|
299
|
-
if not isinstance(data, list):
|
|
300
|
-
msg = "Expected list result from select operation"
|
|
301
|
-
raise TypeError(msg)
|
|
302
|
-
if not data:
|
|
303
|
-
return None
|
|
304
|
-
if len(data) > 1:
|
|
305
|
-
msg = f"Expected at most one row, found {len(data)}"
|
|
306
|
-
raise ValueError(msg)
|
|
307
|
-
row = data[0]
|
|
308
|
-
if isinstance(row, dict):
|
|
309
|
-
if not row:
|
|
310
|
-
return None
|
|
311
|
-
return next(iter(row.values()))
|
|
312
|
-
if isinstance(row, (tuple, list)):
|
|
313
|
-
# Tuple or list-like row
|
|
314
|
-
return row[0]
|
|
315
|
-
try:
|
|
316
|
-
return row[0]
|
|
317
|
-
except (TypeError, IndexError) as e:
|
|
318
|
-
msg = f"Cannot extract value from row type {type(row).__name__}: {e}"
|
|
319
|
-
raise TypeError(msg) from e
|
|
320
|
-
|
|
321
|
-
@overload
|
|
322
|
-
def paginate(
|
|
323
|
-
self,
|
|
324
|
-
statement: "Union[Statement, Select]",
|
|
325
|
-
/,
|
|
326
|
-
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
327
|
-
schema_type: "type[ModelDTOT]",
|
|
328
|
-
_connection: "Optional[ConnectionT]" = None,
|
|
329
|
-
_config: "Optional[SQLConfig]" = None,
|
|
330
|
-
**kwargs: Any,
|
|
331
|
-
) -> "OffsetPagination[ModelDTOT]": ...
|
|
332
|
-
|
|
333
|
-
@overload
|
|
334
|
-
def paginate(
|
|
335
|
-
self,
|
|
336
|
-
statement: "Union[Statement, Select]",
|
|
337
|
-
/,
|
|
338
|
-
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
339
|
-
schema_type: None = None,
|
|
340
|
-
_connection: "Optional[ConnectionT]" = None,
|
|
341
|
-
_config: "Optional[SQLConfig]" = None,
|
|
342
|
-
**kwargs: Any,
|
|
343
|
-
) -> "OffsetPagination[RowT]": ...
|
|
344
|
-
|
|
345
|
-
def paginate(
|
|
346
|
-
self,
|
|
347
|
-
statement: "Union[Statement, Select]",
|
|
348
|
-
/,
|
|
349
|
-
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
350
|
-
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
351
|
-
_connection: "Optional[ConnectionT]" = None,
|
|
352
|
-
_config: "Optional[SQLConfig]" = None,
|
|
353
|
-
**kwargs: Any,
|
|
354
|
-
) -> "Union[OffsetPagination[RowT], OffsetPagination[ModelDTOT]]":
|
|
355
|
-
"""Execute a paginated query with automatic counting.
|
|
356
|
-
|
|
357
|
-
This method performs two queries:
|
|
358
|
-
1. A count query to get the total number of results
|
|
359
|
-
2. A data query with limit/offset applied
|
|
360
|
-
|
|
361
|
-
Pagination can be specified either via LimitOffsetFilter in parameters
|
|
362
|
-
or via 'limit' and 'offset' in kwargs.
|
|
363
|
-
|
|
364
|
-
Args:
|
|
365
|
-
statement: The SELECT statement to paginate
|
|
366
|
-
*parameters: Statement parameters and filters (can include LimitOffsetFilter)
|
|
367
|
-
schema_type: Optional model type for automatic schema conversion
|
|
368
|
-
_connection: Optional connection to use
|
|
369
|
-
_config: Optional SQL configuration
|
|
370
|
-
**kwargs: Additional driver-specific arguments. Can include 'limit' and 'offset'
|
|
371
|
-
if LimitOffsetFilter is not provided
|
|
372
|
-
|
|
373
|
-
Returns:
|
|
374
|
-
OffsetPagination object containing items, limit, offset, and total count
|
|
375
|
-
|
|
376
|
-
Raises:
|
|
377
|
-
ValueError: If neither LimitOffsetFilter nor limit/offset kwargs are provided
|
|
378
|
-
|
|
379
|
-
Example:
|
|
380
|
-
>>> # Using LimitOffsetFilter (recommended)
|
|
381
|
-
>>> from sqlspec.statement.filters import LimitOffsetFilter
|
|
382
|
-
>>> result = service.paginate(
|
|
383
|
-
... sql.select("*").from_("users"),
|
|
384
|
-
... LimitOffsetFilter(limit=10, offset=20),
|
|
385
|
-
... )
|
|
386
|
-
>>> print(
|
|
387
|
-
... f"Showing {len(result.items)} of {result.total} users"
|
|
388
|
-
... )
|
|
389
|
-
|
|
390
|
-
>>> # Using kwargs (convenience)
|
|
391
|
-
>>> result = service.paginate(
|
|
392
|
-
... sql.select("*").from_("users"), limit=10, offset=20
|
|
393
|
-
... )
|
|
394
|
-
|
|
395
|
-
>>> # With schema conversion
|
|
396
|
-
>>> result = service.paginate(
|
|
397
|
-
... sql.select("*").from_("users"),
|
|
398
|
-
... LimitOffsetFilter(limit=10, offset=0),
|
|
399
|
-
... schema_type=User,
|
|
400
|
-
... )
|
|
401
|
-
>>> # result.items is list[User] with proper type inference
|
|
402
|
-
|
|
403
|
-
>>> # With multiple filters
|
|
404
|
-
>>> from sqlspec.statement.filters import (
|
|
405
|
-
... LimitOffsetFilter,
|
|
406
|
-
... OrderByFilter,
|
|
407
|
-
... )
|
|
408
|
-
>>> result = service.paginate(
|
|
409
|
-
... sql.select("*").from_("users"),
|
|
410
|
-
... OrderByFilter("created_at", "desc"),
|
|
411
|
-
... LimitOffsetFilter(limit=20, offset=40),
|
|
412
|
-
... schema_type=User,
|
|
413
|
-
... )
|
|
414
|
-
"""
|
|
415
|
-
|
|
416
|
-
# Separate filters from parameters
|
|
417
|
-
filters: list[StatementFilter] = []
|
|
418
|
-
params: list[Any] = []
|
|
419
|
-
|
|
420
|
-
for p in parameters:
|
|
421
|
-
if is_statement_filter(p):
|
|
422
|
-
filters.append(p)
|
|
423
|
-
else:
|
|
424
|
-
params.append(p)
|
|
425
|
-
|
|
426
|
-
# Check for LimitOffsetFilter in filters
|
|
427
|
-
limit_offset_filter = None
|
|
428
|
-
other_filters = []
|
|
429
|
-
for f in filters:
|
|
430
|
-
if is_limit_offset_filter(f):
|
|
431
|
-
limit_offset_filter = f
|
|
432
|
-
else:
|
|
433
|
-
other_filters.append(f)
|
|
434
|
-
|
|
435
|
-
if limit_offset_filter is not None:
|
|
436
|
-
limit = limit_offset_filter.limit
|
|
437
|
-
offset = limit_offset_filter.offset
|
|
438
|
-
elif "limit" in kwargs and "offset" in kwargs:
|
|
439
|
-
limit = kwargs.pop("limit")
|
|
440
|
-
offset = kwargs.pop("offset")
|
|
441
|
-
else:
|
|
442
|
-
msg = "Pagination requires either a LimitOffsetFilter in parameters or 'limit' and 'offset' in kwargs."
|
|
443
|
-
raise ValueError(msg)
|
|
444
|
-
|
|
445
|
-
base_stmt = self._transform_to_sql(statement, params, _config) # type: ignore[arg-type]
|
|
446
|
-
|
|
447
|
-
filtered_stmt = base_stmt
|
|
448
|
-
for filter_obj in other_filters:
|
|
449
|
-
filtered_stmt = filter_obj.append_to_statement(filtered_stmt)
|
|
450
|
-
|
|
451
|
-
sql_str = filtered_stmt.to_sql()
|
|
452
|
-
parsed = parse_one(sql_str)
|
|
453
|
-
|
|
454
|
-
# Using exp.Subquery to properly wrap the parsed expression
|
|
455
|
-
subquery = exp.Subquery(this=parsed, alias="_count_subquery")
|
|
456
|
-
count_ast = exp.Select().select(exp.func("COUNT", exp.Star()).as_("total")).from_(subquery)
|
|
457
|
-
|
|
458
|
-
# Preserve parameters from the original statement
|
|
459
|
-
count_stmt = SQL(count_ast, parameters=filtered_stmt.parameters, _config=_config)
|
|
460
|
-
|
|
461
|
-
# Execute count query
|
|
462
|
-
total = self.select_value(count_stmt, _connection=_connection, _config=_config, **kwargs)
|
|
463
|
-
|
|
464
|
-
data_stmt = self._transform_to_sql(statement, params, _config) # type: ignore[arg-type]
|
|
465
|
-
|
|
466
|
-
for filter_obj in other_filters:
|
|
467
|
-
data_stmt = filter_obj.append_to_statement(data_stmt)
|
|
468
|
-
|
|
469
|
-
# Apply limit and offset using LimitOffsetFilter
|
|
470
|
-
from sqlspec.statement.filters import LimitOffsetFilter
|
|
471
|
-
|
|
472
|
-
limit_offset = LimitOffsetFilter(limit=limit, offset=offset)
|
|
473
|
-
data_stmt = limit_offset.append_to_statement(data_stmt)
|
|
474
|
-
|
|
475
|
-
# Execute data query
|
|
476
|
-
items = self.select(
|
|
477
|
-
data_stmt, params, schema_type=schema_type, _connection=_connection, _config=_config, **kwargs
|
|
478
|
-
)
|
|
479
|
-
|
|
480
|
-
return OffsetPagination(items=items, limit=limit, offset=offset, total=total) # pyright: ignore
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
class AsyncQueryMixin(QueryBase[ConnectionT]):
|
|
484
|
-
"""Unified query operations for asynchronous drivers."""
|
|
485
|
-
|
|
486
|
-
@overload
|
|
487
|
-
async def select_one(
|
|
488
|
-
self,
|
|
489
|
-
statement: "Union[Statement, Select]",
|
|
490
|
-
/,
|
|
491
|
-
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
492
|
-
schema_type: "type[ModelDTOT]",
|
|
493
|
-
_connection: "Optional[ConnectionT]" = None,
|
|
494
|
-
_config: "Optional[SQLConfig]" = None,
|
|
495
|
-
**kwargs: Any,
|
|
496
|
-
) -> "ModelDTOT": ...
|
|
497
|
-
|
|
498
|
-
@overload
|
|
499
|
-
async def select_one(
|
|
500
|
-
self,
|
|
501
|
-
statement: "Union[Statement, Select]",
|
|
502
|
-
/,
|
|
503
|
-
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
504
|
-
schema_type: None = None,
|
|
505
|
-
_connection: "Optional[ConnectionT]" = None,
|
|
506
|
-
_config: "Optional[SQLConfig]" = None,
|
|
507
|
-
**kwargs: Any,
|
|
508
|
-
) -> "RowT": ...
|
|
509
|
-
|
|
510
|
-
async def select_one(
|
|
511
|
-
self,
|
|
512
|
-
statement: "Union[Statement, Select]",
|
|
513
|
-
/,
|
|
514
|
-
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
515
|
-
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
516
|
-
_connection: "Optional[ConnectionT]" = None,
|
|
517
|
-
_config: "Optional[SQLConfig]" = None,
|
|
518
|
-
**kwargs: Any,
|
|
519
|
-
) -> "Union[RowT, ModelDTOT]":
|
|
520
|
-
"""Execute a select statement and return exactly one row.
|
|
521
|
-
|
|
522
|
-
Raises an exception if no rows or more than one row is returned.
|
|
523
|
-
"""
|
|
524
|
-
result = await self.execute( # type: ignore[attr-defined]
|
|
525
|
-
statement, *parameters, schema_type=schema_type, _connection=_connection, _config=_config, **kwargs
|
|
526
|
-
)
|
|
527
|
-
data = result.get_data()
|
|
528
|
-
if not data:
|
|
529
|
-
msg = "No rows found"
|
|
530
|
-
raise NotFoundError(msg)
|
|
531
|
-
if len(data) > 1:
|
|
532
|
-
msg = f"Expected exactly one row, found {len(data)}"
|
|
533
|
-
raise ValueError(msg)
|
|
534
|
-
return data[0] # type: ignore[no-any-return]
|
|
535
|
-
|
|
536
|
-
@overload
|
|
537
|
-
async def select_one_or_none(
|
|
538
|
-
self,
|
|
539
|
-
statement: "Union[Statement, Select]",
|
|
540
|
-
/,
|
|
541
|
-
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
542
|
-
schema_type: "type[ModelDTOT]",
|
|
543
|
-
_connection: "Optional[ConnectionT]" = None,
|
|
544
|
-
_config: "Optional[SQLConfig]" = None,
|
|
545
|
-
**kwargs: Any,
|
|
546
|
-
) -> "Optional[ModelDTOT]": ...
|
|
547
|
-
|
|
548
|
-
@overload
|
|
549
|
-
async def select_one_or_none(
|
|
550
|
-
self,
|
|
551
|
-
statement: "Union[Statement, Select]",
|
|
552
|
-
/,
|
|
553
|
-
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
554
|
-
schema_type: None = None,
|
|
555
|
-
_connection: "Optional[ConnectionT]" = None,
|
|
556
|
-
_config: "Optional[SQLConfig]" = None,
|
|
557
|
-
**kwargs: Any,
|
|
558
|
-
) -> "Optional[RowT]": ...
|
|
559
|
-
|
|
560
|
-
async def select_one_or_none(
|
|
561
|
-
self,
|
|
562
|
-
statement: "Union[Statement, Select]",
|
|
563
|
-
/,
|
|
564
|
-
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
565
|
-
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
566
|
-
_connection: "Optional[ConnectionT]" = None,
|
|
567
|
-
_config: "Optional[SQLConfig]" = None,
|
|
568
|
-
**kwargs: Any,
|
|
569
|
-
) -> "Optional[Union[RowT, ModelDTOT]]":
|
|
570
|
-
"""Execute a select statement and return at most one row.
|
|
571
|
-
|
|
572
|
-
Returns None if no rows are found.
|
|
573
|
-
Raises an exception if more than one row is returned.
|
|
574
|
-
"""
|
|
575
|
-
result = await self.execute( # type: ignore[attr-defined]
|
|
576
|
-
statement, *parameters, schema_type=schema_type, _connection=_connection, _config=_config, **kwargs
|
|
577
|
-
)
|
|
578
|
-
data = result.get_data()
|
|
579
|
-
if not data:
|
|
580
|
-
return None
|
|
581
|
-
if len(data) > 1:
|
|
582
|
-
msg = f"Expected at most one row, found {len(data)}"
|
|
583
|
-
raise ValueError(msg)
|
|
584
|
-
return data[0] # type: ignore[no-any-return]
|
|
585
|
-
|
|
586
|
-
@overload
|
|
587
|
-
async def select(
|
|
588
|
-
self,
|
|
589
|
-
statement: "Union[Statement, Select]",
|
|
590
|
-
/,
|
|
591
|
-
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
592
|
-
schema_type: "type[ModelDTOT]",
|
|
593
|
-
_connection: "Optional[ConnectionT]" = None,
|
|
594
|
-
_config: "Optional[SQLConfig]" = None,
|
|
595
|
-
**kwargs: Any,
|
|
596
|
-
) -> "list[ModelDTOT]": ...
|
|
597
|
-
|
|
598
|
-
@overload
|
|
599
|
-
async def select(
|
|
600
|
-
self,
|
|
601
|
-
statement: "Union[Statement, Select]",
|
|
602
|
-
/,
|
|
603
|
-
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
604
|
-
schema_type: None = None,
|
|
605
|
-
_connection: "Optional[ConnectionT]" = None,
|
|
606
|
-
_config: "Optional[SQLConfig]" = None,
|
|
607
|
-
**kwargs: Any,
|
|
608
|
-
) -> "list[RowT]": ...
|
|
609
|
-
|
|
610
|
-
async def select(
|
|
611
|
-
self,
|
|
612
|
-
statement: "Union[Statement, Select]",
|
|
613
|
-
/,
|
|
614
|
-
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
615
|
-
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
616
|
-
_connection: "Optional[ConnectionT]" = None,
|
|
617
|
-
_config: "Optional[SQLConfig]" = None,
|
|
618
|
-
**kwargs: Any,
|
|
619
|
-
) -> "Union[list[RowT], list[ModelDTOT]]":
|
|
620
|
-
"""Execute a select statement and return all rows."""
|
|
621
|
-
result = await self.execute( # type: ignore[attr-defined]
|
|
622
|
-
statement, *parameters, schema_type=schema_type, _connection=_connection, _config=_config, **kwargs
|
|
623
|
-
)
|
|
624
|
-
return result.get_data() # type: ignore[no-any-return]
|
|
625
|
-
|
|
626
|
-
async def select_value(
|
|
627
|
-
self,
|
|
628
|
-
statement: "Union[Statement, Select]",
|
|
629
|
-
/,
|
|
630
|
-
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
631
|
-
_connection: "Optional[ConnectionT]" = None,
|
|
632
|
-
_config: "Optional[SQLConfig]" = None,
|
|
633
|
-
**kwargs: Any,
|
|
634
|
-
) -> Any:
|
|
635
|
-
"""Execute a select statement and return a single scalar value.
|
|
636
|
-
|
|
637
|
-
Expects exactly one row with one column.
|
|
638
|
-
Raises an exception if no rows or more than one row/column is returned.
|
|
639
|
-
"""
|
|
640
|
-
result = await self.execute(statement, *parameters, _connection=_connection, _config=_config, **kwargs) # type: ignore[attr-defined]
|
|
641
|
-
row = result.one()
|
|
642
|
-
if not row:
|
|
643
|
-
msg = "No rows found"
|
|
644
|
-
raise NotFoundError(msg)
|
|
645
|
-
if is_dict_row(row):
|
|
646
|
-
if not row:
|
|
647
|
-
msg = "Row has no columns"
|
|
648
|
-
raise ValueError(msg)
|
|
649
|
-
return next(iter(row.values()))
|
|
650
|
-
if is_indexable_row(row):
|
|
651
|
-
# Tuple or list-like row
|
|
652
|
-
if not row:
|
|
653
|
-
msg = "Row has no columns"
|
|
654
|
-
raise ValueError(msg)
|
|
655
|
-
return row[0]
|
|
656
|
-
msg = f"Unexpected row type: {type(row)}"
|
|
657
|
-
raise ValueError(msg)
|
|
658
|
-
|
|
659
|
-
async def select_value_or_none(
|
|
660
|
-
self,
|
|
661
|
-
statement: "Union[Statement, Select]",
|
|
662
|
-
/,
|
|
663
|
-
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
664
|
-
_connection: "Optional[ConnectionT]" = None,
|
|
665
|
-
_config: "Optional[SQLConfig]" = None,
|
|
666
|
-
**kwargs: Any,
|
|
667
|
-
) -> Any:
|
|
668
|
-
"""Execute a select statement and return a single scalar value or None.
|
|
669
|
-
|
|
670
|
-
Returns None if no rows are found.
|
|
671
|
-
Expects at most one row with one column.
|
|
672
|
-
Raises an exception if more than one row is returned.
|
|
673
|
-
"""
|
|
674
|
-
result = await self.execute( # type: ignore[attr-defined]
|
|
675
|
-
statement, *parameters, _connection=_connection, _config=_config, **kwargs
|
|
676
|
-
)
|
|
677
|
-
data = result.get_data()
|
|
678
|
-
# For select operations, data should be a list
|
|
679
|
-
if not isinstance(data, list):
|
|
680
|
-
msg = "Expected list result from select operation"
|
|
681
|
-
raise TypeError(msg)
|
|
682
|
-
if not data:
|
|
683
|
-
return None
|
|
684
|
-
if len(data) > 1:
|
|
685
|
-
msg = f"Expected at most one row, found {len(data)}"
|
|
686
|
-
raise ValueError(msg)
|
|
687
|
-
row = data[0]
|
|
688
|
-
if isinstance(row, dict):
|
|
689
|
-
if not row:
|
|
690
|
-
return None
|
|
691
|
-
return next(iter(row.values()))
|
|
692
|
-
if isinstance(row, (tuple, list)):
|
|
693
|
-
# Tuple or list-like row
|
|
694
|
-
return row[0]
|
|
695
|
-
# Try indexing - if it fails, we'll get a proper error
|
|
696
|
-
try:
|
|
697
|
-
return row[0]
|
|
698
|
-
except (TypeError, IndexError) as e:
|
|
699
|
-
msg = f"Cannot extract value from row type {type(row).__name__}: {e}"
|
|
700
|
-
raise TypeError(msg) from e
|
|
701
|
-
|
|
702
|
-
@overload
|
|
703
|
-
async def paginate(
|
|
704
|
-
self,
|
|
705
|
-
statement: "Union[Statement, Select]",
|
|
706
|
-
/,
|
|
707
|
-
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
708
|
-
schema_type: "type[ModelDTOT]",
|
|
709
|
-
_connection: "Optional[ConnectionT]" = None,
|
|
710
|
-
_config: "Optional[SQLConfig]" = None,
|
|
711
|
-
**kwargs: Any,
|
|
712
|
-
) -> "OffsetPagination[ModelDTOT]": ...
|
|
713
|
-
|
|
714
|
-
@overload
|
|
715
|
-
async def paginate(
|
|
716
|
-
self,
|
|
717
|
-
statement: "Union[Statement, Select]",
|
|
718
|
-
/,
|
|
719
|
-
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
720
|
-
schema_type: None = None,
|
|
721
|
-
_connection: "Optional[ConnectionT]" = None,
|
|
722
|
-
_config: "Optional[SQLConfig]" = None,
|
|
723
|
-
**kwargs: Any,
|
|
724
|
-
) -> "OffsetPagination[RowT]": ...
|
|
725
|
-
|
|
726
|
-
async def paginate(
|
|
727
|
-
self,
|
|
728
|
-
statement: "Union[Statement, Select]",
|
|
729
|
-
/,
|
|
730
|
-
*parameters: "Union[StatementParameters, StatementFilter]",
|
|
731
|
-
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
732
|
-
_connection: "Optional[ConnectionT]" = None,
|
|
733
|
-
_config: "Optional[SQLConfig]" = None,
|
|
734
|
-
**kwargs: Any,
|
|
735
|
-
) -> "Union[OffsetPagination[RowT], OffsetPagination[ModelDTOT]]":
|
|
736
|
-
# Separate filters from parameters
|
|
737
|
-
filters: list[StatementFilter] = []
|
|
738
|
-
params: list[Any] = []
|
|
739
|
-
|
|
740
|
-
for p in parameters:
|
|
741
|
-
# Use type guard to check if it implements the StatementFilter protocol
|
|
742
|
-
if is_statement_filter(p):
|
|
743
|
-
filters.append(p)
|
|
744
|
-
else:
|
|
745
|
-
params.append(p)
|
|
746
|
-
|
|
747
|
-
# Check for LimitOffsetFilter in filters
|
|
748
|
-
limit_offset_filter = None
|
|
749
|
-
other_filters = []
|
|
750
|
-
for f in filters:
|
|
751
|
-
if is_limit_offset_filter(f):
|
|
752
|
-
limit_offset_filter = f
|
|
753
|
-
else:
|
|
754
|
-
other_filters.append(f)
|
|
755
|
-
|
|
756
|
-
if limit_offset_filter is not None:
|
|
757
|
-
limit = limit_offset_filter.limit
|
|
758
|
-
offset = limit_offset_filter.offset
|
|
759
|
-
elif "limit" in kwargs and "offset" in kwargs:
|
|
760
|
-
limit = kwargs.pop("limit")
|
|
761
|
-
offset = kwargs.pop("offset")
|
|
762
|
-
else:
|
|
763
|
-
msg = "Pagination requires either a LimitOffsetFilter in parameters or 'limit' and 'offset' in kwargs."
|
|
764
|
-
raise ValueError(msg)
|
|
765
|
-
|
|
766
|
-
base_stmt = self._transform_to_sql(statement, params, _config) # type: ignore[arg-type]
|
|
767
|
-
|
|
768
|
-
filtered_stmt = base_stmt
|
|
769
|
-
for filter_obj in other_filters:
|
|
770
|
-
filtered_stmt = filter_obj.append_to_statement(filtered_stmt)
|
|
771
|
-
parsed = parse_one(filtered_stmt.to_sql())
|
|
772
|
-
|
|
773
|
-
# Using exp.Subquery to properly wrap the parsed expression
|
|
774
|
-
subquery = exp.Subquery(this=parsed, alias="_count_subquery")
|
|
775
|
-
count_ast = exp.Select().select(exp.func("COUNT", exp.Star()).as_("total")).from_(subquery)
|
|
776
|
-
|
|
777
|
-
# Preserve parameters from the original statement
|
|
778
|
-
count_stmt = SQL(count_ast, *filtered_stmt.parameters, _config=_config)
|
|
779
|
-
|
|
780
|
-
# Execute count query
|
|
781
|
-
total = await self.select_value(count_stmt, _connection=_connection, _config=_config, **kwargs)
|
|
782
|
-
|
|
783
|
-
data_stmt = self._transform_to_sql(statement, params, _config) # type: ignore[arg-type]
|
|
784
|
-
|
|
785
|
-
for filter_obj in other_filters:
|
|
786
|
-
data_stmt = filter_obj.append_to_statement(data_stmt)
|
|
787
|
-
|
|
788
|
-
limit_offset = LimitOffsetFilter(limit=limit, offset=offset)
|
|
789
|
-
data_stmt = limit_offset.append_to_statement(data_stmt)
|
|
790
|
-
|
|
791
|
-
# Execute data query
|
|
792
|
-
items = await self.select(
|
|
793
|
-
data_stmt, *params, schema_type=schema_type, _connection=_connection, _config=_config, **kwargs
|
|
794
|
-
)
|
|
795
|
-
|
|
796
|
-
return OffsetPagination(items=items, limit=limit, offset=offset, total=total) # pyright: ignore
|