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.
- sqlspec/__init__.py +15 -0
- sqlspec/_serialization.py +16 -2
- sqlspec/_typing.py +40 -7
- sqlspec/adapters/adbc/__init__.py +7 -0
- sqlspec/adapters/adbc/config.py +183 -17
- sqlspec/adapters/adbc/driver.py +392 -0
- sqlspec/adapters/aiosqlite/__init__.py +5 -1
- sqlspec/adapters/aiosqlite/config.py +24 -6
- sqlspec/adapters/aiosqlite/driver.py +264 -0
- sqlspec/adapters/asyncmy/__init__.py +7 -2
- sqlspec/adapters/asyncmy/config.py +71 -11
- sqlspec/adapters/asyncmy/driver.py +246 -0
- sqlspec/adapters/asyncpg/__init__.py +9 -0
- sqlspec/adapters/asyncpg/config.py +102 -25
- sqlspec/adapters/asyncpg/driver.py +444 -0
- sqlspec/adapters/duckdb/__init__.py +5 -1
- sqlspec/adapters/duckdb/config.py +194 -12
- sqlspec/adapters/duckdb/driver.py +225 -0
- sqlspec/adapters/oracledb/__init__.py +7 -4
- sqlspec/adapters/oracledb/config/__init__.py +4 -4
- sqlspec/adapters/oracledb/config/_asyncio.py +96 -12
- sqlspec/adapters/oracledb/config/_common.py +1 -1
- sqlspec/adapters/oracledb/config/_sync.py +96 -12
- sqlspec/adapters/oracledb/driver.py +571 -0
- sqlspec/adapters/psqlpy/__init__.py +0 -0
- sqlspec/adapters/psqlpy/config.py +258 -0
- sqlspec/adapters/psqlpy/driver.py +335 -0
- sqlspec/adapters/psycopg/__init__.py +16 -0
- sqlspec/adapters/psycopg/config/__init__.py +6 -6
- sqlspec/adapters/psycopg/config/_async.py +107 -15
- sqlspec/adapters/psycopg/config/_common.py +2 -2
- sqlspec/adapters/psycopg/config/_sync.py +107 -15
- sqlspec/adapters/psycopg/driver.py +578 -0
- sqlspec/adapters/sqlite/__init__.py +7 -0
- sqlspec/adapters/sqlite/config.py +24 -6
- sqlspec/adapters/sqlite/driver.py +305 -0
- sqlspec/base.py +565 -63
- sqlspec/exceptions.py +30 -0
- sqlspec/extensions/litestar/__init__.py +19 -0
- sqlspec/extensions/litestar/_utils.py +56 -0
- sqlspec/extensions/litestar/config.py +87 -0
- sqlspec/extensions/litestar/handlers.py +213 -0
- sqlspec/extensions/litestar/plugin.py +105 -11
- sqlspec/statement.py +373 -0
- sqlspec/typing.py +81 -17
- sqlspec/utils/__init__.py +3 -0
- sqlspec/utils/fixtures.py +4 -5
- sqlspec/utils/sync_tools.py +335 -0
- {sqlspec-0.7.1.dist-info → sqlspec-0.9.0.dist-info}/METADATA +4 -1
- sqlspec-0.9.0.dist-info/RECORD +61 -0
- sqlspec-0.7.1.dist-info/RECORD +0 -46
- {sqlspec-0.7.1.dist-info → sqlspec-0.9.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.7.1.dist-info → sqlspec-0.9.0.dist-info}/licenses/LICENSE +0 -0
- {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]" =
|
|
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
|
|
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
|
|
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=
|
|
140
|
-
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(
|
|
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
|
-
|
|
2
|
+
OracleAsyncConfig,
|
|
3
3
|
OracleAsyncPoolConfig,
|
|
4
|
-
|
|
4
|
+
OracleSyncConfig,
|
|
5
5
|
OracleSyncPoolConfig,
|
|
6
6
|
)
|
|
7
|
+
from sqlspec.adapters.oracledb.driver import OracleAsyncDriver, OracleSyncDriver
|
|
7
8
|
|
|
8
9
|
__all__ = (
|
|
9
|
-
"
|
|
10
|
+
"OracleAsyncConfig",
|
|
11
|
+
"OracleAsyncDriver",
|
|
10
12
|
"OracleAsyncPoolConfig",
|
|
11
|
-
"
|
|
13
|
+
"OracleSyncConfig",
|
|
14
|
+
"OracleSyncDriver",
|
|
12
15
|
"OracleSyncPoolConfig",
|
|
13
16
|
)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
from sqlspec.adapters.oracledb.config._asyncio import
|
|
2
|
-
from sqlspec.adapters.oracledb.config._sync import
|
|
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
|
-
"
|
|
5
|
+
"OracleAsyncConfig",
|
|
6
6
|
"OracleAsyncPoolConfig",
|
|
7
|
-
"
|
|
7
|
+
"OracleSyncConfig",
|
|
8
8
|
"OracleSyncPoolConfig",
|
|
9
9
|
)
|