sqlspec 0.8.0__py3-none-any.whl → 0.9.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 (45) hide show
  1. sqlspec/_typing.py +39 -6
  2. sqlspec/adapters/adbc/__init__.py +2 -2
  3. sqlspec/adapters/adbc/config.py +34 -11
  4. sqlspec/adapters/adbc/driver.py +167 -108
  5. sqlspec/adapters/aiosqlite/__init__.py +2 -2
  6. sqlspec/adapters/aiosqlite/config.py +2 -2
  7. sqlspec/adapters/aiosqlite/driver.py +28 -39
  8. sqlspec/adapters/asyncmy/__init__.py +3 -3
  9. sqlspec/adapters/asyncmy/config.py +11 -12
  10. sqlspec/adapters/asyncmy/driver.py +25 -34
  11. sqlspec/adapters/asyncpg/__init__.py +5 -5
  12. sqlspec/adapters/asyncpg/config.py +17 -19
  13. sqlspec/adapters/asyncpg/driver.py +249 -93
  14. sqlspec/adapters/duckdb/__init__.py +2 -2
  15. sqlspec/adapters/duckdb/config.py +2 -2
  16. sqlspec/adapters/duckdb/driver.py +49 -49
  17. sqlspec/adapters/oracledb/__init__.py +8 -8
  18. sqlspec/adapters/oracledb/config/__init__.py +6 -6
  19. sqlspec/adapters/oracledb/config/_asyncio.py +9 -10
  20. sqlspec/adapters/oracledb/config/_sync.py +8 -9
  21. sqlspec/adapters/oracledb/driver.py +114 -41
  22. sqlspec/adapters/psqlpy/__init__.py +0 -0
  23. sqlspec/adapters/psqlpy/config.py +258 -0
  24. sqlspec/adapters/psqlpy/driver.py +335 -0
  25. sqlspec/adapters/psycopg/__init__.py +10 -5
  26. sqlspec/adapters/psycopg/config/__init__.py +6 -6
  27. sqlspec/adapters/psycopg/config/_async.py +12 -12
  28. sqlspec/adapters/psycopg/config/_sync.py +13 -13
  29. sqlspec/adapters/psycopg/driver.py +180 -218
  30. sqlspec/adapters/sqlite/__init__.py +2 -2
  31. sqlspec/adapters/sqlite/config.py +2 -2
  32. sqlspec/adapters/sqlite/driver.py +43 -41
  33. sqlspec/base.py +275 -153
  34. sqlspec/exceptions.py +30 -0
  35. sqlspec/extensions/litestar/config.py +6 -0
  36. sqlspec/extensions/litestar/handlers.py +25 -0
  37. sqlspec/extensions/litestar/plugin.py +6 -1
  38. sqlspec/statement.py +373 -0
  39. sqlspec/typing.py +10 -1
  40. {sqlspec-0.8.0.dist-info → sqlspec-0.9.0.dist-info}/METADATA +4 -1
  41. sqlspec-0.9.0.dist-info/RECORD +61 -0
  42. sqlspec-0.8.0.dist-info/RECORD +0 -57
  43. {sqlspec-0.8.0.dist-info → sqlspec-0.9.0.dist-info}/WHEEL +0 -0
  44. {sqlspec-0.8.0.dist-info → sqlspec-0.9.0.dist-info}/licenses/LICENSE +0 -0
  45. {sqlspec-0.8.0.dist-info → sqlspec-0.9.0.dist-info}/licenses/NOTICE +0 -0
sqlspec/base.py CHANGED
@@ -1,10 +1,10 @@
1
1
  # ruff: noqa: PLR6301
2
2
  import re
3
3
  from abc import ABC, abstractmethod
4
- from collections.abc import AsyncGenerator, Awaitable, Generator
5
- from contextlib import AbstractAsyncContextManager, AbstractContextManager
4
+ from collections.abc import Awaitable
6
5
  from dataclasses import dataclass, field
7
6
  from typing import (
7
+ TYPE_CHECKING,
8
8
  Annotated,
9
9
  Any,
10
10
  ClassVar,
@@ -17,15 +17,28 @@ from typing import (
17
17
  )
18
18
 
19
19
  from sqlspec.exceptions import NotFoundError
20
+ from sqlspec.statement import SQLStatement
20
21
  from sqlspec.typing import ModelDTOT, StatementParameterType
21
22
 
23
+ if TYPE_CHECKING:
24
+ from contextlib import AbstractAsyncContextManager, AbstractContextManager
25
+
26
+ from pyarrow import Table as ArrowTable
27
+
22
28
  __all__ = (
29
+ "AsyncArrowBulkOperationsMixin",
23
30
  "AsyncDatabaseConfig",
31
+ "AsyncDriverAdapterProtocol",
32
+ "CommonDriverAttributes",
24
33
  "DatabaseConfigProtocol",
25
34
  "GenericPoolConfig",
26
35
  "NoPoolAsyncConfig",
27
36
  "NoPoolSyncConfig",
37
+ "SQLSpec",
38
+ "SQLStatement",
39
+ "SyncArrowBulkOperationsMixin",
28
40
  "SyncDatabaseConfig",
41
+ "SyncDriverAdapterProtocol",
29
42
  )
30
43
 
31
44
  T = TypeVar("T")
@@ -39,13 +52,15 @@ ConfigT = TypeVar(
39
52
  bound="Union[Union[AsyncDatabaseConfig[Any, Any, Any], NoPoolAsyncConfig[Any, Any]], SyncDatabaseConfig[Any, Any, Any], NoPoolSyncConfig[Any, Any]]",
40
53
  )
41
54
  DriverT = TypeVar("DriverT", bound="Union[SyncDriverAdapterProtocol[Any], AsyncDriverAdapterProtocol[Any]]")
42
-
43
- # Regex to find :param style placeholders, avoiding those inside quotes
44
- # Handles basic cases, might need refinement for complex SQL
55
+ # Regex to find :param or %(param)s style placeholders, skipping those inside quotes
45
56
  PARAM_REGEX = re.compile(
46
- r"(?P<dquote>\"(?:[^\"]|\"\")*\")|" # Double-quoted strings
47
- r"(?P<squote>'(?:[^']|'')*')|" # Single-quoted strings
48
- r"(?P<lead>[^:]):(?P<var_name>[a-zA-Z_][a-zA-Z0-9_]*)" # :param placeholder
57
+ r"""
58
+ (?P<dquote>"([^"]|\\")*") | # Double-quoted strings
59
+ (?P<squote>'([^']|\\')*') | # Single-quoted strings
60
+ : (?P<var_name_colon>[a-zA-Z_][a-zA-Z0-9_]*) | # :var_name
61
+ % \( (?P<var_name_perc>[a-zA-Z_][a-zA-Z0-9_]*) \) s # %(var_name)s
62
+ """,
63
+ re.VERBOSE,
49
64
  )
50
65
 
51
66
 
@@ -56,14 +71,14 @@ class DatabaseConfigProtocol(ABC, Generic[ConnectionT, PoolT, DriverT]):
56
71
  connection_type: "type[ConnectionT]" = field(init=False)
57
72
  driver_type: "type[DriverT]" = field(init=False)
58
73
  pool_instance: "Optional[PoolT]" = field(default=None)
59
- __is_async__: ClassVar[bool] = False
60
- __supports_connection_pooling__: ClassVar[bool] = False
74
+ __is_async__: "ClassVar[bool]" = False
75
+ __supports_connection_pooling__: "ClassVar[bool]" = False
61
76
 
62
77
  def __hash__(self) -> int:
63
78
  return id(self)
64
79
 
65
80
  @abstractmethod
66
- def create_connection(self) -> Union[ConnectionT, Awaitable[ConnectionT]]:
81
+ def create_connection(self) -> "Union[ConnectionT, Awaitable[ConnectionT]]":
67
82
  """Create and return a new database connection."""
68
83
  raise NotImplementedError
69
84
 
@@ -72,28 +87,32 @@ class DatabaseConfigProtocol(ABC, Generic[ConnectionT, PoolT, DriverT]):
72
87
  self,
73
88
  *args: Any,
74
89
  **kwargs: Any,
75
- ) -> Union[
76
- Generator[ConnectionT, None, None],
77
- AsyncGenerator[ConnectionT, None],
78
- AbstractContextManager[ConnectionT],
79
- AbstractAsyncContextManager[ConnectionT],
80
- ]:
90
+ ) -> "Union[AbstractContextManager[ConnectionT], AbstractAsyncContextManager[ConnectionT]]":
81
91
  """Provide a database connection context manager."""
82
92
  raise NotImplementedError
83
93
 
94
+ @abstractmethod
95
+ def provide_session(
96
+ self,
97
+ *args: Any,
98
+ **kwargs: Any,
99
+ ) -> "Union[AbstractContextManager[DriverT], AbstractAsyncContextManager[DriverT]]":
100
+ """Provide a database session context manager."""
101
+ raise NotImplementedError
102
+
84
103
  @property
85
104
  @abstractmethod
86
- def connection_config_dict(self) -> dict[str, Any]:
105
+ def connection_config_dict(self) -> "dict[str, Any]":
87
106
  """Return the connection configuration as a dict."""
88
107
  raise NotImplementedError
89
108
 
90
109
  @abstractmethod
91
- def create_pool(self) -> Union[PoolT, Awaitable[PoolT]]:
110
+ def create_pool(self) -> "Union[PoolT, Awaitable[PoolT]]":
92
111
  """Create and return connection pool."""
93
112
  raise NotImplementedError
94
113
 
95
114
  @abstractmethod
96
- def close_pool(self) -> Optional[Awaitable[None]]:
115
+ def close_pool(self) -> "Optional[Awaitable[None]]":
97
116
  """Terminate the connection pool."""
98
117
  raise NotImplementedError
99
118
 
@@ -102,7 +121,7 @@ class DatabaseConfigProtocol(ABC, Generic[ConnectionT, PoolT, DriverT]):
102
121
  self,
103
122
  *args: Any,
104
123
  **kwargs: Any,
105
- ) -> Union[PoolT, Awaitable[PoolT], AbstractContextManager[PoolT], AbstractAsyncContextManager[PoolT]]:
124
+ ) -> "Union[PoolT, Awaitable[PoolT], AbstractContextManager[PoolT], AbstractAsyncContextManager[PoolT]]":
106
125
  """Provide pool instance."""
107
126
  raise NotImplementedError
108
127
 
@@ -185,18 +204,15 @@ class SQLSpec:
185
204
  self._configs: dict[Any, DatabaseConfigProtocol[Any, Any, Any]] = {}
186
205
 
187
206
  @overload
188
- def add_config(self, config: SyncConfigT) -> type[SyncConfigT]: ...
207
+ def add_config(self, config: "SyncConfigT") -> "type[SyncConfigT]": ...
189
208
 
190
209
  @overload
191
- def add_config(self, config: AsyncConfigT) -> type[AsyncConfigT]: ...
210
+ def add_config(self, config: "AsyncConfigT") -> "type[AsyncConfigT]": ...
192
211
 
193
212
  def add_config(
194
213
  self,
195
- config: Union[
196
- SyncConfigT,
197
- AsyncConfigT,
198
- ],
199
- ) -> Union[Annotated[type[SyncConfigT], int], Annotated[type[AsyncConfigT], int]]: # pyright: ignore[reportInvalidTypeVarUse]
214
+ config: "Union[SyncConfigT, AsyncConfigT]",
215
+ ) -> "Union[Annotated[type[SyncConfigT], int], Annotated[type[AsyncConfigT], int]]": # pyright: ignore[reportInvalidTypeVarUse]
200
216
  """Add a new configuration to the manager.
201
217
 
202
218
  Returns:
@@ -207,15 +223,15 @@ class SQLSpec:
207
223
  return key # type: ignore[return-value] # pyright: ignore[reportReturnType]
208
224
 
209
225
  @overload
210
- def get_config(self, name: type[SyncConfigT]) -> SyncConfigT: ...
226
+ def get_config(self, name: "type[SyncConfigT]") -> "SyncConfigT": ...
211
227
 
212
228
  @overload
213
- def get_config(self, name: type[AsyncConfigT]) -> AsyncConfigT: ...
229
+ def get_config(self, name: "type[AsyncConfigT]") -> "AsyncConfigT": ...
214
230
 
215
231
  def get_config(
216
232
  self,
217
- name: Union[type[DatabaseConfigProtocol[ConnectionT, PoolT, DriverT]], Any],
218
- ) -> DatabaseConfigProtocol[ConnectionT, PoolT, DriverT]:
233
+ name: "Union[type[DatabaseConfigProtocol[ConnectionT, PoolT, DriverT]], Any]",
234
+ ) -> "DatabaseConfigProtocol[ConnectionT, PoolT, DriverT]":
219
235
  """Retrieve a configuration by its type.
220
236
 
221
237
  Returns:
@@ -234,61 +250,135 @@ class SQLSpec:
234
250
  def get_connection(
235
251
  self,
236
252
  name: Union[
237
- type[NoPoolSyncConfig[ConnectionT, DriverT]],
238
- type[SyncDatabaseConfig[ConnectionT, PoolT, DriverT]], # pyright: ignore[reportInvalidTypeVarUse]
253
+ "type[NoPoolSyncConfig[ConnectionT, DriverT]]",
254
+ "type[SyncDatabaseConfig[ConnectionT, PoolT, DriverT]]", # pyright: ignore[reportInvalidTypeVarUse]
239
255
  ],
240
- ) -> ConnectionT: ...
256
+ ) -> "ConnectionT": ...
241
257
 
242
258
  @overload
243
259
  def get_connection(
244
260
  self,
245
261
  name: Union[
246
- type[NoPoolAsyncConfig[ConnectionT, DriverT]],
247
- type[AsyncDatabaseConfig[ConnectionT, PoolT, DriverT]], # pyright: ignore[reportInvalidTypeVarUse]
262
+ "type[NoPoolAsyncConfig[ConnectionT, DriverT]]",
263
+ "type[AsyncDatabaseConfig[ConnectionT, PoolT, DriverT]]", # pyright: ignore[reportInvalidTypeVarUse]
248
264
  ],
249
- ) -> Awaitable[ConnectionT]: ...
265
+ ) -> "Awaitable[ConnectionT]": ...
250
266
 
251
267
  def get_connection(
252
268
  self,
253
269
  name: Union[
254
- type[NoPoolSyncConfig[ConnectionT, DriverT]],
255
- type[NoPoolAsyncConfig[ConnectionT, DriverT]],
256
- type[SyncDatabaseConfig[ConnectionT, PoolT, DriverT]],
257
- type[AsyncDatabaseConfig[ConnectionT, PoolT, DriverT]],
270
+ "type[NoPoolSyncConfig[ConnectionT, DriverT]]",
271
+ "type[NoPoolAsyncConfig[ConnectionT, DriverT]]",
272
+ "type[SyncDatabaseConfig[ConnectionT, PoolT, DriverT]]",
273
+ "type[AsyncDatabaseConfig[ConnectionT, PoolT, DriverT]]",
258
274
  ],
259
- ) -> Union[ConnectionT, Awaitable[ConnectionT]]:
260
- """Create and return a connection from the specified configuration.
275
+ ) -> "Union[ConnectionT, Awaitable[ConnectionT]]":
276
+ """Create and return a new database connection from the specified configuration.
261
277
 
262
278
  Args:
263
279
  name: The configuration type to use for creating the connection.
264
280
 
265
281
  Returns:
266
- Either a connection instance or an awaitable that resolves to a connection,
267
- depending on whether the configuration is sync or async.
282
+ Either a connection instance or an awaitable that resolves to a connection instance.
268
283
  """
269
284
  config = self.get_config(name)
270
285
  return config.create_connection()
271
286
 
287
+ def get_session(
288
+ self,
289
+ name: Union[
290
+ "type[NoPoolSyncConfig[ConnectionT, DriverT]]",
291
+ "type[NoPoolAsyncConfig[ConnectionT, DriverT]]",
292
+ "type[SyncDatabaseConfig[ConnectionT, PoolT, DriverT]]",
293
+ "type[AsyncDatabaseConfig[ConnectionT, PoolT, DriverT]]",
294
+ ],
295
+ ) -> "Union[DriverT, Awaitable[DriverT]]":
296
+ """Create and return a new database session from the specified configuration.
297
+
298
+ Args:
299
+ name: The configuration type to use for creating the session.
300
+
301
+ Returns:
302
+ Either a driver instance or an awaitable that resolves to a driver instance.
303
+ """
304
+ config = self.get_config(name)
305
+ connection = self.get_connection(name)
306
+ if isinstance(connection, Awaitable):
307
+
308
+ async def _create_session() -> DriverT:
309
+ return cast("DriverT", config.driver_type(await connection)) # pyright: ignore
310
+
311
+ return _create_session()
312
+ return cast("DriverT", config.driver_type(connection)) # pyright: ignore
313
+
314
+ def provide_connection(
315
+ self,
316
+ name: Union[
317
+ "type[NoPoolSyncConfig[ConnectionT, DriverT]]",
318
+ "type[NoPoolAsyncConfig[ConnectionT, DriverT]]",
319
+ "type[SyncDatabaseConfig[ConnectionT, PoolT, DriverT]]",
320
+ "type[AsyncDatabaseConfig[ConnectionT, PoolT, DriverT]]",
321
+ ],
322
+ *args: Any,
323
+ **kwargs: Any,
324
+ ) -> "Union[AbstractContextManager[ConnectionT], AbstractAsyncContextManager[ConnectionT]]":
325
+ """Create and provide a database connection from the specified configuration.
326
+
327
+ Args:
328
+ name: The configuration type to use for creating the connection.
329
+ *args: Positional arguments to pass to the configuration's provide_connection method.
330
+ **kwargs: Keyword arguments to pass to the configuration's provide_connection method.
331
+
332
+ Returns:
333
+ Either a synchronous or asynchronous context manager that provides a database connection.
334
+ """
335
+ config = self.get_config(name)
336
+ return config.provide_connection(*args, **kwargs)
337
+
338
+ def provide_session(
339
+ self,
340
+ name: Union[
341
+ "type[NoPoolSyncConfig[ConnectionT, DriverT]]",
342
+ "type[NoPoolAsyncConfig[ConnectionT, DriverT]]",
343
+ "type[SyncDatabaseConfig[ConnectionT, PoolT, DriverT]]",
344
+ "type[AsyncDatabaseConfig[ConnectionT, PoolT, DriverT]]",
345
+ ],
346
+ *args: Any,
347
+ **kwargs: Any,
348
+ ) -> "Union[AbstractContextManager[DriverT], AbstractAsyncContextManager[DriverT]]":
349
+ """Create and provide a database session from the specified configuration.
350
+
351
+ Args:
352
+ name: The configuration type to use for creating the session.
353
+ *args: Positional arguments to pass to the configuration's provide_session method.
354
+ **kwargs: Keyword arguments to pass to the configuration's provide_session method.
355
+
356
+ Returns:
357
+ Either a synchronous or asynchronous context manager that provides a database session.
358
+ """
359
+ config = self.get_config(name)
360
+ return config.provide_session(*args, **kwargs)
361
+
272
362
  @overload
273
363
  def get_pool(
274
- self, name: type[Union[NoPoolSyncConfig[ConnectionT, DriverT], NoPoolAsyncConfig[ConnectionT, DriverT]]]
275
- ) -> None: ... # pyright: ignore[reportInvalidTypeVarUse]
364
+ self, name: "type[Union[NoPoolSyncConfig[ConnectionT, DriverT], NoPoolAsyncConfig[ConnectionT, DriverT]]]"
365
+ ) -> "None": ... # pyright: ignore[reportInvalidTypeVarUse]
276
366
 
277
367
  @overload
278
- def get_pool(self, name: type[SyncDatabaseConfig[ConnectionT, PoolT, DriverT]]) -> type[PoolT]: ... # pyright: ignore[reportInvalidTypeVarUse]
368
+ def get_pool(self, name: "type[SyncDatabaseConfig[ConnectionT, PoolT, DriverT]]") -> "type[PoolT]": ... # pyright: ignore[reportInvalidTypeVarUse]
279
369
 
280
370
  @overload
281
- def get_pool(self, name: type[AsyncDatabaseConfig[ConnectionT, PoolT, DriverT]]) -> Awaitable[type[PoolT]]: ... # pyright: ignore[reportInvalidTypeVarUse]
371
+ def get_pool(self, name: "type[AsyncDatabaseConfig[ConnectionT, PoolT, DriverT]]") -> "Awaitable[type[PoolT]]": ... # pyright: ignore[reportInvalidTypeVarUse]
282
372
 
283
373
  def get_pool(
284
374
  self,
285
375
  name: Union[
286
- type[NoPoolSyncConfig[ConnectionT, DriverT]],
287
- type[NoPoolAsyncConfig[ConnectionT, DriverT]],
288
- type[SyncDatabaseConfig[ConnectionT, PoolT, DriverT]],
289
- type[AsyncDatabaseConfig[ConnectionT, PoolT, DriverT]],
376
+ "type[NoPoolSyncConfig[ConnectionT, DriverT]]",
377
+ "type[NoPoolAsyncConfig[ConnectionT, DriverT]]",
378
+ "type[SyncDatabaseConfig[ConnectionT, PoolT, DriverT]]",
379
+ "type[AsyncDatabaseConfig[ConnectionT, PoolT, DriverT]]",
290
380
  ],
291
- ) -> Union[type[PoolT], Awaitable[type[PoolT]], None]:
381
+ ) -> "Union[type[PoolT], Awaitable[type[PoolT]], None]":
292
382
  """Create and return a connection pool from the specified configuration.
293
383
 
294
384
  Args:
@@ -306,12 +396,12 @@ class SQLSpec:
306
396
  def close_pool(
307
397
  self,
308
398
  name: Union[
309
- type[NoPoolSyncConfig[ConnectionT, DriverT]],
310
- type[NoPoolAsyncConfig[ConnectionT, DriverT]],
311
- type[SyncDatabaseConfig[ConnectionT, PoolT, DriverT]],
312
- type[AsyncDatabaseConfig[ConnectionT, PoolT, DriverT]],
399
+ "type[NoPoolSyncConfig[ConnectionT, DriverT]]",
400
+ "type[NoPoolAsyncConfig[ConnectionT, DriverT]]",
401
+ "type[SyncDatabaseConfig[ConnectionT, PoolT, DriverT]]",
402
+ "type[AsyncDatabaseConfig[ConnectionT, PoolT, DriverT]]",
313
403
  ],
314
- ) -> Optional[Awaitable[None]]:
404
+ ) -> "Optional[Awaitable[None]]":
315
405
  """Close the connection pool for the specified configuration.
316
406
 
317
407
  Args:
@@ -329,10 +419,12 @@ class SQLSpec:
329
419
  class CommonDriverAttributes(Generic[ConnectionT]):
330
420
  """Common attributes and methods for driver adapters."""
331
421
 
332
- param_style: str = "?"
333
- """The parameter style placeholder supported by the underlying database driver (e.g., '?', '%s')."""
422
+ dialect: str
423
+ """The SQL dialect supported by the underlying database driver (e.g., 'postgres', 'mysql')."""
334
424
  connection: ConnectionT
335
425
  """The connection to the underlying database."""
426
+ __supports_arrow__: ClassVar[bool] = False
427
+ """Indicates if the driver supports Apache Arrow operations."""
336
428
 
337
429
  def _connection(self, connection: "Optional[ConnectionT]" = None) -> "ConnectionT":
338
430
  return connection if connection is not None else self.connection
@@ -355,99 +447,70 @@ class CommonDriverAttributes(Generic[ConnectionT]):
355
447
  raise NotFoundError(msg)
356
448
  return item_or_none
357
449
 
358
- def _process_sql_statement(self, sql: str) -> str:
359
- """Perform any preprocessing of the SQL query string if needed.
360
- Default implementation returns the SQL unchanged.
450
+ def _process_sql_params(
451
+ self, sql: str, parameters: "Optional[StatementParameterType]" = None, /, **kwargs: Any
452
+ ) -> "tuple[str, Optional[Union[tuple[Any, ...], list[Any], dict[str, Any]]]]":
453
+ """Process SQL query and parameters using SQLStatement for validation and formatting.
361
454
 
362
455
  Args:
363
456
  sql: The SQL query string.
457
+ parameters: Parameters for the query.
458
+ **kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
364
459
 
365
460
  Returns:
366
- The processed SQL query string.
461
+ A tuple containing the processed SQL query and parameters.
367
462
  """
368
- return sql
463
+ # Instantiate SQLStatement with parameters and kwargs for internal merging
464
+ stmt = SQLStatement(sql=sql, parameters=parameters, dialect=self.dialect, kwargs=kwargs or None)
465
+ # Process uses the merged parameters internally
466
+ return stmt.process()
369
467
 
370
- def _process_sql_params(
371
- self, sql: str, parameters: "Optional[StatementParameterType]" = None
372
- ) -> "tuple[str, Optional[Union[tuple[Any, ...], list[Any], dict[str, Any]]]]":
373
- """Process SQL query and parameters for DB-API execution.
374
468
 
375
- Converts named parameters (:name) to positional parameters specified by `self.param_style`
376
- if the input parameters are a dictionary.
469
+ class SyncArrowBulkOperationsMixin(Generic[ConnectionT]):
470
+ """Mixin for sync drivers supporting bulk Apache Arrow operations."""
471
+
472
+ __supports_arrow__: "ClassVar[bool]" = True
473
+
474
+ @abstractmethod
475
+ def select_arrow( # pyright: ignore[reportUnknownParameterType]
476
+ self,
477
+ sql: str,
478
+ parameters: "Optional[StatementParameterType]" = None,
479
+ /,
480
+ *,
481
+ connection: "Optional[ConnectionT]" = None,
482
+ **kwargs: Any,
483
+ ) -> "ArrowTable": # pyright: ignore[reportUnknownReturnType]
484
+ """Execute a SQL query and return results as an Apache Arrow Table.
377
485
 
378
486
  Args:
379
487
  sql: The SQL query string.
380
- parameters: The parameters for the query (dict, tuple, list, or None).
488
+ parameters: Parameters for the query.
489
+ connection: Optional connection override.
490
+ **kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
381
491
 
382
492
  Returns:
383
- A tuple containing the processed SQL string and the processed parameters
384
- (always a tuple or None if the input was a dictionary, otherwise the original type).
385
-
386
- Raises:
387
- ValueError: If a named parameter in the SQL is not found in the dictionary
388
- or if a parameter in the dictionary is not used in the SQL.
493
+ An Apache Arrow Table containing the query results.
389
494
  """
390
- if not isinstance(parameters, dict) or not parameters:
391
- # If parameters are not a dict, or empty dict, assume positional/no params
392
- # Let the underlying driver handle tuples/lists directly
393
- return self._process_sql_statement(sql), parameters
394
-
395
- processed_sql = ""
396
- processed_params_list: list[Any] = []
397
- last_end = 0
398
- found_params: set[str] = set()
399
-
400
- for match in PARAM_REGEX.finditer(sql):
401
- if match.group("dquote") is not None or match.group("squote") is not None:
402
- # Skip placeholders within quotes
403
- continue
404
-
405
- var_name = match.group("var_name")
406
- if var_name is None: # Should not happen with the regex, but safeguard
407
- continue
408
-
409
- if var_name not in parameters:
410
- msg = f"Named parameter ':{var_name}' found in SQL but not provided in parameters dictionary."
411
- raise ValueError(msg)
412
-
413
- # Append segment before the placeholder + the leading character + the driver's positional placeholder
414
- # The match.start("var_name") -1 includes the character before the ':'
415
- processed_sql += sql[last_end : match.start("var_name")] + self.param_style
416
- processed_params_list.append(parameters[var_name])
417
- found_params.add(var_name)
418
- last_end = match.end("var_name")
419
-
420
- # Append the rest of the SQL string
421
- processed_sql += sql[last_end:]
422
-
423
- # Check if all provided parameters were used
424
- unused_params = set(parameters.keys()) - found_params
425
- if unused_params:
426
- msg = f"Parameters provided but not found in SQL: {unused_params}"
427
- # Depending on desired strictness, this could be a warning or an error
428
- # For now, let's raise an error for clarity
429
- raise ValueError(msg)
430
-
431
- processed_params = tuple(processed_params_list)
432
- # Pass the processed SQL through the driver-specific processor if needed
433
- final_sql = self._process_sql_statement(processed_sql)
434
- return final_sql, processed_params
495
+ raise NotImplementedError
435
496
 
436
497
 
437
498
  class SyncDriverAdapterProtocol(CommonDriverAttributes[ConnectionT], ABC, Generic[ConnectionT]):
438
- connection: ConnectionT
499
+ connection: "ConnectionT"
439
500
 
440
- def __init__(self, connection: ConnectionT) -> None:
501
+ def __init__(self, connection: "ConnectionT", **kwargs: Any) -> None:
441
502
  self.connection = connection
442
503
 
443
504
  @abstractmethod
444
505
  def select(
445
506
  self,
446
507
  sql: str,
447
- parameters: Optional[StatementParameterType] = None,
508
+ parameters: "Optional[StatementParameterType]" = None,
448
509
  /,
449
- connection: Optional[ConnectionT] = None,
510
+ *,
511
+ connection: "Optional[ConnectionT]" = None,
450
512
  schema_type: Optional[type[ModelDTOT]] = None,
513
+ **kwargs: Any,
451
514
  ) -> "list[Union[ModelDTOT, dict[str, Any]]]": ...
452
515
 
453
516
  @abstractmethod
@@ -456,8 +519,10 @@ class SyncDriverAdapterProtocol(CommonDriverAttributes[ConnectionT], ABC, Generi
456
519
  sql: str,
457
520
  parameters: Optional[StatementParameterType] = None,
458
521
  /,
522
+ *,
459
523
  connection: Optional[ConnectionT] = None,
460
524
  schema_type: Optional[type[ModelDTOT]] = None,
525
+ **kwargs: Any,
461
526
  ) -> "Union[ModelDTOT, dict[str, Any]]": ...
462
527
 
463
528
  @abstractmethod
@@ -466,8 +531,10 @@ class SyncDriverAdapterProtocol(CommonDriverAttributes[ConnectionT], ABC, Generi
466
531
  sql: str,
467
532
  parameters: Optional[StatementParameterType] = None,
468
533
  /,
534
+ *,
469
535
  connection: Optional[ConnectionT] = None,
470
536
  schema_type: Optional[type[ModelDTOT]] = None,
537
+ **kwargs: Any,
471
538
  ) -> "Optional[Union[ModelDTOT, dict[str, Any]]]": ...
472
539
 
473
540
  @abstractmethod
@@ -476,8 +543,10 @@ class SyncDriverAdapterProtocol(CommonDriverAttributes[ConnectionT], ABC, Generi
476
543
  sql: str,
477
544
  parameters: Optional[StatementParameterType] = None,
478
545
  /,
546
+ *,
479
547
  connection: Optional[ConnectionT] = None,
480
548
  schema_type: Optional[type[T]] = None,
549
+ **kwargs: Any,
481
550
  ) -> "Union[Any, T]": ...
482
551
 
483
552
  @abstractmethod
@@ -486,8 +555,10 @@ class SyncDriverAdapterProtocol(CommonDriverAttributes[ConnectionT], ABC, Generi
486
555
  sql: str,
487
556
  parameters: Optional[StatementParameterType] = None,
488
557
  /,
558
+ *,
489
559
  connection: Optional[ConnectionT] = None,
490
560
  schema_type: Optional[type[T]] = None,
561
+ **kwargs: Any,
491
562
  ) -> "Optional[Union[Any, T]]": ...
492
563
 
493
564
  @abstractmethod
@@ -496,7 +567,9 @@ class SyncDriverAdapterProtocol(CommonDriverAttributes[ConnectionT], ABC, Generi
496
567
  sql: str,
497
568
  parameters: Optional[StatementParameterType] = None,
498
569
  /,
570
+ *,
499
571
  connection: Optional[ConnectionT] = None,
572
+ **kwargs: Any,
500
573
  ) -> int: ...
501
574
 
502
575
  @abstractmethod
@@ -505,8 +578,10 @@ class SyncDriverAdapterProtocol(CommonDriverAttributes[ConnectionT], ABC, Generi
505
578
  sql: str,
506
579
  parameters: Optional[StatementParameterType] = None,
507
580
  /,
581
+ *,
508
582
  connection: Optional[ConnectionT] = None,
509
583
  schema_type: Optional[type[ModelDTOT]] = None,
584
+ **kwargs: Any,
510
585
  ) -> "Optional[Union[dict[str, Any], ModelDTOT]]": ...
511
586
 
512
587
  @abstractmethod
@@ -515,92 +590,139 @@ class SyncDriverAdapterProtocol(CommonDriverAttributes[ConnectionT], ABC, Generi
515
590
  sql: str,
516
591
  parameters: Optional[StatementParameterType] = None,
517
592
  /,
593
+ *,
518
594
  connection: Optional[ConnectionT] = None,
595
+ **kwargs: Any,
519
596
  ) -> str: ...
520
597
 
521
598
 
599
+ class AsyncArrowBulkOperationsMixin(Generic[ConnectionT]):
600
+ """Mixin for async drivers supporting bulk Apache Arrow operations."""
601
+
602
+ __supports_arrow__: "ClassVar[bool]" = True
603
+
604
+ @abstractmethod
605
+ async def select_arrow( # pyright: ignore[reportUnknownParameterType]
606
+ self,
607
+ sql: str,
608
+ parameters: "Optional[StatementParameterType]" = None,
609
+ /,
610
+ *,
611
+ connection: "Optional[ConnectionT]" = None,
612
+ **kwargs: Any,
613
+ ) -> "ArrowTable": # pyright: ignore[reportUnknownReturnType]
614
+ """Execute a SQL query and return results as an Apache Arrow Table.
615
+
616
+ Args:
617
+ sql: The SQL query string.
618
+ parameters: Parameters for the query.
619
+ connection: Optional connection override.
620
+ **kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
621
+
622
+ Returns:
623
+ An Apache Arrow Table containing the query results.
624
+ """
625
+ raise NotImplementedError
626
+
627
+
522
628
  class AsyncDriverAdapterProtocol(CommonDriverAttributes[ConnectionT], ABC, Generic[ConnectionT]):
523
- connection: ConnectionT
629
+ connection: "ConnectionT"
524
630
 
525
- def __init__(self, connection: ConnectionT) -> None:
631
+ def __init__(self, connection: "ConnectionT") -> None:
526
632
  self.connection = connection
527
633
 
528
634
  @abstractmethod
529
635
  async def select(
530
636
  self,
531
637
  sql: str,
532
- parameters: Optional[StatementParameterType] = None,
638
+ parameters: "Optional[StatementParameterType]" = None,
533
639
  /,
534
- connection: Optional[ConnectionT] = None,
535
- schema_type: Optional[type[ModelDTOT]] = None,
640
+ *,
641
+ connection: "Optional[ConnectionT]" = None,
642
+ schema_type: "Optional[type[ModelDTOT]]" = None,
643
+ **kwargs: Any,
536
644
  ) -> "list[Union[ModelDTOT, dict[str, Any]]]": ...
537
645
 
538
646
  @abstractmethod
539
647
  async def select_one(
540
648
  self,
541
649
  sql: str,
542
- parameters: Optional[StatementParameterType] = None,
650
+ parameters: "Optional[StatementParameterType]" = None,
543
651
  /,
544
- connection: Optional[ConnectionT] = None,
545
- schema_type: Optional[type[ModelDTOT]] = None,
652
+ *,
653
+ connection: "Optional[ConnectionT]" = None,
654
+ schema_type: "Optional[type[ModelDTOT]]" = None,
655
+ **kwargs: Any,
546
656
  ) -> "Union[ModelDTOT, dict[str, Any]]": ...
547
657
 
548
658
  @abstractmethod
549
659
  async def select_one_or_none(
550
660
  self,
551
661
  sql: str,
552
- parameters: Optional[StatementParameterType] = None,
662
+ parameters: "Optional[StatementParameterType]" = None,
553
663
  /,
554
- connection: Optional[ConnectionT] = None,
555
- schema_type: Optional[type[ModelDTOT]] = None,
664
+ *,
665
+ connection: "Optional[ConnectionT]" = None,
666
+ schema_type: "Optional[type[ModelDTOT]]" = None,
667
+ **kwargs: Any,
556
668
  ) -> "Optional[Union[ModelDTOT, dict[str, Any]]]": ...
557
669
 
558
670
  @abstractmethod
559
671
  async def select_value(
560
672
  self,
561
673
  sql: str,
562
- parameters: Optional[StatementParameterType] = None,
674
+ parameters: "Optional[StatementParameterType]" = None,
563
675
  /,
564
- connection: Optional[ConnectionT] = None,
565
- schema_type: Optional[type[T]] = None,
676
+ *,
677
+ connection: "Optional[ConnectionT]" = None,
678
+ schema_type: "Optional[type[T]]" = None,
679
+ **kwargs: Any,
566
680
  ) -> "Union[Any, T]": ...
567
681
 
568
682
  @abstractmethod
569
683
  async def select_value_or_none(
570
684
  self,
571
685
  sql: str,
572
- parameters: Optional[StatementParameterType] = None,
686
+ parameters: "Optional[StatementParameterType]" = None,
573
687
  /,
574
- connection: Optional[ConnectionT] = None,
575
- schema_type: Optional[type[T]] = None,
688
+ *,
689
+ connection: "Optional[ConnectionT]" = None,
690
+ schema_type: "Optional[type[T]]" = None,
691
+ **kwargs: Any,
576
692
  ) -> "Optional[Union[Any, T]]": ...
577
693
 
578
694
  @abstractmethod
579
695
  async def insert_update_delete(
580
696
  self,
581
697
  sql: str,
582
- parameters: Optional[StatementParameterType] = None,
698
+ parameters: "Optional[StatementParameterType]" = None,
583
699
  /,
584
- connection: Optional[ConnectionT] = None,
700
+ *,
701
+ connection: "Optional[ConnectionT]" = None,
702
+ **kwargs: Any,
585
703
  ) -> int: ...
586
704
 
587
705
  @abstractmethod
588
706
  async def insert_update_delete_returning(
589
707
  self,
590
708
  sql: str,
591
- parameters: Optional[StatementParameterType] = None,
709
+ parameters: "Optional[StatementParameterType]" = None,
592
710
  /,
593
- connection: Optional[ConnectionT] = None,
594
- schema_type: Optional[type[ModelDTOT]] = None,
711
+ *,
712
+ connection: "Optional[ConnectionT]" = None,
713
+ schema_type: "Optional[type[ModelDTOT]]" = None,
714
+ **kwargs: Any,
595
715
  ) -> "Optional[Union[dict[str, Any], ModelDTOT]]": ...
596
716
 
597
717
  @abstractmethod
598
718
  async def execute_script(
599
719
  self,
600
720
  sql: str,
601
- parameters: Optional[StatementParameterType] = None,
721
+ parameters: "Optional[StatementParameterType]" = None,
602
722
  /,
603
- connection: Optional[ConnectionT] = None,
723
+ *,
724
+ connection: "Optional[ConnectionT]" = None,
725
+ **kwargs: Any,
604
726
  ) -> str: ...
605
727
 
606
728