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
|
@@ -1,36 +1,60 @@
|
|
|
1
1
|
"""SQLite database configuration with thread-local connections."""
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
import uuid
|
|
4
5
|
from contextlib import contextmanager
|
|
5
|
-
from typing import TYPE_CHECKING, Any, ClassVar,
|
|
6
|
+
from typing import TYPE_CHECKING, Any, ClassVar, TypedDict, cast
|
|
6
7
|
|
|
7
8
|
from typing_extensions import NotRequired
|
|
8
9
|
|
|
10
|
+
from sqlspec.adapters.sqlite._type_handlers import register_type_handlers
|
|
9
11
|
from sqlspec.adapters.sqlite._types import SqliteConnection
|
|
10
12
|
from sqlspec.adapters.sqlite.driver import SqliteCursor, SqliteDriver, sqlite_statement_config
|
|
11
13
|
from sqlspec.adapters.sqlite.pool import SqliteConnectionPool
|
|
12
14
|
from sqlspec.config import SyncDatabaseConfig
|
|
15
|
+
from sqlspec.utils.serializers import from_json, to_json
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
13
18
|
|
|
14
19
|
if TYPE_CHECKING:
|
|
15
|
-
from collections.abc import Generator
|
|
20
|
+
from collections.abc import Callable, Generator
|
|
16
21
|
|
|
17
22
|
from sqlspec.core.statement import StatementConfig
|
|
18
23
|
|
|
19
24
|
|
|
20
|
-
class SqliteConnectionParams(TypedDict
|
|
25
|
+
class SqliteConnectionParams(TypedDict):
|
|
21
26
|
"""SQLite connection parameters."""
|
|
22
27
|
|
|
23
28
|
database: NotRequired[str]
|
|
24
29
|
timeout: NotRequired[float]
|
|
25
30
|
detect_types: NotRequired[int]
|
|
26
|
-
isolation_level: "NotRequired[
|
|
31
|
+
isolation_level: "NotRequired[str | None]"
|
|
27
32
|
check_same_thread: NotRequired[bool]
|
|
28
|
-
factory: "NotRequired[
|
|
33
|
+
factory: "NotRequired[type[SqliteConnection] | None]"
|
|
29
34
|
cached_statements: NotRequired[int]
|
|
30
35
|
uri: NotRequired[bool]
|
|
31
36
|
|
|
32
37
|
|
|
33
|
-
|
|
38
|
+
class SqliteDriverFeatures(TypedDict):
|
|
39
|
+
"""SQLite driver feature configuration.
|
|
40
|
+
|
|
41
|
+
Controls optional type handling and serialization features for SQLite connections.
|
|
42
|
+
|
|
43
|
+
enable_custom_adapters: Enable custom type adapters for JSON/UUID/datetime conversion.
|
|
44
|
+
Defaults to True for enhanced Python type support.
|
|
45
|
+
Set to False only if you need pure SQLite behavior without type conversions.
|
|
46
|
+
json_serializer: Custom JSON serializer function.
|
|
47
|
+
Defaults to sqlspec.utils.serializers.to_json.
|
|
48
|
+
json_deserializer: Custom JSON deserializer function.
|
|
49
|
+
Defaults to sqlspec.utils.serializers.from_json.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
enable_custom_adapters: NotRequired[bool]
|
|
53
|
+
json_serializer: "NotRequired[Callable[[Any], str]]"
|
|
54
|
+
json_deserializer: "NotRequired[Callable[[str], Any]]"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
__all__ = ("SqliteConfig", "SqliteConnectionParams", "SqliteDriverFeatures")
|
|
34
58
|
|
|
35
59
|
|
|
36
60
|
class SqliteConfig(SyncDatabaseConfig[SqliteConnection, SqliteConnectionPool, SqliteDriver]):
|
|
@@ -38,15 +62,18 @@ class SqliteConfig(SyncDatabaseConfig[SqliteConnection, SqliteConnectionPool, Sq
|
|
|
38
62
|
|
|
39
63
|
driver_type: "ClassVar[type[SqliteDriver]]" = SqliteDriver
|
|
40
64
|
connection_type: "ClassVar[type[SqliteConnection]]" = SqliteConnection
|
|
65
|
+
supports_transactional_ddl: "ClassVar[bool]" = True
|
|
41
66
|
|
|
42
67
|
def __init__(
|
|
43
68
|
self,
|
|
44
69
|
*,
|
|
45
|
-
pool_config: "
|
|
46
|
-
pool_instance: "
|
|
47
|
-
migration_config: "
|
|
48
|
-
statement_config: "
|
|
49
|
-
driver_features: "
|
|
70
|
+
pool_config: "SqliteConnectionParams | dict[str, Any] | None" = None,
|
|
71
|
+
pool_instance: "SqliteConnectionPool | None" = None,
|
|
72
|
+
migration_config: "dict[str, Any] | None" = None,
|
|
73
|
+
statement_config: "StatementConfig | None" = None,
|
|
74
|
+
driver_features: "SqliteDriverFeatures | dict[str, Any] | None" = None,
|
|
75
|
+
bind_key: "str | None" = None,
|
|
76
|
+
extension_config: "dict[str, dict[str, Any]] | None" = None,
|
|
50
77
|
) -> None:
|
|
51
78
|
"""Initialize SQLite configuration.
|
|
52
79
|
|
|
@@ -56,19 +83,43 @@ class SqliteConfig(SyncDatabaseConfig[SqliteConnection, SqliteConnectionPool, Sq
|
|
|
56
83
|
migration_config: Migration configuration
|
|
57
84
|
statement_config: Default SQL statement configuration
|
|
58
85
|
driver_features: Optional driver feature configuration
|
|
86
|
+
bind_key: Optional bind key for the configuration
|
|
87
|
+
extension_config: Extension-specific configuration (e.g., Litestar plugin settings)
|
|
59
88
|
"""
|
|
60
89
|
if pool_config is None:
|
|
61
90
|
pool_config = {}
|
|
62
91
|
if "database" not in pool_config or pool_config["database"] == ":memory:":
|
|
63
92
|
pool_config["database"] = f"file:memory_{uuid.uuid4().hex}?mode=memory&cache=private"
|
|
64
93
|
pool_config["uri"] = True
|
|
94
|
+
elif "database" in pool_config:
|
|
95
|
+
database_path = str(pool_config["database"])
|
|
96
|
+
if database_path.startswith("file:") and not pool_config.get("uri"):
|
|
97
|
+
logger.debug(
|
|
98
|
+
"Database URI detected (%s) but uri=True not set. "
|
|
99
|
+
"Auto-enabling URI mode to prevent physical file creation.",
|
|
100
|
+
database_path,
|
|
101
|
+
)
|
|
102
|
+
pool_config["uri"] = True
|
|
103
|
+
|
|
104
|
+
processed_driver_features: dict[str, Any] = dict(driver_features) if driver_features else {}
|
|
105
|
+
|
|
106
|
+
if "enable_custom_adapters" not in processed_driver_features:
|
|
107
|
+
processed_driver_features["enable_custom_adapters"] = True
|
|
108
|
+
|
|
109
|
+
if "json_serializer" not in processed_driver_features:
|
|
110
|
+
processed_driver_features["json_serializer"] = to_json
|
|
111
|
+
|
|
112
|
+
if "json_deserializer" not in processed_driver_features:
|
|
113
|
+
processed_driver_features["json_deserializer"] = from_json
|
|
65
114
|
|
|
66
115
|
super().__init__(
|
|
116
|
+
bind_key=bind_key,
|
|
67
117
|
pool_instance=pool_instance,
|
|
68
118
|
pool_config=cast("dict[str, Any]", pool_config),
|
|
69
119
|
migration_config=migration_config,
|
|
70
120
|
statement_config=statement_config or sqlite_statement_config,
|
|
71
|
-
driver_features=
|
|
121
|
+
driver_features=processed_driver_features,
|
|
122
|
+
extension_config=extension_config,
|
|
72
123
|
)
|
|
73
124
|
|
|
74
125
|
def _get_connection_config_dict(self) -> "dict[str, Any]":
|
|
@@ -81,7 +132,24 @@ class SqliteConfig(SyncDatabaseConfig[SqliteConnection, SqliteConnectionPool, Sq
|
|
|
81
132
|
"""Create connection pool from configuration."""
|
|
82
133
|
config_dict = self._get_connection_config_dict()
|
|
83
134
|
|
|
84
|
-
|
|
135
|
+
pool = SqliteConnectionPool(connection_parameters=config_dict, **self.pool_config)
|
|
136
|
+
|
|
137
|
+
if self.driver_features.get("enable_custom_adapters", False):
|
|
138
|
+
self._register_type_adapters()
|
|
139
|
+
|
|
140
|
+
return pool
|
|
141
|
+
|
|
142
|
+
def _register_type_adapters(self) -> None:
|
|
143
|
+
"""Register custom type adapters and converters for SQLite.
|
|
144
|
+
|
|
145
|
+
Called once during pool creation if enable_custom_adapters is True.
|
|
146
|
+
Registers JSON serialization handlers if configured.
|
|
147
|
+
"""
|
|
148
|
+
if self.driver_features.get("enable_custom_adapters", False):
|
|
149
|
+
register_type_handlers(
|
|
150
|
+
json_serializer=self.driver_features.get("json_serializer"),
|
|
151
|
+
json_deserializer=self.driver_features.get("json_deserializer"),
|
|
152
|
+
)
|
|
85
153
|
|
|
86
154
|
def _close_pool(self) -> None:
|
|
87
155
|
"""Close the connection pool."""
|
|
@@ -110,7 +178,7 @@ class SqliteConfig(SyncDatabaseConfig[SqliteConnection, SqliteConnectionPool, Sq
|
|
|
110
178
|
|
|
111
179
|
@contextmanager
|
|
112
180
|
def provide_session(
|
|
113
|
-
self, *args: "Any", statement_config: "
|
|
181
|
+
self, *args: "Any", statement_config: "StatementConfig | None" = None, **kwargs: "Any"
|
|
114
182
|
) -> "Generator[SqliteDriver, None, None]":
|
|
115
183
|
"""Provide a SQLite driver session.
|
|
116
184
|
|
|
@@ -118,7 +186,11 @@ class SqliteConfig(SyncDatabaseConfig[SqliteConnection, SqliteConnectionPool, Sq
|
|
|
118
186
|
SqliteDriver: A driver instance with thread-local connection
|
|
119
187
|
"""
|
|
120
188
|
with self.provide_connection(*args, **kwargs) as connection:
|
|
121
|
-
yield self.driver_type(
|
|
189
|
+
yield self.driver_type(
|
|
190
|
+
connection=connection,
|
|
191
|
+
statement_config=statement_config or self.statement_config,
|
|
192
|
+
driver_features=self.driver_features,
|
|
193
|
+
)
|
|
122
194
|
|
|
123
195
|
def get_signature_namespace(self) -> "dict[str, type[Any]]":
|
|
124
196
|
"""Get the signature namespace for SQLite types.
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""SQLite-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.sqlite.driver import SqliteDriver
|
|
13
|
+
|
|
14
|
+
logger = get_logger("adapters.sqlite.data_dictionary")
|
|
15
|
+
|
|
16
|
+
# Compiled regex patterns
|
|
17
|
+
SQLITE_VERSION_PATTERN = re.compile(r"(\d+)\.(\d+)\.(\d+)")
|
|
18
|
+
|
|
19
|
+
__all__ = ("SqliteSyncDataDictionary",)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SqliteSyncDataDictionary(SyncDataDictionaryBase):
|
|
23
|
+
"""SQLite-specific sync data dictionary."""
|
|
24
|
+
|
|
25
|
+
def get_version(self, driver: SyncDriverAdapterBase) -> "VersionInfo | None":
|
|
26
|
+
"""Get SQLite database version information.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
driver: Sync database driver instance
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
SQLite version information or None if detection fails
|
|
33
|
+
"""
|
|
34
|
+
version_str = cast("SqliteDriver", driver).select_value("SELECT sqlite_version()")
|
|
35
|
+
if not version_str:
|
|
36
|
+
logger.warning("No SQLite version information found")
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
# Parse version like "3.45.0"
|
|
40
|
+
version_match = SQLITE_VERSION_PATTERN.match(str(version_str))
|
|
41
|
+
if not version_match:
|
|
42
|
+
logger.warning("Could not parse SQLite 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 SQLite version: %s", version_info)
|
|
48
|
+
return version_info
|
|
49
|
+
|
|
50
|
+
def get_feature_flag(self, driver: SyncDriverAdapterBase, feature: str) -> bool:
|
|
51
|
+
"""Check if SQLite database supports a specific feature.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
driver: SQLite 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[[VersionInfo], bool]] = {
|
|
65
|
+
"supports_json": lambda v: v >= VersionInfo(3, 38, 0),
|
|
66
|
+
"supports_returning": lambda v: v >= VersionInfo(3, 35, 0),
|
|
67
|
+
"supports_upsert": lambda v: v >= VersionInfo(3, 24, 0),
|
|
68
|
+
"supports_window_functions": lambda v: v >= VersionInfo(3, 25, 0),
|
|
69
|
+
"supports_cte": lambda v: v >= VersionInfo(3, 8, 3),
|
|
70
|
+
"supports_transactions": lambda _: True,
|
|
71
|
+
"supports_prepared_statements": lambda _: True,
|
|
72
|
+
"supports_schemas": lambda _: False, # SQLite has ATTACH but not schemas
|
|
73
|
+
"supports_arrays": lambda _: False,
|
|
74
|
+
"supports_uuid": lambda _: False,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if feature in feature_checks:
|
|
78
|
+
return bool(feature_checks[feature](version_info))
|
|
79
|
+
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
def get_optimal_type(self, driver: SyncDriverAdapterBase, type_category: str) -> str:
|
|
83
|
+
"""Get optimal SQLite type for a category.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
driver: SQLite driver instance
|
|
87
|
+
type_category: Type category
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
SQLite-specific type name
|
|
91
|
+
"""
|
|
92
|
+
version_info = self.get_version(driver)
|
|
93
|
+
|
|
94
|
+
if type_category == "json":
|
|
95
|
+
if version_info and version_info >= VersionInfo(3, 38, 0):
|
|
96
|
+
return "JSON"
|
|
97
|
+
return "TEXT"
|
|
98
|
+
|
|
99
|
+
type_map = {"uuid": "TEXT", "boolean": "INTEGER", "timestamp": "TIMESTAMP", "text": "TEXT", "blob": "BLOB"}
|
|
100
|
+
return type_map.get(type_category, "TEXT")
|
|
101
|
+
|
|
102
|
+
def get_columns(
|
|
103
|
+
self, driver: SyncDriverAdapterBase, table: str, schema: "str | None" = None
|
|
104
|
+
) -> "list[dict[str, Any]]":
|
|
105
|
+
"""Get column information for a table using SQLite PRAGMA.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
driver: SQLite driver instance
|
|
109
|
+
table: Table name to query columns for
|
|
110
|
+
schema: Schema name (unused in SQLite)
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
List of column metadata dictionaries with keys:
|
|
114
|
+
- column_name: Name of the column
|
|
115
|
+
- data_type: SQLite data type
|
|
116
|
+
- nullable: Whether column allows NULL
|
|
117
|
+
- default_value: Default value if any
|
|
118
|
+
"""
|
|
119
|
+
sqlite_driver = cast("SqliteDriver", driver)
|
|
120
|
+
result = sqlite_driver.execute(f"PRAGMA table_info({table})")
|
|
121
|
+
|
|
122
|
+
return [
|
|
123
|
+
{
|
|
124
|
+
"column_name": row["name"] if isinstance(row, dict) else row[1],
|
|
125
|
+
"data_type": row["type"] if isinstance(row, dict) else row[2],
|
|
126
|
+
"nullable": not (row["notnull"] if isinstance(row, dict) else row[3]),
|
|
127
|
+
"default_value": row["dflt_value"] if isinstance(row, dict) else row[4],
|
|
128
|
+
}
|
|
129
|
+
for row in result.data or []
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
def list_available_features(self) -> "list[str]":
|
|
133
|
+
"""List available SQLite feature flags.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
List of supported feature names
|
|
137
|
+
"""
|
|
138
|
+
return [
|
|
139
|
+
"supports_json",
|
|
140
|
+
"supports_returning",
|
|
141
|
+
"supports_upsert",
|
|
142
|
+
"supports_window_functions",
|
|
143
|
+
"supports_cte",
|
|
144
|
+
"supports_transactions",
|
|
145
|
+
"supports_prepared_statements",
|
|
146
|
+
"supports_schemas",
|
|
147
|
+
"supports_arrays",
|
|
148
|
+
"supports_uuid",
|
|
149
|
+
]
|
|
@@ -4,13 +4,24 @@ import contextlib
|
|
|
4
4
|
import datetime
|
|
5
5
|
import sqlite3
|
|
6
6
|
from decimal import Decimal
|
|
7
|
-
from typing import TYPE_CHECKING, Any
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
8
|
|
|
9
9
|
from sqlspec.core.cache import get_cache_config
|
|
10
10
|
from sqlspec.core.parameters import ParameterStyle, ParameterStyleConfig
|
|
11
11
|
from sqlspec.core.statement import StatementConfig
|
|
12
12
|
from sqlspec.driver import SyncDriverAdapterBase
|
|
13
|
-
from sqlspec.exceptions import
|
|
13
|
+
from sqlspec.exceptions import (
|
|
14
|
+
CheckViolationError,
|
|
15
|
+
DatabaseConnectionError,
|
|
16
|
+
DataError,
|
|
17
|
+
ForeignKeyViolationError,
|
|
18
|
+
IntegrityError,
|
|
19
|
+
NotNullViolationError,
|
|
20
|
+
OperationalError,
|
|
21
|
+
SQLParsingError,
|
|
22
|
+
SQLSpecError,
|
|
23
|
+
UniqueViolationError,
|
|
24
|
+
)
|
|
14
25
|
from sqlspec.utils.serializers import to_json
|
|
15
26
|
|
|
16
27
|
if TYPE_CHECKING:
|
|
@@ -20,9 +31,18 @@ if TYPE_CHECKING:
|
|
|
20
31
|
from sqlspec.core.result import SQLResult
|
|
21
32
|
from sqlspec.core.statement import SQL
|
|
22
33
|
from sqlspec.driver import ExecutionResult
|
|
34
|
+
from sqlspec.driver._sync import SyncDataDictionaryBase
|
|
23
35
|
|
|
24
36
|
__all__ = ("SqliteCursor", "SqliteDriver", "SqliteExceptionHandler", "sqlite_statement_config")
|
|
25
37
|
|
|
38
|
+
SQLITE_CONSTRAINT_UNIQUE_CODE = 2067
|
|
39
|
+
SQLITE_CONSTRAINT_FOREIGNKEY_CODE = 787
|
|
40
|
+
SQLITE_CONSTRAINT_NOTNULL_CODE = 1811
|
|
41
|
+
SQLITE_CONSTRAINT_CHECK_CODE = 531
|
|
42
|
+
SQLITE_CONSTRAINT_CODE = 19
|
|
43
|
+
SQLITE_CANTOPEN_CODE = 14
|
|
44
|
+
SQLITE_IOERR_CODE = 10
|
|
45
|
+
SQLITE_MISMATCH_CODE = 20
|
|
26
46
|
|
|
27
47
|
sqlite_statement_config = StatementConfig(
|
|
28
48
|
dialect="sqlite",
|
|
@@ -36,6 +56,7 @@ sqlite_statement_config = StatementConfig(
|
|
|
36
56
|
datetime.datetime: lambda v: v.isoformat(),
|
|
37
57
|
datetime.date: lambda v: v.isoformat(),
|
|
38
58
|
Decimal: str,
|
|
59
|
+
dict: to_json,
|
|
39
60
|
list: to_json,
|
|
40
61
|
},
|
|
41
62
|
has_native_list_expansion=False,
|
|
@@ -64,7 +85,7 @@ class SqliteCursor:
|
|
|
64
85
|
connection: SQLite database connection
|
|
65
86
|
"""
|
|
66
87
|
self.connection = connection
|
|
67
|
-
self.cursor:
|
|
88
|
+
self.cursor: sqlite3.Cursor | None = None
|
|
68
89
|
|
|
69
90
|
def __enter__(self) -> "sqlite3.Cursor":
|
|
70
91
|
"""Create and return a new cursor.
|
|
@@ -75,7 +96,7 @@ class SqliteCursor:
|
|
|
75
96
|
self.cursor = self.connection.cursor()
|
|
76
97
|
return self.cursor
|
|
77
98
|
|
|
78
|
-
def __exit__(self,
|
|
99
|
+
def __exit__(self, *_: Any) -> None:
|
|
79
100
|
"""Clean up cursor resources.
|
|
80
101
|
|
|
81
102
|
Args:
|
|
@@ -83,7 +104,6 @@ class SqliteCursor:
|
|
|
83
104
|
exc_val: Exception value if an exception occurred
|
|
84
105
|
exc_tb: Exception traceback if an exception occurred
|
|
85
106
|
"""
|
|
86
|
-
_ = (exc_type, exc_val, exc_tb)
|
|
87
107
|
if self.cursor is not None:
|
|
88
108
|
with contextlib.suppress(Exception):
|
|
89
109
|
self.cursor.close()
|
|
@@ -92,64 +112,113 @@ class SqliteCursor:
|
|
|
92
112
|
class SqliteExceptionHandler:
|
|
93
113
|
"""Context manager for handling SQLite database exceptions.
|
|
94
114
|
|
|
95
|
-
|
|
115
|
+
Maps SQLite extended result codes to specific SQLSpec exceptions
|
|
116
|
+
for better error handling in application code.
|
|
96
117
|
"""
|
|
97
118
|
|
|
98
119
|
__slots__ = ()
|
|
99
120
|
|
|
100
121
|
def __enter__(self) -> None:
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
Returns:
|
|
104
|
-
None
|
|
105
|
-
"""
|
|
106
|
-
return
|
|
122
|
+
return None
|
|
107
123
|
|
|
108
124
|
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
109
|
-
|
|
125
|
+
if exc_type is None:
|
|
126
|
+
return
|
|
127
|
+
if issubclass(exc_type, sqlite3.Error):
|
|
128
|
+
self._map_sqlite_exception(exc_val)
|
|
129
|
+
|
|
130
|
+
def _map_sqlite_exception(self, e: Any) -> None:
|
|
131
|
+
"""Map SQLite exception to SQLSpec exception.
|
|
110
132
|
|
|
111
133
|
Args:
|
|
112
|
-
|
|
113
|
-
exc_val: Exception value if an exception occurred
|
|
114
|
-
exc_tb: Exception traceback if an exception occurred
|
|
134
|
+
e: sqlite3.Error instance
|
|
115
135
|
|
|
116
136
|
Raises:
|
|
117
|
-
|
|
118
|
-
SQLParsingError: For SQL syntax or parsing errors
|
|
137
|
+
Specific SQLSpec exception based on error code
|
|
119
138
|
"""
|
|
120
|
-
|
|
139
|
+
error_code = getattr(e, "sqlite_errorcode", None)
|
|
140
|
+
error_name = getattr(e, "sqlite_errorname", None)
|
|
141
|
+
error_msg = str(e).lower()
|
|
142
|
+
|
|
143
|
+
if "locked" in error_msg:
|
|
144
|
+
self._raise_operational_error(e, error_code or 0)
|
|
145
|
+
|
|
146
|
+
if not error_code:
|
|
147
|
+
if "unique constraint" in error_msg:
|
|
148
|
+
self._raise_unique_violation(e, 0)
|
|
149
|
+
elif "foreign key constraint" in error_msg:
|
|
150
|
+
self._raise_foreign_key_violation(e, 0)
|
|
151
|
+
elif "not null constraint" in error_msg:
|
|
152
|
+
self._raise_not_null_violation(e, 0)
|
|
153
|
+
elif "check constraint" in error_msg:
|
|
154
|
+
self._raise_check_violation(e, 0)
|
|
155
|
+
elif "syntax" in error_msg:
|
|
156
|
+
self._raise_parsing_error(e, None)
|
|
157
|
+
else:
|
|
158
|
+
self._raise_generic_error(e)
|
|
121
159
|
return
|
|
122
160
|
|
|
123
|
-
if
|
|
124
|
-
e
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
e
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
e
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
e
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
161
|
+
if error_code == SQLITE_CONSTRAINT_UNIQUE_CODE or error_name == "SQLITE_CONSTRAINT_UNIQUE":
|
|
162
|
+
self._raise_unique_violation(e, error_code)
|
|
163
|
+
elif error_code == SQLITE_CONSTRAINT_FOREIGNKEY_CODE or error_name == "SQLITE_CONSTRAINT_FOREIGNKEY":
|
|
164
|
+
self._raise_foreign_key_violation(e, error_code)
|
|
165
|
+
elif error_code == SQLITE_CONSTRAINT_NOTNULL_CODE or error_name == "SQLITE_CONSTRAINT_NOTNULL":
|
|
166
|
+
self._raise_not_null_violation(e, error_code)
|
|
167
|
+
elif error_code == SQLITE_CONSTRAINT_CHECK_CODE or error_name == "SQLITE_CONSTRAINT_CHECK":
|
|
168
|
+
self._raise_check_violation(e, error_code)
|
|
169
|
+
elif error_code == SQLITE_CONSTRAINT_CODE or error_name == "SQLITE_CONSTRAINT":
|
|
170
|
+
self._raise_integrity_error(e, error_code)
|
|
171
|
+
elif error_code == SQLITE_CANTOPEN_CODE or error_name == "SQLITE_CANTOPEN":
|
|
172
|
+
self._raise_connection_error(e, error_code)
|
|
173
|
+
elif error_code == SQLITE_IOERR_CODE or error_name == "SQLITE_IOERR":
|
|
174
|
+
self._raise_operational_error(e, error_code)
|
|
175
|
+
elif error_code == SQLITE_MISMATCH_CODE or error_name == "SQLITE_MISMATCH":
|
|
176
|
+
self._raise_data_error(e, error_code)
|
|
177
|
+
elif error_code == 1 or "syntax" in error_msg:
|
|
178
|
+
self._raise_parsing_error(e, error_code)
|
|
179
|
+
else:
|
|
180
|
+
self._raise_generic_error(e)
|
|
181
|
+
|
|
182
|
+
def _raise_unique_violation(self, e: Any, code: int) -> None:
|
|
183
|
+
msg = f"SQLite unique constraint violation [code {code}]: {e}"
|
|
184
|
+
raise UniqueViolationError(msg) from e
|
|
185
|
+
|
|
186
|
+
def _raise_foreign_key_violation(self, e: Any, code: int) -> None:
|
|
187
|
+
msg = f"SQLite foreign key constraint violation [code {code}]: {e}"
|
|
188
|
+
raise ForeignKeyViolationError(msg) from e
|
|
189
|
+
|
|
190
|
+
def _raise_not_null_violation(self, e: Any, code: int) -> None:
|
|
191
|
+
msg = f"SQLite not-null constraint violation [code {code}]: {e}"
|
|
192
|
+
raise NotNullViolationError(msg) from e
|
|
193
|
+
|
|
194
|
+
def _raise_check_violation(self, e: Any, code: int) -> None:
|
|
195
|
+
msg = f"SQLite check constraint violation [code {code}]: {e}"
|
|
196
|
+
raise CheckViolationError(msg) from e
|
|
197
|
+
|
|
198
|
+
def _raise_integrity_error(self, e: Any, code: int) -> None:
|
|
199
|
+
msg = f"SQLite integrity constraint violation [code {code}]: {e}"
|
|
200
|
+
raise IntegrityError(msg) from e
|
|
201
|
+
|
|
202
|
+
def _raise_parsing_error(self, e: Any, code: "int | None") -> None:
|
|
203
|
+
code_str = f"[code {code}]" if code else ""
|
|
204
|
+
msg = f"SQLite SQL syntax error {code_str}: {e}"
|
|
205
|
+
raise SQLParsingError(msg) from e
|
|
206
|
+
|
|
207
|
+
def _raise_connection_error(self, e: Any, code: int) -> None:
|
|
208
|
+
msg = f"SQLite connection error [code {code}]: {e}"
|
|
209
|
+
raise DatabaseConnectionError(msg) from e
|
|
210
|
+
|
|
211
|
+
def _raise_operational_error(self, e: Any, code: int) -> None:
|
|
212
|
+
msg = f"SQLite operational error [code {code}]: {e}"
|
|
213
|
+
raise OperationalError(msg) from e
|
|
214
|
+
|
|
215
|
+
def _raise_data_error(self, e: Any, code: int) -> None:
|
|
216
|
+
msg = f"SQLite data error [code {code}]: {e}"
|
|
217
|
+
raise DataError(msg) from e
|
|
218
|
+
|
|
219
|
+
def _raise_generic_error(self, e: Any) -> None:
|
|
220
|
+
msg = f"SQLite database error: {e}"
|
|
221
|
+
raise SQLSpecError(msg) from e
|
|
153
222
|
|
|
154
223
|
|
|
155
224
|
class SqliteDriver(SyncDriverAdapterBase):
|
|
@@ -159,14 +228,14 @@ class SqliteDriver(SyncDriverAdapterBase):
|
|
|
159
228
|
for SQLite databases using the standard sqlite3 module.
|
|
160
229
|
"""
|
|
161
230
|
|
|
162
|
-
__slots__ = ()
|
|
231
|
+
__slots__ = ("_data_dictionary",)
|
|
163
232
|
dialect = "sqlite"
|
|
164
233
|
|
|
165
234
|
def __init__(
|
|
166
235
|
self,
|
|
167
236
|
connection: "SqliteConnection",
|
|
168
|
-
statement_config: "
|
|
169
|
-
driver_features: "
|
|
237
|
+
statement_config: "StatementConfig | None" = None,
|
|
238
|
+
driver_features: "dict[str, Any] | None" = None,
|
|
170
239
|
) -> None:
|
|
171
240
|
"""Initialize SQLite driver.
|
|
172
241
|
|
|
@@ -185,6 +254,7 @@ class SqliteDriver(SyncDriverAdapterBase):
|
|
|
185
254
|
)
|
|
186
255
|
|
|
187
256
|
super().__init__(connection=connection, statement_config=statement_config, driver_features=driver_features)
|
|
257
|
+
self._data_dictionary: SyncDataDictionaryBase | None = None
|
|
188
258
|
|
|
189
259
|
def with_cursor(self, connection: "SqliteConnection") -> "SqliteCursor":
|
|
190
260
|
"""Create context manager for SQLite cursor.
|
|
@@ -205,7 +275,7 @@ class SqliteDriver(SyncDriverAdapterBase):
|
|
|
205
275
|
"""
|
|
206
276
|
return SqliteExceptionHandler()
|
|
207
277
|
|
|
208
|
-
def _try_special_handling(self, cursor: "sqlite3.Cursor", statement: "SQL") -> "
|
|
278
|
+
def _try_special_handling(self, cursor: "sqlite3.Cursor", statement: "SQL") -> "SQLResult | None":
|
|
209
279
|
"""Hook for SQLite-specific special operations.
|
|
210
280
|
|
|
211
281
|
Args:
|
|
@@ -280,7 +350,7 @@ class SqliteDriver(SyncDriverAdapterBase):
|
|
|
280
350
|
fetched_data = cursor.fetchall()
|
|
281
351
|
column_names = [col[0] for col in cursor.description or []]
|
|
282
352
|
|
|
283
|
-
data = [dict(zip(column_names, row)) for row in fetched_data]
|
|
353
|
+
data = [dict(zip(column_names, row, strict=False)) for row in fetched_data]
|
|
284
354
|
|
|
285
355
|
return self.create_execution_result(
|
|
286
356
|
cursor, selected_data=data, column_names=column_names, data_row_count=len(data), is_select_result=True
|
|
@@ -325,3 +395,16 @@ class SqliteDriver(SyncDriverAdapterBase):
|
|
|
325
395
|
except sqlite3.Error as e:
|
|
326
396
|
msg = f"Failed to commit transaction: {e}"
|
|
327
397
|
raise SQLSpecError(msg) from e
|
|
398
|
+
|
|
399
|
+
@property
|
|
400
|
+
def data_dictionary(self) -> "SyncDataDictionaryBase":
|
|
401
|
+
"""Get the data dictionary for this driver.
|
|
402
|
+
|
|
403
|
+
Returns:
|
|
404
|
+
Data dictionary instance for metadata queries
|
|
405
|
+
"""
|
|
406
|
+
if self._data_dictionary is None:
|
|
407
|
+
from sqlspec.adapters.sqlite.data_dictionary import SqliteSyncDataDictionary
|
|
408
|
+
|
|
409
|
+
self._data_dictionary = SqliteSyncDataDictionary()
|
|
410
|
+
return self._data_dictionary
|