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/_sync.py CHANGED
@@ -1,14 +1,18 @@
1
1
  """Synchronous 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
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, Statement, 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
  SyncDriverT = TypeVar("SyncDriverT", bound="SyncDriverAdapterBase")
30
34
 
31
35
 
32
- class SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToSchemaMixin):
36
+ class SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin):
33
37
  """Base class for synchronous database drivers."""
34
38
 
35
39
  __slots__ = ()
@@ -96,7 +100,7 @@ class SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToS
96
100
  """Commit the current transaction on the current connection."""
97
101
 
98
102
  @abstractmethod
99
- def _try_special_handling(self, cursor: Any, statement: "SQL") -> "Optional[SQLResult]":
103
+ 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 SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToS
169
173
 
170
174
  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 SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToS
183
187
 
184
188
  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 SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToS
206
210
 
207
211
  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 SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToS
225
229
  @overload
226
230
  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
  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
  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 = 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
  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
  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
  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 = 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
  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
  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
  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 = 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
  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 SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToS
368
356
  """
369
357
  result = 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
  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,54 +375,39 @@ class SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToS
399
375
  Raises an exception if more than one row is returned.
400
376
  """
401
377
  result = 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
- msg = f"Expected at most one row, found {data_len}"
408
- raise ValueError(msg)
409
- row = data[0]
410
- if isinstance(row, dict):
411
- if not row:
412
- return None
413
- return next(iter(row.values()))
414
- if isinstance(row, (tuple, list)):
415
- return row[0]
416
- msg = f"Cannot extract value from row type {type(row).__name__}"
417
- raise TypeError(msg)
378
+ return result.scalar_or_none()
418
379
 
419
380
  @overload
420
381
  def select_with_total(
421
382
  self,
422
- statement: "Union[Statement, QueryBuilder]",
383
+ statement: "Statement | QueryBuilder",
423
384
  /,
424
- *parameters: "Union[StatementParameters, StatementFilter]",
425
- schema_type: "type[ModelDTOT]",
426
- statement_config: "Optional[StatementConfig]" = None,
385
+ *parameters: "StatementParameters | StatementFilter",
386
+ schema_type: "type[SchemaT]",
387
+ statement_config: "StatementConfig | None" = None,
427
388
  **kwargs: Any,
428
- ) -> "tuple[list[ModelDTOT], int]": ...
389
+ ) -> "tuple[list[SchemaT], int]": ...
429
390
 
430
391
  @overload
431
392
  def select_with_total(
432
393
  self,
433
- statement: "Union[Statement, QueryBuilder]",
394
+ statement: "Statement | QueryBuilder",
434
395
  /,
435
- *parameters: "Union[StatementParameters, StatementFilter]",
396
+ *parameters: "StatementParameters | StatementFilter",
436
397
  schema_type: None = None,
437
- statement_config: "Optional[StatementConfig]" = None,
398
+ statement_config: "StatementConfig | None" = None,
438
399
  **kwargs: Any,
439
400
  ) -> "tuple[list[dict[str, Any]], int]": ...
440
401
 
441
402
  def select_with_total(
442
403
  self,
443
- statement: "Union[Statement, QueryBuilder]",
404
+ statement: "Statement | QueryBuilder",
444
405
  /,
445
- *parameters: "Union[StatementParameters, StatementFilter]",
446
- schema_type: "Optional[type[ModelDTOT]]" = None,
447
- statement_config: "Optional[StatementConfig]" = None,
406
+ *parameters: "StatementParameters | StatementFilter",
407
+ schema_type: "type[SchemaT] | None" = None,
408
+ statement_config: "StatementConfig | None" = None,
448
409
  **kwargs: Any,
449
- ) -> "tuple[Union[list[dict[str, Any]], list[ModelDTOT]], int]":
410
+ ) -> "tuple[list[SchemaT] | list[dict[str, Any]], int]":
450
411
  """Execute a select statement and return both the data and total count.
451
412
 
452
413
  This method is designed for pagination scenarios where you need both
@@ -470,42 +431,14 @@ class SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToS
470
431
  count_result = self.dispatch_statement_execution(self._create_count_query(sql_statement), self.connection)
471
432
  select_result = self.execute(sql_statement)
472
433
 
473
- return (self.to_schema(select_result.get_data(), schema_type=schema_type), count_result.scalar())
474
-
475
- def _raise_no_rows_found(self) -> NoReturn:
476
- msg = "No rows found"
477
- raise NotFoundError(msg)
478
-
479
- def _raise_no_rows_found_from_exception(self, e: ValueError) -> NoReturn:
480
- msg = "No rows found"
481
- raise NotFoundError(msg) from e
482
-
483
- def _raise_expected_one_row(self, data_len: int) -> NoReturn:
484
- msg = f"Expected exactly one row, found {data_len}"
485
- raise ValueError(msg)
486
-
487
- def _raise_expected_at_most_one_row(self, data_len: int) -> NoReturn:
488
- msg = f"Expected at most one row, found {data_len}"
489
- raise ValueError(msg)
490
-
491
- def _raise_row_no_columns(self) -> NoReturn:
492
- msg = "Row has no columns"
493
- raise ValueError(msg)
494
-
495
- def _raise_unexpected_row_type(self, row_type: type) -> NoReturn:
496
- msg = f"Unexpected row type: {row_type}"
497
- raise ValueError(msg)
498
-
499
- def _raise_cannot_extract_value_from_row_type(self, type_name: str) -> NoReturn:
500
- msg = f"Cannot extract value from row type {type_name}"
501
- raise TypeError(msg)
434
+ return (select_result.get_data(schema_type=schema_type), count_result.scalar())
502
435
 
503
436
 
504
437
  class SyncDataDictionaryBase(DataDictionaryMixin):
505
438
  """Base class for synchronous data dictionary implementations."""
506
439
 
507
440
  @abstractmethod
508
- def get_version(self, driver: "SyncDriverAdapterBase") -> "Optional[VersionInfo]":
441
+ def get_version(self, driver: "SyncDriverAdapterBase") -> "VersionInfo | None":
509
442
  """Get database version information.
510
443
 
511
444
  Args:
@@ -539,7 +472,7 @@ class SyncDataDictionaryBase(DataDictionaryMixin):
539
472
  Database-specific type name
540
473
  """
541
474
 
542
- def get_tables(self, driver: "SyncDriverAdapterBase", schema: "Optional[str]" = None) -> "list[str]":
475
+ def get_tables(self, driver: "SyncDriverAdapterBase", schema: "str | None" = None) -> "list[str]":
543
476
  """Get list of tables in schema.
544
477
 
545
478
  Args:
@@ -553,7 +486,7 @@ class SyncDataDictionaryBase(DataDictionaryMixin):
553
486
  return []
554
487
 
555
488
  def get_columns(
556
- self, driver: "SyncDriverAdapterBase", table: str, schema: "Optional[str]" = None
489
+ self, driver: "SyncDriverAdapterBase", table: str, schema: "str | None" = None
557
490
  ) -> "list[dict[str, Any]]":
558
491
  """Get column information for a table.
559
492
 
@@ -569,7 +502,7 @@ class SyncDataDictionaryBase(DataDictionaryMixin):
569
502
  return []
570
503
 
571
504
  def get_indexes(
572
- self, driver: "SyncDriverAdapterBase", table: str, schema: "Optional[str]" = None
505
+ self, driver: "SyncDriverAdapterBase", table: str, schema: "str | None" = None
573
506
  ) -> "list[dict[str, Any]]":
574
507
  """Get index information for a table.
575
508