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