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/_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, 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, 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, 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)
23
27
 
24
- __all__ = ("SyncDriverAdapterBase",)
28
+ __all__ = ("SyncDataDictionaryBase", "SyncDriverAdapterBase", "SyncDriverT")
25
29
 
26
30
 
27
31
  EMPTY_FILTERS: Final["list[StatementFilter]"] = []
28
32
 
33
+ SyncDriverT = TypeVar("SyncDriverT", bound="SyncDriverAdapterBase")
29
34
 
30
- class SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToSchemaMixin):
35
+
36
+ class SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin):
31
37
  """Base class for synchronous database drivers."""
32
38
 
33
39
  __slots__ = ()
34
40
 
41
+ @property
42
+ @abstractmethod
43
+ def data_dictionary(self) -> "SyncDataDictionaryBase":
44
+ """Get the data dictionary for this driver.
45
+
46
+ Returns:
47
+ Data dictionary instance for metadata queries
48
+ """
49
+
35
50
  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 SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToS
85
100
  """Commit the current transaction on the current connection."""
86
101
 
87
102
  @abstractmethod
88
- def _try_special_handling(self, cursor: Any, statement: "SQL") -> "Optional[SQLResult]":
103
+ 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 SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToS
158
173
 
159
174
  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 SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToS
172
187
 
173
188
  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 SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToS
195
210
 
196
211
  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 SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToS
214
229
  @overload
215
230
  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
  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
  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 = 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
  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
  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
  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 = 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
  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
  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
  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 = 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
  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 SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToS
357
356
  """
358
357
  result = 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
  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,54 +375,39 @@ class SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToS
388
375
  Raises an exception if more than one row is returned.
389
376
  """
390
377
  result = 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
- msg = f"Expected at most one row, found {data_len}"
397
- raise ValueError(msg)
398
- row = data[0]
399
- if isinstance(row, dict):
400
- if not row:
401
- return None
402
- return next(iter(row.values()))
403
- if isinstance(row, (tuple, list)):
404
- return row[0]
405
- msg = f"Cannot extract value from row type {type(row).__name__}"
406
- raise TypeError(msg)
378
+ return result.scalar_or_none()
407
379
 
408
380
  @overload
409
381
  def select_with_total(
410
382
  self,
411
- statement: "Union[Statement, QueryBuilder]",
383
+ statement: "Statement | QueryBuilder",
412
384
  /,
413
- *parameters: "Union[StatementParameters, StatementFilter]",
414
- schema_type: "type[ModelDTOT]",
415
- statement_config: "Optional[StatementConfig]" = None,
385
+ *parameters: "StatementParameters | StatementFilter",
386
+ schema_type: "type[SchemaT]",
387
+ statement_config: "StatementConfig | None" = None,
416
388
  **kwargs: Any,
417
- ) -> "tuple[list[ModelDTOT], int]": ...
389
+ ) -> "tuple[list[SchemaT], int]": ...
418
390
 
419
391
  @overload
420
392
  def select_with_total(
421
393
  self,
422
- statement: "Union[Statement, QueryBuilder]",
394
+ statement: "Statement | QueryBuilder",
423
395
  /,
424
- *parameters: "Union[StatementParameters, StatementFilter]",
396
+ *parameters: "StatementParameters | StatementFilter",
425
397
  schema_type: None = None,
426
- statement_config: "Optional[StatementConfig]" = None,
398
+ statement_config: "StatementConfig | None" = None,
427
399
  **kwargs: Any,
428
400
  ) -> "tuple[list[dict[str, Any]], int]": ...
429
401
 
430
402
  def select_with_total(
431
403
  self,
432
- statement: "Union[Statement, QueryBuilder]",
404
+ statement: "Statement | QueryBuilder",
433
405
  /,
434
- *parameters: "Union[StatementParameters, StatementFilter]",
435
- schema_type: "Optional[type[ModelDTOT]]" = None,
436
- statement_config: "Optional[StatementConfig]" = None,
406
+ *parameters: "StatementParameters | StatementFilter",
407
+ schema_type: "type[SchemaT] | None" = None,
408
+ statement_config: "StatementConfig | None" = None,
437
409
  **kwargs: Any,
438
- ) -> "tuple[Union[list[dict[str, Any]], list[ModelDTOT]], int]":
410
+ ) -> "tuple[list[SchemaT] | list[dict[str, Any]], int]":
439
411
  """Execute a select statement and return both the data and total count.
440
412
 
441
413
  This method is designed for pagination scenarios where you need both
@@ -459,32 +431,96 @@ class SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToS
459
431
  count_result = self.dispatch_statement_execution(self._create_count_query(sql_statement), self.connection)
460
432
  select_result = self.execute(sql_statement)
461
433
 
462
- 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 SyncDataDictionaryBase(DataDictionaryMixin):
438
+ """Base class for synchronous data dictionary implementations."""
439
+
440
+ @abstractmethod
441
+ def get_version(self, driver: "SyncDriverAdapterBase") -> "VersionInfo | None":
442
+ """Get database version information.
443
+
444
+ Args:
445
+ driver: Sync database driver instance
463
446
 
464
- def _raise_no_rows_found(self) -> NoReturn:
465
- msg = "No rows found"
466
- raise NotFoundError(msg)
447
+ Returns:
448
+ Version information or None if detection fails
449
+ """
467
450
 
468
- def _raise_no_rows_found_from_exception(self, e: ValueError) -> NoReturn:
469
- msg = "No rows found"
470
- raise NotFoundError(msg) from e
451
+ @abstractmethod
452
+ def get_feature_flag(self, driver: "SyncDriverAdapterBase", feature: str) -> bool:
453
+ """Check if database supports a specific feature.
471
454
 
472
- def _raise_expected_one_row(self, data_len: int) -> NoReturn:
473
- msg = f"Expected exactly one row, found {data_len}"
474
- raise ValueError(msg)
455
+ Args:
456
+ driver: Sync database driver instance
457
+ feature: Feature name to check
475
458
 
476
- def _raise_expected_at_most_one_row(self, data_len: int) -> NoReturn:
477
- msg = f"Expected at most one row, found {data_len}"
478
- raise ValueError(msg)
459
+ Returns:
460
+ True if feature is supported, False otherwise
461
+ """
479
462
 
480
- def _raise_row_no_columns(self) -> NoReturn:
481
- msg = "Row has no columns"
482
- raise ValueError(msg)
463
+ @abstractmethod
464
+ def get_optimal_type(self, driver: "SyncDriverAdapterBase", type_category: str) -> str:
465
+ """Get optimal database type for a category.
483
466
 
484
- def _raise_unexpected_row_type(self, row_type: type) -> NoReturn:
485
- msg = f"Unexpected row type: {row_type}"
486
- raise ValueError(msg)
467
+ Args:
468
+ driver: Sync database driver instance
469
+ type_category: Type category (e.g., 'json', 'uuid', 'boolean')
487
470
 
488
- def _raise_cannot_extract_value_from_row_type(self, type_name: str) -> NoReturn:
489
- msg = f"Cannot extract value from row type {type_name}"
490
- raise TypeError(msg)
471
+ Returns:
472
+ Database-specific type name
473
+ """
474
+
475
+ def get_tables(self, driver: "SyncDriverAdapterBase", schema: "str | None" = None) -> "list[str]":
476
+ """Get list of tables in schema.
477
+
478
+ Args:
479
+ driver: Sync 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
+ def get_columns(
489
+ self, driver: "SyncDriverAdapterBase", table: str, schema: "str | None" = None
490
+ ) -> "list[dict[str, Any]]":
491
+ """Get column information for a table.
492
+
493
+ Args:
494
+ driver: Sync 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
+ def get_indexes(
505
+ self, driver: "SyncDriverAdapterBase", table: str, schema: "str | None" = None
506
+ ) -> "list[dict[str, Any]]":
507
+ """Get index information for a table.
508
+
509
+ Args:
510
+ driver: Sync 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()