sqlspec 0.26.0__py3-none-any.whl → 0.27.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.

Files changed (197) hide show
  1. sqlspec/__init__.py +7 -15
  2. sqlspec/_serialization.py +55 -25
  3. sqlspec/_typing.py +62 -52
  4. sqlspec/adapters/adbc/_types.py +1 -1
  5. sqlspec/adapters/adbc/adk/__init__.py +5 -0
  6. sqlspec/adapters/adbc/adk/store.py +870 -0
  7. sqlspec/adapters/adbc/config.py +62 -12
  8. sqlspec/adapters/adbc/data_dictionary.py +52 -2
  9. sqlspec/adapters/adbc/driver.py +144 -45
  10. sqlspec/adapters/adbc/litestar/__init__.py +5 -0
  11. sqlspec/adapters/adbc/litestar/store.py +504 -0
  12. sqlspec/adapters/adbc/type_converter.py +44 -50
  13. sqlspec/adapters/aiosqlite/_types.py +1 -1
  14. sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
  15. sqlspec/adapters/aiosqlite/adk/store.py +527 -0
  16. sqlspec/adapters/aiosqlite/config.py +86 -16
  17. sqlspec/adapters/aiosqlite/data_dictionary.py +34 -2
  18. sqlspec/adapters/aiosqlite/driver.py +127 -38
  19. sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
  20. sqlspec/adapters/aiosqlite/litestar/store.py +281 -0
  21. sqlspec/adapters/aiosqlite/pool.py +7 -7
  22. sqlspec/adapters/asyncmy/__init__.py +7 -1
  23. sqlspec/adapters/asyncmy/_types.py +1 -1
  24. sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
  25. sqlspec/adapters/asyncmy/adk/store.py +493 -0
  26. sqlspec/adapters/asyncmy/config.py +59 -17
  27. sqlspec/adapters/asyncmy/data_dictionary.py +41 -2
  28. sqlspec/adapters/asyncmy/driver.py +293 -62
  29. sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
  30. sqlspec/adapters/asyncmy/litestar/store.py +296 -0
  31. sqlspec/adapters/asyncpg/__init__.py +2 -1
  32. sqlspec/adapters/asyncpg/_type_handlers.py +71 -0
  33. sqlspec/adapters/asyncpg/_types.py +11 -7
  34. sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
  35. sqlspec/adapters/asyncpg/adk/store.py +450 -0
  36. sqlspec/adapters/asyncpg/config.py +57 -36
  37. sqlspec/adapters/asyncpg/data_dictionary.py +41 -2
  38. sqlspec/adapters/asyncpg/driver.py +153 -23
  39. sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
  40. sqlspec/adapters/asyncpg/litestar/store.py +253 -0
  41. sqlspec/adapters/bigquery/_types.py +1 -1
  42. sqlspec/adapters/bigquery/adk/__init__.py +5 -0
  43. sqlspec/adapters/bigquery/adk/store.py +576 -0
  44. sqlspec/adapters/bigquery/config.py +25 -11
  45. sqlspec/adapters/bigquery/data_dictionary.py +42 -2
  46. sqlspec/adapters/bigquery/driver.py +352 -144
  47. sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
  48. sqlspec/adapters/bigquery/litestar/store.py +327 -0
  49. sqlspec/adapters/bigquery/type_converter.py +55 -23
  50. sqlspec/adapters/duckdb/_types.py +2 -2
  51. sqlspec/adapters/duckdb/adk/__init__.py +14 -0
  52. sqlspec/adapters/duckdb/adk/store.py +553 -0
  53. sqlspec/adapters/duckdb/config.py +79 -21
  54. sqlspec/adapters/duckdb/data_dictionary.py +41 -2
  55. sqlspec/adapters/duckdb/driver.py +138 -43
  56. sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
  57. sqlspec/adapters/duckdb/litestar/store.py +332 -0
  58. sqlspec/adapters/duckdb/pool.py +5 -5
  59. sqlspec/adapters/duckdb/type_converter.py +51 -21
  60. sqlspec/adapters/oracledb/_numpy_handlers.py +133 -0
  61. sqlspec/adapters/oracledb/_types.py +20 -2
  62. sqlspec/adapters/oracledb/adk/__init__.py +5 -0
  63. sqlspec/adapters/oracledb/adk/store.py +1745 -0
  64. sqlspec/adapters/oracledb/config.py +120 -36
  65. sqlspec/adapters/oracledb/data_dictionary.py +87 -20
  66. sqlspec/adapters/oracledb/driver.py +292 -84
  67. sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
  68. sqlspec/adapters/oracledb/litestar/store.py +767 -0
  69. sqlspec/adapters/oracledb/migrations.py +316 -25
  70. sqlspec/adapters/oracledb/type_converter.py +91 -16
  71. sqlspec/adapters/psqlpy/_type_handlers.py +44 -0
  72. sqlspec/adapters/psqlpy/_types.py +2 -1
  73. sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
  74. sqlspec/adapters/psqlpy/adk/store.py +482 -0
  75. sqlspec/adapters/psqlpy/config.py +45 -19
  76. sqlspec/adapters/psqlpy/data_dictionary.py +41 -2
  77. sqlspec/adapters/psqlpy/driver.py +101 -31
  78. sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
  79. sqlspec/adapters/psqlpy/litestar/store.py +272 -0
  80. sqlspec/adapters/psqlpy/type_converter.py +40 -11
  81. sqlspec/adapters/psycopg/_type_handlers.py +80 -0
  82. sqlspec/adapters/psycopg/_types.py +2 -1
  83. sqlspec/adapters/psycopg/adk/__init__.py +5 -0
  84. sqlspec/adapters/psycopg/adk/store.py +944 -0
  85. sqlspec/adapters/psycopg/config.py +65 -37
  86. sqlspec/adapters/psycopg/data_dictionary.py +77 -3
  87. sqlspec/adapters/psycopg/driver.py +200 -78
  88. sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
  89. sqlspec/adapters/psycopg/litestar/store.py +554 -0
  90. sqlspec/adapters/sqlite/__init__.py +2 -1
  91. sqlspec/adapters/sqlite/_type_handlers.py +86 -0
  92. sqlspec/adapters/sqlite/_types.py +1 -1
  93. sqlspec/adapters/sqlite/adk/__init__.py +5 -0
  94. sqlspec/adapters/sqlite/adk/store.py +572 -0
  95. sqlspec/adapters/sqlite/config.py +85 -16
  96. sqlspec/adapters/sqlite/data_dictionary.py +34 -2
  97. sqlspec/adapters/sqlite/driver.py +120 -52
  98. sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
  99. sqlspec/adapters/sqlite/litestar/store.py +318 -0
  100. sqlspec/adapters/sqlite/pool.py +5 -5
  101. sqlspec/base.py +45 -26
  102. sqlspec/builder/__init__.py +73 -4
  103. sqlspec/builder/_base.py +91 -58
  104. sqlspec/builder/_column.py +5 -5
  105. sqlspec/builder/_ddl.py +98 -89
  106. sqlspec/builder/_delete.py +5 -4
  107. sqlspec/builder/_dml.py +388 -0
  108. sqlspec/{_sql.py → builder/_factory.py} +41 -44
  109. sqlspec/builder/_insert.py +5 -82
  110. sqlspec/builder/{mixins/_join_operations.py → _join.py} +145 -143
  111. sqlspec/builder/_merge.py +446 -11
  112. sqlspec/builder/_parsing_utils.py +9 -11
  113. sqlspec/builder/_select.py +1313 -25
  114. sqlspec/builder/_update.py +11 -42
  115. sqlspec/cli.py +76 -69
  116. sqlspec/config.py +231 -60
  117. sqlspec/core/__init__.py +5 -4
  118. sqlspec/core/cache.py +18 -18
  119. sqlspec/core/compiler.py +6 -8
  120. sqlspec/core/filters.py +37 -37
  121. sqlspec/core/hashing.py +9 -9
  122. sqlspec/core/parameters.py +76 -45
  123. sqlspec/core/result.py +102 -46
  124. sqlspec/core/splitter.py +16 -17
  125. sqlspec/core/statement.py +32 -31
  126. sqlspec/core/type_conversion.py +3 -2
  127. sqlspec/driver/__init__.py +1 -3
  128. sqlspec/driver/_async.py +95 -161
  129. sqlspec/driver/_common.py +133 -80
  130. sqlspec/driver/_sync.py +95 -162
  131. sqlspec/driver/mixins/_result_tools.py +20 -236
  132. sqlspec/driver/mixins/_sql_translator.py +4 -4
  133. sqlspec/exceptions.py +70 -7
  134. sqlspec/extensions/adk/__init__.py +53 -0
  135. sqlspec/extensions/adk/_types.py +51 -0
  136. sqlspec/extensions/adk/converters.py +172 -0
  137. sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +144 -0
  138. sqlspec/extensions/adk/migrations/__init__.py +0 -0
  139. sqlspec/extensions/adk/service.py +181 -0
  140. sqlspec/extensions/adk/store.py +536 -0
  141. sqlspec/extensions/aiosql/adapter.py +73 -53
  142. sqlspec/extensions/litestar/__init__.py +21 -4
  143. sqlspec/extensions/litestar/cli.py +54 -10
  144. sqlspec/extensions/litestar/config.py +59 -266
  145. sqlspec/extensions/litestar/handlers.py +46 -17
  146. sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
  147. sqlspec/extensions/litestar/migrations/__init__.py +3 -0
  148. sqlspec/extensions/litestar/plugin.py +324 -223
  149. sqlspec/extensions/litestar/providers.py +25 -25
  150. sqlspec/extensions/litestar/store.py +265 -0
  151. sqlspec/loader.py +30 -49
  152. sqlspec/migrations/base.py +200 -76
  153. sqlspec/migrations/commands.py +591 -62
  154. sqlspec/migrations/context.py +6 -9
  155. sqlspec/migrations/fix.py +199 -0
  156. sqlspec/migrations/loaders.py +47 -19
  157. sqlspec/migrations/runner.py +241 -75
  158. sqlspec/migrations/tracker.py +237 -21
  159. sqlspec/migrations/utils.py +51 -3
  160. sqlspec/migrations/validation.py +177 -0
  161. sqlspec/protocols.py +66 -36
  162. sqlspec/storage/_utils.py +98 -0
  163. sqlspec/storage/backends/fsspec.py +134 -106
  164. sqlspec/storage/backends/local.py +78 -51
  165. sqlspec/storage/backends/obstore.py +278 -162
  166. sqlspec/storage/registry.py +75 -39
  167. sqlspec/typing.py +14 -84
  168. sqlspec/utils/config_resolver.py +6 -6
  169. sqlspec/utils/correlation.py +4 -5
  170. sqlspec/utils/data_transformation.py +3 -2
  171. sqlspec/utils/deprecation.py +9 -8
  172. sqlspec/utils/fixtures.py +4 -4
  173. sqlspec/utils/logging.py +46 -6
  174. sqlspec/utils/module_loader.py +2 -2
  175. sqlspec/utils/schema.py +288 -0
  176. sqlspec/utils/serializers.py +3 -3
  177. sqlspec/utils/sync_tools.py +21 -17
  178. sqlspec/utils/text.py +1 -2
  179. sqlspec/utils/type_guards.py +111 -20
  180. sqlspec/utils/version.py +433 -0
  181. {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/METADATA +40 -21
  182. sqlspec-0.27.0.dist-info/RECORD +207 -0
  183. sqlspec/builder/mixins/__init__.py +0 -55
  184. sqlspec/builder/mixins/_cte_and_set_ops.py +0 -253
  185. sqlspec/builder/mixins/_delete_operations.py +0 -50
  186. sqlspec/builder/mixins/_insert_operations.py +0 -282
  187. sqlspec/builder/mixins/_merge_operations.py +0 -698
  188. sqlspec/builder/mixins/_order_limit_operations.py +0 -145
  189. sqlspec/builder/mixins/_pivot_operations.py +0 -157
  190. sqlspec/builder/mixins/_select_operations.py +0 -930
  191. sqlspec/builder/mixins/_update_operations.py +0 -199
  192. sqlspec/builder/mixins/_where_clause.py +0 -1298
  193. sqlspec-0.26.0.dist-info/RECORD +0 -157
  194. sqlspec-0.26.0.dist-info/licenses/NOTICE +0 -29
  195. {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/WHEEL +0 -0
  196. {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/entry_points.txt +0 -0
  197. {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/licenses/LICENSE +0 -0
sqlspec/driver/_async.py CHANGED
@@ -1,14 +1,18 @@
1
1
  """Asynchronous driver protocol implementation."""
2
2
 
3
3
  from abc import abstractmethod
4
- from typing import TYPE_CHECKING, Any, Final, NoReturn, Optional, TypeVar, Union, cast, overload
4
+ from typing import TYPE_CHECKING, Any, Final, TypeVar, overload
5
5
 
6
6
  from sqlspec.core import SQL, Statement
7
- from sqlspec.driver._common import CommonDriverAttributesMixin, DataDictionaryMixin, ExecutionResult, VersionInfo
8
- from sqlspec.driver.mixins import SQLTranslatorMixin, ToSchemaMixin
9
- from sqlspec.exceptions import NotFoundError
7
+ from sqlspec.driver._common import (
8
+ CommonDriverAttributesMixin,
9
+ DataDictionaryMixin,
10
+ ExecutionResult,
11
+ VersionInfo,
12
+ handle_single_row_error,
13
+ )
14
+ from sqlspec.driver.mixins import SQLTranslatorMixin
10
15
  from sqlspec.utils.logging import get_logger
11
- from sqlspec.utils.type_guards import is_dict_row, is_indexable_row
12
16
 
13
17
  if TYPE_CHECKING:
14
18
  from collections.abc import Sequence
@@ -16,7 +20,7 @@ if TYPE_CHECKING:
16
20
 
17
21
  from sqlspec.builder import QueryBuilder
18
22
  from sqlspec.core import SQLResult, StatementConfig, StatementFilter
19
- from sqlspec.typing import ModelDTOT, StatementParameters
23
+ from sqlspec.typing import SchemaT, StatementParameters
20
24
 
21
25
  _LOGGER_NAME: Final[str] = "sqlspec"
22
26
  logger = get_logger(_LOGGER_NAME)
@@ -29,7 +33,7 @@ EMPTY_FILTERS: Final["list[StatementFilter]"] = []
29
33
  AsyncDriverT = TypeVar("AsyncDriverT", bound="AsyncDriverAdapterBase")
30
34
 
31
35
 
32
- class AsyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToSchemaMixin):
36
+ class AsyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin):
33
37
  """Base class for asynchronous database drivers."""
34
38
 
35
39
  __slots__ = ()
@@ -96,7 +100,7 @@ class AsyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, To
96
100
  """Commit the current transaction on the current connection."""
97
101
 
98
102
  @abstractmethod
99
- async def _try_special_handling(self, cursor: Any, statement: "SQL") -> "Optional[SQLResult]":
103
+ async def _try_special_handling(self, cursor: Any, statement: "SQL") -> "SQLResult | None":
100
104
  """Hook for database-specific special operations (e.g., PostgreSQL COPY, bulk operations).
101
105
 
102
106
  This method is called first in dispatch_statement_execution() to allow drivers to handle
@@ -169,10 +173,10 @@ class AsyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, To
169
173
 
170
174
  async def execute(
171
175
  self,
172
- statement: "Union[SQL, Statement, QueryBuilder]",
176
+ statement: "SQL | Statement | QueryBuilder",
173
177
  /,
174
- *parameters: "Union[StatementParameters, StatementFilter]",
175
- statement_config: "Optional[StatementConfig]" = None,
178
+ *parameters: "StatementParameters | StatementFilter",
179
+ statement_config: "StatementConfig | None" = None,
176
180
  **kwargs: Any,
177
181
  ) -> "SQLResult":
178
182
  """Execute a statement with parameter handling."""
@@ -183,11 +187,11 @@ class AsyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, To
183
187
 
184
188
  async def execute_many(
185
189
  self,
186
- statement: "Union[SQL, Statement, QueryBuilder]",
190
+ statement: "SQL | Statement | QueryBuilder",
187
191
  /,
188
192
  parameters: "Sequence[StatementParameters]",
189
- *filters: "Union[StatementParameters, StatementFilter]",
190
- statement_config: "Optional[StatementConfig]" = None,
193
+ *filters: "StatementParameters | StatementFilter",
194
+ statement_config: "StatementConfig | None" = None,
191
195
  **kwargs: Any,
192
196
  ) -> "SQLResult":
193
197
  """Execute statement multiple times with different parameters.
@@ -206,10 +210,10 @@ class AsyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, To
206
210
 
207
211
  async def execute_script(
208
212
  self,
209
- statement: "Union[str, SQL]",
213
+ statement: "str | SQL",
210
214
  /,
211
- *parameters: "Union[StatementParameters, StatementFilter]",
212
- statement_config: "Optional[StatementConfig]" = None,
215
+ *parameters: "StatementParameters | StatementFilter",
216
+ statement_config: "StatementConfig | None" = None,
213
217
  **kwargs: Any,
214
218
  ) -> "SQLResult":
215
219
  """Execute a multi-statement script.
@@ -225,140 +229,124 @@ class AsyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, To
225
229
  @overload
226
230
  async def select_one(
227
231
  self,
228
- statement: "Union[Statement, QueryBuilder]",
232
+ statement: "Statement | QueryBuilder",
229
233
  /,
230
- *parameters: "Union[StatementParameters, StatementFilter]",
231
- schema_type: "type[ModelDTOT]",
232
- statement_config: "Optional[StatementConfig]" = None,
234
+ *parameters: "StatementParameters | StatementFilter",
235
+ schema_type: "type[SchemaT]",
236
+ statement_config: "StatementConfig | None" = None,
233
237
  **kwargs: Any,
234
- ) -> "ModelDTOT": ...
238
+ ) -> "SchemaT": ...
235
239
 
236
240
  @overload
237
241
  async def select_one(
238
242
  self,
239
- statement: "Union[Statement, QueryBuilder]",
243
+ statement: "Statement | QueryBuilder",
240
244
  /,
241
- *parameters: "Union[StatementParameters, StatementFilter]",
245
+ *parameters: "StatementParameters | StatementFilter",
242
246
  schema_type: None = None,
243
- statement_config: "Optional[StatementConfig]" = None,
247
+ statement_config: "StatementConfig | None" = None,
244
248
  **kwargs: Any,
245
249
  ) -> "dict[str, Any]": ...
246
250
 
247
251
  async def select_one(
248
252
  self,
249
- statement: "Union[Statement, QueryBuilder]",
253
+ statement: "Statement | QueryBuilder",
250
254
  /,
251
- *parameters: "Union[StatementParameters, StatementFilter]",
252
- schema_type: "Optional[type[ModelDTOT]]" = None,
253
- statement_config: "Optional[StatementConfig]" = None,
255
+ *parameters: "StatementParameters | StatementFilter",
256
+ schema_type: "type[SchemaT] | None" = None,
257
+ statement_config: "StatementConfig | None" = None,
254
258
  **kwargs: Any,
255
- ) -> "Union[dict[str, Any], ModelDTOT]":
259
+ ) -> "SchemaT | dict[str, Any]":
256
260
  """Execute a select statement and return exactly one row.
257
261
 
258
262
  Raises an exception if no rows or more than one row is returned.
259
263
  """
260
264
  result = await self.execute(statement, *parameters, statement_config=statement_config, **kwargs)
261
- data = result.get_data()
262
- data_len: int = len(data)
263
- if data_len == 0:
264
- self._raise_no_rows_found()
265
- if data_len > 1:
266
- self._raise_expected_one_row(data_len)
267
- first_row = data[0]
268
- return self.to_schema(first_row, schema_type=schema_type) if schema_type else first_row
265
+ try:
266
+ return result.one(schema_type=schema_type)
267
+ except ValueError as error:
268
+ handle_single_row_error(error)
269
269
 
270
270
  @overload
271
271
  async def select_one_or_none(
272
272
  self,
273
- statement: "Union[Statement, QueryBuilder]",
273
+ statement: "Statement | QueryBuilder",
274
274
  /,
275
- *parameters: "Union[StatementParameters, StatementFilter]",
276
- schema_type: "type[ModelDTOT]",
277
- statement_config: "Optional[StatementConfig]" = None,
275
+ *parameters: "StatementParameters | StatementFilter",
276
+ schema_type: "type[SchemaT]",
277
+ statement_config: "StatementConfig | None" = None,
278
278
  **kwargs: Any,
279
- ) -> "Optional[ModelDTOT]": ...
279
+ ) -> "SchemaT | None": ...
280
280
 
281
281
  @overload
282
282
  async def select_one_or_none(
283
283
  self,
284
- statement: "Union[Statement, QueryBuilder]",
284
+ statement: "Statement | QueryBuilder",
285
285
  /,
286
- *parameters: "Union[StatementParameters, StatementFilter]",
286
+ *parameters: "StatementParameters | StatementFilter",
287
287
  schema_type: None = None,
288
- statement_config: "Optional[StatementConfig]" = None,
288
+ statement_config: "StatementConfig | None" = None,
289
289
  **kwargs: Any,
290
- ) -> "Optional[dict[str, Any]]": ...
290
+ ) -> "dict[str, Any] | None": ...
291
291
 
292
292
  async def select_one_or_none(
293
293
  self,
294
- statement: "Union[Statement, QueryBuilder]",
294
+ statement: "Statement | QueryBuilder",
295
295
  /,
296
- *parameters: "Union[StatementParameters, StatementFilter]",
297
- schema_type: "Optional[type[ModelDTOT]]" = None,
298
- statement_config: "Optional[StatementConfig]" = None,
296
+ *parameters: "StatementParameters | StatementFilter",
297
+ schema_type: "type[SchemaT] | None" = None,
298
+ statement_config: "StatementConfig | None" = None,
299
299
  **kwargs: Any,
300
- ) -> "Optional[Union[dict[str, Any], ModelDTOT]]":
300
+ ) -> "SchemaT | dict[str, Any] | None":
301
301
  """Execute a select statement and return at most one row.
302
302
 
303
303
  Returns None if no rows are found.
304
304
  Raises an exception if more than one row is returned.
305
305
  """
306
306
  result = await self.execute(statement, *parameters, statement_config=statement_config, **kwargs)
307
- data = result.get_data()
308
- data_len: int = len(data)
309
- if data_len == 0:
310
- return None
311
- if data_len > 1:
312
- self._raise_expected_at_most_one_row(data_len)
313
- first_row = data[0]
314
- return cast(
315
- "Optional[Union[dict[str, Any], ModelDTOT]]",
316
- self.to_schema(first_row, schema_type=schema_type) if schema_type else first_row,
317
- )
307
+ return result.one_or_none(schema_type=schema_type)
318
308
 
319
309
  @overload
320
310
  async def select(
321
311
  self,
322
- statement: "Union[Statement, QueryBuilder]",
312
+ statement: "Statement | QueryBuilder",
323
313
  /,
324
- *parameters: "Union[StatementParameters, StatementFilter]",
325
- schema_type: "type[ModelDTOT]",
326
- statement_config: "Optional[StatementConfig]" = None,
314
+ *parameters: "StatementParameters | StatementFilter",
315
+ schema_type: "type[SchemaT]",
316
+ statement_config: "StatementConfig | None" = None,
327
317
  **kwargs: Any,
328
- ) -> "list[ModelDTOT]": ...
318
+ ) -> "list[SchemaT]": ...
329
319
 
330
320
  @overload
331
321
  async def select(
332
322
  self,
333
- statement: "Union[Statement, QueryBuilder]",
323
+ statement: "Statement | QueryBuilder",
334
324
  /,
335
- *parameters: "Union[StatementParameters, StatementFilter]",
325
+ *parameters: "StatementParameters | StatementFilter",
336
326
  schema_type: None = None,
337
- statement_config: "Optional[StatementConfig]" = None,
327
+ statement_config: "StatementConfig | None" = None,
338
328
  **kwargs: Any,
339
329
  ) -> "list[dict[str, Any]]": ...
340
330
 
341
331
  async def select(
342
332
  self,
343
- statement: "Union[Statement, QueryBuilder]",
333
+ statement: "Statement | QueryBuilder",
344
334
  /,
345
- *parameters: "Union[StatementParameters, StatementFilter]",
346
- schema_type: "Optional[type[ModelDTOT]]" = None,
347
- statement_config: "Optional[StatementConfig]" = None,
335
+ *parameters: "StatementParameters | StatementFilter",
336
+ schema_type: "type[SchemaT] | None" = None,
337
+ statement_config: "StatementConfig | None" = None,
348
338
  **kwargs: Any,
349
- ) -> "Union[list[dict[str, Any]], list[ModelDTOT]]":
339
+ ) -> "list[SchemaT] | list[dict[str, Any]]":
350
340
  """Execute a select statement and return all rows."""
351
341
  result = await self.execute(statement, *parameters, statement_config=statement_config, **kwargs)
352
- return cast(
353
- "Union[list[dict[str, Any]], list[ModelDTOT]]", self.to_schema(result.get_data(), schema_type=schema_type)
354
- )
342
+ return result.get_data(schema_type=schema_type)
355
343
 
356
344
  async def select_value(
357
345
  self,
358
- statement: "Union[Statement, QueryBuilder]",
346
+ statement: "Statement | QueryBuilder",
359
347
  /,
360
- *parameters: "Union[StatementParameters, StatementFilter]",
361
- statement_config: "Optional[StatementConfig]" = None,
348
+ *parameters: "StatementParameters | StatementFilter",
349
+ statement_config: "StatementConfig | None" = None,
362
350
  **kwargs: Any,
363
351
  ) -> Any:
364
352
  """Execute a select statement and return a single scalar value.
@@ -368,28 +356,16 @@ class AsyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, To
368
356
  """
369
357
  result = await self.execute(statement, *parameters, statement_config=statement_config, **kwargs)
370
358
  try:
371
- row = result.one()
372
- except ValueError as e:
373
- self._raise_no_rows_found_from_exception(e)
374
- if not row:
375
- self._raise_no_rows_found()
376
- if is_dict_row(row):
377
- if not row:
378
- self._raise_row_no_columns()
379
- return next(iter(row.values()))
380
- if is_indexable_row(row):
381
- if not row:
382
- self._raise_row_no_columns()
383
- return row[0]
384
- self._raise_unexpected_row_type(type(row))
385
- return None
359
+ return result.scalar()
360
+ except ValueError as error:
361
+ handle_single_row_error(error)
386
362
 
387
363
  async def select_value_or_none(
388
364
  self,
389
- statement: "Union[Statement, QueryBuilder]",
365
+ statement: "Statement | QueryBuilder",
390
366
  /,
391
- *parameters: "Union[StatementParameters, StatementFilter]",
392
- statement_config: "Optional[StatementConfig]" = None,
367
+ *parameters: "StatementParameters | StatementFilter",
368
+ statement_config: "StatementConfig | None" = None,
393
369
  **kwargs: Any,
394
370
  ) -> Any:
395
371
  """Execute a select statement and return a single scalar value or None.
@@ -399,53 +375,39 @@ class AsyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, To
399
375
  Raises an exception if more than one row is returned.
400
376
  """
401
377
  result = await self.execute(statement, *parameters, statement_config=statement_config, **kwargs)
402
- data = result.get_data()
403
- data_len: int = len(data)
404
- if data_len == 0:
405
- return None
406
- if data_len > 1:
407
- self._raise_expected_at_most_one_row(data_len)
408
- row = data[0]
409
- if is_dict_row(row):
410
- if not row:
411
- return None
412
- return next(iter(row.values()))
413
- if is_indexable_row(row):
414
- return row[0]
415
- self._raise_cannot_extract_value_from_row_type(type(row).__name__)
416
- return None
378
+ return result.scalar_or_none()
417
379
 
418
380
  @overload
419
381
  async def select_with_total(
420
382
  self,
421
- statement: "Union[Statement, QueryBuilder]",
383
+ statement: "Statement | QueryBuilder",
422
384
  /,
423
- *parameters: "Union[StatementParameters, StatementFilter]",
424
- schema_type: "type[ModelDTOT]",
425
- statement_config: "Optional[StatementConfig]" = None,
385
+ *parameters: "StatementParameters | StatementFilter",
386
+ schema_type: "type[SchemaT]",
387
+ statement_config: "StatementConfig | None" = None,
426
388
  **kwargs: Any,
427
- ) -> "tuple[list[ModelDTOT], int]": ...
389
+ ) -> "tuple[list[SchemaT], int]": ...
428
390
 
429
391
  @overload
430
392
  async def select_with_total(
431
393
  self,
432
- statement: "Union[Statement, QueryBuilder]",
394
+ statement: "Statement | QueryBuilder",
433
395
  /,
434
- *parameters: "Union[StatementParameters, StatementFilter]",
396
+ *parameters: "StatementParameters | StatementFilter",
435
397
  schema_type: None = None,
436
- statement_config: "Optional[StatementConfig]" = None,
398
+ statement_config: "StatementConfig | None" = None,
437
399
  **kwargs: Any,
438
400
  ) -> "tuple[list[dict[str, Any]], int]": ...
439
401
 
440
402
  async def select_with_total(
441
403
  self,
442
- statement: "Union[Statement, QueryBuilder]",
404
+ statement: "Statement | QueryBuilder",
443
405
  /,
444
- *parameters: "Union[StatementParameters, StatementFilter]",
445
- schema_type: "Optional[type[ModelDTOT]]" = None,
446
- statement_config: "Optional[StatementConfig]" = None,
406
+ *parameters: "StatementParameters | StatementFilter",
407
+ schema_type: "type[SchemaT] | None" = None,
408
+ statement_config: "StatementConfig | None" = None,
447
409
  **kwargs: Any,
448
- ) -> "tuple[Union[list[dict[str, Any]], list[ModelDTOT]], int]":
410
+ ) -> "tuple[list[SchemaT] | list[dict[str, Any]], int]":
449
411
  """Execute a select statement and return both the data and total count.
450
412
 
451
413
  This method is designed for pagination scenarios where you need both
@@ -469,42 +431,14 @@ class AsyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, To
469
431
  count_result = await self.dispatch_statement_execution(self._create_count_query(sql_statement), self.connection)
470
432
  select_result = await self.execute(sql_statement)
471
433
 
472
- return (self.to_schema(select_result.get_data(), schema_type=schema_type), count_result.scalar())
473
-
474
- def _raise_no_rows_found(self) -> NoReturn:
475
- msg = "No rows found"
476
- raise NotFoundError(msg)
477
-
478
- def _raise_no_rows_found_from_exception(self, e: ValueError) -> NoReturn:
479
- msg = "No rows found"
480
- raise NotFoundError(msg) from e
481
-
482
- def _raise_expected_one_row(self, data_len: int) -> NoReturn:
483
- msg = f"Expected exactly one row, found {data_len}"
484
- raise ValueError(msg)
485
-
486
- def _raise_expected_at_most_one_row(self, data_len: int) -> NoReturn:
487
- msg = f"Expected at most one row, found {data_len}"
488
- raise ValueError(msg)
489
-
490
- def _raise_row_no_columns(self) -> NoReturn:
491
- msg = "Row has no columns"
492
- raise ValueError(msg)
493
-
494
- def _raise_unexpected_row_type(self, row_type: type) -> NoReturn:
495
- msg = f"Unexpected row type: {row_type}"
496
- raise ValueError(msg)
497
-
498
- def _raise_cannot_extract_value_from_row_type(self, type_name: str) -> NoReturn:
499
- msg = f"Cannot extract value from row type {type_name}"
500
- raise TypeError(msg)
434
+ return (select_result.get_data(schema_type=schema_type), count_result.scalar())
501
435
 
502
436
 
503
437
  class AsyncDataDictionaryBase(DataDictionaryMixin):
504
438
  """Base class for asynchronous data dictionary implementations."""
505
439
 
506
440
  @abstractmethod
507
- async def get_version(self, driver: "AsyncDriverAdapterBase") -> "Optional[VersionInfo]":
441
+ async def get_version(self, driver: "AsyncDriverAdapterBase") -> "VersionInfo | None":
508
442
  """Get database version information.
509
443
 
510
444
  Args:
@@ -538,7 +472,7 @@ class AsyncDataDictionaryBase(DataDictionaryMixin):
538
472
  Database-specific type name
539
473
  """
540
474
 
541
- async def get_tables(self, driver: "AsyncDriverAdapterBase", schema: "Optional[str]" = None) -> "list[str]":
475
+ async def get_tables(self, driver: "AsyncDriverAdapterBase", schema: "str | None" = None) -> "list[str]":
542
476
  """Get list of tables in schema.
543
477
 
544
478
  Args:
@@ -552,7 +486,7 @@ class AsyncDataDictionaryBase(DataDictionaryMixin):
552
486
  return []
553
487
 
554
488
  async def get_columns(
555
- self, driver: "AsyncDriverAdapterBase", table: str, schema: "Optional[str]" = None
489
+ self, driver: "AsyncDriverAdapterBase", table: str, schema: "str | None" = None
556
490
  ) -> "list[dict[str, Any]]":
557
491
  """Get column information for a table.
558
492
 
@@ -568,7 +502,7 @@ class AsyncDataDictionaryBase(DataDictionaryMixin):
568
502
  return []
569
503
 
570
504
  async def get_indexes(
571
- self, driver: "AsyncDriverAdapterBase", table: str, schema: "Optional[str]" = None
505
+ self, driver: "AsyncDriverAdapterBase", table: str, schema: "str | None" = None
572
506
  ) -> "list[dict[str, Any]]":
573
507
  """Get index information for a table.
574
508