sqlspec 0.62.2__py3-none-any.whl → 0.63.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 +5 -0
- sqlspec/_typing.py +0 -2
- sqlspec/adapters/adbc/config.py +36 -24
- sqlspec/adapters/adbc/core.py +29 -28
- sqlspec/adapters/adbc/driver.py +33 -7
- sqlspec/adapters/adbc/type_converter.py +10 -2
- sqlspec/adapters/aiomysql/config.py +12 -22
- sqlspec/adapters/aiosqlite/config.py +4 -0
- sqlspec/adapters/aiosqlite/core.py +7 -2
- sqlspec/adapters/arrow_odbc/config.py +5 -0
- sqlspec/adapters/asyncmy/_typing.py +78 -2
- sqlspec/adapters/asyncmy/config.py +13 -13
- sqlspec/adapters/asyncmy/core.py +54 -0
- sqlspec/adapters/asyncmy/driver.py +41 -11
- sqlspec/adapters/asyncpg/config.py +22 -14
- sqlspec/adapters/asyncpg/core.py +3 -0
- sqlspec/adapters/asyncpg/data_dictionary.py +3 -2
- sqlspec/adapters/asyncpg/driver.py +6 -1
- sqlspec/adapters/bigquery/adk/store.py +0 -5
- sqlspec/adapters/bigquery/config.py +12 -2
- sqlspec/adapters/bigquery/core.py +81 -1
- sqlspec/adapters/bigquery/driver.py +77 -1
- sqlspec/adapters/bigquery/litestar/store.py +6 -6
- sqlspec/adapters/cockroach_asyncpg/config.py +40 -1
- sqlspec/adapters/cockroach_asyncpg/core.py +94 -1
- sqlspec/adapters/cockroach_asyncpg/data_dictionary.py +3 -2
- sqlspec/adapters/cockroach_asyncpg/driver.py +106 -0
- sqlspec/adapters/cockroach_psycopg/config.py +57 -1
- sqlspec/adapters/cockroach_psycopg/core.py +91 -0
- sqlspec/adapters/cockroach_psycopg/data_dictionary.py +6 -4
- sqlspec/adapters/cockroach_psycopg/driver.py +219 -0
- sqlspec/adapters/duckdb/config.py +29 -3
- sqlspec/adapters/duckdb/core.py +308 -1
- sqlspec/adapters/duckdb/driver.py +125 -4
- sqlspec/adapters/duckdb/pool.py +199 -28
- sqlspec/adapters/mssql_python/adk/__init__.py +2 -2
- sqlspec/adapters/mssql_python/adk/store.py +212 -30
- sqlspec/adapters/mssql_python/config.py +5 -0
- sqlspec/adapters/mssql_python/data_dictionary.py +9 -8
- sqlspec/adapters/mssql_python/driver.py +45 -1
- sqlspec/adapters/mssql_python/litestar/store.py +7 -7
- sqlspec/adapters/mysqlconnector/_typing.py +2 -6
- sqlspec/adapters/mysqlconnector/config.py +14 -4
- sqlspec/adapters/oracledb/config.py +7 -0
- sqlspec/adapters/oracledb/driver.py +9 -0
- sqlspec/adapters/oracledb/type_converter.py +2 -6
- sqlspec/adapters/psqlpy/config.py +21 -6
- sqlspec/adapters/psqlpy/core.py +5 -15
- sqlspec/adapters/psqlpy/data_dictionary.py +3 -2
- sqlspec/adapters/psqlpy/driver.py +13 -3
- sqlspec/adapters/psycopg/_typing.py +4 -4
- sqlspec/adapters/psycopg/config.py +35 -31
- sqlspec/adapters/psycopg/core.py +3 -0
- sqlspec/adapters/psycopg/data_dictionary.py +6 -4
- sqlspec/adapters/pymssql/adk/store.py +11 -10
- sqlspec/adapters/pymssql/config.py +7 -1
- sqlspec/adapters/pymssql/core.py +3 -1
- sqlspec/adapters/pymssql/data_dictionary.py +9 -8
- sqlspec/adapters/pymssql/driver.py +78 -6
- sqlspec/adapters/pymssql/litestar/store.py +1 -1
- sqlspec/adapters/pymysql/config.py +10 -11
- sqlspec/adapters/spanner/config.py +4 -1
- sqlspec/adapters/spanner/driver.py +27 -0
- sqlspec/adapters/spanner/type_converter.py +1 -1
- sqlspec/adapters/sqlite/config.py +4 -0
- sqlspec/adapters/sqlite/core.py +7 -2
- sqlspec/adapters/sqlite/driver.py +8 -16
- sqlspec/base.py +5 -3
- sqlspec/builder/__init__.py +3 -1
- sqlspec/builder/_base.py +277 -45
- sqlspec/builder/_ddl.py +45 -13
- sqlspec/builder/_delete.py +2 -12
- sqlspec/builder/_dml.py +110 -14
- sqlspec/builder/_explain.py +8 -11
- sqlspec/builder/_factory.py +87 -122
- sqlspec/builder/_insert.py +1 -12
- sqlspec/builder/_locking.py +5 -4
- sqlspec/builder/_merge.py +0 -10
- sqlspec/builder/_parsing_utils.py +32 -0
- sqlspec/builder/_select.py +54 -170
- sqlspec/builder/_update.py +7 -8
- sqlspec/builder/_values.py +237 -0
- sqlspec/builder/_vector_distance.py +23 -23
- sqlspec/cli.py +40 -30
- sqlspec/config.py +72 -25
- sqlspec/core/__init__.py +16 -77
- sqlspec/core/cache.py +8 -2
- sqlspec/core/capabilities.py +67 -0
- sqlspec/core/compiler.py +21 -55
- sqlspec/core/config_runtime.py +29 -0
- sqlspec/core/hashing.py +1 -1
- sqlspec/core/parameters/__init__.py +11 -1
- sqlspec/core/parameters/_alignment.py +1 -9
- sqlspec/core/parameters/_converter.py +201 -138
- sqlspec/core/parameters/_processor.py +57 -96
- sqlspec/core/parameters/_transformers.py +2 -3
- sqlspec/core/parameters/_types.py +6 -0
- sqlspec/core/splitter.py +2 -0
- sqlspec/core/statement.py +2 -2
- sqlspec/data_dictionary/_loader.py +0 -8
- sqlspec/data_dictionary/_types.py +37 -3
- sqlspec/data_dictionary/dialects/bigquery/config.py +6 -0
- sqlspec/data_dictionary/dialects/cockroachdb/config.py +6 -0
- sqlspec/data_dictionary/dialects/cockroachdb/sql/columns.sql +49 -15
- sqlspec/data_dictionary/dialects/duckdb/config.py +6 -0
- sqlspec/data_dictionary/dialects/duckdb/sql/columns.sql +128 -35
- sqlspec/data_dictionary/dialects/mariadb/sql/columns.sql +1 -0
- sqlspec/data_dictionary/dialects/mssql/config.py +6 -0
- sqlspec/data_dictionary/dialects/mssql/sql/schemas.sql +4 -0
- sqlspec/data_dictionary/dialects/mysql/config.py +8 -0
- sqlspec/data_dictionary/dialects/mysql/sql/columns.sql +6 -1
- sqlspec/data_dictionary/dialects/oracle/config.py +9 -0
- sqlspec/data_dictionary/dialects/postgres/config.py +6 -0
- sqlspec/data_dictionary/dialects/postgres/sql/columns.sql +13 -2
- sqlspec/data_dictionary/dialects/spanner/config.py +6 -0
- sqlspec/data_dictionary/dialects/sqlite/config.py +6 -0
- sqlspec/dialects/__init__.py +4 -3
- sqlspec/dialects/postgres/__init__.py +2 -1
- sqlspec/dialects/postgres/_generators.py +13 -3
- sqlspec/dialects/postgres/_operators.py +16 -2
- sqlspec/dialects/postgres/_paradedb.py +11 -6
- sqlspec/dialects/postgres/_pg_textsearch.py +38 -0
- sqlspec/driver/_async.py +126 -68
- sqlspec/driver/_common.py +44 -63
- sqlspec/driver/_query_cache.py +0 -4
- sqlspec/driver/_sync.py +131 -62
- sqlspec/exceptions.py +54 -29
- sqlspec/extensions/adk/artifact/service.py +138 -159
- sqlspec/extensions/adk/memory/store.py +0 -18
- sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +1 -1
- sqlspec/extensions/adk/migrations/0002_create_mssql_python_memory.py +54 -0
- sqlspec/extensions/events/__init__.py +5 -0
- sqlspec/extensions/events/_channel.py +36 -3
- sqlspec/extensions/events/_queue.py +19 -24
- sqlspec/extensions/events/primitives.py +82 -0
- sqlspec/extensions/litestar/__init__.py +4 -0
- sqlspec/extensions/litestar/channels.py +52 -5
- sqlspec/extensions/litestar/config.py +2 -95
- sqlspec/extensions/litestar/plugin.py +142 -20
- sqlspec/extensions/litestar/store.py +1 -0
- sqlspec/loader.py +948 -253
- sqlspec/migrations/base.py +5 -15
- sqlspec/migrations/commands.py +33 -79
- sqlspec/migrations/loaders.py +13 -11
- sqlspec/migrations/runner.py +68 -18
- sqlspec/migrations/tracker.py +3 -0
- sqlspec/protocols.py +2 -2
- sqlspec/service.py +499 -85
- sqlspec/storage/__init__.py +2 -4
- sqlspec/storage/_paths.py +157 -1
- sqlspec/storage/backends/local.py +8 -5
- sqlspec/storage/backends/obstore.py +123 -64
- sqlspec/storage/pipeline.py +47 -94
- sqlspec/typing.py +1 -3
- sqlspec/utils/arrow_helpers.py +106 -111
- sqlspec/utils/correlation.py +7 -7
- sqlspec/utils/deprecation.py +19 -4
- sqlspec/utils/fixtures.py +915 -3
- sqlspec/utils/logging.py +2 -2
- sqlspec/utils/schema.py +102 -119
- sqlspec/utils/text.py +1 -1
- sqlspec/utils/type_converters.py +1 -3
- sqlspec/utils/type_guards.py +4 -2
- sqlspec/utils/uuids.py +1 -1
- {sqlspec-0.62.2.dist-info → sqlspec-0.63.0.dist-info}/METADATA +7 -5
- {sqlspec-0.62.2.dist-info → sqlspec-0.63.0.dist-info}/RECORD +170 -166
- {sqlspec-0.62.2.dist-info → sqlspec-0.63.0.dist-info}/entry_points.txt +1 -0
- sqlspec/utils/singleton.py +0 -41
- /sqlspec/core/{pipeline.py → _pipeline.py} +0 -0
- {sqlspec-0.62.2.dist-info → sqlspec-0.63.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.62.2.dist-info → sqlspec-0.63.0.dist-info}/licenses/LICENSE +0 -0
sqlspec/__init__.py
CHANGED
|
@@ -82,6 +82,7 @@ from sqlspec.observability import (
|
|
|
82
82
|
)
|
|
83
83
|
from sqlspec.typing import ConnectionT, PoolT, SchemaT, StatementParameters, SupportedSchemaModel
|
|
84
84
|
from sqlspec.utils.logging import suppress_erroneous_sqlglot_log_messages
|
|
85
|
+
from sqlspec.utils.uuids import nanoid, uuid4, uuid6, uuid7
|
|
85
86
|
|
|
86
87
|
if TYPE_CHECKING:
|
|
87
88
|
from sqlspec import dialects
|
|
@@ -159,11 +160,15 @@ __all__ = (
|
|
|
159
160
|
"loader",
|
|
160
161
|
"matches_param_type",
|
|
161
162
|
"migrations",
|
|
163
|
+
"nanoid",
|
|
162
164
|
"register_param_type",
|
|
163
165
|
"resolve_param_type",
|
|
164
166
|
"sql",
|
|
165
167
|
"typing",
|
|
166
168
|
"utils",
|
|
169
|
+
"uuid4",
|
|
170
|
+
"uuid6",
|
|
171
|
+
"uuid7",
|
|
167
172
|
)
|
|
168
173
|
|
|
169
174
|
suppress_erroneous_sqlglot_log_messages()
|
sqlspec/_typing.py
CHANGED
|
@@ -102,7 +102,6 @@ __all__ = (
|
|
|
102
102
|
"Struct",
|
|
103
103
|
"StructStub",
|
|
104
104
|
"T",
|
|
105
|
-
"T_co",
|
|
106
105
|
"Tracer",
|
|
107
106
|
"TypeAdapter",
|
|
108
107
|
"TypeAdapterStub",
|
|
@@ -139,7 +138,6 @@ class DataclassProtocol(Protocol):
|
|
|
139
138
|
|
|
140
139
|
|
|
141
140
|
T = TypeVar("T")
|
|
142
|
-
T_co = TypeVar("T_co", covariant=True)
|
|
143
141
|
|
|
144
142
|
# Always define stub types for type checking
|
|
145
143
|
|
sqlspec/adapters/adbc/config.py
CHANGED
|
@@ -8,19 +8,23 @@ from sqlspec.adapters.adbc._typing import AdbcConnection, AdbcCursor, AdbcSessio
|
|
|
8
8
|
from sqlspec.adapters.adbc.core import (
|
|
9
9
|
apply_driver_features,
|
|
10
10
|
build_connection_config,
|
|
11
|
-
build_postgres_extension_probe_names,
|
|
12
11
|
detect_postgres_extensions,
|
|
13
12
|
get_statement_config,
|
|
14
13
|
is_postgres_dialect,
|
|
15
14
|
resolve_dialect_from_config,
|
|
16
15
|
resolve_dialect_name,
|
|
17
16
|
resolve_driver_connect_func,
|
|
18
|
-
resolve_postgres_extension_state,
|
|
19
|
-
resolve_runtime_statement_config,
|
|
20
17
|
)
|
|
21
18
|
from sqlspec.adapters.adbc.driver import AdbcDriver, AdbcExceptionHandler
|
|
22
19
|
from sqlspec.config import ExtensionConfigs, NoPoolSyncConfig
|
|
23
20
|
from sqlspec.core import StatementConfig
|
|
21
|
+
from sqlspec.core.capabilities import TypeCoercionCapabilities
|
|
22
|
+
from sqlspec.core.config_runtime import (
|
|
23
|
+
build_postgres_extension_probe_names,
|
|
24
|
+
is_postgres_extension_active,
|
|
25
|
+
resolve_postgres_extension_state,
|
|
26
|
+
resolve_runtime_statement_config,
|
|
27
|
+
)
|
|
24
28
|
from sqlspec.driver._sync import SyncPoolConnectionContext, SyncPoolSessionFactory
|
|
25
29
|
from sqlspec.exceptions import ImproperConfigurationError
|
|
26
30
|
from sqlspec.extensions.events import EventRuntimeHints
|
|
@@ -81,20 +85,20 @@ class AdbcDriverFeatures(TypedDict):
|
|
|
81
85
|
Attributes:
|
|
82
86
|
json_serializer: JSON serialization function to use.
|
|
83
87
|
Callable that takes Any and returns str (JSON string).
|
|
84
|
-
|
|
88
|
+
Default: sqlspec.utils.serializers.to_json
|
|
85
89
|
enable_cast_detection: Enable cast-aware parameter processing.
|
|
86
90
|
When True, detects SQL casts and applies appropriate
|
|
87
91
|
serialization. Currently used for PostgreSQL JSONB handling.
|
|
88
|
-
|
|
92
|
+
Default: True
|
|
89
93
|
enable_strict_type_coercion: Enforce strict type coercion rules.
|
|
90
94
|
When True, raises errors for unsupported type conversions.
|
|
91
95
|
When False, attempts best-effort conversion.
|
|
92
|
-
|
|
96
|
+
Default: False
|
|
93
97
|
strict_type_coercion: Alias for enable_strict_type_coercion.
|
|
94
98
|
enable_arrow_extension_types: Enable PyArrow extension type support.
|
|
95
99
|
When True, preserves Arrow extension type metadata when reading data.
|
|
96
100
|
When False, falls back to storage types.
|
|
97
|
-
|
|
101
|
+
Default: True
|
|
98
102
|
arrow_extension_types: Alias for enable_arrow_extension_types.
|
|
99
103
|
enable_pgvector: Enable automatic pgvector extension detection.
|
|
100
104
|
When True and the resolved dialect is PostgreSQL, queries ``pg_extension``
|
|
@@ -104,6 +108,10 @@ class AdbcDriverFeatures(TypedDict):
|
|
|
104
108
|
When True and the resolved dialect is PostgreSQL, queries ``pg_extension``
|
|
105
109
|
on the first connection to check for the ``pg_search`` extension.
|
|
106
110
|
Defaults to True. Independent of enable_pgvector.
|
|
111
|
+
enable_pg_textsearch: Enable pg_textsearch extension detection for BM25 search.
|
|
112
|
+
When True and the resolved dialect is PostgreSQL, queries ``pg_extension``
|
|
113
|
+
on the first connection to check for the ``pg_textsearch`` extension.
|
|
114
|
+
Defaults to True.
|
|
107
115
|
enable_events: Enable database event channel support.
|
|
108
116
|
Defaults to True when extension_config["events"] is configured.
|
|
109
117
|
Provides pub/sub capabilities via table-backed queue (ADBC has no native pub/sub).
|
|
@@ -111,7 +119,7 @@ class AdbcDriverFeatures(TypedDict):
|
|
|
111
119
|
on_connection_create: Callback executed when a connection is created.
|
|
112
120
|
Receives the raw ADBC connection for low-level driver configuration.
|
|
113
121
|
events_backend: Event channel backend selection.
|
|
114
|
-
|
|
122
|
+
Only option: "poll_queue" (durable table-backed queue with lease-based retries and acknowledgements).
|
|
115
123
|
ADBC does not have native pub/sub, so poll_queue is the only backend.
|
|
116
124
|
Defaults to "poll_queue".
|
|
117
125
|
"""
|
|
@@ -124,6 +132,7 @@ class AdbcDriverFeatures(TypedDict):
|
|
|
124
132
|
arrow_extension_types: NotRequired[bool]
|
|
125
133
|
enable_pgvector: NotRequired[bool]
|
|
126
134
|
enable_paradedb: NotRequired[bool]
|
|
135
|
+
enable_pg_textsearch: NotRequired[bool]
|
|
127
136
|
enable_events: NotRequired[bool]
|
|
128
137
|
on_connection_create: "NotRequired[Callable[[AdbcConnection], None]]"
|
|
129
138
|
events_backend: NotRequired[Literal["poll_queue"]]
|
|
@@ -188,7 +197,11 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
|
|
|
188
197
|
supports_native_row_streaming: "ClassVar[bool]" = True
|
|
189
198
|
supports_native_parquet_export: "ClassVar[bool]" = True
|
|
190
199
|
supports_native_parquet_import: "ClassVar[bool]" = True
|
|
200
|
+
supports_reliable_rowcount: ClassVar[bool] = False
|
|
191
201
|
storage_partition_strategies: "ClassVar[tuple[str, ...]]" = ("fixed", "rows_per_chunk")
|
|
202
|
+
type_coercion_capabilities: "ClassVar[TypeCoercionCapabilities]" = TypeCoercionCapabilities(
|
|
203
|
+
datetime_binding="native", timestamp_precision="microsecond", json_columns_decoded=False, uuid_binding="native"
|
|
204
|
+
)
|
|
192
205
|
_connection_context_class: "ClassVar[type[AdbcConnectionContext]]" = AdbcConnectionContext
|
|
193
206
|
_session_factory_class: "ClassVar[type[_AdbcSessionConnectionHandler]]" = _AdbcSessionConnectionHandler
|
|
194
207
|
_session_context_class: "ClassVar[type[AdbcSessionContext]]" = AdbcSessionContext
|
|
@@ -196,6 +209,7 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
|
|
|
196
209
|
__slots__ = (
|
|
197
210
|
"_default_session_config",
|
|
198
211
|
"_paradedb_available",
|
|
212
|
+
"_pg_textsearch_available",
|
|
199
213
|
"_pgvector_available",
|
|
200
214
|
"_resolved_dialect",
|
|
201
215
|
"_user_connection_hook",
|
|
@@ -230,6 +244,7 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
|
|
|
230
244
|
self.connection_config = normalize_connection_config(connection_config)
|
|
231
245
|
self._pgvector_available: bool | None = None
|
|
232
246
|
self._paradedb_available: bool | None = None
|
|
247
|
+
self._pg_textsearch_available: bool | None = None
|
|
233
248
|
|
|
234
249
|
self._resolved_dialect = resolve_dialect_from_config(self.connection_config)
|
|
235
250
|
|
|
@@ -284,20 +299,10 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
|
|
|
284
299
|
msg = f"Could not configure connection using driver '{err_driver_name}'. Error: {e}"
|
|
285
300
|
raise ImproperConfigurationError(msg) from e
|
|
286
301
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
Only switches when current dialect is ``postgres``.
|
|
292
|
-
"""
|
|
293
|
-
current_dialect = self.statement_config.dialect or "postgres"
|
|
294
|
-
if current_dialect != "postgres":
|
|
295
|
-
return
|
|
296
|
-
|
|
297
|
-
if self._paradedb_available:
|
|
298
|
-
self.statement_config = self.statement_config.replace(dialect="paradedb")
|
|
299
|
-
elif self._pgvector_available:
|
|
300
|
-
self.statement_config = self.statement_config.replace(dialect="pgvector")
|
|
302
|
+
@property
|
|
303
|
+
def pg_textsearch_available(self) -> bool:
|
|
304
|
+
"""Return True if the pg_textsearch extension is available."""
|
|
305
|
+
return bool(self._pg_textsearch_available)
|
|
301
306
|
|
|
302
307
|
def _detect_extensions_if_needed(self) -> None:
|
|
303
308
|
"""Detect postgres extensions on first call, caching results.
|
|
@@ -312,13 +317,17 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
|
|
|
312
317
|
if not is_postgres_dialect(dialect):
|
|
313
318
|
self._pgvector_available = False
|
|
314
319
|
self._paradedb_available = False
|
|
320
|
+
self._pg_textsearch_available = False
|
|
315
321
|
return
|
|
316
322
|
|
|
317
323
|
connection = self.create_connection()
|
|
318
324
|
try:
|
|
319
325
|
probe_names = build_postgres_extension_probe_names(self.driver_features)
|
|
320
|
-
pgvector_available, paradedb_available = detect_postgres_extensions(
|
|
321
|
-
connection,
|
|
326
|
+
pgvector_available, paradedb_available, pg_textsearch_available = detect_postgres_extensions(
|
|
327
|
+
connection,
|
|
328
|
+
enable_pgvector="vector" in probe_names,
|
|
329
|
+
enable_paradedb="pg_search" in probe_names,
|
|
330
|
+
enable_pg_textsearch="pg_textsearch" in probe_names,
|
|
322
331
|
)
|
|
323
332
|
finally:
|
|
324
333
|
connection.close()
|
|
@@ -328,9 +337,12 @@ class AdbcConfig(NoPoolSyncConfig[AdbcConnection, AdbcDriver]):
|
|
|
328
337
|
detected_extensions.add("vector")
|
|
329
338
|
if paradedb_available:
|
|
330
339
|
detected_extensions.add("pg_search")
|
|
340
|
+
if pg_textsearch_available:
|
|
341
|
+
detected_extensions.add("pg_textsearch")
|
|
331
342
|
self.statement_config, self._pgvector_available, self._paradedb_available = resolve_postgres_extension_state(
|
|
332
343
|
self.statement_config, self.driver_features, detected_extensions
|
|
333
344
|
)
|
|
345
|
+
self._pg_textsearch_available = is_postgres_extension_active(self.driver_features, "pg_textsearch")
|
|
334
346
|
|
|
335
347
|
def provide_connection(self, *args: Any, **kwargs: Any) -> "AdbcConnectionContext":
|
|
336
348
|
"""Provide a connection context manager.
|
sqlspec/adapters/adbc/core.py
CHANGED
|
@@ -21,9 +21,11 @@ from sqlspec.core import (
|
|
|
21
21
|
)
|
|
22
22
|
from sqlspec.core.config_runtime import (
|
|
23
23
|
build_postgres_extension_probe_names,
|
|
24
|
+
is_postgres_extension_active,
|
|
24
25
|
resolve_postgres_extension_state,
|
|
25
26
|
resolve_runtime_statement_config,
|
|
26
27
|
)
|
|
28
|
+
from sqlspec.core.parameters import type_coercion_dispatcher
|
|
27
29
|
from sqlspec.exceptions import (
|
|
28
30
|
CheckViolationError,
|
|
29
31
|
DatabaseConnectionError,
|
|
@@ -44,7 +46,6 @@ from sqlspec.exceptions import (
|
|
|
44
46
|
map_sqlstate_to_exception,
|
|
45
47
|
)
|
|
46
48
|
from sqlspec.typing import PGVECTOR_INSTALLED, Empty
|
|
47
|
-
from sqlspec.utils.dispatch import TypeDispatcher
|
|
48
49
|
from sqlspec.utils.module_loader import import_string
|
|
49
50
|
from sqlspec.utils.serializers import to_json
|
|
50
51
|
from sqlspec.utils.type_converters import build_uuid_coercions
|
|
@@ -55,6 +56,7 @@ if TYPE_CHECKING:
|
|
|
55
56
|
from collections.abc import Callable, Mapping
|
|
56
57
|
|
|
57
58
|
from sqlspec.core import SQL
|
|
59
|
+
from sqlspec.utils.dispatch import TypeDispatcher
|
|
58
60
|
|
|
59
61
|
__all__ = (
|
|
60
62
|
"_prepare_batch_with_casts",
|
|
@@ -73,6 +75,7 @@ __all__ = (
|
|
|
73
75
|
"get_statement_config",
|
|
74
76
|
"handle_postgres_rollback",
|
|
75
77
|
"is_postgres_dialect",
|
|
78
|
+
"is_postgres_extension_active",
|
|
76
79
|
"normalize_driver_path",
|
|
77
80
|
"normalize_postgres_empty_parameters",
|
|
78
81
|
"normalize_script_rowcount",
|
|
@@ -171,7 +174,6 @@ _BIGQUERY_DB_KWARGS_FIELDS: "tuple[str, ...]" = ("project_id", "dataset_id", "to
|
|
|
171
174
|
_FLIGHTSQL_DB_KWARGS_FIELDS: "tuple[str, ...]" = ("username", "password")
|
|
172
175
|
_FLIGHTSQL_TLS_SKIP_VERIFY_KEY: Final[str] = "adbc.flight.sql.client_option.tls_skip_verify"
|
|
173
176
|
_FLIGHTSQL_AUTHORIZATION_HEADER_KEY: Final[str] = "adbc.flight.sql.authorization_header"
|
|
174
|
-
_TYPE_COERCION_DISPATCHERS: "dict[tuple[tuple[type, Callable[[Any], Any]], ...], TypeDispatcher[Callable[[Any], Any]]]" = {}
|
|
175
177
|
_SQLSTATE_CLASS_CODE_LEN = 2
|
|
176
178
|
_SQLSTATE_DESCRIPTIONS: dict[str, str] = {
|
|
177
179
|
"23": "integrity constraint violation",
|
|
@@ -231,29 +233,32 @@ def detect_dialect(connection: Any, logger: Any | None = None, *, fallback_diale
|
|
|
231
233
|
|
|
232
234
|
|
|
233
235
|
def detect_postgres_extensions(
|
|
234
|
-
connection: Any, *, enable_pgvector: bool = False, enable_paradedb: bool = False
|
|
235
|
-
) -> "tuple[bool, bool]":
|
|
236
|
-
"""Detect pgvector and
|
|
236
|
+
connection: Any, *, enable_pgvector: bool = False, enable_paradedb: bool = False, enable_pg_textsearch: bool = False
|
|
237
|
+
) -> "tuple[bool, bool, bool]":
|
|
238
|
+
"""Detect pgvector, paradedb, and pg_textsearch extensions on a postgres connection.
|
|
237
239
|
|
|
238
|
-
Queries ``pg_extension`` for the ``vector`` and ``
|
|
240
|
+
Queries ``pg_extension`` for the ``vector``, ``pg_search``, and ``pg_textsearch`` extensions.
|
|
239
241
|
Returns cached-friendly booleans suitable for storing on the config instance.
|
|
240
242
|
|
|
241
243
|
Args:
|
|
242
244
|
connection: ADBC connection to a PostgreSQL database.
|
|
243
245
|
enable_pgvector: Whether to check for the pgvector extension.
|
|
244
246
|
enable_paradedb: Whether to check for the pg_search extension.
|
|
247
|
+
enable_pg_textsearch: Whether to check for the pg_textsearch extension.
|
|
245
248
|
|
|
246
249
|
Returns:
|
|
247
|
-
Tuple of ``(pgvector_available, paradedb_available)``.
|
|
250
|
+
Tuple of ``(pgvector_available, paradedb_available, pg_textsearch_available)``.
|
|
248
251
|
"""
|
|
249
252
|
extensions: list[str] = []
|
|
250
253
|
if enable_pgvector:
|
|
251
254
|
extensions.append("vector")
|
|
252
255
|
if enable_paradedb:
|
|
253
256
|
extensions.append("pg_search")
|
|
257
|
+
if enable_pg_textsearch:
|
|
258
|
+
extensions.append("pg_textsearch")
|
|
254
259
|
|
|
255
260
|
if not extensions:
|
|
256
|
-
return False, False
|
|
261
|
+
return False, False, False
|
|
257
262
|
|
|
258
263
|
try:
|
|
259
264
|
cursor = connection.cursor()
|
|
@@ -261,11 +266,11 @@ def detect_postgres_extensions(
|
|
|
261
266
|
cursor.execute("SELECT extname FROM pg_extension WHERE extname = ANY($1::text[])", [extensions])
|
|
262
267
|
rows = cursor.fetchall()
|
|
263
268
|
detected: set[str] = {row[0] for row in rows} if rows else set()
|
|
264
|
-
return "vector" in detected, "pg_search" in detected
|
|
269
|
+
return "vector" in detected, "pg_search" in detected, "pg_textsearch" in detected
|
|
265
270
|
finally:
|
|
266
271
|
cursor.close()
|
|
267
272
|
except Exception:
|
|
268
|
-
return False, False
|
|
273
|
+
return False, False, False
|
|
269
274
|
|
|
270
275
|
|
|
271
276
|
def normalize_driver_path(driver_name: str) -> str:
|
|
@@ -455,15 +460,21 @@ def resolve_dialect_name(dialect: Any) -> str:
|
|
|
455
460
|
"""Return the normalized dialect name string."""
|
|
456
461
|
if dialect is None:
|
|
457
462
|
return ""
|
|
463
|
+
if isinstance(dialect, str):
|
|
464
|
+
return dialect.lower()
|
|
465
|
+
if isinstance(dialect, type) and issubclass(dialect, sqlglot.Dialect):
|
|
466
|
+
return dialect.__name__.lower()
|
|
467
|
+
if isinstance(dialect, sqlglot.Dialect):
|
|
468
|
+
return type(dialect).__name__.lower()
|
|
458
469
|
return str(dialect)
|
|
459
470
|
|
|
460
471
|
|
|
461
472
|
def is_postgres_dialect(dialect_name: str) -> bool:
|
|
462
473
|
"""Return True when the dialect indicates PostgreSQL.
|
|
463
474
|
|
|
464
|
-
Includes pgvector
|
|
475
|
+
Includes pgvector, paradedb, and pg_textsearch extension dialects.
|
|
465
476
|
"""
|
|
466
|
-
return dialect_name in {"postgres", "postgresql", "pgvector", "paradedb"}
|
|
477
|
+
return dialect_name in {"postgres", "postgresql", "pgvector", "paradedb", "pg_textsearch", "pgtextsearch"}
|
|
467
478
|
|
|
468
479
|
|
|
469
480
|
def handle_postgres_rollback(dialect: str, cursor: Any, logger: Any | None = None) -> None:
|
|
@@ -726,7 +737,8 @@ def build_profile() -> "DriverParameterProfile":
|
|
|
726
737
|
def get_statement_config(detected_dialect: str) -> StatementConfig:
|
|
727
738
|
"""Create statement configuration for the specified dialect."""
|
|
728
739
|
default_style, supported_styles = DIALECT_PARAMETER_STYLES.get(
|
|
729
|
-
detected_dialect
|
|
740
|
+
"postgres" if is_postgres_dialect(detected_dialect) else detected_dialect,
|
|
741
|
+
(ParameterStyle.QMARK, [ParameterStyle.QMARK]),
|
|
730
742
|
)
|
|
731
743
|
|
|
732
744
|
sqlglot_dialect = "postgres" if detected_dialect == "postgresql" else detected_dialect
|
|
@@ -744,7 +756,7 @@ def get_statement_config(detected_dialect: str) -> StatementConfig:
|
|
|
744
756
|
parameter_overrides["preserve_parameter_format"] = False
|
|
745
757
|
parameter_overrides["supported_execution_parameter_styles"] = {ParameterStyle.QMARK, ParameterStyle.NUMERIC}
|
|
746
758
|
|
|
747
|
-
if detected_dialect
|
|
759
|
+
if is_postgres_dialect(detected_dialect):
|
|
748
760
|
parameter_overrides["ast_transformer"] = build_null_pruning_transform(dialect=sqlglot_dialect)
|
|
749
761
|
|
|
750
762
|
return build_statement_config_from_profile(
|
|
@@ -775,6 +787,7 @@ def apply_driver_features(
|
|
|
775
787
|
processed_features.setdefault("enable_arrow_extension_types", processed_features["arrow_extension_types"])
|
|
776
788
|
processed_features.setdefault("enable_pgvector", PGVECTOR_INSTALLED)
|
|
777
789
|
processed_features.setdefault("enable_paradedb", True)
|
|
790
|
+
processed_features.setdefault("enable_pg_textsearch", True)
|
|
778
791
|
|
|
779
792
|
if json_serializer is not None:
|
|
780
793
|
statement_config = _apply_adbc_json_serializer(statement_config, json_serializer)
|
|
@@ -898,7 +911,7 @@ def prepare_parameters_with_casts(
|
|
|
898
911
|
if isinstance(parameters, (list, tuple)):
|
|
899
912
|
converter = get_adbc_type_converter(dialect)
|
|
900
913
|
type_map = statement_config.parameter_config.type_coercion_map
|
|
901
|
-
dispatcher =
|
|
914
|
+
dispatcher = type_coercion_dispatcher(tuple(type_map.items())) if type_map else None
|
|
902
915
|
return _prepare_parameter_sequence_with_casts(
|
|
903
916
|
parameters, parameter_casts, type_map, dispatcher, converter, json_encoder
|
|
904
917
|
)
|
|
@@ -1211,18 +1224,6 @@ def _apply_adbc_json_serializer(
|
|
|
1211
1224
|
return statement_config.replace(parameter_config=updated_parameter_config.replace(type_coercion_map=updated_map))
|
|
1212
1225
|
|
|
1213
1226
|
|
|
1214
|
-
def _type_coercion_dispatcher(type_map: "dict[type, Callable[[Any], Any]]") -> "TypeDispatcher[Callable[[Any], Any]]":
|
|
1215
|
-
fallback_items = tuple(type_map.items())
|
|
1216
|
-
dispatcher = _TYPE_COERCION_DISPATCHERS.get(fallback_items)
|
|
1217
|
-
if dispatcher is not None:
|
|
1218
|
-
return dispatcher
|
|
1219
|
-
|
|
1220
|
-
dispatcher = TypeDispatcher["Callable[[Any], Any]"]()
|
|
1221
|
-
dispatcher.register_all(fallback_items)
|
|
1222
|
-
_TYPE_COERCION_DISPATCHERS[fallback_items] = dispatcher
|
|
1223
|
-
return dispatcher
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
1227
|
def _prepare_parameter_sequence_with_casts(
|
|
1227
1228
|
parameters: "list[Any] | tuple[Any, ...]",
|
|
1228
1229
|
parameter_casts: "dict[int, str]",
|
|
@@ -1287,7 +1288,7 @@ def _prepare_batch_with_casts(
|
|
|
1287
1288
|
json_encoder = statement_config.parameter_config.json_serializer or json_serializer
|
|
1288
1289
|
converter = get_adbc_type_converter(dialect)
|
|
1289
1290
|
type_map = statement_config.parameter_config.type_coercion_map
|
|
1290
|
-
dispatcher =
|
|
1291
|
+
dispatcher = type_coercion_dispatcher(tuple(type_map.items())) if type_map else None
|
|
1291
1292
|
return [
|
|
1292
1293
|
_prepare_parameter_sequence_with_casts(row, parameter_casts, type_map, dispatcher, converter, json_encoder)
|
|
1293
1294
|
if isinstance(row, (list, tuple))
|
sqlspec/adapters/adbc/driver.py
CHANGED
|
@@ -59,6 +59,7 @@ if TYPE_CHECKING:
|
|
|
59
59
|
__all__ = ("AdbcCursor", "AdbcDriver", "AdbcExceptionHandler", "AdbcSessionContext")
|
|
60
60
|
|
|
61
61
|
logger = get_logger("sqlspec.adapters.adbc")
|
|
62
|
+
_DIALECTS_WITHOUT_SAVEPOINTS = frozenset({"bigquery", "duckdb", "snowflake"})
|
|
62
63
|
|
|
63
64
|
_MULTI_ROW_BIND_UNSUPPORTED = "Binding multiple rows at once is not supported"
|
|
64
65
|
|
|
@@ -377,6 +378,33 @@ class AdbcDriver(SyncDriverAdapterBase):
|
|
|
377
378
|
finally:
|
|
378
379
|
self._transaction_active = False
|
|
379
380
|
|
|
381
|
+
def create_savepoint(self, name: str) -> None:
|
|
382
|
+
"""Create a savepoint within the current transaction.
|
|
383
|
+
|
|
384
|
+
Raises:
|
|
385
|
+
NotImplementedError: If the connected database does not support savepoints.
|
|
386
|
+
"""
|
|
387
|
+
self._require_savepoints()
|
|
388
|
+
super().create_savepoint(name)
|
|
389
|
+
|
|
390
|
+
def release_savepoint(self, name: str) -> None:
|
|
391
|
+
"""Release a previously created savepoint.
|
|
392
|
+
|
|
393
|
+
Raises:
|
|
394
|
+
NotImplementedError: If the connected database does not support savepoints.
|
|
395
|
+
"""
|
|
396
|
+
self._require_savepoints()
|
|
397
|
+
super().release_savepoint(name)
|
|
398
|
+
|
|
399
|
+
def rollback_to_savepoint(self, name: str) -> None:
|
|
400
|
+
"""Roll back the current transaction to a previously created savepoint.
|
|
401
|
+
|
|
402
|
+
Raises:
|
|
403
|
+
NotImplementedError: If the connected database does not support savepoints.
|
|
404
|
+
"""
|
|
405
|
+
self._require_savepoints()
|
|
406
|
+
super().rollback_to_savepoint(name)
|
|
407
|
+
|
|
380
408
|
def set_migration_session_schema(self, schema: str) -> None:
|
|
381
409
|
"""Set the PostgreSQL search path for migration SQL when using ADBC PostgreSQL."""
|
|
382
410
|
if not self._is_postgres:
|
|
@@ -516,19 +544,13 @@ class AdbcDriver(SyncDriverAdapterBase):
|
|
|
516
544
|
arrow_schema=arrow_schema,
|
|
517
545
|
)
|
|
518
546
|
|
|
519
|
-
# Use ADBC cursor for native Arrow
|
|
520
547
|
with self.with_cursor(self.connection) as cursor, exc_handler:
|
|
521
548
|
if cursor is None:
|
|
522
549
|
msg = "Failed to create cursor"
|
|
523
550
|
raise DatabaseConnectionError(msg)
|
|
524
551
|
|
|
525
|
-
# Get compiled SQL and parameters
|
|
526
552
|
sql, driver_params = self._compiled_sql(prepared_statement, config)
|
|
527
|
-
|
|
528
|
-
# Execute query
|
|
529
553
|
cursor.execute(sql, driver_params or ())
|
|
530
|
-
|
|
531
|
-
# Fetch as Arrow table (zero-copy!)
|
|
532
554
|
arrow_table = cursor.fetch_arrow_table()
|
|
533
555
|
|
|
534
556
|
arrow_result = build_arrow_result_from_table(
|
|
@@ -566,7 +588,6 @@ class AdbcDriver(SyncDriverAdapterBase):
|
|
|
566
588
|
) -> "StorageBridgeJob":
|
|
567
589
|
"""Stream query results to storage via the Arrow fast path."""
|
|
568
590
|
|
|
569
|
-
_ = kwargs
|
|
570
591
|
self._require_capability("arrow_export_enabled")
|
|
571
592
|
arrow_result = self.select_to_arrow(statement, *parameters, statement_config=statement_config, **kwargs)
|
|
572
593
|
sync_pipeline = self._storage_pipeline()
|
|
@@ -729,6 +750,11 @@ class AdbcDriver(SyncDriverAdapterBase):
|
|
|
729
750
|
"""
|
|
730
751
|
return self._transaction_active
|
|
731
752
|
|
|
753
|
+
def _require_savepoints(self) -> None:
|
|
754
|
+
if self._dialect_name in _DIALECTS_WITHOUT_SAVEPOINTS:
|
|
755
|
+
msg = f"ADBC {self._dialect_name} connections do not support savepoints."
|
|
756
|
+
raise NotImplementedError(msg)
|
|
757
|
+
|
|
732
758
|
@staticmethod
|
|
733
759
|
def _detect_flightsql_connection(connection: "AdbcConnection") -> bool:
|
|
734
760
|
try:
|
|
@@ -34,7 +34,15 @@ class ADBCOutputConverter:
|
|
|
34
34
|
Returns:
|
|
35
35
|
Converted value appropriate for the dialect.
|
|
36
36
|
"""
|
|
37
|
-
if self.dialect in {
|
|
37
|
+
if self.dialect in {
|
|
38
|
+
"postgres",
|
|
39
|
+
"postgresql",
|
|
40
|
+
"pgvector",
|
|
41
|
+
"paradedb",
|
|
42
|
+
"pg_textsearch",
|
|
43
|
+
"pgtextsearch",
|
|
44
|
+
"bigquery",
|
|
45
|
+
}:
|
|
38
46
|
return to_json(value)
|
|
39
47
|
return value
|
|
40
48
|
|
|
@@ -51,7 +59,7 @@ class ADBCOutputConverter:
|
|
|
51
59
|
Converted list parameter appropriate for the dialect.
|
|
52
60
|
"""
|
|
53
61
|
items = list(value)
|
|
54
|
-
if self.dialect in {"postgres", "postgresql", "pgvector", "paradedb"}:
|
|
62
|
+
if self.dialect in {"postgres", "postgresql", "pgvector", "paradedb", "pg_textsearch", "pgtextsearch"}:
|
|
55
63
|
return [item if item is not None else None for item in items]
|
|
56
64
|
return items
|
|
57
65
|
|
|
@@ -18,6 +18,7 @@ from sqlspec.adapters.aiomysql._typing import (
|
|
|
18
18
|
from sqlspec.adapters.aiomysql.core import apply_driver_features, default_statement_config
|
|
19
19
|
from sqlspec.adapters.aiomysql.driver import AiomysqlDriver, AiomysqlExceptionHandler
|
|
20
20
|
from sqlspec.config import AsyncDatabaseConfig, ExtensionConfigs
|
|
21
|
+
from sqlspec.core import TypeCoercionCapabilities
|
|
21
22
|
from sqlspec.driver._async import AsyncPoolConnectionContext, AsyncPoolSessionFactory
|
|
22
23
|
from sqlspec.exceptions import ImproperConfigurationError
|
|
23
24
|
from sqlspec.extensions.events import EventRuntimeHints
|
|
@@ -35,7 +36,6 @@ if TYPE_CHECKING:
|
|
|
35
36
|
__all__ = ("AiomysqlConfig", "AiomysqlConnectionParams", "AiomysqlDriverFeatures", "AiomysqlPoolParams")
|
|
36
37
|
|
|
37
38
|
_POOL_ONLY_CONFIG_KEYS = frozenset({"maxsize", "minsize", "pool_recycle"})
|
|
38
|
-
_AIOMYSQL_LOCAL_INFILE_GATE = "allow_local_infile"
|
|
39
39
|
aiomysql: "AiomysqlModule" = cast("AiomysqlModule", AiomysqlModule)
|
|
40
40
|
|
|
41
41
|
|
|
@@ -84,28 +84,19 @@ class AiomysqlPoolParams(AiomysqlConnectionParams):
|
|
|
84
84
|
pool_recycle: NotRequired[int]
|
|
85
85
|
|
|
86
86
|
|
|
87
|
-
def _normalize_local_infile(connection_config: "Mapping[str, Any]"
|
|
88
|
-
"""Normalize aiomysql local-infile
|
|
87
|
+
def _normalize_local_infile(connection_config: "Mapping[str, Any]") -> "dict[str, Any]":
|
|
88
|
+
"""Normalize aiomysql local-infile aliases to the native connection flag."""
|
|
89
89
|
config = dict(connection_config)
|
|
90
90
|
|
|
91
91
|
config.pop("enable_local_infile", None)
|
|
92
|
-
allow_local_infile = bool(config.
|
|
93
|
-
local_infile = bool(config.get("local_infile", False))
|
|
94
|
-
if local_infile and not allow_local_infile:
|
|
95
|
-
msg = (
|
|
96
|
-
"Aiomysql local_infile=True requires allow_local_infile=True because "
|
|
97
|
-
"LOAD DATA LOCAL INFILE can read client files."
|
|
98
|
-
)
|
|
99
|
-
raise ImproperConfigurationError(msg)
|
|
100
|
-
config["local_infile"] = bool(local_infile and allow_local_infile)
|
|
101
|
-
if strip_consent_gate:
|
|
102
|
-
config.pop(_AIOMYSQL_LOCAL_INFILE_GATE, None)
|
|
92
|
+
allow_local_infile = bool(config.pop("allow_local_infile", False))
|
|
93
|
+
config["local_infile"] = bool(config.get("local_infile", False) or allow_local_infile)
|
|
103
94
|
return config
|
|
104
95
|
|
|
105
96
|
|
|
106
97
|
def _normalize_connection_kwargs(connection_config: "Mapping[str, Any]") -> "dict[str, Any]":
|
|
107
98
|
"""Build aiomysql.connect-compatible kwargs from SQLSpec connection config."""
|
|
108
|
-
config = _normalize_local_infile(connection_config
|
|
99
|
+
config = _normalize_local_infile(connection_config)
|
|
109
100
|
|
|
110
101
|
for key in _POOL_ONLY_CONFIG_KEYS:
|
|
111
102
|
config.pop(key, None)
|
|
@@ -222,6 +213,9 @@ class AiomysqlConfig(AsyncDatabaseConfig[AiomysqlConnection, "AiomysqlPool", Aio
|
|
|
222
213
|
supports_native_arrow_import: ClassVar[bool] = True
|
|
223
214
|
supports_native_parquet_import: ClassVar[bool] = True
|
|
224
215
|
supports_native_row_streaming: ClassVar[bool] = True
|
|
216
|
+
type_coercion_capabilities: ClassVar[TypeCoercionCapabilities] = TypeCoercionCapabilities(
|
|
217
|
+
datetime_binding="native", timestamp_precision="microsecond", json_columns_decoded=False, uuid_binding="text"
|
|
218
|
+
)
|
|
225
219
|
_connection_context_class: "ClassVar[type[AiomysqlConnectionContext]]" = AiomysqlConnectionContext
|
|
226
220
|
_session_factory_class: "ClassVar[type[_AiomysqlSessionFactory]]" = _AiomysqlSessionFactory
|
|
227
221
|
_session_context_class: "ClassVar[type[AiomysqlSessionContext]]" = AiomysqlSessionContext
|
|
@@ -253,9 +247,7 @@ class AiomysqlConfig(AsyncDatabaseConfig[AiomysqlConnection, "AiomysqlPool", Aio
|
|
|
253
247
|
observability_config: Adapter-level observability overrides for lifecycle hooks and observers
|
|
254
248
|
**kwargs: Additional keyword arguments
|
|
255
249
|
"""
|
|
256
|
-
connection_config = _normalize_local_infile(
|
|
257
|
-
normalize_connection_config(connection_config), strip_consent_gate=False
|
|
258
|
-
)
|
|
250
|
+
connection_config = _normalize_local_infile(normalize_connection_config(connection_config))
|
|
259
251
|
|
|
260
252
|
connection_config.setdefault("host", "localhost")
|
|
261
253
|
connection_config.setdefault("port", 3306)
|
|
@@ -271,11 +263,9 @@ class AiomysqlConfig(AsyncDatabaseConfig[AiomysqlConnection, "AiomysqlPool", Aio
|
|
|
271
263
|
# Track initialized connections to ensure callback runs exactly once per physical connection
|
|
272
264
|
self._initialized_connections: WeakSet[Any] = WeakSet()
|
|
273
265
|
|
|
266
|
+
features_dict.setdefault("enable_local_infile_bulk_load", connection_config["local_infile"])
|
|
274
267
|
if features_dict.get("enable_local_infile_bulk_load") and not connection_config.get("local_infile"):
|
|
275
|
-
msg =
|
|
276
|
-
"enable_local_infile_bulk_load requires local_infile=True and "
|
|
277
|
-
"allow_local_infile=True in connection_config."
|
|
278
|
-
)
|
|
268
|
+
msg = "enable_local_infile_bulk_load requires local_infile=True or allow_local_infile=True in connection_config."
|
|
279
269
|
raise ImproperConfigurationError(msg)
|
|
280
270
|
|
|
281
271
|
super().__init__(
|
|
@@ -23,6 +23,7 @@ from sqlspec.adapters.aiosqlite.pool import (
|
|
|
23
23
|
)
|
|
24
24
|
from sqlspec.adapters.aiosqlite.type_converter import register_type_handlers
|
|
25
25
|
from sqlspec.config import AsyncDatabaseConfig, ExtensionConfigs
|
|
26
|
+
from sqlspec.core.capabilities import TypeCoercionCapabilities
|
|
26
27
|
from sqlspec.driver._async import AsyncPoolConnectionContext, AsyncPoolSessionFactory
|
|
27
28
|
from sqlspec.exceptions import ImproperConfigurationError
|
|
28
29
|
from sqlspec.utils.config_tools import normalize_connection_config
|
|
@@ -251,6 +252,9 @@ class AiosqliteConfig(AsyncDatabaseConfig["AiosqliteConnection", AiosqliteConnec
|
|
|
251
252
|
supports_native_parquet_export: "ClassVar[bool]" = True
|
|
252
253
|
supports_native_parquet_import: "ClassVar[bool]" = True
|
|
253
254
|
supports_native_row_streaming: "ClassVar[bool]" = True
|
|
255
|
+
type_coercion_capabilities: "ClassVar[TypeCoercionCapabilities]" = TypeCoercionCapabilities(
|
|
256
|
+
datetime_binding="iso_text", timestamp_precision="microsecond", json_columns_decoded=False, uuid_binding="text"
|
|
257
|
+
)
|
|
254
258
|
_connection_context_class: "ClassVar[type[AiosqliteConnectionContext]]" = AiosqliteConnectionContext
|
|
255
259
|
_session_factory_class: "ClassVar[type[_AiosqliteSessionFactory]]" = _AiosqliteSessionFactory
|
|
256
260
|
_session_context_class: "ClassVar[type[AiosqliteSessionContext]]" = AiosqliteSessionContext
|
|
@@ -68,6 +68,7 @@ _TIME_TO_ISO = time_iso_convert
|
|
|
68
68
|
_DECIMAL_TO_STRING = build_decimal_converter(mode="string")
|
|
69
69
|
|
|
70
70
|
SQLITE_CONSTRAINT_UNIQUE_CODE = 2067
|
|
71
|
+
SQLITE_CONSTRAINT_PRIMARYKEY_CODE = 1555
|
|
71
72
|
SQLITE_CONSTRAINT_FOREIGNKEY_CODE = 787
|
|
72
73
|
SQLITE_CONSTRAINT_NOTNULL_CODE = 1811
|
|
73
74
|
SQLITE_CONSTRAINT_CHECK_CODE = 531
|
|
@@ -348,6 +349,12 @@ def create_mapped_exception(error: BaseException, *, logger: Any | None = None)
|
|
|
348
349
|
error_name = None
|
|
349
350
|
error_msg = str(error).lower()
|
|
350
351
|
|
|
352
|
+
if error_code in (SQLITE_CONSTRAINT_UNIQUE_CODE, SQLITE_CONSTRAINT_PRIMARYKEY_CODE) or error_name in (
|
|
353
|
+
"SQLITE_CONSTRAINT_UNIQUE",
|
|
354
|
+
"SQLITE_CONSTRAINT_PRIMARYKEY",
|
|
355
|
+
):
|
|
356
|
+
return _create_aiosqlite_error(error, error_code, UniqueViolationError, "unique constraint violation")
|
|
357
|
+
|
|
351
358
|
# Check for busy/locked conditions first (deadlock-like scenarios in SQLite)
|
|
352
359
|
# SQLITE_BUSY means another process has the database locked
|
|
353
360
|
# SQLITE_LOCKED means another connection has the table/rows locked
|
|
@@ -385,8 +392,6 @@ def create_mapped_exception(error: BaseException, *, logger: Any | None = None)
|
|
|
385
392
|
return _create_aiosqlite_error(error, None, SQLParsingError, "SQL syntax error")
|
|
386
393
|
return _create_aiosqlite_error(error, None, SQLSpecError, "database error")
|
|
387
394
|
|
|
388
|
-
if error_code == SQLITE_CONSTRAINT_UNIQUE_CODE or error_name == "SQLITE_CONSTRAINT_UNIQUE":
|
|
389
|
-
return _create_aiosqlite_error(error, error_code, UniqueViolationError, "unique constraint violation")
|
|
390
395
|
if error_code == SQLITE_CONSTRAINT_FOREIGNKEY_CODE or error_name == "SQLITE_CONSTRAINT_FOREIGNKEY":
|
|
391
396
|
return _create_aiosqlite_error(error, error_code, ForeignKeyViolationError, "foreign key constraint violation")
|
|
392
397
|
if error_code == SQLITE_CONSTRAINT_NOTNULL_CODE or error_name == "SQLITE_CONSTRAINT_NOTNULL":
|
|
@@ -14,6 +14,7 @@ from sqlspec.adapters.arrow_odbc.core import (
|
|
|
14
14
|
)
|
|
15
15
|
from sqlspec.adapters.arrow_odbc.driver import ArrowOdbcDriver
|
|
16
16
|
from sqlspec.config import ExtensionConfigs, NoPoolSyncConfig
|
|
17
|
+
from sqlspec.core.capabilities import TypeCoercionCapabilities
|
|
17
18
|
from sqlspec.driver._sync import SyncPoolConnectionContext, SyncPoolSessionFactory
|
|
18
19
|
from sqlspec.exceptions import ImproperConfigurationError
|
|
19
20
|
from sqlspec.extensions.events import EventRuntimeHints
|
|
@@ -137,6 +138,10 @@ class ArrowOdbcConfig(NoPoolSyncConfig[ArrowOdbcConnection, ArrowOdbcDriver]):
|
|
|
137
138
|
supports_native_row_streaming: "ClassVar[bool]" = True
|
|
138
139
|
supports_native_parquet_export: "ClassVar[bool]" = False
|
|
139
140
|
supports_native_parquet_import: "ClassVar[bool]" = False
|
|
141
|
+
supports_reliable_rowcount: "ClassVar[bool]" = False
|
|
142
|
+
type_coercion_capabilities: "ClassVar[TypeCoercionCapabilities]" = TypeCoercionCapabilities(
|
|
143
|
+
datetime_binding="iso_text", timestamp_precision="microsecond", json_columns_decoded=False, uuid_binding="text"
|
|
144
|
+
)
|
|
140
145
|
_connection_context_class: "ClassVar[type[ArrowOdbcConnectionContext]]" = ArrowOdbcConnectionContext
|
|
141
146
|
_session_factory_class: "ClassVar[type[_ArrowOdbcSessionConnectionHandler]]" = _ArrowOdbcSessionConnectionHandler
|
|
142
147
|
_session_context_class: "ClassVar[type[ArrowOdbcSessionContext]]" = ArrowOdbcSessionContext
|