sqlspec 0.26.0__py3-none-any.whl → 0.28.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 (212) hide show
  1. sqlspec/__init__.py +7 -15
  2. sqlspec/_serialization.py +55 -25
  3. sqlspec/_typing.py +155 -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 +880 -0
  7. sqlspec/adapters/adbc/config.py +62 -12
  8. sqlspec/adapters/adbc/data_dictionary.py +74 -2
  9. sqlspec/adapters/adbc/driver.py +226 -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 +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 +536 -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 +503 -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 +460 -0
  36. sqlspec/adapters/asyncpg/config.py +57 -36
  37. sqlspec/adapters/asyncpg/data_dictionary.py +48 -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 +585 -0
  44. sqlspec/adapters/bigquery/config.py +36 -11
  45. sqlspec/adapters/bigquery/data_dictionary.py +42 -2
  46. sqlspec/adapters/bigquery/driver.py +489 -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 +563 -0
  53. sqlspec/adapters/duckdb/config.py +79 -21
  54. sqlspec/adapters/duckdb/data_dictionary.py +41 -2
  55. sqlspec/adapters/duckdb/driver.py +225 -44
  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 +1628 -0
  64. sqlspec/adapters/oracledb/config.py +120 -36
  65. sqlspec/adapters/oracledb/data_dictionary.py +87 -20
  66. sqlspec/adapters/oracledb/driver.py +475 -86
  67. sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
  68. sqlspec/adapters/oracledb/litestar/store.py +765 -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 +483 -0
  75. sqlspec/adapters/psqlpy/config.py +45 -19
  76. sqlspec/adapters/psqlpy/data_dictionary.py +48 -2
  77. sqlspec/adapters/psqlpy/driver.py +108 -41
  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 +962 -0
  85. sqlspec/adapters/psycopg/config.py +65 -37
  86. sqlspec/adapters/psycopg/data_dictionary.py +91 -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 +582 -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 +331 -62
  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 +55 -47
  121. sqlspec/core/hashing.py +9 -9
  122. sqlspec/core/parameters.py +76 -45
  123. sqlspec/core/result.py +234 -47
  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 +183 -160
  129. sqlspec/driver/_common.py +197 -109
  130. sqlspec/driver/_sync.py +189 -161
  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 +69 -61
  142. sqlspec/extensions/fastapi/__init__.py +21 -0
  143. sqlspec/extensions/fastapi/extension.py +331 -0
  144. sqlspec/extensions/fastapi/providers.py +543 -0
  145. sqlspec/extensions/flask/__init__.py +36 -0
  146. sqlspec/extensions/flask/_state.py +71 -0
  147. sqlspec/extensions/flask/_utils.py +40 -0
  148. sqlspec/extensions/flask/extension.py +389 -0
  149. sqlspec/extensions/litestar/__init__.py +21 -4
  150. sqlspec/extensions/litestar/cli.py +54 -10
  151. sqlspec/extensions/litestar/config.py +56 -266
  152. sqlspec/extensions/litestar/handlers.py +46 -17
  153. sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
  154. sqlspec/extensions/litestar/migrations/__init__.py +3 -0
  155. sqlspec/extensions/litestar/plugin.py +349 -224
  156. sqlspec/extensions/litestar/providers.py +25 -25
  157. sqlspec/extensions/litestar/store.py +265 -0
  158. sqlspec/extensions/starlette/__init__.py +10 -0
  159. sqlspec/extensions/starlette/_state.py +25 -0
  160. sqlspec/extensions/starlette/_utils.py +52 -0
  161. sqlspec/extensions/starlette/extension.py +254 -0
  162. sqlspec/extensions/starlette/middleware.py +154 -0
  163. sqlspec/loader.py +30 -49
  164. sqlspec/migrations/base.py +200 -76
  165. sqlspec/migrations/commands.py +591 -62
  166. sqlspec/migrations/context.py +6 -9
  167. sqlspec/migrations/fix.py +199 -0
  168. sqlspec/migrations/loaders.py +47 -19
  169. sqlspec/migrations/runner.py +241 -75
  170. sqlspec/migrations/tracker.py +237 -21
  171. sqlspec/migrations/utils.py +51 -3
  172. sqlspec/migrations/validation.py +177 -0
  173. sqlspec/protocols.py +106 -36
  174. sqlspec/storage/_utils.py +85 -0
  175. sqlspec/storage/backends/fsspec.py +133 -107
  176. sqlspec/storage/backends/local.py +78 -51
  177. sqlspec/storage/backends/obstore.py +276 -168
  178. sqlspec/storage/registry.py +75 -39
  179. sqlspec/typing.py +30 -84
  180. sqlspec/utils/__init__.py +25 -4
  181. sqlspec/utils/arrow_helpers.py +81 -0
  182. sqlspec/utils/config_resolver.py +6 -6
  183. sqlspec/utils/correlation.py +4 -5
  184. sqlspec/utils/data_transformation.py +3 -2
  185. sqlspec/utils/deprecation.py +9 -8
  186. sqlspec/utils/fixtures.py +4 -4
  187. sqlspec/utils/logging.py +46 -6
  188. sqlspec/utils/module_loader.py +205 -5
  189. sqlspec/utils/portal.py +311 -0
  190. sqlspec/utils/schema.py +288 -0
  191. sqlspec/utils/serializers.py +113 -4
  192. sqlspec/utils/sync_tools.py +36 -22
  193. sqlspec/utils/text.py +1 -2
  194. sqlspec/utils/type_guards.py +136 -20
  195. sqlspec/utils/version.py +433 -0
  196. {sqlspec-0.26.0.dist-info → sqlspec-0.28.0.dist-info}/METADATA +41 -22
  197. sqlspec-0.28.0.dist-info/RECORD +221 -0
  198. sqlspec/builder/mixins/__init__.py +0 -55
  199. sqlspec/builder/mixins/_cte_and_set_ops.py +0 -253
  200. sqlspec/builder/mixins/_delete_operations.py +0 -50
  201. sqlspec/builder/mixins/_insert_operations.py +0 -282
  202. sqlspec/builder/mixins/_merge_operations.py +0 -698
  203. sqlspec/builder/mixins/_order_limit_operations.py +0 -145
  204. sqlspec/builder/mixins/_pivot_operations.py +0 -157
  205. sqlspec/builder/mixins/_select_operations.py +0 -930
  206. sqlspec/builder/mixins/_update_operations.py +0 -199
  207. sqlspec/builder/mixins/_where_clause.py +0 -1298
  208. sqlspec-0.26.0.dist-info/RECORD +0 -157
  209. sqlspec-0.26.0.dist-info/licenses/NOTICE +0 -29
  210. {sqlspec-0.26.0.dist-info → sqlspec-0.28.0.dist-info}/WHEEL +0 -0
  211. {sqlspec-0.26.0.dist-info → sqlspec-0.28.0.dist-info}/entry_points.txt +0 -0
  212. {sqlspec-0.26.0.dist-info → sqlspec-0.28.0.dist-info}/licenses/LICENSE +0 -0
sqlspec/driver/_sync.py CHANGED
@@ -1,14 +1,22 @@
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.core.result import create_arrow_result
8
+ from sqlspec.driver._common import (
9
+ CommonDriverAttributesMixin,
10
+ DataDictionaryMixin,
11
+ ExecutionResult,
12
+ VersionInfo,
13
+ handle_single_row_error,
14
+ )
15
+ from sqlspec.driver.mixins import SQLTranslatorMixin
16
+ from sqlspec.exceptions import ImproperConfigurationError
17
+ from sqlspec.utils.arrow_helpers import convert_dict_to_arrow
10
18
  from sqlspec.utils.logging import get_logger
11
- from sqlspec.utils.type_guards import is_dict_row, is_indexable_row
19
+ from sqlspec.utils.module_loader import ensure_pyarrow
12
20
 
13
21
  if TYPE_CHECKING:
14
22
  from collections.abc import Sequence
@@ -16,7 +24,7 @@ if TYPE_CHECKING:
16
24
 
17
25
  from sqlspec.builder import QueryBuilder
18
26
  from sqlspec.core import SQLResult, Statement, StatementConfig, StatementFilter
19
- from sqlspec.typing import ModelDTOT, StatementParameters
27
+ from sqlspec.typing import SchemaT, StatementParameters
20
28
 
21
29
  _LOGGER_NAME: Final[str] = "sqlspec"
22
30
  logger = get_logger(_LOGGER_NAME)
@@ -29,7 +37,7 @@ EMPTY_FILTERS: Final["list[StatementFilter]"] = []
29
37
  SyncDriverT = TypeVar("SyncDriverT", bound="SyncDriverAdapterBase")
30
38
 
31
39
 
32
- class SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToSchemaMixin):
40
+ class SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin):
33
41
  """Base class for synchronous database drivers."""
34
42
 
35
43
  __slots__ = ()
@@ -96,7 +104,7 @@ class SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToS
96
104
  """Commit the current transaction on the current connection."""
97
105
 
98
106
  @abstractmethod
99
- def _try_special_handling(self, cursor: Any, statement: "SQL") -> "Optional[SQLResult]":
107
+ def _try_special_handling(self, cursor: Any, statement: "SQL") -> "SQLResult | None":
100
108
  """Hook for database-specific special operations (e.g., PostgreSQL COPY, bulk operations).
101
109
 
102
110
  This method is called first in dispatch_statement_execution() to allow drivers to handle
@@ -169,10 +177,10 @@ class SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToS
169
177
 
170
178
  def execute(
171
179
  self,
172
- statement: "Union[SQL, Statement, QueryBuilder]",
180
+ statement: "SQL | Statement | QueryBuilder",
173
181
  /,
174
- *parameters: "Union[StatementParameters, StatementFilter]",
175
- statement_config: "Optional[StatementConfig]" = None,
182
+ *parameters: "StatementParameters | StatementFilter",
183
+ statement_config: "StatementConfig | None" = None,
176
184
  **kwargs: Any,
177
185
  ) -> "SQLResult":
178
186
  """Execute a statement with parameter handling."""
@@ -183,11 +191,11 @@ class SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToS
183
191
 
184
192
  def execute_many(
185
193
  self,
186
- statement: "Union[SQL, Statement, QueryBuilder]",
194
+ statement: "SQL | Statement | QueryBuilder",
187
195
  /,
188
196
  parameters: "Sequence[StatementParameters]",
189
- *filters: "Union[StatementParameters, StatementFilter]",
190
- statement_config: "Optional[StatementConfig]" = None,
197
+ *filters: "StatementParameters | StatementFilter",
198
+ statement_config: "StatementConfig | None" = None,
191
199
  **kwargs: Any,
192
200
  ) -> "SQLResult":
193
201
  """Execute statement multiple times with different parameters.
@@ -206,10 +214,10 @@ class SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToS
206
214
 
207
215
  def execute_script(
208
216
  self,
209
- statement: "Union[str, SQL]",
217
+ statement: "str | SQL",
210
218
  /,
211
- *parameters: "Union[StatementParameters, StatementFilter]",
212
- statement_config: "Optional[StatementConfig]" = None,
219
+ *parameters: "StatementParameters | StatementFilter",
220
+ statement_config: "StatementConfig | None" = None,
213
221
  **kwargs: Any,
214
222
  ) -> "SQLResult":
215
223
  """Execute a multi-statement script.
@@ -225,140 +233,215 @@ class SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToS
225
233
  @overload
226
234
  def select_one(
227
235
  self,
228
- statement: "Union[Statement, QueryBuilder]",
236
+ statement: "Statement | QueryBuilder",
229
237
  /,
230
- *parameters: "Union[StatementParameters, StatementFilter]",
231
- schema_type: "type[ModelDTOT]",
232
- statement_config: "Optional[StatementConfig]" = None,
238
+ *parameters: "StatementParameters | StatementFilter",
239
+ schema_type: "type[SchemaT]",
240
+ statement_config: "StatementConfig | None" = None,
233
241
  **kwargs: Any,
234
- ) -> "ModelDTOT": ...
242
+ ) -> "SchemaT": ...
235
243
 
236
244
  @overload
237
245
  def select_one(
238
246
  self,
239
- statement: "Union[Statement, QueryBuilder]",
247
+ statement: "Statement | QueryBuilder",
240
248
  /,
241
- *parameters: "Union[StatementParameters, StatementFilter]",
249
+ *parameters: "StatementParameters | StatementFilter",
242
250
  schema_type: None = None,
243
- statement_config: "Optional[StatementConfig]" = None,
251
+ statement_config: "StatementConfig | None" = None,
244
252
  **kwargs: Any,
245
253
  ) -> "dict[str, Any]": ...
246
254
 
247
255
  def select_one(
248
256
  self,
249
- statement: "Union[Statement, QueryBuilder]",
257
+ statement: "Statement | QueryBuilder",
250
258
  /,
251
- *parameters: "Union[StatementParameters, StatementFilter]",
252
- schema_type: "Optional[type[ModelDTOT]]" = None,
253
- statement_config: "Optional[StatementConfig]" = None,
259
+ *parameters: "StatementParameters | StatementFilter",
260
+ schema_type: "type[SchemaT] | None" = None,
261
+ statement_config: "StatementConfig | None" = None,
254
262
  **kwargs: Any,
255
- ) -> "Union[dict[str, Any], ModelDTOT]":
263
+ ) -> "SchemaT | dict[str, Any]":
256
264
  """Execute a select statement and return exactly one row.
257
265
 
258
266
  Raises an exception if no rows or more than one row is returned.
259
267
  """
260
268
  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
269
+ try:
270
+ return result.one(schema_type=schema_type)
271
+ except ValueError as error:
272
+ handle_single_row_error(error)
269
273
 
270
274
  @overload
271
275
  def select_one_or_none(
272
276
  self,
273
- statement: "Union[Statement, QueryBuilder]",
277
+ statement: "Statement | QueryBuilder",
274
278
  /,
275
- *parameters: "Union[StatementParameters, StatementFilter]",
276
- schema_type: "type[ModelDTOT]",
277
- statement_config: "Optional[StatementConfig]" = None,
279
+ *parameters: "StatementParameters | StatementFilter",
280
+ schema_type: "type[SchemaT]",
281
+ statement_config: "StatementConfig | None" = None,
278
282
  **kwargs: Any,
279
- ) -> "Optional[ModelDTOT]": ...
283
+ ) -> "SchemaT | None": ...
280
284
 
281
285
  @overload
282
286
  def select_one_or_none(
283
287
  self,
284
- statement: "Union[Statement, QueryBuilder]",
288
+ statement: "Statement | QueryBuilder",
285
289
  /,
286
- *parameters: "Union[StatementParameters, StatementFilter]",
290
+ *parameters: "StatementParameters | StatementFilter",
287
291
  schema_type: None = None,
288
- statement_config: "Optional[StatementConfig]" = None,
292
+ statement_config: "StatementConfig | None" = None,
289
293
  **kwargs: Any,
290
- ) -> "Optional[dict[str, Any]]": ...
294
+ ) -> "dict[str, Any] | None": ...
291
295
 
292
296
  def select_one_or_none(
293
297
  self,
294
- statement: "Union[Statement, QueryBuilder]",
298
+ statement: "Statement | QueryBuilder",
295
299
  /,
296
- *parameters: "Union[StatementParameters, StatementFilter]",
297
- schema_type: "Optional[type[ModelDTOT]]" = None,
298
- statement_config: "Optional[StatementConfig]" = None,
300
+ *parameters: "StatementParameters | StatementFilter",
301
+ schema_type: "type[SchemaT] | None" = None,
302
+ statement_config: "StatementConfig | None" = None,
299
303
  **kwargs: Any,
300
- ) -> "Optional[Union[dict[str, Any], ModelDTOT]]":
304
+ ) -> "SchemaT | dict[str, Any] | None":
301
305
  """Execute a select statement and return at most one row.
302
306
 
303
307
  Returns None if no rows are found.
304
308
  Raises an exception if more than one row is returned.
305
309
  """
306
310
  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
- )
311
+ return result.one_or_none(schema_type=schema_type)
318
312
 
319
313
  @overload
320
314
  def select(
321
315
  self,
322
- statement: "Union[Statement, QueryBuilder]",
316
+ statement: "Statement | QueryBuilder",
323
317
  /,
324
- *parameters: "Union[StatementParameters, StatementFilter]",
325
- schema_type: "type[ModelDTOT]",
326
- statement_config: "Optional[StatementConfig]" = None,
318
+ *parameters: "StatementParameters | StatementFilter",
319
+ schema_type: "type[SchemaT]",
320
+ statement_config: "StatementConfig | None" = None,
327
321
  **kwargs: Any,
328
- ) -> "list[ModelDTOT]": ...
322
+ ) -> "list[SchemaT]": ...
329
323
 
330
324
  @overload
331
325
  def select(
332
326
  self,
333
- statement: "Union[Statement, QueryBuilder]",
327
+ statement: "Statement | QueryBuilder",
334
328
  /,
335
- *parameters: "Union[StatementParameters, StatementFilter]",
329
+ *parameters: "StatementParameters | StatementFilter",
336
330
  schema_type: None = None,
337
- statement_config: "Optional[StatementConfig]" = None,
331
+ statement_config: "StatementConfig | None" = None,
338
332
  **kwargs: Any,
339
333
  ) -> "list[dict[str, Any]]": ...
340
334
 
341
335
  def select(
342
336
  self,
343
- statement: "Union[Statement, QueryBuilder]",
337
+ statement: "Statement | QueryBuilder",
344
338
  /,
345
- *parameters: "Union[StatementParameters, StatementFilter]",
346
- schema_type: "Optional[type[ModelDTOT]]" = None,
347
- statement_config: "Optional[StatementConfig]" = None,
339
+ *parameters: "StatementParameters | StatementFilter",
340
+ schema_type: "type[SchemaT] | None" = None,
341
+ statement_config: "StatementConfig | None" = None,
348
342
  **kwargs: Any,
349
- ) -> "Union[list[dict[str, Any]], list[ModelDTOT]]":
343
+ ) -> "list[SchemaT] | list[dict[str, Any]]":
350
344
  """Execute a select statement and return all rows."""
351
345
  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)
346
+ return result.get_data(schema_type=schema_type)
347
+
348
+ def select_to_arrow(
349
+ self,
350
+ statement: "Statement | QueryBuilder",
351
+ /,
352
+ *parameters: "StatementParameters | StatementFilter",
353
+ statement_config: "StatementConfig | None" = None,
354
+ return_format: str = "table",
355
+ native_only: bool = False,
356
+ batch_size: int | None = None,
357
+ arrow_schema: Any = None,
358
+ **kwargs: Any,
359
+ ) -> "Any":
360
+ """Execute query and return results as Apache Arrow format.
361
+
362
+ This base implementation uses the conversion path: execute() → dict → Arrow.
363
+ Adapters with native Arrow support (ADBC, DuckDB, BigQuery) override this
364
+ method to use zero-copy native paths for 5-10x performance improvement.
365
+
366
+ Args:
367
+ statement: SQL query string, Statement, or QueryBuilder
368
+ *parameters: Query parameters (same format as execute()/select())
369
+ statement_config: Optional statement configuration override
370
+ return_format: "table" for pyarrow.Table (default), "reader" for RecordBatchReader,
371
+ "batches" for iterator of RecordBatches
372
+ native_only: If True, raise error if native Arrow unavailable (default: False)
373
+ batch_size: Rows per batch for "batches" format (default: None = all rows)
374
+ arrow_schema: Optional pyarrow.Schema for type casting
375
+ **kwargs: Additional keyword arguments
376
+
377
+ Returns:
378
+ ArrowResult containing pyarrow.Table, RecordBatchReader, or RecordBatches
379
+
380
+ Raises:
381
+ MissingDependencyError: If pyarrow not installed
382
+ ImproperConfigurationError: If native_only=True and adapter doesn't support native Arrow
383
+ SQLExecutionError: If query execution fails
384
+
385
+ Examples:
386
+ >>> result = driver.select_to_arrow(
387
+ ... "SELECT * FROM users WHERE age > ?", 18
388
+ ... )
389
+ >>> df = result.to_pandas()
390
+ >>> print(df.head())
391
+
392
+ >>> # Force native Arrow path (raises error if unavailable)
393
+ >>> result = driver.select_to_arrow(
394
+ ... "SELECT * FROM users", native_only=True
395
+ ... )
396
+ """
397
+ # Check pyarrow is available
398
+ ensure_pyarrow()
399
+
400
+ # Check if native_only requested but not supported
401
+ if native_only:
402
+ msg = (
403
+ f"Adapter '{self.__class__.__name__}' does not support native Arrow results. "
404
+ f"Use native_only=False to allow conversion path, or switch to an adapter "
405
+ f"with native Arrow support (ADBC, DuckDB, BigQuery)."
406
+ )
407
+ raise ImproperConfigurationError(msg)
408
+
409
+ # Execute query using standard path
410
+ result = self.execute(statement, *parameters, statement_config=statement_config, **kwargs)
411
+
412
+ # Convert dict results to Arrow
413
+ arrow_data = convert_dict_to_arrow(
414
+ result.data,
415
+ return_format=return_format, # type: ignore[arg-type]
416
+ batch_size=batch_size,
417
+ )
418
+
419
+ # Apply schema casting if requested
420
+ if arrow_schema is not None:
421
+ import pyarrow as pa
422
+
423
+ if not isinstance(arrow_schema, pa.Schema):
424
+ msg = f"arrow_schema must be a pyarrow.Schema, got {type(arrow_schema).__name__}"
425
+ raise TypeError(msg)
426
+
427
+ arrow_data = arrow_data.cast(arrow_schema)
428
+
429
+ # Create ArrowResult
430
+ return create_arrow_result(
431
+ statement=result.statement,
432
+ data=arrow_data,
433
+ rows_affected=result.rows_affected,
434
+ last_inserted_id=result.last_inserted_id,
435
+ execution_time=result.execution_time,
436
+ metadata=result.metadata,
354
437
  )
355
438
 
356
439
  def select_value(
357
440
  self,
358
- statement: "Union[Statement, QueryBuilder]",
441
+ statement: "Statement | QueryBuilder",
359
442
  /,
360
- *parameters: "Union[StatementParameters, StatementFilter]",
361
- statement_config: "Optional[StatementConfig]" = None,
443
+ *parameters: "StatementParameters | StatementFilter",
444
+ statement_config: "StatementConfig | None" = None,
362
445
  **kwargs: Any,
363
446
  ) -> Any:
364
447
  """Execute a select statement and return a single scalar value.
@@ -368,28 +451,16 @@ class SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToS
368
451
  """
369
452
  result = self.execute(statement, *parameters, statement_config=statement_config, **kwargs)
370
453
  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
454
+ return result.scalar()
455
+ except ValueError as error:
456
+ handle_single_row_error(error)
386
457
 
387
458
  def select_value_or_none(
388
459
  self,
389
- statement: "Union[Statement, QueryBuilder]",
460
+ statement: "Statement | QueryBuilder",
390
461
  /,
391
- *parameters: "Union[StatementParameters, StatementFilter]",
392
- statement_config: "Optional[StatementConfig]" = None,
462
+ *parameters: "StatementParameters | StatementFilter",
463
+ statement_config: "StatementConfig | None" = None,
393
464
  **kwargs: Any,
394
465
  ) -> Any:
395
466
  """Execute a select statement and return a single scalar value or None.
@@ -399,54 +470,39 @@ class SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToS
399
470
  Raises an exception if more than one row is returned.
400
471
  """
401
472
  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)
473
+ return result.scalar_or_none()
418
474
 
419
475
  @overload
420
476
  def select_with_total(
421
477
  self,
422
- statement: "Union[Statement, QueryBuilder]",
478
+ statement: "Statement | QueryBuilder",
423
479
  /,
424
- *parameters: "Union[StatementParameters, StatementFilter]",
425
- schema_type: "type[ModelDTOT]",
426
- statement_config: "Optional[StatementConfig]" = None,
480
+ *parameters: "StatementParameters | StatementFilter",
481
+ schema_type: "type[SchemaT]",
482
+ statement_config: "StatementConfig | None" = None,
427
483
  **kwargs: Any,
428
- ) -> "tuple[list[ModelDTOT], int]": ...
484
+ ) -> "tuple[list[SchemaT], int]": ...
429
485
 
430
486
  @overload
431
487
  def select_with_total(
432
488
  self,
433
- statement: "Union[Statement, QueryBuilder]",
489
+ statement: "Statement | QueryBuilder",
434
490
  /,
435
- *parameters: "Union[StatementParameters, StatementFilter]",
491
+ *parameters: "StatementParameters | StatementFilter",
436
492
  schema_type: None = None,
437
- statement_config: "Optional[StatementConfig]" = None,
493
+ statement_config: "StatementConfig | None" = None,
438
494
  **kwargs: Any,
439
495
  ) -> "tuple[list[dict[str, Any]], int]": ...
440
496
 
441
497
  def select_with_total(
442
498
  self,
443
- statement: "Union[Statement, QueryBuilder]",
499
+ statement: "Statement | QueryBuilder",
444
500
  /,
445
- *parameters: "Union[StatementParameters, StatementFilter]",
446
- schema_type: "Optional[type[ModelDTOT]]" = None,
447
- statement_config: "Optional[StatementConfig]" = None,
501
+ *parameters: "StatementParameters | StatementFilter",
502
+ schema_type: "type[SchemaT] | None" = None,
503
+ statement_config: "StatementConfig | None" = None,
448
504
  **kwargs: Any,
449
- ) -> "tuple[Union[list[dict[str, Any]], list[ModelDTOT]], int]":
505
+ ) -> "tuple[list[SchemaT] | list[dict[str, Any]], int]":
450
506
  """Execute a select statement and return both the data and total count.
451
507
 
452
508
  This method is designed for pagination scenarios where you need both
@@ -470,42 +526,14 @@ class SyncDriverAdapterBase(CommonDriverAttributesMixin, SQLTranslatorMixin, ToS
470
526
  count_result = self.dispatch_statement_execution(self._create_count_query(sql_statement), self.connection)
471
527
  select_result = self.execute(sql_statement)
472
528
 
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)
529
+ return (select_result.get_data(schema_type=schema_type), count_result.scalar())
502
530
 
503
531
 
504
532
  class SyncDataDictionaryBase(DataDictionaryMixin):
505
533
  """Base class for synchronous data dictionary implementations."""
506
534
 
507
535
  @abstractmethod
508
- def get_version(self, driver: "SyncDriverAdapterBase") -> "Optional[VersionInfo]":
536
+ def get_version(self, driver: "SyncDriverAdapterBase") -> "VersionInfo | None":
509
537
  """Get database version information.
510
538
 
511
539
  Args:
@@ -539,7 +567,7 @@ class SyncDataDictionaryBase(DataDictionaryMixin):
539
567
  Database-specific type name
540
568
  """
541
569
 
542
- def get_tables(self, driver: "SyncDriverAdapterBase", schema: "Optional[str]" = None) -> "list[str]":
570
+ def get_tables(self, driver: "SyncDriverAdapterBase", schema: "str | None" = None) -> "list[str]":
543
571
  """Get list of tables in schema.
544
572
 
545
573
  Args:
@@ -553,7 +581,7 @@ class SyncDataDictionaryBase(DataDictionaryMixin):
553
581
  return []
554
582
 
555
583
  def get_columns(
556
- self, driver: "SyncDriverAdapterBase", table: str, schema: "Optional[str]" = None
584
+ self, driver: "SyncDriverAdapterBase", table: str, schema: "str | None" = None
557
585
  ) -> "list[dict[str, Any]]":
558
586
  """Get column information for a table.
559
587
 
@@ -569,7 +597,7 @@ class SyncDataDictionaryBase(DataDictionaryMixin):
569
597
  return []
570
598
 
571
599
  def get_indexes(
572
- self, driver: "SyncDriverAdapterBase", table: str, schema: "Optional[str]" = None
600
+ self, driver: "SyncDriverAdapterBase", table: str, schema: "str | None" = None
573
601
  ) -> "list[dict[str, Any]]":
574
602
  """Get index information for a table.
575
603