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/protocols.py CHANGED
@@ -4,7 +4,7 @@ This module provides protocols that can be used for static type checking
4
4
  and runtime isinstance() checks.
5
5
  """
6
6
 
7
- from typing import TYPE_CHECKING, Any, Optional, Protocol, Union, runtime_checkable
7
+ from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
8
8
 
9
9
  from typing_extensions import Self
10
10
 
@@ -39,6 +39,7 @@ __all__ = (
39
39
  "ParameterValueProtocol",
40
40
  "SQLBuilderProtocol",
41
41
  "SelectBuilderProtocol",
42
+ "SupportsArrowResults",
42
43
  "WithMethodProtocol",
43
44
  )
44
45
 
@@ -186,7 +187,7 @@ class ObjectStoreItemProtocol(Protocol):
186
187
  """Protocol for object store items with path/key attributes."""
187
188
 
188
189
  path: str
189
- key: "Optional[str]"
190
+ key: "str | None"
190
191
 
191
192
 
192
193
  @runtime_checkable
@@ -199,35 +200,35 @@ class ObjectStoreProtocol(Protocol):
199
200
  def __init__(self, uri: str, **kwargs: Any) -> None:
200
201
  return
201
202
 
202
- def read_bytes(self, path: "Union[str, Path]", **kwargs: Any) -> bytes:
203
+ def read_bytes(self, path: "str | Path", **kwargs: Any) -> bytes:
203
204
  """Read bytes from an object."""
204
205
  return b""
205
206
 
206
- def write_bytes(self, path: "Union[str, Path]", data: bytes, **kwargs: Any) -> None:
207
+ def write_bytes(self, path: "str | Path", data: bytes, **kwargs: Any) -> None:
207
208
  """Write bytes to an object."""
208
209
  return
209
210
 
210
- def read_text(self, path: "Union[str, Path]", encoding: str = "utf-8", **kwargs: Any) -> str:
211
+ def read_text(self, path: "str | Path", encoding: str = "utf-8", **kwargs: Any) -> str:
211
212
  """Read text from an object."""
212
213
  return ""
213
214
 
214
- def write_text(self, path: "Union[str, Path]", data: str, encoding: str = "utf-8", **kwargs: Any) -> None:
215
+ def write_text(self, path: "str | Path", data: str, encoding: str = "utf-8", **kwargs: Any) -> None:
215
216
  """Write text to an object."""
216
217
  return
217
218
 
218
- def exists(self, path: "Union[str, Path]", **kwargs: Any) -> bool:
219
+ def exists(self, path: "str | Path", **kwargs: Any) -> bool:
219
220
  """Check if an object exists."""
220
221
  return False
221
222
 
222
- def delete(self, path: "Union[str, Path]", **kwargs: Any) -> None:
223
+ def delete(self, path: "str | Path", **kwargs: Any) -> None:
223
224
  """Delete an object."""
224
225
  return
225
226
 
226
- def copy(self, source: "Union[str, Path]", destination: "Union[str, Path]", **kwargs: Any) -> None:
227
+ def copy(self, source: "str | Path", destination: "str | Path", **kwargs: Any) -> None:
227
228
  """Copy an object."""
228
229
  return
229
230
 
230
- def move(self, source: "Union[str, Path]", destination: "Union[str, Path]", **kwargs: Any) -> None:
231
+ def move(self, source: "str | Path", destination: "str | Path", **kwargs: Any) -> None:
231
232
  """Move an object."""
232
233
  return
233
234
 
@@ -239,24 +240,24 @@ class ObjectStoreProtocol(Protocol):
239
240
  """Find objects matching a glob pattern."""
240
241
  return []
241
242
 
242
- def is_object(self, path: "Union[str, Path]") -> bool:
243
+ def is_object(self, path: "str | Path") -> bool:
243
244
  """Check if path points to an object."""
244
245
  return False
245
246
 
246
- def is_path(self, path: "Union[str, Path]") -> bool:
247
+ def is_path(self, path: "str | Path") -> bool:
247
248
  """Check if path points to a prefix (directory-like)."""
248
249
  return False
249
250
 
250
- def get_metadata(self, path: "Union[str, Path]", **kwargs: Any) -> dict[str, Any]:
251
+ def get_metadata(self, path: "str | Path", **kwargs: Any) -> dict[str, Any]:
251
252
  """Get object metadata."""
252
253
  return {}
253
254
 
254
- def read_arrow(self, path: "Union[str, Path]", **kwargs: Any) -> "ArrowTable":
255
+ def read_arrow(self, path: "str | Path", **kwargs: Any) -> "ArrowTable":
255
256
  """Read an Arrow table from storage."""
256
257
  msg = "Arrow reading not implemented"
257
258
  raise NotImplementedError(msg)
258
259
 
259
- def write_arrow(self, path: "Union[str, Path]", table: "ArrowTable", **kwargs: Any) -> None:
260
+ def write_arrow(self, path: "str | Path", table: "ArrowTable", **kwargs: Any) -> None:
260
261
  """Write an Arrow table to storage."""
261
262
  msg = "Arrow writing not implemented"
262
263
  raise NotImplementedError(msg)
@@ -266,34 +267,32 @@ class ObjectStoreProtocol(Protocol):
266
267
  msg = "Arrow streaming not implemented"
267
268
  raise NotImplementedError(msg)
268
269
 
269
- async def read_bytes_async(self, path: "Union[str, Path]", **kwargs: Any) -> bytes:
270
+ async def read_bytes_async(self, path: "str | Path", **kwargs: Any) -> bytes:
270
271
  """Async read bytes from an object."""
271
272
  msg = "Async operations not implemented"
272
273
  raise NotImplementedError(msg)
273
274
 
274
- async def write_bytes_async(self, path: "Union[str, Path]", data: bytes, **kwargs: Any) -> None:
275
+ async def write_bytes_async(self, path: "str | Path", data: bytes, **kwargs: Any) -> None:
275
276
  """Async write bytes to an object."""
276
277
  msg = "Async operations not implemented"
277
278
  raise NotImplementedError(msg)
278
279
 
279
- async def read_text_async(self, path: "Union[str, Path]", encoding: str = "utf-8", **kwargs: Any) -> str:
280
+ async def read_text_async(self, path: "str | Path", encoding: str = "utf-8", **kwargs: Any) -> str:
280
281
  """Async read text from an object."""
281
282
  msg = "Async operations not implemented"
282
283
  raise NotImplementedError(msg)
283
284
 
284
- async def write_text_async(
285
- self, path: "Union[str, Path]", data: str, encoding: str = "utf-8", **kwargs: Any
286
- ) -> None:
285
+ async def write_text_async(self, path: "str | Path", data: str, encoding: str = "utf-8", **kwargs: Any) -> None:
287
286
  """Async write text to an object."""
288
287
  msg = "Async operations not implemented"
289
288
  raise NotImplementedError(msg)
290
289
 
291
- async def exists_async(self, path: "Union[str, Path]", **kwargs: Any) -> bool:
290
+ async def exists_async(self, path: "str | Path", **kwargs: Any) -> bool:
292
291
  """Async check if an object exists."""
293
292
  msg = "Async operations not implemented"
294
293
  raise NotImplementedError(msg)
295
294
 
296
- async def delete_async(self, path: "Union[str, Path]", **kwargs: Any) -> None:
295
+ async def delete_async(self, path: "str | Path", **kwargs: Any) -> None:
297
296
  """Async delete an object."""
298
297
  msg = "Async operations not implemented"
299
298
  raise NotImplementedError(msg)
@@ -303,27 +302,27 @@ class ObjectStoreProtocol(Protocol):
303
302
  msg = "Async operations not implemented"
304
303
  raise NotImplementedError(msg)
305
304
 
306
- async def copy_async(self, source: "Union[str, Path]", destination: "Union[str, Path]", **kwargs: Any) -> None:
305
+ async def copy_async(self, source: "str | Path", destination: "str | Path", **kwargs: Any) -> None:
307
306
  """Async copy an object."""
308
307
  msg = "Async operations not implemented"
309
308
  raise NotImplementedError(msg)
310
309
 
311
- async def move_async(self, source: "Union[str, Path]", destination: "Union[str, Path]", **kwargs: Any) -> None:
310
+ async def move_async(self, source: "str | Path", destination: "str | Path", **kwargs: Any) -> None:
312
311
  """Async move an object."""
313
312
  msg = "Async operations not implemented"
314
313
  raise NotImplementedError(msg)
315
314
 
316
- async def get_metadata_async(self, path: "Union[str, Path]", **kwargs: Any) -> dict[str, Any]:
315
+ async def get_metadata_async(self, path: "str | Path", **kwargs: Any) -> dict[str, Any]:
317
316
  """Async get object metadata."""
318
317
  msg = "Async operations not implemented"
319
318
  raise NotImplementedError(msg)
320
319
 
321
- async def read_arrow_async(self, path: "Union[str, Path]", **kwargs: Any) -> "ArrowTable":
320
+ async def read_arrow_async(self, path: "str | Path", **kwargs: Any) -> "ArrowTable":
322
321
  """Async read an Arrow table from storage."""
323
322
  msg = "Async arrow reading not implemented"
324
323
  raise NotImplementedError(msg)
325
324
 
326
- async def write_arrow_async(self, path: "Union[str, Path]", table: "ArrowTable", **kwargs: Any) -> None:
325
+ async def write_arrow_async(self, path: "str | Path", table: "ArrowTable", **kwargs: Any) -> None:
327
326
  """Async write an Arrow table to storage."""
328
327
  msg = "Async arrow writing not implemented"
329
328
  raise NotImplementedError(msg)
@@ -339,7 +338,7 @@ class HasSQLGlotExpressionProtocol(Protocol):
339
338
  """Protocol for objects with a sqlglot_expression property."""
340
339
 
341
340
  @property
342
- def sqlglot_expression(self) -> "Optional[exp.Expression]":
341
+ def sqlglot_expression(self) -> "exp.Expression | None":
343
342
  """Return the SQLGlot expression for this object."""
344
343
  ...
345
344
 
@@ -348,16 +347,24 @@ class HasSQLGlotExpressionProtocol(Protocol):
348
347
  class HasParameterBuilderProtocol(Protocol):
349
348
  """Protocol for objects that can add parameters."""
350
349
 
351
- def add_parameter(self, value: Any, name: "Optional[str]" = None) -> tuple[Any, str]:
350
+ def add_parameter(self, value: Any, name: "str | None" = None) -> tuple[Any, str]:
352
351
  """Add a parameter to the builder."""
353
352
  ...
354
353
 
354
+ def get_expression(self) -> "exp.Expression | None":
355
+ """Return the underlying SQLGlot expression."""
356
+ ...
357
+
358
+ def set_expression(self, expression: "exp.Expression") -> None:
359
+ """Replace the underlying SQLGlot expression."""
360
+ ...
361
+
355
362
 
356
363
  @runtime_checkable
357
364
  class HasExpressionProtocol(Protocol):
358
365
  """Protocol for objects with an _expression attribute."""
359
366
 
360
- _expression: "Optional[exp.Expression]"
367
+ _expression: "exp.Expression | None"
361
368
 
362
369
 
363
370
  @runtime_checkable
@@ -373,21 +380,21 @@ class HasToStatementProtocol(Protocol):
373
380
  class SQLBuilderProtocol(Protocol):
374
381
  """Protocol for SQL query builders."""
375
382
 
376
- _expression: "Optional[exp.Expression]"
383
+ _expression: "exp.Expression | None"
377
384
  _parameters: dict[str, Any]
378
385
  _parameter_counter: int
379
386
  _columns: Any # Optional attribute for some builders
380
387
  _table: Any # Optional attribute for some builders
381
388
  _with_ctes: Any # Optional attribute for some builders
382
389
  dialect: Any
383
- dialect_name: "Optional[str]"
390
+ dialect_name: "str | None"
384
391
 
385
392
  @property
386
393
  def parameters(self) -> dict[str, Any]:
387
394
  """Public access to query parameters."""
388
395
  ...
389
396
 
390
- def add_parameter(self, value: Any, name: "Optional[str]" = None) -> tuple[Any, str]:
397
+ def add_parameter(self, value: Any, name: "str | None" = None) -> tuple[Any, str]:
391
398
  """Add a parameter to the builder."""
392
399
  ...
393
400
 
@@ -399,14 +406,77 @@ class SQLBuilderProtocol(Protocol):
399
406
  """Replace literal values in an expression with bound parameters."""
400
407
  ...
401
408
 
402
- def build(self) -> "Union[exp.Expression, Any]":
409
+ def build(self) -> "exp.Expression | Any":
403
410
  """Build and return the final expression."""
404
411
  ...
405
412
 
413
+ def _merge_sql_object_parameters(self, sql_obj: Any) -> None:
414
+ """Merge parameters from SQL objects into the builder."""
415
+ ...
416
+
417
+ def _build_final_expression(self, *, copy: bool = False) -> "exp.Expression":
418
+ """Return the expression with attached CTEs."""
419
+ ...
420
+
421
+ def _spawn_like_self(self) -> "Self":
422
+ """Create a new builder with matching configuration."""
423
+ ...
424
+
425
+ def get_expression(self) -> "exp.Expression | None":
426
+ """Return the underlying SQLGlot expression."""
427
+ ...
428
+
429
+ def set_expression(self, expression: "exp.Expression") -> None:
430
+ """Replace the underlying SQLGlot expression."""
431
+ ...
432
+
433
+ def generate_unique_parameter_name(self, base_name: str) -> str:
434
+ """Generate a unique parameter name exposed via public API."""
435
+ ...
436
+
406
437
 
407
438
  class SelectBuilderProtocol(SQLBuilderProtocol, Protocol):
408
439
  """Protocol for SELECT query builders."""
409
440
 
410
- def select(self, *columns: "Union[str, exp.Expression]") -> Self:
441
+ def select(self, *columns: "str | exp.Expression") -> Self:
411
442
  """Add SELECT columns to the query."""
412
443
  ...
444
+
445
+
446
+ @runtime_checkable
447
+ class SupportsArrowResults(Protocol):
448
+ """Protocol for adapters that support Arrow result format.
449
+
450
+ Adapters implementing this protocol can return query results in Apache Arrow
451
+ format via the select_to_arrow() method, enabling zero-copy data transfer and
452
+ efficient integration with data science tools.
453
+ """
454
+
455
+ def select_to_arrow(
456
+ self,
457
+ statement: Any,
458
+ /,
459
+ *parameters: Any,
460
+ statement_config: Any | None = None,
461
+ return_format: str = "table",
462
+ native_only: bool = False,
463
+ batch_size: int | None = None,
464
+ arrow_schema: Any | None = None,
465
+ **kwargs: Any,
466
+ ) -> "ArrowTable | ArrowRecordBatch":
467
+ """Execute query and return results as Apache Arrow Table or RecordBatch.
468
+
469
+ Args:
470
+ statement: SQL statement to execute.
471
+ *parameters: Query parameters and filters.
472
+ statement_config: Optional statement configuration override.
473
+ return_format: Output format - "table", "reader", or "batches".
474
+ native_only: If True, raise error when native Arrow path unavailable.
475
+ batch_size: Chunk size for streaming modes.
476
+ arrow_schema: Optional target Arrow schema for type casting.
477
+ **kwargs: Additional keyword arguments.
478
+
479
+ Returns:
480
+ ArrowResult containing Arrow data.
481
+ """
482
+ ...
@@ -0,0 +1,85 @@
1
+ """Shared utilities for storage backends."""
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from pathlib import Path
7
+
8
+ __all__ = ("resolve_storage_path",)
9
+
10
+
11
+ def resolve_storage_path(
12
+ path: "str | Path", base_path: str = "", protocol: str = "file", strip_file_scheme: bool = True
13
+ ) -> str:
14
+ """Resolve path relative to base_path with protocol-specific handling.
15
+
16
+ Args:
17
+ path: Path to resolve (may include file:// scheme).
18
+ base_path: Base path to prepend if path is relative.
19
+ protocol: Storage protocol (file, s3, gs, etc.).
20
+ strip_file_scheme: Whether to strip file:// prefix.
21
+
22
+ Returns:
23
+ Resolved path string suitable for the storage backend.
24
+
25
+ Examples:
26
+ >>> resolve_storage_path("/data/file.txt", protocol="file")
27
+ 'data/file.txt'
28
+
29
+ >>> resolve_storage_path(
30
+ ... "file.txt", base_path="/base", protocol="file"
31
+ ... )
32
+ 'base/file.txt'
33
+
34
+ >>> resolve_storage_path(
35
+ ... "file:///data/file.txt", strip_file_scheme=True
36
+ ... )
37
+ 'data/file.txt'
38
+
39
+ >>> resolve_storage_path(
40
+ ... "/data/subdir/file.txt",
41
+ ... base_path="/data",
42
+ ... protocol="file",
43
+ ... )
44
+ 'subdir/file.txt'
45
+ """
46
+ from pathlib import Path as PathlibPath
47
+
48
+ path_str = str(path)
49
+
50
+ if strip_file_scheme and path_str.startswith("file://"):
51
+ path_str = path_str.removeprefix("file://")
52
+
53
+ # For local file protocol
54
+ if protocol == "file":
55
+ path_obj = PathlibPath(path_str)
56
+
57
+ # Absolute path handling
58
+ if path_obj.is_absolute():
59
+ if base_path:
60
+ base_obj = PathlibPath(base_path)
61
+ # Try to make path relative to base_path
62
+ try:
63
+ relative = path_obj.relative_to(base_obj)
64
+ # Return joined path for FSSpec-style backends
65
+ return f"{base_path.rstrip('/')}/{relative}"
66
+ except ValueError:
67
+ # Path is outside base_path
68
+ return path_str.lstrip("/")
69
+ # No base_path - strip leading /
70
+ return path_str.lstrip("/")
71
+
72
+ # Relative path with base_path - join them
73
+ if base_path:
74
+ return f"{base_path.rstrip('/')}/{path_str}"
75
+
76
+ # Relative path without base_path
77
+ return path_str
78
+
79
+ # For cloud storage protocols (s3, gs, etc.), join with base_path
80
+ if not base_path:
81
+ return path_str
82
+
83
+ clean_base = base_path.rstrip("/")
84
+ clean_path = path_str.lstrip("/")
85
+ return f"{clean_base}/{clean_path}"