sqlspec 0.7.1__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 (54) hide show
  1. sqlspec/__init__.py +15 -0
  2. sqlspec/_serialization.py +16 -2
  3. sqlspec/_typing.py +40 -7
  4. sqlspec/adapters/adbc/__init__.py +7 -0
  5. sqlspec/adapters/adbc/config.py +183 -17
  6. sqlspec/adapters/adbc/driver.py +392 -0
  7. sqlspec/adapters/aiosqlite/__init__.py +5 -1
  8. sqlspec/adapters/aiosqlite/config.py +24 -6
  9. sqlspec/adapters/aiosqlite/driver.py +264 -0
  10. sqlspec/adapters/asyncmy/__init__.py +7 -2
  11. sqlspec/adapters/asyncmy/config.py +71 -11
  12. sqlspec/adapters/asyncmy/driver.py +246 -0
  13. sqlspec/adapters/asyncpg/__init__.py +9 -0
  14. sqlspec/adapters/asyncpg/config.py +102 -25
  15. sqlspec/adapters/asyncpg/driver.py +444 -0
  16. sqlspec/adapters/duckdb/__init__.py +5 -1
  17. sqlspec/adapters/duckdb/config.py +194 -12
  18. sqlspec/adapters/duckdb/driver.py +225 -0
  19. sqlspec/adapters/oracledb/__init__.py +7 -4
  20. sqlspec/adapters/oracledb/config/__init__.py +4 -4
  21. sqlspec/adapters/oracledb/config/_asyncio.py +96 -12
  22. sqlspec/adapters/oracledb/config/_common.py +1 -1
  23. sqlspec/adapters/oracledb/config/_sync.py +96 -12
  24. sqlspec/adapters/oracledb/driver.py +571 -0
  25. sqlspec/adapters/psqlpy/__init__.py +0 -0
  26. sqlspec/adapters/psqlpy/config.py +258 -0
  27. sqlspec/adapters/psqlpy/driver.py +335 -0
  28. sqlspec/adapters/psycopg/__init__.py +16 -0
  29. sqlspec/adapters/psycopg/config/__init__.py +6 -6
  30. sqlspec/adapters/psycopg/config/_async.py +107 -15
  31. sqlspec/adapters/psycopg/config/_common.py +2 -2
  32. sqlspec/adapters/psycopg/config/_sync.py +107 -15
  33. sqlspec/adapters/psycopg/driver.py +578 -0
  34. sqlspec/adapters/sqlite/__init__.py +7 -0
  35. sqlspec/adapters/sqlite/config.py +24 -6
  36. sqlspec/adapters/sqlite/driver.py +305 -0
  37. sqlspec/base.py +565 -63
  38. sqlspec/exceptions.py +30 -0
  39. sqlspec/extensions/litestar/__init__.py +19 -0
  40. sqlspec/extensions/litestar/_utils.py +56 -0
  41. sqlspec/extensions/litestar/config.py +87 -0
  42. sqlspec/extensions/litestar/handlers.py +213 -0
  43. sqlspec/extensions/litestar/plugin.py +105 -11
  44. sqlspec/statement.py +373 -0
  45. sqlspec/typing.py +81 -17
  46. sqlspec/utils/__init__.py +3 -0
  47. sqlspec/utils/fixtures.py +4 -5
  48. sqlspec/utils/sync_tools.py +335 -0
  49. {sqlspec-0.7.1.dist-info → sqlspec-0.9.0.dist-info}/METADATA +4 -1
  50. sqlspec-0.9.0.dist-info/RECORD +61 -0
  51. sqlspec-0.7.1.dist-info/RECORD +0 -46
  52. {sqlspec-0.7.1.dist-info → sqlspec-0.9.0.dist-info}/WHEEL +0 -0
  53. {sqlspec-0.7.1.dist-info → sqlspec-0.9.0.dist-info}/licenses/LICENSE +0 -0
  54. {sqlspec-0.7.1.dist-info → sqlspec-0.9.0.dist-info}/licenses/NOTICE +0 -0
@@ -1,10 +1,11 @@
1
1
  from contextlib import contextmanager
2
- from dataclasses import dataclass
3
- from typing import TYPE_CHECKING, Any, Union, cast
2
+ from dataclasses import dataclass, field
3
+ from typing import TYPE_CHECKING, Any, Callable, Optional, Union, cast
4
4
 
5
5
  from duckdb import DuckDBPyConnection
6
- from typing_extensions import NotRequired, TypedDict
6
+ from typing_extensions import Literal, NotRequired, TypedDict
7
7
 
8
+ from sqlspec.adapters.duckdb.driver import DuckDBDriver
8
9
  from sqlspec.base import NoPoolSyncConfig
9
10
  from sqlspec.exceptions import ImproperConfigurationError
10
11
  from sqlspec.typing import Empty, EmptyType, dataclass_to_dict
@@ -29,6 +30,8 @@ class ExtensionConfig(TypedDict):
29
30
  """The name of the extension to install"""
30
31
  config: "NotRequired[dict[str, Any]]"
31
32
  """Optional configuration settings to apply after installation"""
33
+ install_if_missing: "NotRequired[bool]"
34
+ """Whether to install if missing"""
32
35
  force_install: "NotRequired[bool]"
33
36
  """Whether to force reinstall if already present"""
34
37
  repository: "NotRequired[str]"
@@ -39,8 +42,34 @@ class ExtensionConfig(TypedDict):
39
42
  """Optional version of the extension to install"""
40
43
 
41
44
 
45
+ class SecretConfig(TypedDict):
46
+ """Configuration for a secret to store in a connection.
47
+
48
+ This class provides configuration options for storing a secret in a connection for later retrieval.
49
+
50
+ For details see: https://duckdb.org/docs/stable/configuration/secrets_manager
51
+ """
52
+
53
+ secret_type: Union[
54
+ Literal[
55
+ "azure", "gcs", "s3", "r2", "huggingface", "http", "mysql", "postgres", "bigquery", "openai", "open_prompt" # noqa: PYI051
56
+ ],
57
+ str,
58
+ ]
59
+ provider: NotRequired[str]
60
+ """The provider of the secret"""
61
+ name: str
62
+ """The name of the secret to store"""
63
+ value: dict[str, Any]
64
+ """The secret value to store"""
65
+ persist: NotRequired[bool]
66
+ """Whether to persist the secret"""
67
+ replace_if_exists: NotRequired[bool]
68
+ """Whether to replace the secret if it already exists"""
69
+
70
+
42
71
  @dataclass
43
- class DuckDBConfig(NoPoolSyncConfig[DuckDBPyConnection]):
72
+ class DuckDBConfig(NoPoolSyncConfig["DuckDBPyConnection", "DuckDBDriver"]):
44
73
  """Configuration for DuckDB database connections.
45
74
 
46
75
  This class provides configuration options for DuckDB database connections, wrapping all parameters
@@ -49,7 +78,7 @@ class DuckDBConfig(NoPoolSyncConfig[DuckDBPyConnection]):
49
78
  For details see: https://duckdb.org/docs/api/python/overview#connection-options
50
79
  """
51
80
 
52
- database: "Union[str, EmptyType]" = Empty
81
+ database: "Union[str, EmptyType]" = field(default=":memory:")
53
82
  """The path to the database file to be opened. Pass ":memory:" to open a connection to a database that resides in RAM instead of on disk. If not specified, an in-memory database will be created."""
54
83
 
55
84
  read_only: "Union[bool, EmptyType]" = Empty
@@ -63,6 +92,18 @@ class DuckDBConfig(NoPoolSyncConfig[DuckDBPyConnection]):
63
92
 
64
93
  extensions: "Union[Sequence[ExtensionConfig], ExtensionConfig, EmptyType]" = Empty
65
94
  """A sequence of extension configurations to install and configure upon connection creation."""
95
+ secrets: "Union[Sequence[SecretConfig], SecretConfig , EmptyType]" = Empty
96
+ """A dictionary of secrets to store in the connection for later retrieval."""
97
+ auto_update_extensions: "bool" = False
98
+ """Whether to automatically update on connection creation"""
99
+ on_connection_create: "Optional[Callable[[DuckDBPyConnection], Optional[DuckDBPyConnection]]]" = None
100
+ """A callable to be called after the connection is created."""
101
+ connection_type: "type[DuckDBPyConnection]" = field(init=False, default_factory=lambda: DuckDBPyConnection)
102
+ """The type of connection to create. Defaults to DuckDBPyConnection."""
103
+ driver_type: "type[DuckDBDriver]" = field(init=False, default_factory=lambda: DuckDBDriver) # type: ignore[type-abstract,unused-ignore]
104
+ """The type of driver to use. Defaults to DuckDBDriver."""
105
+ pool_instance: "None" = field(init=False, default=None)
106
+ """The pool instance to use. Defaults to None."""
66
107
 
67
108
  def __post_init__(self) -> None:
68
109
  """Post-initialization validation and processing.
@@ -73,9 +114,10 @@ class DuckDBConfig(NoPoolSyncConfig[DuckDBPyConnection]):
73
114
  """
74
115
  if self.config is Empty:
75
116
  self.config = {}
76
-
77
117
  if self.extensions is Empty:
78
118
  self.extensions = []
119
+ if self.secrets is Empty:
120
+ self.secrets = []
79
121
  if isinstance(self.extensions, dict):
80
122
  self.extensions = [self.extensions]
81
123
  # this is purely for mypy
@@ -119,9 +161,104 @@ class DuckDBConfig(NoPoolSyncConfig[DuckDBPyConnection]):
119
161
 
120
162
  for extension in cast("list[ExtensionConfig]", self.extensions):
121
163
  self._configure_extension(connection, extension)
164
+ if self.auto_update_extensions:
165
+ connection.execute("update extensions")
122
166
 
123
167
  @staticmethod
124
- def _configure_extension(connection: "DuckDBPyConnection", extension: ExtensionConfig) -> None:
168
+ def _secret_exists(connection: "DuckDBPyConnection", name: "str") -> bool:
169
+ """Check if a secret exists in the connection.
170
+
171
+ Args:
172
+ connection: The DuckDB connection to check for the secret.
173
+ name: The name of the secret to check for.
174
+
175
+ Returns:
176
+ bool: True if the secret exists, False otherwise.
177
+ """
178
+ results = connection.execute("select 1 from duckdb_secrets() where name=?", [name]).fetchone() # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
179
+ return results is not None
180
+
181
+ @classmethod
182
+ def _is_community_extension(cls, connection: "DuckDBPyConnection", name: "str") -> bool:
183
+ """Check if an extension is a community extension.
184
+
185
+ Args:
186
+ connection: The DuckDB connection to check for the extension.
187
+ name: The name of the extension to check.
188
+
189
+ Returns:
190
+ bool: True if the extension is a community extension, False otherwise.
191
+ """
192
+ results = connection.execute( # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
193
+ "select 1 from duckdb_extensions() where extension_name=?", [name]
194
+ ).fetchone()
195
+ return results is None
196
+
197
+ @classmethod
198
+ def _extension_installed(cls, connection: "DuckDBPyConnection", name: "str") -> bool:
199
+ """Check if a extension exists in the connection.
200
+
201
+ Args:
202
+ connection: The DuckDB connection to check for the secret.
203
+ name: The name of the secret to check for.
204
+
205
+ Returns:
206
+ bool: True if the extension is installed, False otherwise.
207
+ """
208
+ results = connection.execute( # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
209
+ "select 1 from duckdb_extensions() where extension_name=? and installed=true", [name]
210
+ ).fetchone()
211
+ return results is not None
212
+
213
+ @classmethod
214
+ def _extension_loaded(cls, connection: "DuckDBPyConnection", name: "str") -> bool:
215
+ """Check if a extension is loaded in the connection.
216
+
217
+ Args:
218
+ connection: The DuckDB connection to check for the extension.
219
+ name: The name of the extension to check for.
220
+
221
+ Returns:
222
+ bool: True if the extension is loaded, False otherwise.
223
+ """
224
+ results = connection.execute( # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
225
+ "select 1 from duckdb_extensions() where extension_name=? and loaded=true", [name]
226
+ ).fetchone()
227
+ return results is not None
228
+
229
+ @classmethod
230
+ def _configure_secrets(
231
+ cls,
232
+ connection: "DuckDBPyConnection",
233
+ secrets: "Sequence[SecretConfig]",
234
+ ) -> None:
235
+ """Configure persistent secrets for the connection.
236
+
237
+ Args:
238
+ connection: The DuckDB connection to configure secrets for.
239
+ secrets: The list of secrets to store in the connection.
240
+
241
+ Raises:
242
+ ImproperConfigurationError: If a secret could not be stored in the connection.
243
+ """
244
+ try:
245
+ for secret in secrets:
246
+ secret_exists = cls._secret_exists(connection, secret["name"])
247
+ if not secret_exists or secret.get("replace_if_exists", False):
248
+ provider_type = "" if not secret.get("provider") else f"provider {secret.get('provider')},"
249
+ connection.execute(
250
+ f"""create or replace {"persistent" if secret.get("persist", False) else ""} secret {secret["name"]} (
251
+ type {secret["secret_type"]},
252
+ {provider_type}
253
+ {" ,".join([f"{k} '{v}'" for k, v in secret["value"].items()])}
254
+ ) """
255
+ )
256
+ except Exception as e:
257
+ msg = f"Failed to store secret. Error: {e!s}"
258
+ raise ImproperConfigurationError(msg) from e
259
+
260
+ @classmethod
261
+ def _configure_extension(cls, connection: "DuckDBPyConnection", extension: "ExtensionConfig") -> None:
125
262
  """Configure a single extension for the connection.
126
263
 
127
264
  Args:
@@ -132,16 +269,32 @@ class DuckDBConfig(NoPoolSyncConfig[DuckDBPyConnection]):
132
269
  ImproperConfigurationError: If extension installation or configuration fails.
133
270
  """
134
271
  try:
135
- if extension.get("force_install"):
272
+ # Install extension if needed
273
+ if (
274
+ not cls._extension_installed(connection, extension["name"])
275
+ and extension.get("install_if_missing", True)
276
+ ) or extension.get("force_install", False):
277
+ repository = extension.get("repository", None)
278
+ repository_url = (
279
+ "https://community-extensions.duckdb.org"
280
+ if repository is None
281
+ and cls._is_community_extension(connection, extension["name"])
282
+ and extension.get("repository_url") is None
283
+ else extension.get("repository_url", None)
284
+ )
136
285
  connection.install_extension(
137
286
  extension=extension["name"],
138
287
  force_install=extension.get("force_install", False),
139
- repository=extension.get("repository"),
140
- repository_url=extension.get("repository_url"),
288
+ repository=repository,
289
+ repository_url=repository_url,
141
290
  version=extension.get("version"),
142
291
  )
143
- connection.load_extension(extension["name"])
144
292
 
293
+ # Load extension if not already loaded
294
+ if not cls._extension_loaded(connection, extension["name"]):
295
+ connection.load_extension(extension["name"])
296
+
297
+ # Apply any configuration settings
145
298
  if extension.get("config"):
146
299
  for key, value in extension.get("config", {}).items():
147
300
  connection.execute(f"SET {key}={value}")
@@ -156,7 +309,20 @@ class DuckDBConfig(NoPoolSyncConfig[DuckDBPyConnection]):
156
309
  Returns:
157
310
  A string keyed dict of config kwargs for the duckdb.connect() function.
158
311
  """
159
- config = dataclass_to_dict(self, exclude_empty=True, exclude={"extensions"}, convert_nested=False)
312
+ config = dataclass_to_dict(
313
+ self,
314
+ exclude_empty=True,
315
+ exclude={
316
+ "extensions",
317
+ "pool_instance",
318
+ "secrets",
319
+ "on_connection_create",
320
+ "auto_update_extensions",
321
+ "driver_type",
322
+ "connection_type",
323
+ },
324
+ convert_nested=False,
325
+ )
160
326
  if not config.get("database"):
161
327
  config["database"] = ":memory:"
162
328
  return config
@@ -175,7 +341,11 @@ class DuckDBConfig(NoPoolSyncConfig[DuckDBPyConnection]):
175
341
  try:
176
342
  connection = duckdb.connect(**self.connection_config_dict) # pyright: ignore[reportUnknownMemberType]
177
343
  self._configure_extensions(connection)
344
+ self._configure_secrets(connection, cast("list[SecretConfig]", self.secrets))
178
345
  self._configure_connection(connection)
346
+ if self.on_connection_create:
347
+ self.on_connection_create(connection)
348
+
179
349
  except Exception as e:
180
350
  msg = f"Could not configure the DuckDB connection. Error: {e!s}"
181
351
  raise ImproperConfigurationError(msg) from e
@@ -196,3 +366,15 @@ class DuckDBConfig(NoPoolSyncConfig[DuckDBPyConnection]):
196
366
  yield connection
197
367
  finally:
198
368
  connection.close()
369
+
370
+ @contextmanager
371
+ def provide_session(self, *args: Any, **kwargs: Any) -> "Generator[DuckDBDriver, None, None]":
372
+ """Create and provide a database connection.
373
+
374
+ Yields:
375
+ A DuckDB connection instance.
376
+
377
+
378
+ """
379
+ with self.provide_connection(*args, **kwargs) as connection:
380
+ yield self.driver_type(connection, use_cursor=True)
@@ -0,0 +1,225 @@
1
+ from contextlib import contextmanager
2
+ from typing import TYPE_CHECKING, Any, Optional, Union, cast
3
+
4
+ from sqlspec.base import SyncArrowBulkOperationsMixin, SyncDriverAdapterProtocol, T
5
+
6
+ if TYPE_CHECKING:
7
+ from collections.abc import Generator
8
+
9
+ from duckdb import DuckDBPyConnection
10
+
11
+ from sqlspec.typing import ArrowTable, ModelDTOT, StatementParameterType
12
+
13
+ __all__ = ("DuckDBDriver",)
14
+
15
+
16
+ class DuckDBDriver(SyncArrowBulkOperationsMixin["DuckDBPyConnection"], SyncDriverAdapterProtocol["DuckDBPyConnection"]):
17
+ """DuckDB Sync Driver Adapter."""
18
+
19
+ connection: "DuckDBPyConnection"
20
+ use_cursor: bool = True
21
+ dialect: str = "duckdb"
22
+
23
+ def __init__(self, connection: "DuckDBPyConnection", use_cursor: bool = True) -> None:
24
+ self.connection = connection
25
+ self.use_cursor = use_cursor
26
+
27
+ # --- Helper Methods --- #
28
+ def _cursor(self, connection: "DuckDBPyConnection") -> "DuckDBPyConnection":
29
+ if self.use_cursor:
30
+ return connection.cursor()
31
+ return connection
32
+
33
+ @contextmanager
34
+ def _with_cursor(self, connection: "DuckDBPyConnection") -> "Generator[DuckDBPyConnection, None, None]":
35
+ if self.use_cursor:
36
+ cursor = self._cursor(connection)
37
+ try:
38
+ yield cursor
39
+ finally:
40
+ cursor.close()
41
+ else:
42
+ yield connection
43
+
44
+ # --- Public API Methods --- #
45
+
46
+ def select(
47
+ self,
48
+ sql: str,
49
+ parameters: Optional["StatementParameterType"] = None,
50
+ /,
51
+ *,
52
+ connection: Optional["DuckDBPyConnection"] = None,
53
+ schema_type: "Optional[type[ModelDTOT]]" = None,
54
+ **kwargs: Any,
55
+ ) -> "list[Union[ModelDTOT, dict[str, Any]]]":
56
+ connection = self._connection(connection)
57
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
58
+ with self._with_cursor(connection) as cursor:
59
+ cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
60
+ results = cursor.fetchall() # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
61
+ if not results:
62
+ return []
63
+
64
+ column_names = [col[0] for col in cursor.description or []] # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
65
+
66
+ if schema_type is not None:
67
+ return [cast("ModelDTOT", schema_type(**dict(zip(column_names, row)))) for row in results] # pyright: ignore[reportUnknownArgumentType]
68
+ return [dict(zip(column_names, row)) for row in results] # pyright: ignore[reportUnknownArgumentType]
69
+
70
+ def select_one(
71
+ self,
72
+ sql: str,
73
+ parameters: Optional["StatementParameterType"] = None,
74
+ /,
75
+ *,
76
+ connection: Optional["DuckDBPyConnection"] = None,
77
+ schema_type: "Optional[type[ModelDTOT]]" = None,
78
+ **kwargs: Any,
79
+ ) -> "Union[ModelDTOT, dict[str, Any]]":
80
+ connection = self._connection(connection)
81
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
82
+ with self._with_cursor(connection) as cursor:
83
+ cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
84
+ result = cursor.fetchone() # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
85
+ result = self.check_not_found(result) # pyright: ignore
86
+
87
+ column_names = [col[0] for col in cursor.description or []] # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
88
+ if schema_type is not None:
89
+ return cast("ModelDTOT", schema_type(**dict(zip(column_names, result)))) # pyright: ignore[reportUnknownArgumentType]
90
+ # Always return dictionaries
91
+ return dict(zip(column_names, result)) # pyright: ignore[reportUnknownArgumentType,reportUnknownVariableType]
92
+
93
+ def select_one_or_none(
94
+ self,
95
+ sql: str,
96
+ parameters: Optional["StatementParameterType"] = None,
97
+ /,
98
+ *,
99
+ connection: Optional["DuckDBPyConnection"] = None,
100
+ schema_type: "Optional[type[ModelDTOT]]" = None,
101
+ **kwargs: Any,
102
+ ) -> "Optional[Union[ModelDTOT, dict[str, Any]]]":
103
+ connection = self._connection(connection)
104
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
105
+ with self._with_cursor(connection) as cursor:
106
+ cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
107
+ result = cursor.fetchone() # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
108
+ if result is None:
109
+ return None
110
+
111
+ column_names = [col[0] for col in cursor.description or []] # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
112
+ if schema_type is not None:
113
+ return cast("ModelDTOT", schema_type(**dict(zip(column_names, result)))) # pyright: ignore[reportUnknownArgumentType]
114
+ return dict(zip(column_names, result)) # pyright: ignore[reportUnknownArgumentType,reportUnknownVariableType]
115
+
116
+ def select_value(
117
+ self,
118
+ sql: str,
119
+ parameters: "Optional[StatementParameterType]" = None,
120
+ /,
121
+ *,
122
+ connection: "Optional[DuckDBPyConnection]" = None,
123
+ schema_type: "Optional[type[T]]" = None,
124
+ **kwargs: Any,
125
+ ) -> "Union[T, Any]":
126
+ connection = self._connection(connection)
127
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
128
+ with self._with_cursor(connection) as cursor:
129
+ cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
130
+ result = cursor.fetchone() # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
131
+ result = self.check_not_found(result) # pyright: ignore
132
+ if schema_type is None:
133
+ return result[0] # pyright: ignore
134
+ return schema_type(result[0]) # type: ignore[call-arg]
135
+
136
+ def select_value_or_none(
137
+ self,
138
+ sql: str,
139
+ parameters: "Optional[StatementParameterType]" = None,
140
+ /,
141
+ *,
142
+ connection: "Optional[DuckDBPyConnection]" = None,
143
+ schema_type: "Optional[type[T]]" = None,
144
+ **kwargs: Any,
145
+ ) -> "Optional[Union[T, Any]]":
146
+ connection = self._connection(connection)
147
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
148
+ with self._with_cursor(connection) as cursor:
149
+ cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
150
+ result = cursor.fetchone() # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
151
+ if result is None:
152
+ return None
153
+ if schema_type is None:
154
+ return result[0] # pyright: ignore
155
+ return schema_type(result[0]) # type: ignore[call-arg]
156
+
157
+ def insert_update_delete(
158
+ self,
159
+ sql: str,
160
+ parameters: Optional["StatementParameterType"] = None,
161
+ /,
162
+ *,
163
+ connection: Optional["DuckDBPyConnection"] = None,
164
+ **kwargs: Any,
165
+ ) -> int:
166
+ connection = self._connection(connection)
167
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
168
+ with self._with_cursor(connection) as cursor:
169
+ cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
170
+ return getattr(cursor, "rowcount", -1) # pyright: ignore[reportUnknownMemberType]
171
+
172
+ def insert_update_delete_returning(
173
+ self,
174
+ sql: str,
175
+ parameters: Optional["StatementParameterType"] = None,
176
+ /,
177
+ *,
178
+ connection: Optional["DuckDBPyConnection"] = None,
179
+ schema_type: "Optional[type[ModelDTOT]]" = None,
180
+ **kwargs: Any,
181
+ ) -> "Optional[Union[dict[str, Any], ModelDTOT]]":
182
+ connection = self._connection(connection)
183
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
184
+ with self._with_cursor(connection) as cursor:
185
+ cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
186
+ result = cursor.fetchall() # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
187
+ if not result:
188
+ return None # pyright: ignore[reportUnknownArgumentType]
189
+ column_names = [col[0] for col in cursor.description or []] # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
190
+ if schema_type is not None:
191
+ return cast("ModelDTOT", schema_type(**dict(zip(column_names, result[0])))) # pyright: ignore[reportUnknownArgumentType]
192
+ # Always return dictionaries
193
+ return dict(zip(column_names, result[0])) # pyright: ignore[reportUnknownArgumentType,reportUnknownVariableType]
194
+
195
+ def execute_script(
196
+ self,
197
+ sql: str,
198
+ parameters: Optional["StatementParameterType"] = None,
199
+ /,
200
+ *,
201
+ connection: Optional["DuckDBPyConnection"] = None,
202
+ **kwargs: Any,
203
+ ) -> str:
204
+ connection = self._connection(connection)
205
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
206
+ with self._with_cursor(connection) as cursor:
207
+ cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
208
+ return cast("str", getattr(cursor, "statusmessage", "DONE")) # pyright: ignore[reportUnknownMemberType]
209
+
210
+ # --- Arrow Bulk Operations ---
211
+
212
+ def select_arrow( # pyright: ignore[reportUnknownParameterType]
213
+ self,
214
+ sql: str,
215
+ parameters: "Optional[StatementParameterType]" = None,
216
+ /,
217
+ *,
218
+ connection: "Optional[DuckDBPyConnection]" = None,
219
+ **kwargs: Any,
220
+ ) -> "ArrowTable":
221
+ connection = self._connection(connection)
222
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
223
+ with self._with_cursor(connection) as cursor:
224
+ cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
225
+ return cast("ArrowTable", cursor.fetch_arrow_table()) # pyright: ignore[reportUnknownMemberType]
@@ -1,13 +1,16 @@
1
1
  from sqlspec.adapters.oracledb.config import (
2
- OracleAsyncDatabaseConfig,
2
+ OracleAsyncConfig,
3
3
  OracleAsyncPoolConfig,
4
- OracleSyncDatabaseConfig,
4
+ OracleSyncConfig,
5
5
  OracleSyncPoolConfig,
6
6
  )
7
+ from sqlspec.adapters.oracledb.driver import OracleAsyncDriver, OracleSyncDriver
7
8
 
8
9
  __all__ = (
9
- "OracleAsyncDatabaseConfig",
10
+ "OracleAsyncConfig",
11
+ "OracleAsyncDriver",
10
12
  "OracleAsyncPoolConfig",
11
- "OracleSyncDatabaseConfig",
13
+ "OracleSyncConfig",
14
+ "OracleSyncDriver",
12
15
  "OracleSyncPoolConfig",
13
16
  )
@@ -1,9 +1,9 @@
1
- from sqlspec.adapters.oracledb.config._asyncio import OracleAsyncDatabaseConfig, OracleAsyncPoolConfig
2
- from sqlspec.adapters.oracledb.config._sync import OracleSyncDatabaseConfig, OracleSyncPoolConfig
1
+ from sqlspec.adapters.oracledb.config._asyncio import OracleAsyncConfig, OracleAsyncPoolConfig
2
+ from sqlspec.adapters.oracledb.config._sync import OracleSyncConfig, OracleSyncPoolConfig
3
3
 
4
4
  __all__ = (
5
- "OracleAsyncDatabaseConfig",
5
+ "OracleAsyncConfig",
6
6
  "OracleAsyncPoolConfig",
7
- "OracleSyncDatabaseConfig",
7
+ "OracleSyncConfig",
8
8
  "OracleSyncPoolConfig",
9
9
  )