sqlspec 0.25.0__py3-none-any.whl → 0.27.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 +7 -15
- sqlspec/_serialization.py +256 -24
- sqlspec/_typing.py +71 -52
- sqlspec/adapters/adbc/_types.py +1 -1
- sqlspec/adapters/adbc/adk/__init__.py +5 -0
- sqlspec/adapters/adbc/adk/store.py +870 -0
- sqlspec/adapters/adbc/config.py +69 -12
- sqlspec/adapters/adbc/data_dictionary.py +340 -0
- sqlspec/adapters/adbc/driver.py +266 -58
- sqlspec/adapters/adbc/litestar/__init__.py +5 -0
- sqlspec/adapters/adbc/litestar/store.py +504 -0
- sqlspec/adapters/adbc/type_converter.py +153 -0
- sqlspec/adapters/aiosqlite/_types.py +1 -1
- sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/adk/store.py +527 -0
- sqlspec/adapters/aiosqlite/config.py +88 -15
- sqlspec/adapters/aiosqlite/data_dictionary.py +149 -0
- sqlspec/adapters/aiosqlite/driver.py +143 -40
- sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/litestar/store.py +281 -0
- sqlspec/adapters/aiosqlite/pool.py +7 -7
- sqlspec/adapters/asyncmy/__init__.py +7 -1
- sqlspec/adapters/asyncmy/_types.py +2 -2
- sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
- sqlspec/adapters/asyncmy/adk/store.py +493 -0
- sqlspec/adapters/asyncmy/config.py +68 -23
- sqlspec/adapters/asyncmy/data_dictionary.py +161 -0
- sqlspec/adapters/asyncmy/driver.py +313 -58
- sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncmy/litestar/store.py +296 -0
- sqlspec/adapters/asyncpg/__init__.py +2 -1
- sqlspec/adapters/asyncpg/_type_handlers.py +71 -0
- sqlspec/adapters/asyncpg/_types.py +11 -7
- sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
- sqlspec/adapters/asyncpg/adk/store.py +450 -0
- sqlspec/adapters/asyncpg/config.py +59 -35
- sqlspec/adapters/asyncpg/data_dictionary.py +173 -0
- sqlspec/adapters/asyncpg/driver.py +170 -25
- sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncpg/litestar/store.py +253 -0
- sqlspec/adapters/bigquery/_types.py +1 -1
- sqlspec/adapters/bigquery/adk/__init__.py +5 -0
- sqlspec/adapters/bigquery/adk/store.py +576 -0
- sqlspec/adapters/bigquery/config.py +27 -10
- sqlspec/adapters/bigquery/data_dictionary.py +149 -0
- sqlspec/adapters/bigquery/driver.py +368 -142
- sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
- sqlspec/adapters/bigquery/litestar/store.py +327 -0
- sqlspec/adapters/bigquery/type_converter.py +125 -0
- sqlspec/adapters/duckdb/_types.py +1 -1
- sqlspec/adapters/duckdb/adk/__init__.py +14 -0
- sqlspec/adapters/duckdb/adk/store.py +553 -0
- sqlspec/adapters/duckdb/config.py +80 -20
- sqlspec/adapters/duckdb/data_dictionary.py +163 -0
- sqlspec/adapters/duckdb/driver.py +167 -45
- sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
- sqlspec/adapters/duckdb/litestar/store.py +332 -0
- sqlspec/adapters/duckdb/pool.py +4 -4
- sqlspec/adapters/duckdb/type_converter.py +133 -0
- sqlspec/adapters/oracledb/_numpy_handlers.py +133 -0
- sqlspec/adapters/oracledb/_types.py +20 -2
- sqlspec/adapters/oracledb/adk/__init__.py +5 -0
- sqlspec/adapters/oracledb/adk/store.py +1745 -0
- sqlspec/adapters/oracledb/config.py +122 -32
- sqlspec/adapters/oracledb/data_dictionary.py +509 -0
- sqlspec/adapters/oracledb/driver.py +353 -91
- sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
- sqlspec/adapters/oracledb/litestar/store.py +767 -0
- sqlspec/adapters/oracledb/migrations.py +348 -73
- sqlspec/adapters/oracledb/type_converter.py +207 -0
- sqlspec/adapters/psqlpy/_type_handlers.py +44 -0
- sqlspec/adapters/psqlpy/_types.py +2 -1
- sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
- sqlspec/adapters/psqlpy/adk/store.py +482 -0
- sqlspec/adapters/psqlpy/config.py +46 -17
- sqlspec/adapters/psqlpy/data_dictionary.py +172 -0
- sqlspec/adapters/psqlpy/driver.py +123 -209
- sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
- sqlspec/adapters/psqlpy/litestar/store.py +272 -0
- sqlspec/adapters/psqlpy/type_converter.py +102 -0
- sqlspec/adapters/psycopg/_type_handlers.py +80 -0
- sqlspec/adapters/psycopg/_types.py +2 -1
- sqlspec/adapters/psycopg/adk/__init__.py +5 -0
- sqlspec/adapters/psycopg/adk/store.py +944 -0
- sqlspec/adapters/psycopg/config.py +69 -35
- sqlspec/adapters/psycopg/data_dictionary.py +331 -0
- sqlspec/adapters/psycopg/driver.py +238 -81
- sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
- sqlspec/adapters/psycopg/litestar/store.py +554 -0
- sqlspec/adapters/sqlite/__init__.py +2 -1
- sqlspec/adapters/sqlite/_type_handlers.py +86 -0
- sqlspec/adapters/sqlite/_types.py +1 -1
- sqlspec/adapters/sqlite/adk/__init__.py +5 -0
- sqlspec/adapters/sqlite/adk/store.py +572 -0
- sqlspec/adapters/sqlite/config.py +87 -15
- sqlspec/adapters/sqlite/data_dictionary.py +149 -0
- sqlspec/adapters/sqlite/driver.py +137 -54
- sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/sqlite/litestar/store.py +318 -0
- sqlspec/adapters/sqlite/pool.py +18 -9
- sqlspec/base.py +45 -26
- sqlspec/builder/__init__.py +73 -4
- sqlspec/builder/_base.py +162 -89
- sqlspec/builder/_column.py +62 -29
- sqlspec/builder/_ddl.py +180 -121
- sqlspec/builder/_delete.py +5 -4
- sqlspec/builder/_dml.py +388 -0
- sqlspec/{_sql.py → builder/_factory.py} +53 -94
- sqlspec/builder/_insert.py +32 -131
- sqlspec/builder/_join.py +375 -0
- sqlspec/builder/_merge.py +446 -11
- sqlspec/builder/_parsing_utils.py +111 -17
- sqlspec/builder/_select.py +1457 -24
- sqlspec/builder/_update.py +11 -42
- sqlspec/cli.py +307 -194
- sqlspec/config.py +252 -67
- sqlspec/core/__init__.py +5 -4
- sqlspec/core/cache.py +17 -17
- sqlspec/core/compiler.py +62 -9
- sqlspec/core/filters.py +37 -37
- sqlspec/core/hashing.py +9 -9
- sqlspec/core/parameters.py +83 -48
- sqlspec/core/result.py +102 -46
- sqlspec/core/splitter.py +16 -17
- sqlspec/core/statement.py +36 -30
- sqlspec/core/type_conversion.py +235 -0
- sqlspec/driver/__init__.py +7 -6
- sqlspec/driver/_async.py +188 -151
- sqlspec/driver/_common.py +285 -80
- sqlspec/driver/_sync.py +188 -152
- sqlspec/driver/mixins/_result_tools.py +20 -236
- sqlspec/driver/mixins/_sql_translator.py +4 -4
- sqlspec/exceptions.py +75 -7
- sqlspec/extensions/adk/__init__.py +53 -0
- sqlspec/extensions/adk/_types.py +51 -0
- sqlspec/extensions/adk/converters.py +172 -0
- sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +144 -0
- sqlspec/extensions/adk/migrations/__init__.py +0 -0
- sqlspec/extensions/adk/service.py +181 -0
- sqlspec/extensions/adk/store.py +536 -0
- sqlspec/extensions/aiosql/adapter.py +73 -53
- sqlspec/extensions/litestar/__init__.py +21 -4
- sqlspec/extensions/litestar/cli.py +54 -10
- sqlspec/extensions/litestar/config.py +59 -266
- sqlspec/extensions/litestar/handlers.py +46 -17
- sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
- sqlspec/extensions/litestar/migrations/__init__.py +3 -0
- sqlspec/extensions/litestar/plugin.py +324 -223
- sqlspec/extensions/litestar/providers.py +25 -25
- sqlspec/extensions/litestar/store.py +265 -0
- sqlspec/loader.py +30 -49
- sqlspec/migrations/__init__.py +4 -3
- sqlspec/migrations/base.py +302 -39
- sqlspec/migrations/commands.py +611 -144
- sqlspec/migrations/context.py +142 -0
- sqlspec/migrations/fix.py +199 -0
- sqlspec/migrations/loaders.py +68 -23
- sqlspec/migrations/runner.py +543 -107
- sqlspec/migrations/tracker.py +237 -21
- sqlspec/migrations/utils.py +51 -3
- sqlspec/migrations/validation.py +177 -0
- sqlspec/protocols.py +66 -36
- sqlspec/storage/_utils.py +98 -0
- sqlspec/storage/backends/fsspec.py +134 -106
- sqlspec/storage/backends/local.py +78 -51
- sqlspec/storage/backends/obstore.py +278 -162
- sqlspec/storage/registry.py +75 -39
- sqlspec/typing.py +16 -84
- sqlspec/utils/config_resolver.py +153 -0
- sqlspec/utils/correlation.py +4 -5
- sqlspec/utils/data_transformation.py +3 -2
- sqlspec/utils/deprecation.py +9 -8
- sqlspec/utils/fixtures.py +4 -4
- sqlspec/utils/logging.py +46 -6
- sqlspec/utils/module_loader.py +2 -2
- sqlspec/utils/schema.py +288 -0
- sqlspec/utils/serializers.py +50 -2
- sqlspec/utils/sync_tools.py +21 -17
- sqlspec/utils/text.py +1 -2
- sqlspec/utils/type_guards.py +111 -20
- sqlspec/utils/version.py +433 -0
- {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/METADATA +40 -21
- sqlspec-0.27.0.dist-info/RECORD +207 -0
- sqlspec/builder/mixins/__init__.py +0 -55
- sqlspec/builder/mixins/_cte_and_set_ops.py +0 -254
- sqlspec/builder/mixins/_delete_operations.py +0 -50
- sqlspec/builder/mixins/_insert_operations.py +0 -282
- sqlspec/builder/mixins/_join_operations.py +0 -389
- sqlspec/builder/mixins/_merge_operations.py +0 -592
- sqlspec/builder/mixins/_order_limit_operations.py +0 -152
- sqlspec/builder/mixins/_pivot_operations.py +0 -157
- sqlspec/builder/mixins/_select_operations.py +0 -936
- sqlspec/builder/mixins/_update_operations.py +0 -218
- sqlspec/builder/mixins/_where_clause.py +0 -1304
- sqlspec-0.25.0.dist-info/RECORD +0 -139
- sqlspec-0.25.0.dist-info/licenses/NOTICE +0 -29
- {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from collections.abc import Sequence
|
|
4
4
|
from contextlib import contextmanager
|
|
5
|
-
from typing import TYPE_CHECKING, Any, ClassVar,
|
|
5
|
+
from typing import TYPE_CHECKING, Any, ClassVar, TypedDict
|
|
6
6
|
|
|
7
7
|
from typing_extensions import NotRequired
|
|
8
8
|
|
|
@@ -26,7 +26,7 @@ __all__ = (
|
|
|
26
26
|
)
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
class DuckDBConnectionParams(TypedDict
|
|
29
|
+
class DuckDBConnectionParams(TypedDict):
|
|
30
30
|
"""DuckDB connection parameters."""
|
|
31
31
|
|
|
32
32
|
database: NotRequired[str]
|
|
@@ -65,7 +65,7 @@ class DuckDBConnectionParams(TypedDict, total=False):
|
|
|
65
65
|
extra: NotRequired[dict[str, Any]]
|
|
66
66
|
|
|
67
67
|
|
|
68
|
-
class DuckDBPoolParams(DuckDBConnectionParams
|
|
68
|
+
class DuckDBPoolParams(DuckDBConnectionParams):
|
|
69
69
|
"""Complete pool configuration for DuckDB adapter.
|
|
70
70
|
|
|
71
71
|
Combines standardized pool parameters with DuckDB-specific connection parameters.
|
|
@@ -77,7 +77,7 @@ class DuckDBPoolParams(DuckDBConnectionParams, total=False):
|
|
|
77
77
|
pool_recycle_seconds: NotRequired[int]
|
|
78
78
|
|
|
79
79
|
|
|
80
|
-
class DuckDBExtensionConfig(TypedDict
|
|
80
|
+
class DuckDBExtensionConfig(TypedDict):
|
|
81
81
|
"""DuckDB extension configuration for auto-management."""
|
|
82
82
|
|
|
83
83
|
name: str
|
|
@@ -93,7 +93,7 @@ class DuckDBExtensionConfig(TypedDict, total=False):
|
|
|
93
93
|
"""Force reinstallation of the extension."""
|
|
94
94
|
|
|
95
95
|
|
|
96
|
-
class DuckDBSecretConfig(TypedDict
|
|
96
|
+
class DuckDBSecretConfig(TypedDict):
|
|
97
97
|
"""DuckDB secret configuration for AI/API integrations."""
|
|
98
98
|
|
|
99
99
|
secret_type: str
|
|
@@ -109,15 +109,25 @@ class DuckDBSecretConfig(TypedDict, total=False):
|
|
|
109
109
|
"""Scope of the secret (LOCAL or PERSISTENT)."""
|
|
110
110
|
|
|
111
111
|
|
|
112
|
-
class DuckDBDriverFeatures(TypedDict
|
|
113
|
-
"""TypedDict for DuckDB driver features configuration.
|
|
112
|
+
class DuckDBDriverFeatures(TypedDict):
|
|
113
|
+
"""TypedDict for DuckDB driver features configuration.
|
|
114
|
+
|
|
115
|
+
Attributes:
|
|
116
|
+
extensions: List of extensions to install/load on connection creation.
|
|
117
|
+
secrets: List of secrets to create for AI/API integrations.
|
|
118
|
+
on_connection_create: Callback executed when connection is created.
|
|
119
|
+
json_serializer: Custom JSON serializer for dict/list parameter conversion.
|
|
120
|
+
Defaults to sqlspec.utils.serializers.to_json if not provided.
|
|
121
|
+
enable_uuid_conversion: Enable automatic UUID string conversion.
|
|
122
|
+
When True (default), UUID strings are automatically converted to UUID objects.
|
|
123
|
+
When False, UUID strings are treated as regular strings.
|
|
124
|
+
"""
|
|
114
125
|
|
|
115
126
|
extensions: NotRequired[Sequence[DuckDBExtensionConfig]]
|
|
116
|
-
"""List of extensions to install/load on connection creation."""
|
|
117
127
|
secrets: NotRequired[Sequence[DuckDBSecretConfig]]
|
|
118
|
-
"
|
|
119
|
-
|
|
120
|
-
|
|
128
|
+
on_connection_create: NotRequired["Callable[[DuckDBConnection], DuckDBConnection | None]"]
|
|
129
|
+
json_serializer: NotRequired["Callable[[Any], str]"]
|
|
130
|
+
enable_uuid_conversion: NotRequired[bool]
|
|
121
131
|
|
|
122
132
|
|
|
123
133
|
class DuckDBConfig(SyncDatabaseConfig[DuckDBConnection, DuckDBConnectionPool, DuckDBDriver]):
|
|
@@ -131,26 +141,66 @@ class DuckDBConfig(SyncDatabaseConfig[DuckDBConnection, DuckDBConnectionPool, Du
|
|
|
131
141
|
- Auto configuration settings
|
|
132
142
|
- Arrow integration
|
|
133
143
|
- Direct file querying capabilities
|
|
144
|
+
- Configurable type handlers for JSON serialization and UUID conversion
|
|
134
145
|
|
|
135
146
|
DuckDB Connection Pool Configuration:
|
|
136
147
|
- Default pool size is 1-4 connections (DuckDB uses single connection by default)
|
|
137
148
|
- Connection recycling is set to 24 hours by default (set to 0 to disable)
|
|
138
149
|
- Shared memory databases use `:memory:shared_db` for proper concurrency
|
|
150
|
+
|
|
151
|
+
Type Handler Configuration via driver_features:
|
|
152
|
+
- `json_serializer`: Custom JSON serializer for dict/list parameters.
|
|
153
|
+
Defaults to `sqlspec.utils.serializers.to_json` if not provided.
|
|
154
|
+
Example: `json_serializer=msgspec.json.encode(...).decode('utf-8')`
|
|
155
|
+
|
|
156
|
+
- `enable_uuid_conversion`: Enable automatic UUID string conversion (default: True).
|
|
157
|
+
When True, UUID strings in query results are automatically converted to UUID objects.
|
|
158
|
+
When False, UUID strings are treated as regular strings.
|
|
159
|
+
|
|
160
|
+
Example:
|
|
161
|
+
>>> import msgspec
|
|
162
|
+
>>> from sqlspec.adapters.duckdb import DuckDBConfig
|
|
163
|
+
>>>
|
|
164
|
+
>>> # Custom JSON serializer
|
|
165
|
+
>>> def custom_json(obj):
|
|
166
|
+
... return msgspec.json.encode(obj).decode("utf-8")
|
|
167
|
+
>>>
|
|
168
|
+
>>> config = DuckDBConfig(
|
|
169
|
+
... pool_config={"database": ":memory:"},
|
|
170
|
+
... driver_features={
|
|
171
|
+
... "json_serializer": custom_json,
|
|
172
|
+
... "enable_uuid_conversion": False,
|
|
173
|
+
... },
|
|
174
|
+
... )
|
|
139
175
|
"""
|
|
140
176
|
|
|
141
177
|
driver_type: "ClassVar[type[DuckDBDriver]]" = DuckDBDriver
|
|
142
178
|
connection_type: "ClassVar[type[DuckDBConnection]]" = DuckDBConnection
|
|
179
|
+
supports_transactional_ddl: "ClassVar[bool]" = True
|
|
143
180
|
|
|
144
181
|
def __init__(
|
|
145
182
|
self,
|
|
146
183
|
*,
|
|
147
|
-
pool_config: "
|
|
148
|
-
pool_instance: "
|
|
149
|
-
migration_config:
|
|
150
|
-
statement_config: "
|
|
151
|
-
driver_features: "
|
|
184
|
+
pool_config: "DuckDBPoolParams | dict[str, Any] | None" = None,
|
|
185
|
+
pool_instance: "DuckDBConnectionPool | None" = None,
|
|
186
|
+
migration_config: dict[str, Any] | None = None,
|
|
187
|
+
statement_config: "StatementConfig | None" = None,
|
|
188
|
+
driver_features: "DuckDBDriverFeatures | dict[str, Any] | None" = None,
|
|
189
|
+
bind_key: "str | None" = None,
|
|
190
|
+
extension_config: "dict[str, dict[str, Any]] | None" = None,
|
|
152
191
|
) -> None:
|
|
153
|
-
"""Initialize DuckDB configuration.
|
|
192
|
+
"""Initialize DuckDB configuration.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
pool_config: Pool configuration parameters
|
|
196
|
+
pool_instance: Pre-created pool instance
|
|
197
|
+
migration_config: Migration configuration
|
|
198
|
+
statement_config: Statement configuration override
|
|
199
|
+
driver_features: DuckDB-specific driver features including json_serializer
|
|
200
|
+
and enable_uuid_conversion options
|
|
201
|
+
bind_key: Optional unique identifier for this configuration
|
|
202
|
+
extension_config: Extension-specific configuration (e.g., Litestar plugin settings)
|
|
203
|
+
"""
|
|
154
204
|
if pool_config is None:
|
|
155
205
|
pool_config = {}
|
|
156
206
|
if "database" not in pool_config:
|
|
@@ -159,12 +209,18 @@ class DuckDBConfig(SyncDatabaseConfig[DuckDBConnection, DuckDBConnectionPool, Du
|
|
|
159
209
|
if pool_config.get("database") in {":memory:", ""}:
|
|
160
210
|
pool_config["database"] = ":memory:shared_db"
|
|
161
211
|
|
|
212
|
+
processed_features = dict(driver_features) if driver_features else {}
|
|
213
|
+
if "enable_uuid_conversion" not in processed_features:
|
|
214
|
+
processed_features["enable_uuid_conversion"] = True
|
|
215
|
+
|
|
162
216
|
super().__init__(
|
|
217
|
+
bind_key=bind_key,
|
|
163
218
|
pool_config=dict(pool_config),
|
|
164
219
|
pool_instance=pool_instance,
|
|
165
220
|
migration_config=migration_config,
|
|
166
221
|
statement_config=statement_config or duckdb_statement_config,
|
|
167
|
-
driver_features=
|
|
222
|
+
driver_features=processed_features,
|
|
223
|
+
extension_config=extension_config,
|
|
168
224
|
)
|
|
169
225
|
|
|
170
226
|
def _get_connection_config_dict(self) -> "dict[str, Any]":
|
|
@@ -245,7 +301,7 @@ class DuckDBConfig(SyncDatabaseConfig[DuckDBConnection, DuckDBConnectionPool, Du
|
|
|
245
301
|
|
|
246
302
|
@contextmanager
|
|
247
303
|
def provide_session(
|
|
248
|
-
self, *args: Any, statement_config: "
|
|
304
|
+
self, *args: Any, statement_config: "StatementConfig | None" = None, **kwargs: Any
|
|
249
305
|
) -> "Generator[DuckDBDriver, None, None]":
|
|
250
306
|
"""Provide a DuckDB driver session context manager.
|
|
251
307
|
|
|
@@ -258,7 +314,11 @@ class DuckDBConfig(SyncDatabaseConfig[DuckDBConnection, DuckDBConnectionPool, Du
|
|
|
258
314
|
A context manager that yields a DuckDBDriver instance.
|
|
259
315
|
"""
|
|
260
316
|
with self.provide_connection(*args, **kwargs) as connection:
|
|
261
|
-
driver = self.driver_type(
|
|
317
|
+
driver = self.driver_type(
|
|
318
|
+
connection=connection,
|
|
319
|
+
statement_config=statement_config or self.statement_config,
|
|
320
|
+
driver_features=self.driver_features,
|
|
321
|
+
)
|
|
262
322
|
yield driver
|
|
263
323
|
|
|
264
324
|
def get_signature_namespace(self) -> "dict[str, type[Any]]":
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""DuckDB-specific data dictionary for metadata queries."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
5
|
+
|
|
6
|
+
from sqlspec.driver import SyncDataDictionaryBase, SyncDriverAdapterBase, VersionInfo
|
|
7
|
+
from sqlspec.utils.logging import get_logger
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
|
|
12
|
+
from sqlspec.adapters.duckdb.driver import DuckDBDriver
|
|
13
|
+
|
|
14
|
+
logger = get_logger("adapters.duckdb.data_dictionary")
|
|
15
|
+
|
|
16
|
+
# Compiled regex patterns
|
|
17
|
+
DUCKDB_VERSION_PATTERN = re.compile(r"v?(\d+)\.(\d+)\.(\d+)")
|
|
18
|
+
|
|
19
|
+
__all__ = ("DuckDBSyncDataDictionary",)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class DuckDBSyncDataDictionary(SyncDataDictionaryBase):
|
|
23
|
+
"""DuckDB-specific sync data dictionary."""
|
|
24
|
+
|
|
25
|
+
def get_version(self, driver: SyncDriverAdapterBase) -> "VersionInfo | None":
|
|
26
|
+
"""Get DuckDB database version information.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
driver: DuckDB driver instance
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
DuckDB version information or None if detection fails
|
|
33
|
+
"""
|
|
34
|
+
version_str = cast("DuckDBDriver", driver).select_value("SELECT version()")
|
|
35
|
+
if not version_str:
|
|
36
|
+
logger.warning("No DuckDB version information found")
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
# Parse version like "v0.9.2" or "0.9.2"
|
|
40
|
+
version_match = DUCKDB_VERSION_PATTERN.search(str(version_str))
|
|
41
|
+
if not version_match:
|
|
42
|
+
logger.warning("Could not parse DuckDB version: %s", version_str)
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
major, minor, patch = map(int, version_match.groups())
|
|
46
|
+
version_info = VersionInfo(major, minor, patch)
|
|
47
|
+
logger.debug("Detected DuckDB version: %s", version_info)
|
|
48
|
+
return version_info
|
|
49
|
+
|
|
50
|
+
def get_feature_flag(self, driver: SyncDriverAdapterBase, feature: str) -> bool:
|
|
51
|
+
"""Check if DuckDB database supports a specific feature.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
driver: DuckDB driver instance
|
|
55
|
+
feature: Feature name to check
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
True if feature is supported, False otherwise
|
|
59
|
+
"""
|
|
60
|
+
version_info = self.get_version(driver)
|
|
61
|
+
if not version_info:
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
feature_checks: dict[str, Callable[..., bool]] = {
|
|
65
|
+
"supports_json": lambda _: True, # DuckDB has excellent JSON support
|
|
66
|
+
"supports_arrays": lambda _: True, # LIST type
|
|
67
|
+
"supports_maps": lambda _: True, # MAP type
|
|
68
|
+
"supports_structs": lambda _: True, # STRUCT type
|
|
69
|
+
"supports_returning": lambda v: v >= VersionInfo(0, 8, 0),
|
|
70
|
+
"supports_upsert": lambda v: v >= VersionInfo(0, 8, 0),
|
|
71
|
+
"supports_window_functions": lambda _: True,
|
|
72
|
+
"supports_cte": lambda _: True,
|
|
73
|
+
"supports_transactions": lambda _: True,
|
|
74
|
+
"supports_prepared_statements": lambda _: True,
|
|
75
|
+
"supports_schemas": lambda _: True,
|
|
76
|
+
"supports_uuid": lambda _: True,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if feature in feature_checks:
|
|
80
|
+
return bool(feature_checks[feature](version_info))
|
|
81
|
+
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
def get_optimal_type(self, driver: SyncDriverAdapterBase, type_category: str) -> str: # pyright: ignore
|
|
85
|
+
"""Get optimal DuckDB type for a category.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
driver: DuckDB driver instance
|
|
89
|
+
type_category: Type category
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
DuckDB-specific type name
|
|
93
|
+
"""
|
|
94
|
+
type_map = {
|
|
95
|
+
"json": "JSON",
|
|
96
|
+
"uuid": "UUID",
|
|
97
|
+
"boolean": "BOOLEAN",
|
|
98
|
+
"timestamp": "TIMESTAMP",
|
|
99
|
+
"text": "TEXT",
|
|
100
|
+
"blob": "BLOB",
|
|
101
|
+
"array": "LIST",
|
|
102
|
+
"map": "MAP",
|
|
103
|
+
"struct": "STRUCT",
|
|
104
|
+
}
|
|
105
|
+
return type_map.get(type_category, "VARCHAR")
|
|
106
|
+
|
|
107
|
+
def get_columns(
|
|
108
|
+
self, driver: SyncDriverAdapterBase, table: str, schema: "str | None" = None
|
|
109
|
+
) -> "list[dict[str, Any]]":
|
|
110
|
+
"""Get column information for a table using information_schema.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
driver: DuckDB driver instance
|
|
114
|
+
table: Table name to query columns for
|
|
115
|
+
schema: Schema name (None for default)
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
List of column metadata dictionaries with keys:
|
|
119
|
+
- column_name: Name of the column
|
|
120
|
+
- data_type: DuckDB data type
|
|
121
|
+
- nullable: Whether column allows NULL (YES/NO)
|
|
122
|
+
- column_default: Default value if any
|
|
123
|
+
"""
|
|
124
|
+
duckdb_driver = cast("DuckDBDriver", driver)
|
|
125
|
+
|
|
126
|
+
if schema:
|
|
127
|
+
sql = f"""
|
|
128
|
+
SELECT column_name, data_type, is_nullable, column_default
|
|
129
|
+
FROM information_schema.columns
|
|
130
|
+
WHERE table_name = '{table}' AND table_schema = '{schema}'
|
|
131
|
+
ORDER BY ordinal_position
|
|
132
|
+
"""
|
|
133
|
+
else:
|
|
134
|
+
sql = f"""
|
|
135
|
+
SELECT column_name, data_type, is_nullable, column_default
|
|
136
|
+
FROM information_schema.columns
|
|
137
|
+
WHERE table_name = '{table}'
|
|
138
|
+
ORDER BY ordinal_position
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
result = duckdb_driver.execute(sql)
|
|
142
|
+
return result.data or []
|
|
143
|
+
|
|
144
|
+
def list_available_features(self) -> "list[str]":
|
|
145
|
+
"""List available DuckDB feature flags.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
List of supported feature names
|
|
149
|
+
"""
|
|
150
|
+
return [
|
|
151
|
+
"supports_json",
|
|
152
|
+
"supports_arrays",
|
|
153
|
+
"supports_maps",
|
|
154
|
+
"supports_structs",
|
|
155
|
+
"supports_returning",
|
|
156
|
+
"supports_upsert",
|
|
157
|
+
"supports_window_functions",
|
|
158
|
+
"supports_cte",
|
|
159
|
+
"supports_transactions",
|
|
160
|
+
"supports_prepared_statements",
|
|
161
|
+
"supports_schemas",
|
|
162
|
+
"supports_uuid",
|
|
163
|
+
]
|
|
@@ -1,16 +1,32 @@
|
|
|
1
1
|
"""DuckDB driver implementation."""
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import datetime
|
|
4
|
+
from decimal import Decimal
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Final
|
|
4
6
|
|
|
5
7
|
import duckdb
|
|
6
8
|
from sqlglot import exp
|
|
7
9
|
|
|
10
|
+
from sqlspec.adapters.duckdb.data_dictionary import DuckDBSyncDataDictionary
|
|
11
|
+
from sqlspec.adapters.duckdb.type_converter import DuckDBTypeConverter
|
|
8
12
|
from sqlspec.core.cache import get_cache_config
|
|
9
13
|
from sqlspec.core.parameters import ParameterStyle, ParameterStyleConfig
|
|
10
14
|
from sqlspec.core.statement import SQL, StatementConfig
|
|
11
15
|
from sqlspec.driver import SyncDriverAdapterBase
|
|
12
|
-
from sqlspec.exceptions import
|
|
16
|
+
from sqlspec.exceptions import (
|
|
17
|
+
CheckViolationError,
|
|
18
|
+
DataError,
|
|
19
|
+
ForeignKeyViolationError,
|
|
20
|
+
IntegrityError,
|
|
21
|
+
NotFoundError,
|
|
22
|
+
NotNullViolationError,
|
|
23
|
+
OperationalError,
|
|
24
|
+
SQLParsingError,
|
|
25
|
+
SQLSpecError,
|
|
26
|
+
UniqueViolationError,
|
|
27
|
+
)
|
|
13
28
|
from sqlspec.utils.logging import get_logger
|
|
29
|
+
from sqlspec.utils.serializers import to_json
|
|
14
30
|
|
|
15
31
|
if TYPE_CHECKING:
|
|
16
32
|
from contextlib import AbstractContextManager
|
|
@@ -18,11 +34,14 @@ if TYPE_CHECKING:
|
|
|
18
34
|
from sqlspec.adapters.duckdb._types import DuckDBConnection
|
|
19
35
|
from sqlspec.core.result import SQLResult
|
|
20
36
|
from sqlspec.driver import ExecutionResult
|
|
37
|
+
from sqlspec.driver._sync import SyncDataDictionaryBase
|
|
21
38
|
|
|
22
39
|
__all__ = ("DuckDBCursor", "DuckDBDriver", "DuckDBExceptionHandler", "duckdb_statement_config")
|
|
23
40
|
|
|
24
41
|
logger = get_logger("adapters.duckdb")
|
|
25
42
|
|
|
43
|
+
_type_converter = DuckDBTypeConverter()
|
|
44
|
+
|
|
26
45
|
|
|
27
46
|
duckdb_statement_config = StatementConfig(
|
|
28
47
|
dialect="duckdb",
|
|
@@ -31,7 +50,15 @@ duckdb_statement_config = StatementConfig(
|
|
|
31
50
|
supported_parameter_styles={ParameterStyle.QMARK, ParameterStyle.NUMERIC, ParameterStyle.NAMED_DOLLAR},
|
|
32
51
|
default_execution_parameter_style=ParameterStyle.QMARK,
|
|
33
52
|
supported_execution_parameter_styles={ParameterStyle.QMARK, ParameterStyle.NUMERIC},
|
|
34
|
-
type_coercion_map={
|
|
53
|
+
type_coercion_map={
|
|
54
|
+
bool: int,
|
|
55
|
+
datetime.datetime: lambda v: v.isoformat(),
|
|
56
|
+
datetime.date: lambda v: v.isoformat(),
|
|
57
|
+
Decimal: str,
|
|
58
|
+
dict: to_json,
|
|
59
|
+
list: to_json,
|
|
60
|
+
str: _type_converter.convert_if_detected,
|
|
61
|
+
},
|
|
35
62
|
has_native_list_expansion=True,
|
|
36
63
|
needs_static_script_compilation=False,
|
|
37
64
|
preserve_parameter_format=True,
|
|
@@ -54,14 +81,13 @@ class DuckDBCursor:
|
|
|
54
81
|
|
|
55
82
|
def __init__(self, connection: "DuckDBConnection") -> None:
|
|
56
83
|
self.connection = connection
|
|
57
|
-
self.cursor:
|
|
84
|
+
self.cursor: Any | None = None
|
|
58
85
|
|
|
59
86
|
def __enter__(self) -> Any:
|
|
60
87
|
self.cursor = self.connection.cursor()
|
|
61
88
|
return self.cursor
|
|
62
89
|
|
|
63
|
-
def __exit__(self,
|
|
64
|
-
_ = (exc_type, exc_val, exc_tb)
|
|
90
|
+
def __exit__(self, *_: Any) -> None:
|
|
65
91
|
if self.cursor is not None:
|
|
66
92
|
self.cursor.close()
|
|
67
93
|
|
|
@@ -69,8 +95,8 @@ class DuckDBCursor:
|
|
|
69
95
|
class DuckDBExceptionHandler:
|
|
70
96
|
"""Context manager for handling DuckDB database exceptions.
|
|
71
97
|
|
|
72
|
-
|
|
73
|
-
SQLSpec
|
|
98
|
+
Uses exception type and message-based detection to map DuckDB errors
|
|
99
|
+
to specific SQLSpec exceptions for better error handling.
|
|
74
100
|
"""
|
|
75
101
|
|
|
76
102
|
__slots__ = ()
|
|
@@ -79,41 +105,93 @@ class DuckDBExceptionHandler:
|
|
|
79
105
|
return None
|
|
80
106
|
|
|
81
107
|
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
108
|
+
_ = exc_tb
|
|
82
109
|
if exc_type is None:
|
|
83
110
|
return
|
|
111
|
+
self._map_duckdb_exception(exc_type, exc_val)
|
|
84
112
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if
|
|
98
|
-
e
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
e
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
113
|
+
def _map_duckdb_exception(self, exc_type: Any, e: Any) -> None:
|
|
114
|
+
"""Map DuckDB exception to SQLSpec exception.
|
|
115
|
+
|
|
116
|
+
Uses exception type and message-based detection.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
exc_type: Exception type
|
|
120
|
+
e: Exception instance
|
|
121
|
+
"""
|
|
122
|
+
error_msg = str(e).lower()
|
|
123
|
+
exc_name = exc_type.__name__ if hasattr(exc_type, "__name__") else str(exc_type)
|
|
124
|
+
|
|
125
|
+
if "constraintexception" in exc_name.lower():
|
|
126
|
+
self._handle_constraint_exception(e, error_msg)
|
|
127
|
+
elif "catalogexception" in exc_name.lower():
|
|
128
|
+
self._raise_not_found_error(e)
|
|
129
|
+
elif "parserexception" in exc_name.lower() or "binderexception" in exc_name.lower():
|
|
130
|
+
self._raise_parsing_error(e)
|
|
131
|
+
elif "ioexception" in exc_name.lower():
|
|
132
|
+
self._raise_operational_error(e)
|
|
133
|
+
elif "conversionexception" in exc_name.lower() or "type mismatch" in error_msg:
|
|
134
|
+
self._raise_data_error(e)
|
|
135
|
+
else:
|
|
136
|
+
self._raise_generic_error(e)
|
|
137
|
+
|
|
138
|
+
def _handle_constraint_exception(self, e: Any, error_msg: str) -> None:
|
|
139
|
+
"""Handle constraint exceptions using message-based detection.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
e: Exception instance
|
|
143
|
+
error_msg: Lowercase error message
|
|
144
|
+
"""
|
|
145
|
+
if "unique" in error_msg or "duplicate" in error_msg:
|
|
146
|
+
self._raise_unique_violation(e)
|
|
147
|
+
elif "foreign key" in error_msg or "violates foreign key" in error_msg:
|
|
148
|
+
self._raise_foreign_key_violation(e)
|
|
149
|
+
elif "not null" in error_msg or "null value" in error_msg:
|
|
150
|
+
self._raise_not_null_violation(e)
|
|
151
|
+
elif "check constraint" in error_msg or "check condition" in error_msg:
|
|
152
|
+
self._raise_check_violation(e)
|
|
153
|
+
else:
|
|
154
|
+
self._raise_integrity_error(e)
|
|
155
|
+
|
|
156
|
+
def _raise_unique_violation(self, e: Any) -> None:
|
|
157
|
+
msg = f"DuckDB unique constraint violation: {e}"
|
|
158
|
+
raise UniqueViolationError(msg) from e
|
|
159
|
+
|
|
160
|
+
def _raise_foreign_key_violation(self, e: Any) -> None:
|
|
161
|
+
msg = f"DuckDB foreign key constraint violation: {e}"
|
|
162
|
+
raise ForeignKeyViolationError(msg) from e
|
|
163
|
+
|
|
164
|
+
def _raise_not_null_violation(self, e: Any) -> None:
|
|
165
|
+
msg = f"DuckDB not-null constraint violation: {e}"
|
|
166
|
+
raise NotNullViolationError(msg) from e
|
|
167
|
+
|
|
168
|
+
def _raise_check_violation(self, e: Any) -> None:
|
|
169
|
+
msg = f"DuckDB check constraint violation: {e}"
|
|
170
|
+
raise CheckViolationError(msg) from e
|
|
171
|
+
|
|
172
|
+
def _raise_integrity_error(self, e: Any) -> None:
|
|
173
|
+
msg = f"DuckDB integrity constraint violation: {e}"
|
|
174
|
+
raise IntegrityError(msg) from e
|
|
175
|
+
|
|
176
|
+
def _raise_not_found_error(self, e: Any) -> None:
|
|
177
|
+
msg = f"DuckDB catalog error: {e}"
|
|
178
|
+
raise NotFoundError(msg) from e
|
|
179
|
+
|
|
180
|
+
def _raise_parsing_error(self, e: Any) -> None:
|
|
181
|
+
msg = f"DuckDB SQL parsing error: {e}"
|
|
182
|
+
raise SQLParsingError(msg) from e
|
|
183
|
+
|
|
184
|
+
def _raise_operational_error(self, e: Any) -> None:
|
|
185
|
+
msg = f"DuckDB operational error: {e}"
|
|
186
|
+
raise OperationalError(msg) from e
|
|
187
|
+
|
|
188
|
+
def _raise_data_error(self, e: Any) -> None:
|
|
189
|
+
msg = f"DuckDB data error: {e}"
|
|
190
|
+
raise DataError(msg) from e
|
|
191
|
+
|
|
192
|
+
def _raise_generic_error(self, e: Any) -> None:
|
|
193
|
+
msg = f"DuckDB database error: {e}"
|
|
194
|
+
raise SQLSpecError(msg) from e
|
|
117
195
|
|
|
118
196
|
|
|
119
197
|
class DuckDBDriver(SyncDriverAdapterBase):
|
|
@@ -127,14 +205,14 @@ class DuckDBDriver(SyncDriverAdapterBase):
|
|
|
127
205
|
the sqlspec.core modules for statement processing and caching.
|
|
128
206
|
"""
|
|
129
207
|
|
|
130
|
-
__slots__ = ()
|
|
208
|
+
__slots__ = ("_data_dictionary",)
|
|
131
209
|
dialect = "duckdb"
|
|
132
210
|
|
|
133
211
|
def __init__(
|
|
134
212
|
self,
|
|
135
213
|
connection: "DuckDBConnection",
|
|
136
|
-
statement_config: "
|
|
137
|
-
driver_features: "
|
|
214
|
+
statement_config: "StatementConfig | None" = None,
|
|
215
|
+
driver_features: "dict[str, Any] | None" = None,
|
|
138
216
|
) -> None:
|
|
139
217
|
if statement_config is None:
|
|
140
218
|
cache_config = get_cache_config()
|
|
@@ -146,7 +224,40 @@ class DuckDBDriver(SyncDriverAdapterBase):
|
|
|
146
224
|
)
|
|
147
225
|
statement_config = updated_config
|
|
148
226
|
|
|
227
|
+
if driver_features:
|
|
228
|
+
json_serializer = driver_features.get("json_serializer")
|
|
229
|
+
enable_uuid_conversion = driver_features.get("enable_uuid_conversion", True)
|
|
230
|
+
|
|
231
|
+
if json_serializer or not enable_uuid_conversion:
|
|
232
|
+
type_converter = DuckDBTypeConverter(enable_uuid_conversion=enable_uuid_conversion)
|
|
233
|
+
type_coercion_map = dict(statement_config.parameter_config.type_coercion_map)
|
|
234
|
+
|
|
235
|
+
if json_serializer:
|
|
236
|
+
type_coercion_map[dict] = json_serializer
|
|
237
|
+
type_coercion_map[list] = json_serializer
|
|
238
|
+
|
|
239
|
+
if not enable_uuid_conversion:
|
|
240
|
+
type_coercion_map[str] = type_converter.convert_if_detected
|
|
241
|
+
|
|
242
|
+
param_config = statement_config.parameter_config
|
|
243
|
+
updated_param_config = ParameterStyleConfig(
|
|
244
|
+
default_parameter_style=param_config.default_parameter_style,
|
|
245
|
+
supported_parameter_styles=param_config.supported_parameter_styles,
|
|
246
|
+
supported_execution_parameter_styles=param_config.supported_execution_parameter_styles,
|
|
247
|
+
default_execution_parameter_style=param_config.default_execution_parameter_style,
|
|
248
|
+
type_coercion_map=type_coercion_map,
|
|
249
|
+
has_native_list_expansion=param_config.has_native_list_expansion,
|
|
250
|
+
needs_static_script_compilation=param_config.needs_static_script_compilation,
|
|
251
|
+
allow_mixed_parameter_styles=param_config.allow_mixed_parameter_styles,
|
|
252
|
+
preserve_parameter_format=param_config.preserve_parameter_format,
|
|
253
|
+
preserve_original_params_for_many=param_config.preserve_original_params_for_many,
|
|
254
|
+
output_transformer=param_config.output_transformer,
|
|
255
|
+
ast_transformer=param_config.ast_transformer,
|
|
256
|
+
)
|
|
257
|
+
statement_config = statement_config.replace(parameter_config=updated_param_config)
|
|
258
|
+
|
|
149
259
|
super().__init__(connection=connection, statement_config=statement_config, driver_features=driver_features)
|
|
260
|
+
self._data_dictionary: SyncDataDictionaryBase | None = None
|
|
150
261
|
|
|
151
262
|
def with_cursor(self, connection: "DuckDBConnection") -> "DuckDBCursor":
|
|
152
263
|
"""Create context manager for DuckDB cursor.
|
|
@@ -167,7 +278,7 @@ class DuckDBDriver(SyncDriverAdapterBase):
|
|
|
167
278
|
"""
|
|
168
279
|
return DuckDBExceptionHandler()
|
|
169
280
|
|
|
170
|
-
def _try_special_handling(self, cursor: Any, statement: SQL) -> "
|
|
281
|
+
def _try_special_handling(self, cursor: Any, statement: SQL) -> "SQLResult | None":
|
|
171
282
|
"""Handle DuckDB-specific special operations.
|
|
172
283
|
|
|
173
284
|
DuckDB does not require special operation handling, so this method
|
|
@@ -282,7 +393,7 @@ class DuckDBDriver(SyncDriverAdapterBase):
|
|
|
282
393
|
column_names = [col[0] for col in cursor.description or []]
|
|
283
394
|
|
|
284
395
|
if fetched_data and isinstance(fetched_data[0], tuple):
|
|
285
|
-
dict_data = [dict(zip(column_names, row)) for row in fetched_data]
|
|
396
|
+
dict_data = [dict(zip(column_names, row, strict=False)) for row in fetched_data]
|
|
286
397
|
else:
|
|
287
398
|
dict_data = fetched_data
|
|
288
399
|
|
|
@@ -325,3 +436,14 @@ class DuckDBDriver(SyncDriverAdapterBase):
|
|
|
325
436
|
except duckdb.Error as e:
|
|
326
437
|
msg = f"Failed to commit DuckDB transaction: {e}"
|
|
327
438
|
raise SQLSpecError(msg) from e
|
|
439
|
+
|
|
440
|
+
@property
|
|
441
|
+
def data_dictionary(self) -> "SyncDataDictionaryBase":
|
|
442
|
+
"""Get the data dictionary for this driver.
|
|
443
|
+
|
|
444
|
+
Returns:
|
|
445
|
+
Data dictionary instance for metadata queries
|
|
446
|
+
"""
|
|
447
|
+
if self._data_dictionary is None:
|
|
448
|
+
self._data_dictionary = DuckDBSyncDataDictionary()
|
|
449
|
+
return self._data_dictionary
|