sqlspec 0.32.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.
- sqlspec/__init__.py +104 -0
- sqlspec/__main__.py +12 -0
- sqlspec/__metadata__.py +14 -0
- sqlspec/_serialization.py +312 -0
- sqlspec/_typing.py +784 -0
- sqlspec/adapters/__init__.py +0 -0
- sqlspec/adapters/adbc/__init__.py +5 -0
- sqlspec/adapters/adbc/_types.py +12 -0
- sqlspec/adapters/adbc/adk/__init__.py +5 -0
- sqlspec/adapters/adbc/adk/store.py +880 -0
- sqlspec/adapters/adbc/config.py +436 -0
- sqlspec/adapters/adbc/data_dictionary.py +537 -0
- sqlspec/adapters/adbc/driver.py +841 -0
- 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/__init__.py +29 -0
- sqlspec/adapters/aiosqlite/_types.py +13 -0
- sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/adk/store.py +536 -0
- sqlspec/adapters/aiosqlite/config.py +310 -0
- sqlspec/adapters/aiosqlite/data_dictionary.py +260 -0
- sqlspec/adapters/aiosqlite/driver.py +463 -0
- sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/litestar/store.py +281 -0
- sqlspec/adapters/aiosqlite/pool.py +500 -0
- sqlspec/adapters/asyncmy/__init__.py +25 -0
- sqlspec/adapters/asyncmy/_types.py +12 -0
- sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
- sqlspec/adapters/asyncmy/adk/store.py +503 -0
- sqlspec/adapters/asyncmy/config.py +246 -0
- sqlspec/adapters/asyncmy/data_dictionary.py +241 -0
- sqlspec/adapters/asyncmy/driver.py +632 -0
- sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncmy/litestar/store.py +296 -0
- sqlspec/adapters/asyncpg/__init__.py +23 -0
- sqlspec/adapters/asyncpg/_type_handlers.py +76 -0
- sqlspec/adapters/asyncpg/_types.py +23 -0
- sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
- sqlspec/adapters/asyncpg/adk/store.py +460 -0
- sqlspec/adapters/asyncpg/config.py +464 -0
- sqlspec/adapters/asyncpg/data_dictionary.py +321 -0
- sqlspec/adapters/asyncpg/driver.py +720 -0
- sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncpg/litestar/store.py +253 -0
- sqlspec/adapters/bigquery/__init__.py +18 -0
- sqlspec/adapters/bigquery/_types.py +12 -0
- sqlspec/adapters/bigquery/adk/__init__.py +5 -0
- sqlspec/adapters/bigquery/adk/store.py +585 -0
- sqlspec/adapters/bigquery/config.py +298 -0
- sqlspec/adapters/bigquery/data_dictionary.py +256 -0
- sqlspec/adapters/bigquery/driver.py +1073 -0
- 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/__init__.py +24 -0
- sqlspec/adapters/duckdb/_types.py +12 -0
- sqlspec/adapters/duckdb/adk/__init__.py +14 -0
- sqlspec/adapters/duckdb/adk/store.py +563 -0
- sqlspec/adapters/duckdb/config.py +396 -0
- sqlspec/adapters/duckdb/data_dictionary.py +264 -0
- sqlspec/adapters/duckdb/driver.py +604 -0
- sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
- sqlspec/adapters/duckdb/litestar/store.py +332 -0
- sqlspec/adapters/duckdb/pool.py +273 -0
- sqlspec/adapters/duckdb/type_converter.py +133 -0
- sqlspec/adapters/oracledb/__init__.py +32 -0
- sqlspec/adapters/oracledb/_numpy_handlers.py +133 -0
- sqlspec/adapters/oracledb/_types.py +39 -0
- sqlspec/adapters/oracledb/_uuid_handlers.py +130 -0
- sqlspec/adapters/oracledb/adk/__init__.py +5 -0
- sqlspec/adapters/oracledb/adk/store.py +1632 -0
- sqlspec/adapters/oracledb/config.py +469 -0
- sqlspec/adapters/oracledb/data_dictionary.py +717 -0
- sqlspec/adapters/oracledb/driver.py +1493 -0
- sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
- sqlspec/adapters/oracledb/litestar/store.py +765 -0
- sqlspec/adapters/oracledb/migrations.py +532 -0
- sqlspec/adapters/oracledb/type_converter.py +207 -0
- sqlspec/adapters/psqlpy/__init__.py +16 -0
- sqlspec/adapters/psqlpy/_type_handlers.py +44 -0
- sqlspec/adapters/psqlpy/_types.py +12 -0
- sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
- sqlspec/adapters/psqlpy/adk/store.py +483 -0
- sqlspec/adapters/psqlpy/config.py +271 -0
- sqlspec/adapters/psqlpy/data_dictionary.py +179 -0
- sqlspec/adapters/psqlpy/driver.py +892 -0
- 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/__init__.py +32 -0
- sqlspec/adapters/psycopg/_type_handlers.py +90 -0
- sqlspec/adapters/psycopg/_types.py +18 -0
- sqlspec/adapters/psycopg/adk/__init__.py +5 -0
- sqlspec/adapters/psycopg/adk/store.py +962 -0
- sqlspec/adapters/psycopg/config.py +487 -0
- sqlspec/adapters/psycopg/data_dictionary.py +630 -0
- sqlspec/adapters/psycopg/driver.py +1336 -0
- sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
- sqlspec/adapters/psycopg/litestar/store.py +554 -0
- sqlspec/adapters/spanner/__init__.py +38 -0
- sqlspec/adapters/spanner/_type_handlers.py +186 -0
- sqlspec/adapters/spanner/_types.py +12 -0
- sqlspec/adapters/spanner/adk/__init__.py +5 -0
- sqlspec/adapters/spanner/adk/store.py +435 -0
- sqlspec/adapters/spanner/config.py +241 -0
- sqlspec/adapters/spanner/data_dictionary.py +95 -0
- sqlspec/adapters/spanner/dialect/__init__.py +6 -0
- sqlspec/adapters/spanner/dialect/_spangres.py +52 -0
- sqlspec/adapters/spanner/dialect/_spanner.py +123 -0
- sqlspec/adapters/spanner/driver.py +366 -0
- sqlspec/adapters/spanner/litestar/__init__.py +5 -0
- sqlspec/adapters/spanner/litestar/store.py +266 -0
- sqlspec/adapters/spanner/type_converter.py +46 -0
- sqlspec/adapters/sqlite/__init__.py +18 -0
- sqlspec/adapters/sqlite/_type_handlers.py +86 -0
- sqlspec/adapters/sqlite/_types.py +11 -0
- sqlspec/adapters/sqlite/adk/__init__.py +5 -0
- sqlspec/adapters/sqlite/adk/store.py +582 -0
- sqlspec/adapters/sqlite/config.py +221 -0
- sqlspec/adapters/sqlite/data_dictionary.py +256 -0
- sqlspec/adapters/sqlite/driver.py +527 -0
- sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/sqlite/litestar/store.py +318 -0
- sqlspec/adapters/sqlite/pool.py +140 -0
- sqlspec/base.py +811 -0
- sqlspec/builder/__init__.py +146 -0
- sqlspec/builder/_base.py +900 -0
- sqlspec/builder/_column.py +517 -0
- sqlspec/builder/_ddl.py +1642 -0
- sqlspec/builder/_delete.py +84 -0
- sqlspec/builder/_dml.py +381 -0
- sqlspec/builder/_expression_wrappers.py +46 -0
- sqlspec/builder/_factory.py +1537 -0
- sqlspec/builder/_insert.py +315 -0
- sqlspec/builder/_join.py +375 -0
- sqlspec/builder/_merge.py +848 -0
- sqlspec/builder/_parsing_utils.py +297 -0
- sqlspec/builder/_select.py +1615 -0
- sqlspec/builder/_update.py +161 -0
- sqlspec/builder/_vector_expressions.py +259 -0
- sqlspec/cli.py +764 -0
- sqlspec/config.py +1540 -0
- sqlspec/core/__init__.py +305 -0
- sqlspec/core/cache.py +785 -0
- sqlspec/core/compiler.py +603 -0
- sqlspec/core/filters.py +872 -0
- sqlspec/core/hashing.py +274 -0
- sqlspec/core/metrics.py +83 -0
- sqlspec/core/parameters/__init__.py +64 -0
- sqlspec/core/parameters/_alignment.py +266 -0
- sqlspec/core/parameters/_converter.py +413 -0
- sqlspec/core/parameters/_processor.py +341 -0
- sqlspec/core/parameters/_registry.py +201 -0
- sqlspec/core/parameters/_transformers.py +226 -0
- sqlspec/core/parameters/_types.py +430 -0
- sqlspec/core/parameters/_validator.py +123 -0
- sqlspec/core/pipeline.py +187 -0
- sqlspec/core/result.py +1124 -0
- sqlspec/core/splitter.py +940 -0
- sqlspec/core/stack.py +163 -0
- sqlspec/core/statement.py +835 -0
- sqlspec/core/type_conversion.py +235 -0
- sqlspec/driver/__init__.py +36 -0
- sqlspec/driver/_async.py +1027 -0
- sqlspec/driver/_common.py +1236 -0
- sqlspec/driver/_sync.py +1025 -0
- sqlspec/driver/mixins/__init__.py +7 -0
- sqlspec/driver/mixins/_result_tools.py +61 -0
- sqlspec/driver/mixins/_sql_translator.py +122 -0
- sqlspec/driver/mixins/_storage.py +311 -0
- sqlspec/exceptions.py +321 -0
- sqlspec/extensions/__init__.py +0 -0
- 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/__init__.py +10 -0
- sqlspec/extensions/aiosql/adapter.py +471 -0
- sqlspec/extensions/fastapi/__init__.py +19 -0
- sqlspec/extensions/fastapi/extension.py +341 -0
- sqlspec/extensions/fastapi/providers.py +543 -0
- sqlspec/extensions/flask/__init__.py +36 -0
- sqlspec/extensions/flask/_state.py +72 -0
- sqlspec/extensions/flask/_utils.py +40 -0
- sqlspec/extensions/flask/extension.py +402 -0
- sqlspec/extensions/litestar/__init__.py +23 -0
- sqlspec/extensions/litestar/_utils.py +52 -0
- sqlspec/extensions/litestar/cli.py +92 -0
- sqlspec/extensions/litestar/config.py +90 -0
- sqlspec/extensions/litestar/handlers.py +316 -0
- sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
- sqlspec/extensions/litestar/migrations/__init__.py +3 -0
- sqlspec/extensions/litestar/plugin.py +638 -0
- sqlspec/extensions/litestar/providers.py +454 -0
- sqlspec/extensions/litestar/store.py +265 -0
- sqlspec/extensions/otel/__init__.py +58 -0
- sqlspec/extensions/prometheus/__init__.py +107 -0
- sqlspec/extensions/starlette/__init__.py +10 -0
- sqlspec/extensions/starlette/_state.py +26 -0
- sqlspec/extensions/starlette/_utils.py +52 -0
- sqlspec/extensions/starlette/extension.py +257 -0
- sqlspec/extensions/starlette/middleware.py +154 -0
- sqlspec/loader.py +716 -0
- sqlspec/migrations/__init__.py +36 -0
- sqlspec/migrations/base.py +728 -0
- sqlspec/migrations/commands.py +1140 -0
- sqlspec/migrations/context.py +142 -0
- sqlspec/migrations/fix.py +203 -0
- sqlspec/migrations/loaders.py +450 -0
- sqlspec/migrations/runner.py +1024 -0
- sqlspec/migrations/templates.py +234 -0
- sqlspec/migrations/tracker.py +403 -0
- sqlspec/migrations/utils.py +256 -0
- sqlspec/migrations/validation.py +203 -0
- sqlspec/observability/__init__.py +22 -0
- sqlspec/observability/_config.py +228 -0
- sqlspec/observability/_diagnostics.py +67 -0
- sqlspec/observability/_dispatcher.py +151 -0
- sqlspec/observability/_observer.py +180 -0
- sqlspec/observability/_runtime.py +381 -0
- sqlspec/observability/_spans.py +158 -0
- sqlspec/protocols.py +530 -0
- sqlspec/py.typed +0 -0
- sqlspec/storage/__init__.py +46 -0
- sqlspec/storage/_utils.py +104 -0
- sqlspec/storage/backends/__init__.py +1 -0
- sqlspec/storage/backends/base.py +163 -0
- sqlspec/storage/backends/fsspec.py +398 -0
- sqlspec/storage/backends/local.py +377 -0
- sqlspec/storage/backends/obstore.py +580 -0
- sqlspec/storage/errors.py +104 -0
- sqlspec/storage/pipeline.py +604 -0
- sqlspec/storage/registry.py +289 -0
- sqlspec/typing.py +219 -0
- sqlspec/utils/__init__.py +31 -0
- sqlspec/utils/arrow_helpers.py +95 -0
- sqlspec/utils/config_resolver.py +153 -0
- sqlspec/utils/correlation.py +132 -0
- sqlspec/utils/data_transformation.py +114 -0
- sqlspec/utils/dependencies.py +79 -0
- sqlspec/utils/deprecation.py +113 -0
- sqlspec/utils/fixtures.py +250 -0
- sqlspec/utils/logging.py +172 -0
- sqlspec/utils/module_loader.py +273 -0
- sqlspec/utils/portal.py +325 -0
- sqlspec/utils/schema.py +288 -0
- sqlspec/utils/serializers.py +396 -0
- sqlspec/utils/singleton.py +41 -0
- sqlspec/utils/sync_tools.py +277 -0
- sqlspec/utils/text.py +108 -0
- sqlspec/utils/type_converters.py +99 -0
- sqlspec/utils/type_guards.py +1324 -0
- sqlspec/utils/version.py +444 -0
- sqlspec-0.32.0.dist-info/METADATA +202 -0
- sqlspec-0.32.0.dist-info/RECORD +262 -0
- sqlspec-0.32.0.dist-info/WHEEL +4 -0
- sqlspec-0.32.0.dist-info/entry_points.txt +2 -0
- sqlspec-0.32.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
"""DuckDB database configuration with connection pooling."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable, Sequence
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
from typing import TYPE_CHECKING, Any, ClassVar, TypedDict, cast
|
|
6
|
+
|
|
7
|
+
from typing_extensions import NotRequired
|
|
8
|
+
|
|
9
|
+
from sqlspec.adapters.duckdb._types import DuckDBConnection
|
|
10
|
+
from sqlspec.adapters.duckdb.driver import (
|
|
11
|
+
DuckDBCursor,
|
|
12
|
+
DuckDBDriver,
|
|
13
|
+
DuckDBExceptionHandler,
|
|
14
|
+
build_duckdb_statement_config,
|
|
15
|
+
)
|
|
16
|
+
from sqlspec.adapters.duckdb.pool import DuckDBConnectionPool
|
|
17
|
+
from sqlspec.config import ExtensionConfigs, SyncDatabaseConfig
|
|
18
|
+
from sqlspec.observability import ObservabilityConfig
|
|
19
|
+
from sqlspec.utils.serializers import to_json
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from collections.abc import Callable, Generator
|
|
23
|
+
|
|
24
|
+
from sqlspec.core import StatementConfig
|
|
25
|
+
__all__ = (
|
|
26
|
+
"DuckDBConfig",
|
|
27
|
+
"DuckDBConnectionParams",
|
|
28
|
+
"DuckDBDriverFeatures",
|
|
29
|
+
"DuckDBExtensionConfig",
|
|
30
|
+
"DuckDBPoolParams",
|
|
31
|
+
"DuckDBSecretConfig",
|
|
32
|
+
)
|
|
33
|
+
EXTENSION_FLAG_KEYS: "tuple[str, ...]" = (
|
|
34
|
+
"allow_community_extensions",
|
|
35
|
+
"allow_unsigned_extensions",
|
|
36
|
+
"enable_external_access",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class DuckDBConnectionParams(TypedDict):
|
|
41
|
+
"""DuckDB connection parameters.
|
|
42
|
+
|
|
43
|
+
Mirrors the keyword arguments accepted by duckdb.connect so callers can drive every DuckDB
|
|
44
|
+
configuration switch directly through SQLSpec. All keys are optional and forwarded verbatim
|
|
45
|
+
to DuckDB, either as top-level parameters or via the nested ``config`` dictionary when DuckDB
|
|
46
|
+
expects them there.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
database: NotRequired[str]
|
|
50
|
+
read_only: NotRequired[bool]
|
|
51
|
+
config: NotRequired[dict[str, Any]]
|
|
52
|
+
memory_limit: NotRequired[str]
|
|
53
|
+
threads: NotRequired[int]
|
|
54
|
+
temp_directory: NotRequired[str]
|
|
55
|
+
max_temp_directory_size: NotRequired[str]
|
|
56
|
+
autoload_known_extensions: NotRequired[bool]
|
|
57
|
+
autoinstall_known_extensions: NotRequired[bool]
|
|
58
|
+
allow_community_extensions: NotRequired[bool]
|
|
59
|
+
allow_unsigned_extensions: NotRequired[bool]
|
|
60
|
+
extension_directory: NotRequired[str]
|
|
61
|
+
custom_extension_repository: NotRequired[str]
|
|
62
|
+
autoinstall_extension_repository: NotRequired[str]
|
|
63
|
+
allow_persistent_secrets: NotRequired[bool]
|
|
64
|
+
enable_external_access: NotRequired[bool]
|
|
65
|
+
secret_directory: NotRequired[str]
|
|
66
|
+
enable_object_cache: NotRequired[bool]
|
|
67
|
+
parquet_metadata_cache: NotRequired[str]
|
|
68
|
+
enable_external_file_cache: NotRequired[bool]
|
|
69
|
+
checkpoint_threshold: NotRequired[str]
|
|
70
|
+
enable_progress_bar: NotRequired[bool]
|
|
71
|
+
progress_bar_time: NotRequired[float]
|
|
72
|
+
enable_logging: NotRequired[bool]
|
|
73
|
+
log_query_path: NotRequired[str]
|
|
74
|
+
logging_level: NotRequired[str]
|
|
75
|
+
preserve_insertion_order: NotRequired[bool]
|
|
76
|
+
default_null_order: NotRequired[str]
|
|
77
|
+
default_order: NotRequired[str]
|
|
78
|
+
ieee_floating_point_ops: NotRequired[bool]
|
|
79
|
+
binary_as_string: NotRequired[bool]
|
|
80
|
+
arrow_large_buffer_size: NotRequired[bool]
|
|
81
|
+
errors_as_json: NotRequired[bool]
|
|
82
|
+
extra: NotRequired[dict[str, Any]]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class DuckDBPoolParams(DuckDBConnectionParams):
|
|
86
|
+
"""Complete pool configuration for DuckDB adapter.
|
|
87
|
+
|
|
88
|
+
Extends DuckDBConnectionParams with pool sizing and lifecycle settings so SQLSpec can manage
|
|
89
|
+
per-thread DuckDB connections safely while honoring DuckDB's thread-safety constraints.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
pool_min_size: NotRequired[int]
|
|
93
|
+
pool_max_size: NotRequired[int]
|
|
94
|
+
pool_timeout: NotRequired[float]
|
|
95
|
+
pool_recycle_seconds: NotRequired[int]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class DuckDBExtensionConfig(TypedDict):
|
|
99
|
+
"""DuckDB extension configuration for auto-management."""
|
|
100
|
+
|
|
101
|
+
name: str
|
|
102
|
+
"""Name of the extension to install/load."""
|
|
103
|
+
|
|
104
|
+
version: NotRequired[str]
|
|
105
|
+
"""Specific version of the extension."""
|
|
106
|
+
|
|
107
|
+
repository: NotRequired[str]
|
|
108
|
+
"""Repository for the extension (core, community, or custom URL)."""
|
|
109
|
+
|
|
110
|
+
force_install: NotRequired[bool]
|
|
111
|
+
"""Force reinstallation of the extension."""
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class DuckDBSecretConfig(TypedDict):
|
|
115
|
+
"""DuckDB secret configuration for AI/API integrations."""
|
|
116
|
+
|
|
117
|
+
secret_type: str
|
|
118
|
+
"""Type of secret (e.g., 'openai', 'aws', 'azure', 'gcp')."""
|
|
119
|
+
|
|
120
|
+
name: str
|
|
121
|
+
"""Name of the secret."""
|
|
122
|
+
|
|
123
|
+
value: dict[str, Any]
|
|
124
|
+
"""Secret configuration values."""
|
|
125
|
+
|
|
126
|
+
scope: NotRequired[str]
|
|
127
|
+
"""Scope of the secret (LOCAL or PERSISTENT)."""
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class DuckDBDriverFeatures(TypedDict):
|
|
131
|
+
"""TypedDict for DuckDB driver features configuration.
|
|
132
|
+
|
|
133
|
+
Attributes:
|
|
134
|
+
extensions: List of extensions to install/load on connection creation.
|
|
135
|
+
secrets: List of secrets to create for AI/API integrations.
|
|
136
|
+
on_connection_create: Callback executed when connection is created.
|
|
137
|
+
json_serializer: Custom JSON serializer for dict/list parameter conversion.
|
|
138
|
+
Defaults to sqlspec.utils.serializers.to_json if not provided.
|
|
139
|
+
enable_uuid_conversion: Enable automatic UUID string conversion.
|
|
140
|
+
When True (default), UUID strings are automatically converted to UUID objects.
|
|
141
|
+
When False, UUID strings are treated as regular strings.
|
|
142
|
+
extension_flags: Connection-level flags (e.g., allow_community_extensions) applied
|
|
143
|
+
via SET statements immediately after connection creation.
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
extensions: NotRequired[Sequence[DuckDBExtensionConfig]]
|
|
147
|
+
secrets: NotRequired[Sequence[DuckDBSecretConfig]]
|
|
148
|
+
on_connection_create: NotRequired["Callable[[DuckDBConnection], DuckDBConnection | None]"]
|
|
149
|
+
json_serializer: NotRequired["Callable[[Any], str]"]
|
|
150
|
+
enable_uuid_conversion: NotRequired[bool]
|
|
151
|
+
extension_flags: NotRequired[dict[str, Any]]
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class DuckDBConfig(SyncDatabaseConfig[DuckDBConnection, DuckDBConnectionPool, DuckDBDriver]):
|
|
155
|
+
"""DuckDB configuration with connection pooling.
|
|
156
|
+
|
|
157
|
+
This configuration supports DuckDB's features including:
|
|
158
|
+
|
|
159
|
+
- Connection pooling
|
|
160
|
+
- Extension management and installation
|
|
161
|
+
- Secret management for API integrations
|
|
162
|
+
- Auto configuration settings
|
|
163
|
+
- Arrow integration
|
|
164
|
+
- Direct file querying capabilities
|
|
165
|
+
- Configurable type handlers for JSON serialization and UUID conversion
|
|
166
|
+
|
|
167
|
+
DuckDB Connection Pool Configuration:
|
|
168
|
+
- Default pool size is 1-4 connections (DuckDB uses single connection by default)
|
|
169
|
+
- Connection recycling is set to 24 hours by default (set to 0 to disable)
|
|
170
|
+
- Shared memory databases use `:memory:shared_db` for proper concurrency
|
|
171
|
+
|
|
172
|
+
Type Handler Configuration via driver_features:
|
|
173
|
+
- `json_serializer`: Custom JSON serializer for dict/list parameters.
|
|
174
|
+
Defaults to `sqlspec.utils.serializers.to_json` if not provided.
|
|
175
|
+
Example: `json_serializer=msgspec.json.encode(...).decode('utf-8')`
|
|
176
|
+
|
|
177
|
+
- `enable_uuid_conversion`: Enable automatic UUID string conversion (default: True).
|
|
178
|
+
When True, UUID strings in query results are automatically converted to UUID objects.
|
|
179
|
+
When False, UUID strings are treated as regular strings.
|
|
180
|
+
|
|
181
|
+
Example:
|
|
182
|
+
>>> import msgspec
|
|
183
|
+
>>> from sqlspec.adapters.duckdb import DuckDBConfig
|
|
184
|
+
>>>
|
|
185
|
+
>>> # Custom JSON serializer
|
|
186
|
+
>>> def custom_json(obj):
|
|
187
|
+
... return msgspec.json.encode(obj).decode("utf-8")
|
|
188
|
+
>>>
|
|
189
|
+
>>> config = DuckDBConfig(
|
|
190
|
+
... pool_config={"database": ":memory:"},
|
|
191
|
+
... driver_features={
|
|
192
|
+
... "json_serializer": custom_json,
|
|
193
|
+
... "enable_uuid_conversion": False,
|
|
194
|
+
... },
|
|
195
|
+
... )
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
driver_type: "ClassVar[type[DuckDBDriver]]" = DuckDBDriver
|
|
199
|
+
connection_type: "ClassVar[type[DuckDBConnection]]" = DuckDBConnection
|
|
200
|
+
supports_transactional_ddl: "ClassVar[bool]" = True
|
|
201
|
+
supports_native_arrow_export: "ClassVar[bool]" = True
|
|
202
|
+
supports_native_arrow_import: "ClassVar[bool]" = True
|
|
203
|
+
supports_native_parquet_export: "ClassVar[bool]" = True
|
|
204
|
+
supports_native_parquet_import: "ClassVar[bool]" = True
|
|
205
|
+
storage_partition_strategies: "ClassVar[tuple[str, ...]]" = ("fixed", "rows_per_chunk", "manifest")
|
|
206
|
+
|
|
207
|
+
def __init__(
|
|
208
|
+
self,
|
|
209
|
+
*,
|
|
210
|
+
pool_config: "DuckDBPoolParams | dict[str, Any] | None" = None,
|
|
211
|
+
pool_instance: "DuckDBConnectionPool | None" = None,
|
|
212
|
+
migration_config: dict[str, Any] | None = None,
|
|
213
|
+
statement_config: "StatementConfig | None" = None,
|
|
214
|
+
driver_features: "DuckDBDriverFeatures | dict[str, Any] | None" = None,
|
|
215
|
+
bind_key: "str | None" = None,
|
|
216
|
+
extension_config: "ExtensionConfigs | None" = None,
|
|
217
|
+
observability_config: "ObservabilityConfig | None" = None,
|
|
218
|
+
) -> None:
|
|
219
|
+
"""Initialize DuckDB configuration.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
pool_config: Pool configuration parameters
|
|
223
|
+
pool_instance: Pre-created pool instance
|
|
224
|
+
migration_config: Migration configuration
|
|
225
|
+
statement_config: Statement configuration override
|
|
226
|
+
driver_features: DuckDB-specific driver features including json_serializer
|
|
227
|
+
and enable_uuid_conversion options
|
|
228
|
+
bind_key: Optional unique identifier for this configuration
|
|
229
|
+
extension_config: Extension-specific configuration (e.g., Litestar plugin settings)
|
|
230
|
+
observability_config: Adapter-level observability overrides for lifecycle hooks and observers
|
|
231
|
+
"""
|
|
232
|
+
if pool_config is None:
|
|
233
|
+
pool_config = {}
|
|
234
|
+
pool_config.setdefault("database", ":memory:shared_db")
|
|
235
|
+
|
|
236
|
+
if pool_config.get("database") in {":memory:", ""}:
|
|
237
|
+
pool_config["database"] = ":memory:shared_db"
|
|
238
|
+
|
|
239
|
+
extension_flags: dict[str, Any] = {}
|
|
240
|
+
for key in tuple(pool_config.keys()):
|
|
241
|
+
if key in EXTENSION_FLAG_KEYS:
|
|
242
|
+
extension_flags[key] = pool_config.pop(key) # type: ignore[misc]
|
|
243
|
+
|
|
244
|
+
processed_features: dict[str, Any] = dict(driver_features) if driver_features else {}
|
|
245
|
+
user_connection_hook = cast(
|
|
246
|
+
"Callable[[Any], None] | None", processed_features.pop("on_connection_create", None)
|
|
247
|
+
)
|
|
248
|
+
processed_features.setdefault("enable_uuid_conversion", True)
|
|
249
|
+
serializer = processed_features.setdefault("json_serializer", to_json)
|
|
250
|
+
|
|
251
|
+
if extension_flags:
|
|
252
|
+
existing_flags = cast("dict[str, Any]", processed_features.get("extension_flags", {}))
|
|
253
|
+
merged_flags = {**existing_flags, **extension_flags}
|
|
254
|
+
processed_features["extension_flags"] = merged_flags
|
|
255
|
+
|
|
256
|
+
local_observability = observability_config
|
|
257
|
+
if user_connection_hook is not None:
|
|
258
|
+
|
|
259
|
+
def _wrap_lifecycle_hook(context: dict[str, Any]) -> None:
|
|
260
|
+
connection = context.get("connection")
|
|
261
|
+
if connection is None:
|
|
262
|
+
return
|
|
263
|
+
user_connection_hook(connection)
|
|
264
|
+
|
|
265
|
+
lifecycle_override = ObservabilityConfig(lifecycle={"on_connection_create": [_wrap_lifecycle_hook]})
|
|
266
|
+
local_observability = ObservabilityConfig.merge(local_observability, lifecycle_override)
|
|
267
|
+
|
|
268
|
+
base_statement_config = statement_config or build_duckdb_statement_config(
|
|
269
|
+
json_serializer=cast("Callable[[Any], str]", serializer)
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
super().__init__(
|
|
273
|
+
bind_key=bind_key,
|
|
274
|
+
pool_config=dict(pool_config),
|
|
275
|
+
pool_instance=pool_instance,
|
|
276
|
+
migration_config=migration_config,
|
|
277
|
+
statement_config=base_statement_config,
|
|
278
|
+
driver_features=processed_features,
|
|
279
|
+
extension_config=extension_config,
|
|
280
|
+
observability_config=local_observability,
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
def _get_connection_config_dict(self) -> "dict[str, Any]":
|
|
284
|
+
"""Get connection configuration as plain dict for pool creation."""
|
|
285
|
+
return {
|
|
286
|
+
k: v
|
|
287
|
+
for k, v in self.pool_config.items()
|
|
288
|
+
if v is not None
|
|
289
|
+
and k not in {"pool_min_size", "pool_max_size", "pool_timeout", "pool_recycle_seconds", "extra"}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
def _create_pool(self) -> DuckDBConnectionPool:
|
|
293
|
+
"""Create connection pool from configuration."""
|
|
294
|
+
connection_config = self._get_connection_config_dict()
|
|
295
|
+
|
|
296
|
+
extensions = self.driver_features.get("extensions", None)
|
|
297
|
+
secrets = self.driver_features.get("secrets", None)
|
|
298
|
+
extension_flags = self.driver_features.get("extension_flags", None)
|
|
299
|
+
extensions_dicts = [dict(ext) for ext in extensions] if extensions else None
|
|
300
|
+
secrets_dicts = [dict(secret) for secret in secrets] if secrets else None
|
|
301
|
+
extension_flags_dict = dict(extension_flags) if extension_flags else None
|
|
302
|
+
|
|
303
|
+
return DuckDBConnectionPool(
|
|
304
|
+
connection_config=connection_config,
|
|
305
|
+
extensions=extensions_dicts,
|
|
306
|
+
extension_flags=extension_flags_dict,
|
|
307
|
+
secrets=secrets_dicts,
|
|
308
|
+
**self.pool_config,
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
def _close_pool(self) -> None:
|
|
312
|
+
"""Close the connection pool."""
|
|
313
|
+
if self.pool_instance:
|
|
314
|
+
self.pool_instance.close()
|
|
315
|
+
|
|
316
|
+
def create_connection(self) -> DuckDBConnection:
|
|
317
|
+
"""Get a DuckDB connection from the pool.
|
|
318
|
+
|
|
319
|
+
This method ensures the pool is created and returns a connection
|
|
320
|
+
from the pool. The connection is checked out from the pool and must
|
|
321
|
+
be properly managed by the caller.
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
DuckDBConnection: A connection from the pool
|
|
325
|
+
|
|
326
|
+
Note:
|
|
327
|
+
For automatic connection management, prefer using provide_connection()
|
|
328
|
+
or provide_session() which handle returning connections to the pool.
|
|
329
|
+
The caller is responsible for returning the connection to the pool
|
|
330
|
+
using pool.release(connection) when done.
|
|
331
|
+
"""
|
|
332
|
+
pool = self.provide_pool()
|
|
333
|
+
|
|
334
|
+
return pool.acquire()
|
|
335
|
+
|
|
336
|
+
@contextmanager
|
|
337
|
+
def provide_connection(self, *args: Any, **kwargs: Any) -> "Generator[DuckDBConnection, None, None]":
|
|
338
|
+
"""Provide a pooled DuckDB connection context manager.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
*args: Additional arguments.
|
|
342
|
+
**kwargs: Additional keyword arguments.
|
|
343
|
+
|
|
344
|
+
Yields:
|
|
345
|
+
A DuckDB connection instance.
|
|
346
|
+
"""
|
|
347
|
+
pool = self.provide_pool()
|
|
348
|
+
with pool.get_connection() as connection:
|
|
349
|
+
yield connection
|
|
350
|
+
|
|
351
|
+
@contextmanager
|
|
352
|
+
def provide_session(
|
|
353
|
+
self, *args: Any, statement_config: "StatementConfig | None" = None, **kwargs: Any
|
|
354
|
+
) -> "Generator[DuckDBDriver, None, None]":
|
|
355
|
+
"""Provide a DuckDB driver session context manager.
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
*args: Additional arguments.
|
|
359
|
+
statement_config: Optional statement configuration override.
|
|
360
|
+
**kwargs: Additional keyword arguments.
|
|
361
|
+
|
|
362
|
+
Yields:
|
|
363
|
+
A context manager that yields a DuckDBDriver instance.
|
|
364
|
+
"""
|
|
365
|
+
with self.provide_connection(*args, **kwargs) as connection:
|
|
366
|
+
driver = self.driver_type(
|
|
367
|
+
connection=connection,
|
|
368
|
+
statement_config=statement_config or self.statement_config,
|
|
369
|
+
driver_features=self.driver_features,
|
|
370
|
+
)
|
|
371
|
+
yield self._prepare_driver(driver)
|
|
372
|
+
|
|
373
|
+
def get_signature_namespace(self) -> "dict[str, Any]":
|
|
374
|
+
"""Get the signature namespace for DuckDB types.
|
|
375
|
+
|
|
376
|
+
This provides all DuckDB-specific types that Litestar needs to recognize
|
|
377
|
+
to avoid serialization attempts.
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
Dictionary mapping type names to types.
|
|
381
|
+
"""
|
|
382
|
+
|
|
383
|
+
namespace = super().get_signature_namespace()
|
|
384
|
+
namespace.update({
|
|
385
|
+
"DuckDBConnection": DuckDBConnection,
|
|
386
|
+
"DuckDBConnectionParams": DuckDBConnectionParams,
|
|
387
|
+
"DuckDBConnectionPool": DuckDBConnectionPool,
|
|
388
|
+
"DuckDBCursor": DuckDBCursor,
|
|
389
|
+
"DuckDBDriver": DuckDBDriver,
|
|
390
|
+
"DuckDBDriverFeatures": DuckDBDriverFeatures,
|
|
391
|
+
"DuckDBExceptionHandler": DuckDBExceptionHandler,
|
|
392
|
+
"DuckDBExtensionConfig": DuckDBExtensionConfig,
|
|
393
|
+
"DuckDBPoolParams": DuckDBPoolParams,
|
|
394
|
+
"DuckDBSecretConfig": DuckDBSecretConfig,
|
|
395
|
+
})
|
|
396
|
+
return namespace
|
|
@@ -0,0 +1,264 @@
|
|
|
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 ForeignKeyMetadata, 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 get_tables(self, driver: "SyncDriverAdapterBase", schema: "str | None" = None) -> "list[str]":
|
|
145
|
+
"""Get tables sorted by topological dependency order using DuckDB catalog."""
|
|
146
|
+
duckdb_driver = cast("DuckDBDriver", driver)
|
|
147
|
+
schema_clause = f"'{schema}'" if schema else "current_schema()"
|
|
148
|
+
|
|
149
|
+
sql = f"""
|
|
150
|
+
WITH RECURSIVE dependency_tree AS (
|
|
151
|
+
SELECT
|
|
152
|
+
table_name,
|
|
153
|
+
0 AS level,
|
|
154
|
+
[table_name] AS path
|
|
155
|
+
FROM information_schema.tables t
|
|
156
|
+
WHERE t.table_type = 'BASE TABLE'
|
|
157
|
+
AND t.table_schema = {schema_clause}
|
|
158
|
+
AND NOT EXISTS (
|
|
159
|
+
SELECT 1
|
|
160
|
+
FROM information_schema.key_column_usage kcu
|
|
161
|
+
WHERE kcu.table_name = t.table_name
|
|
162
|
+
AND kcu.table_schema = t.table_schema
|
|
163
|
+
AND kcu.constraint_name IN (SELECT constraint_name FROM information_schema.referential_constraints)
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
UNION ALL
|
|
167
|
+
|
|
168
|
+
SELECT
|
|
169
|
+
kcu.table_name,
|
|
170
|
+
dt.level + 1,
|
|
171
|
+
list_append(dt.path, kcu.table_name)
|
|
172
|
+
FROM information_schema.key_column_usage kcu
|
|
173
|
+
JOIN information_schema.referential_constraints rc ON kcu.constraint_name = rc.constraint_name
|
|
174
|
+
JOIN information_schema.key_column_usage pk_kcu
|
|
175
|
+
ON rc.unique_constraint_name = pk_kcu.constraint_name
|
|
176
|
+
AND rc.unique_constraint_schema = pk_kcu.constraint_schema
|
|
177
|
+
JOIN dependency_tree dt ON dt.table_name = pk_kcu.table_name
|
|
178
|
+
WHERE kcu.table_schema = {schema_clause}
|
|
179
|
+
AND NOT list_contains(dt.path, kcu.table_name)
|
|
180
|
+
)
|
|
181
|
+
SELECT DISTINCT table_name, level
|
|
182
|
+
FROM dependency_tree
|
|
183
|
+
ORDER BY level, table_name
|
|
184
|
+
"""
|
|
185
|
+
result = duckdb_driver.execute(sql)
|
|
186
|
+
return [row["table_name"] for row in result.get_data()]
|
|
187
|
+
|
|
188
|
+
def get_foreign_keys(
|
|
189
|
+
self, driver: "SyncDriverAdapterBase", table: "str | None" = None, schema: "str | None" = None
|
|
190
|
+
) -> "list[ForeignKeyMetadata]":
|
|
191
|
+
"""Get foreign key metadata."""
|
|
192
|
+
duckdb_driver = cast("DuckDBDriver", driver)
|
|
193
|
+
|
|
194
|
+
where_clauses = []
|
|
195
|
+
if schema:
|
|
196
|
+
where_clauses.append(f"kcu.table_schema = '{schema}'")
|
|
197
|
+
if table:
|
|
198
|
+
where_clauses.append(f"kcu.table_name = '{table}'")
|
|
199
|
+
|
|
200
|
+
where_str = " AND ".join(where_clauses) if where_clauses else "1=1"
|
|
201
|
+
|
|
202
|
+
sql = f"""
|
|
203
|
+
SELECT
|
|
204
|
+
kcu.table_name,
|
|
205
|
+
kcu.column_name,
|
|
206
|
+
pk_kcu.table_name AS referenced_table_name,
|
|
207
|
+
pk_kcu.column_name AS referenced_column_name,
|
|
208
|
+
kcu.constraint_name,
|
|
209
|
+
kcu.table_schema,
|
|
210
|
+
pk_kcu.table_schema AS referenced_table_schema
|
|
211
|
+
FROM information_schema.key_column_usage kcu
|
|
212
|
+
JOIN information_schema.referential_constraints rc
|
|
213
|
+
ON kcu.constraint_name = rc.constraint_name
|
|
214
|
+
JOIN information_schema.key_column_usage pk_kcu
|
|
215
|
+
ON rc.unique_constraint_name = pk_kcu.constraint_name
|
|
216
|
+
AND kcu.ordinal_position = pk_kcu.ordinal_position
|
|
217
|
+
WHERE {where_str}
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
result = duckdb_driver.execute(sql)
|
|
221
|
+
|
|
222
|
+
return [
|
|
223
|
+
ForeignKeyMetadata(
|
|
224
|
+
table_name=row["table_name"],
|
|
225
|
+
column_name=row["column_name"],
|
|
226
|
+
referenced_table=row["referenced_table_name"],
|
|
227
|
+
referenced_column=row["referenced_column_name"],
|
|
228
|
+
constraint_name=row["constraint_name"],
|
|
229
|
+
schema=row["table_schema"],
|
|
230
|
+
referenced_schema=row["referenced_table_schema"],
|
|
231
|
+
)
|
|
232
|
+
for row in result.get_data()
|
|
233
|
+
]
|
|
234
|
+
|
|
235
|
+
def get_indexes(
|
|
236
|
+
self, driver: "SyncDriverAdapterBase", table: str, schema: "str | None" = None
|
|
237
|
+
) -> "list[dict[str, Any]]":
|
|
238
|
+
"""Get index information for a table."""
|
|
239
|
+
# DuckDB doesn't expose indexes easily in IS yet, usually just constraints?
|
|
240
|
+
# Fallback to empty for now or implementation specific.
|
|
241
|
+
# PRD mentions it but no specific instruction on implementation detail if missing.
|
|
242
|
+
# Returning empty list.
|
|
243
|
+
return []
|
|
244
|
+
|
|
245
|
+
def list_available_features(self) -> "list[str]":
|
|
246
|
+
"""List available DuckDB feature flags.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
List of supported feature names
|
|
250
|
+
"""
|
|
251
|
+
return [
|
|
252
|
+
"supports_json",
|
|
253
|
+
"supports_arrays",
|
|
254
|
+
"supports_maps",
|
|
255
|
+
"supports_structs",
|
|
256
|
+
"supports_returning",
|
|
257
|
+
"supports_upsert",
|
|
258
|
+
"supports_window_functions",
|
|
259
|
+
"supports_cte",
|
|
260
|
+
"supports_transactions",
|
|
261
|
+
"supports_prepared_statements",
|
|
262
|
+
"supports_schemas",
|
|
263
|
+
"supports_uuid",
|
|
264
|
+
]
|