sqlspec 0.26.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 +55 -25
- sqlspec/_typing.py +62 -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 +62 -12
- sqlspec/adapters/adbc/data_dictionary.py +52 -2
- sqlspec/adapters/adbc/driver.py +144 -45
- sqlspec/adapters/adbc/litestar/__init__.py +5 -0
- sqlspec/adapters/adbc/litestar/store.py +504 -0
- sqlspec/adapters/adbc/type_converter.py +44 -50
- 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 +86 -16
- sqlspec/adapters/aiosqlite/data_dictionary.py +34 -2
- sqlspec/adapters/aiosqlite/driver.py +127 -38
- 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 +1 -1
- sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
- sqlspec/adapters/asyncmy/adk/store.py +493 -0
- sqlspec/adapters/asyncmy/config.py +59 -17
- sqlspec/adapters/asyncmy/data_dictionary.py +41 -2
- sqlspec/adapters/asyncmy/driver.py +293 -62
- 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 +57 -36
- sqlspec/adapters/asyncpg/data_dictionary.py +41 -2
- sqlspec/adapters/asyncpg/driver.py +153 -23
- 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 +25 -11
- sqlspec/adapters/bigquery/data_dictionary.py +42 -2
- sqlspec/adapters/bigquery/driver.py +352 -144
- sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
- sqlspec/adapters/bigquery/litestar/store.py +327 -0
- sqlspec/adapters/bigquery/type_converter.py +55 -23
- sqlspec/adapters/duckdb/_types.py +2 -2
- sqlspec/adapters/duckdb/adk/__init__.py +14 -0
- sqlspec/adapters/duckdb/adk/store.py +553 -0
- sqlspec/adapters/duckdb/config.py +79 -21
- sqlspec/adapters/duckdb/data_dictionary.py +41 -2
- sqlspec/adapters/duckdb/driver.py +138 -43
- sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
- sqlspec/adapters/duckdb/litestar/store.py +332 -0
- sqlspec/adapters/duckdb/pool.py +5 -5
- sqlspec/adapters/duckdb/type_converter.py +51 -21
- 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 +120 -36
- sqlspec/adapters/oracledb/data_dictionary.py +87 -20
- sqlspec/adapters/oracledb/driver.py +292 -84
- sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
- sqlspec/adapters/oracledb/litestar/store.py +767 -0
- sqlspec/adapters/oracledb/migrations.py +316 -25
- sqlspec/adapters/oracledb/type_converter.py +91 -16
- 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 +45 -19
- sqlspec/adapters/psqlpy/data_dictionary.py +41 -2
- sqlspec/adapters/psqlpy/driver.py +101 -31
- sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
- sqlspec/adapters/psqlpy/litestar/store.py +272 -0
- sqlspec/adapters/psqlpy/type_converter.py +40 -11
- 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 +65 -37
- sqlspec/adapters/psycopg/data_dictionary.py +77 -3
- sqlspec/adapters/psycopg/driver.py +200 -78
- 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 +85 -16
- sqlspec/adapters/sqlite/data_dictionary.py +34 -2
- sqlspec/adapters/sqlite/driver.py +120 -52
- sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/sqlite/litestar/store.py +318 -0
- sqlspec/adapters/sqlite/pool.py +5 -5
- sqlspec/base.py +45 -26
- sqlspec/builder/__init__.py +73 -4
- sqlspec/builder/_base.py +91 -58
- sqlspec/builder/_column.py +5 -5
- sqlspec/builder/_ddl.py +98 -89
- sqlspec/builder/_delete.py +5 -4
- sqlspec/builder/_dml.py +388 -0
- sqlspec/{_sql.py → builder/_factory.py} +41 -44
- sqlspec/builder/_insert.py +5 -82
- sqlspec/builder/{mixins/_join_operations.py → _join.py} +145 -143
- sqlspec/builder/_merge.py +446 -11
- sqlspec/builder/_parsing_utils.py +9 -11
- sqlspec/builder/_select.py +1313 -25
- sqlspec/builder/_update.py +11 -42
- sqlspec/cli.py +76 -69
- sqlspec/config.py +231 -60
- sqlspec/core/__init__.py +5 -4
- sqlspec/core/cache.py +18 -18
- sqlspec/core/compiler.py +6 -8
- sqlspec/core/filters.py +37 -37
- sqlspec/core/hashing.py +9 -9
- sqlspec/core/parameters.py +76 -45
- sqlspec/core/result.py +102 -46
- sqlspec/core/splitter.py +16 -17
- sqlspec/core/statement.py +32 -31
- sqlspec/core/type_conversion.py +3 -2
- sqlspec/driver/__init__.py +1 -3
- sqlspec/driver/_async.py +95 -161
- sqlspec/driver/_common.py +133 -80
- sqlspec/driver/_sync.py +95 -162
- sqlspec/driver/mixins/_result_tools.py +20 -236
- sqlspec/driver/mixins/_sql_translator.py +4 -4
- sqlspec/exceptions.py +70 -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/base.py +200 -76
- sqlspec/migrations/commands.py +591 -62
- sqlspec/migrations/context.py +6 -9
- sqlspec/migrations/fix.py +199 -0
- sqlspec/migrations/loaders.py +47 -19
- sqlspec/migrations/runner.py +241 -75
- 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 +14 -84
- sqlspec/utils/config_resolver.py +6 -6
- 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 +3 -3
- 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.26.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 -253
- sqlspec/builder/mixins/_delete_operations.py +0 -50
- sqlspec/builder/mixins/_insert_operations.py +0 -282
- sqlspec/builder/mixins/_merge_operations.py +0 -698
- sqlspec/builder/mixins/_order_limit_operations.py +0 -145
- sqlspec/builder/mixins/_pivot_operations.py +0 -157
- sqlspec/builder/mixins/_select_operations.py +0 -930
- sqlspec/builder/mixins/_update_operations.py +0 -199
- sqlspec/builder/mixins/_where_clause.py +0 -1298
- sqlspec-0.26.0.dist-info/RECORD +0 -157
- sqlspec-0.26.0.dist-info/licenses/NOTICE +0 -29
- {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/licenses/LICENSE +0 -0
sqlspec/driver/_common.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
4
|
from contextlib import suppress
|
|
5
|
-
from typing import TYPE_CHECKING, Any, Final, NamedTuple, Optional, TypeVar,
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Final, NamedTuple, NoReturn, Optional, TypeVar, cast
|
|
6
6
|
|
|
7
7
|
from mypy_extensions import trait
|
|
8
8
|
from sqlglot import exp
|
|
@@ -11,7 +11,7 @@ from sqlspec.builder import QueryBuilder
|
|
|
11
11
|
from sqlspec.core import SQL, ParameterStyle, SQLResult, Statement, StatementConfig, TypedParameter
|
|
12
12
|
from sqlspec.core.cache import CachedStatement, get_cache, get_cache_config
|
|
13
13
|
from sqlspec.core.splitter import split_sql_script
|
|
14
|
-
from sqlspec.exceptions import ImproperConfigurationError
|
|
14
|
+
from sqlspec.exceptions import ImproperConfigurationError, NotFoundError
|
|
15
15
|
from sqlspec.utils.logging import get_logger
|
|
16
16
|
|
|
17
17
|
if TYPE_CHECKING:
|
|
@@ -31,6 +31,8 @@ __all__ = (
|
|
|
31
31
|
"ExecutionResult",
|
|
32
32
|
"ScriptExecutionResult",
|
|
33
33
|
"VersionInfo",
|
|
34
|
+
"handle_single_row_error",
|
|
35
|
+
"make_cache_key_hashable",
|
|
34
36
|
)
|
|
35
37
|
|
|
36
38
|
|
|
@@ -41,6 +43,69 @@ VERSION_GROUPS_MIN_FOR_MINOR = 1
|
|
|
41
43
|
VERSION_GROUPS_MIN_FOR_PATCH = 2
|
|
42
44
|
|
|
43
45
|
|
|
46
|
+
def make_cache_key_hashable(obj: Any) -> Any:
|
|
47
|
+
"""Recursively convert unhashable types to hashable ones for cache keys.
|
|
48
|
+
|
|
49
|
+
For array-like objects (NumPy arrays, Python arrays, etc.), we use structural
|
|
50
|
+
info (dtype + shape or typecode + length) rather than content for cache keys.
|
|
51
|
+
This ensures high cache hit rates for parameterized queries with different
|
|
52
|
+
vector values while avoiding expensive content hashing.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
obj: Object to make hashable.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
A hashable representation of the object. Collections become tuples,
|
|
59
|
+
arrays become structural tuples like ("ndarray", dtype, shape).
|
|
60
|
+
|
|
61
|
+
Examples:
|
|
62
|
+
>>> make_cache_key_hashable([1, 2, 3])
|
|
63
|
+
(1, 2, 3)
|
|
64
|
+
>>> make_cache_key_hashable({"a": 1, "b": 2})
|
|
65
|
+
(('a', 1), ('b', 2))
|
|
66
|
+
"""
|
|
67
|
+
if isinstance(obj, (list, tuple)):
|
|
68
|
+
return tuple(make_cache_key_hashable(item) for item in obj)
|
|
69
|
+
if isinstance(obj, dict):
|
|
70
|
+
return tuple(sorted((k, make_cache_key_hashable(v)) for k, v in obj.items()))
|
|
71
|
+
if isinstance(obj, set):
|
|
72
|
+
return frozenset(make_cache_key_hashable(item) for item in obj)
|
|
73
|
+
|
|
74
|
+
typecode = getattr(obj, "typecode", None)
|
|
75
|
+
if typecode is not None:
|
|
76
|
+
try:
|
|
77
|
+
length = len(obj)
|
|
78
|
+
except (AttributeError, TypeError):
|
|
79
|
+
return ("array", typecode)
|
|
80
|
+
else:
|
|
81
|
+
return ("array", typecode, length)
|
|
82
|
+
|
|
83
|
+
if hasattr(obj, "__array__"):
|
|
84
|
+
try:
|
|
85
|
+
dtype_str = getattr(obj.dtype, "str", str(type(obj)))
|
|
86
|
+
shape = tuple(int(s) for s in obj.shape)
|
|
87
|
+
except (AttributeError, TypeError):
|
|
88
|
+
try:
|
|
89
|
+
length = len(obj)
|
|
90
|
+
except (AttributeError, TypeError):
|
|
91
|
+
return ("array_like", type(obj).__name__)
|
|
92
|
+
else:
|
|
93
|
+
return ("array_like", type(obj).__name__, length)
|
|
94
|
+
else:
|
|
95
|
+
return ("ndarray", dtype_str, shape)
|
|
96
|
+
return obj
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def handle_single_row_error(error: ValueError) -> "NoReturn":
|
|
100
|
+
"""Normalize single-row selection errors to SQLSpec exceptions."""
|
|
101
|
+
|
|
102
|
+
message = str(error)
|
|
103
|
+
if message.startswith("No result found"):
|
|
104
|
+
msg = "No rows found"
|
|
105
|
+
raise NotFoundError(msg) from error
|
|
106
|
+
raise error
|
|
107
|
+
|
|
108
|
+
|
|
44
109
|
class VersionInfo:
|
|
45
110
|
"""Database version information."""
|
|
46
111
|
|
|
@@ -100,7 +165,7 @@ class VersionInfo:
|
|
|
100
165
|
class DataDictionaryMixin:
|
|
101
166
|
"""Mixin providing common data dictionary functionality."""
|
|
102
167
|
|
|
103
|
-
def parse_version_string(self, version_str: str) -> "
|
|
168
|
+
def parse_version_string(self, version_str: str) -> "VersionInfo | None":
|
|
104
169
|
"""Parse version string into VersionInfo.
|
|
105
170
|
|
|
106
171
|
Args:
|
|
@@ -128,7 +193,7 @@ class DataDictionaryMixin:
|
|
|
128
193
|
|
|
129
194
|
return None
|
|
130
195
|
|
|
131
|
-
def detect_version_with_queries(self, driver: Any, queries: "list[str]") -> "
|
|
196
|
+
def detect_version_with_queries(self, driver: Any, queries: "list[str]") -> "VersionInfo | None":
|
|
132
197
|
"""Try multiple version queries to detect database version.
|
|
133
198
|
|
|
134
199
|
Args:
|
|
@@ -184,7 +249,7 @@ class ScriptExecutionResult(NamedTuple):
|
|
|
184
249
|
"""Result from script execution with statement count information."""
|
|
185
250
|
|
|
186
251
|
cursor_result: Any
|
|
187
|
-
rowcount_override:
|
|
252
|
+
rowcount_override: int | None
|
|
188
253
|
special_data: Any
|
|
189
254
|
statement_count: int
|
|
190
255
|
successful_statements: int
|
|
@@ -194,23 +259,23 @@ class ExecutionResult(NamedTuple):
|
|
|
194
259
|
"""Execution result containing all data needed for SQLResult building."""
|
|
195
260
|
|
|
196
261
|
cursor_result: Any
|
|
197
|
-
rowcount_override:
|
|
262
|
+
rowcount_override: int | None
|
|
198
263
|
special_data: Any
|
|
199
264
|
selected_data: Optional["list[dict[str, Any]]"]
|
|
200
265
|
column_names: Optional["list[str]"]
|
|
201
|
-
data_row_count:
|
|
202
|
-
statement_count:
|
|
203
|
-
successful_statements:
|
|
266
|
+
data_row_count: int | None
|
|
267
|
+
statement_count: int | None
|
|
268
|
+
successful_statements: int | None
|
|
204
269
|
is_script_result: bool
|
|
205
270
|
is_select_result: bool
|
|
206
271
|
is_many_result: bool
|
|
207
|
-
last_inserted_id:
|
|
272
|
+
last_inserted_id: int | str | None = None
|
|
208
273
|
|
|
209
274
|
|
|
210
275
|
EXEC_CURSOR_RESULT: Final[int] = 0
|
|
211
276
|
EXEC_ROWCOUNT_OVERRIDE: Final[int] = 1
|
|
212
277
|
EXEC_SPECIAL_DATA: Final[int] = 2
|
|
213
|
-
DEFAULT_EXECUTION_RESULT: Final[tuple[Any,
|
|
278
|
+
DEFAULT_EXECUTION_RESULT: Final[tuple[Any, int | None, Any]] = (None, None, None)
|
|
214
279
|
|
|
215
280
|
|
|
216
281
|
@trait
|
|
@@ -223,7 +288,7 @@ class CommonDriverAttributesMixin:
|
|
|
223
288
|
driver_features: "dict[str, Any]"
|
|
224
289
|
|
|
225
290
|
def __init__(
|
|
226
|
-
self, connection: "Any", statement_config: "StatementConfig", driver_features: "
|
|
291
|
+
self, connection: "Any", statement_config: "StatementConfig", driver_features: "dict[str, Any] | None" = None
|
|
227
292
|
) -> None:
|
|
228
293
|
"""Initialize driver adapter with connection and configuration.
|
|
229
294
|
|
|
@@ -240,17 +305,17 @@ class CommonDriverAttributesMixin:
|
|
|
240
305
|
self,
|
|
241
306
|
cursor_result: Any,
|
|
242
307
|
*,
|
|
243
|
-
rowcount_override:
|
|
308
|
+
rowcount_override: int | None = None,
|
|
244
309
|
special_data: Any = None,
|
|
245
310
|
selected_data: Optional["list[dict[str, Any]]"] = None,
|
|
246
311
|
column_names: Optional["list[str]"] = None,
|
|
247
|
-
data_row_count:
|
|
248
|
-
statement_count:
|
|
249
|
-
successful_statements:
|
|
312
|
+
data_row_count: int | None = None,
|
|
313
|
+
statement_count: int | None = None,
|
|
314
|
+
successful_statements: int | None = None,
|
|
250
315
|
is_script_result: bool = False,
|
|
251
316
|
is_select_result: bool = False,
|
|
252
317
|
is_many_result: bool = False,
|
|
253
|
-
last_inserted_id:
|
|
318
|
+
last_inserted_id: int | str | None = None,
|
|
254
319
|
) -> ExecutionResult:
|
|
255
320
|
"""Create ExecutionResult with all necessary data for any operation type.
|
|
256
321
|
|
|
@@ -328,11 +393,11 @@ class CommonDriverAttributesMixin:
|
|
|
328
393
|
|
|
329
394
|
def prepare_statement(
|
|
330
395
|
self,
|
|
331
|
-
statement: "
|
|
332
|
-
parameters: "tuple[
|
|
396
|
+
statement: "Statement | QueryBuilder",
|
|
397
|
+
parameters: "tuple[StatementParameters | StatementFilter, ...]" = (),
|
|
333
398
|
*,
|
|
334
399
|
statement_config: "StatementConfig",
|
|
335
|
-
kwargs: "
|
|
400
|
+
kwargs: "dict[str, Any] | None" = None,
|
|
336
401
|
) -> "SQL":
|
|
337
402
|
"""Build SQL statement from various input types.
|
|
338
403
|
|
|
@@ -421,7 +486,7 @@ class CommonDriverAttributesMixin:
|
|
|
421
486
|
parameters: Any,
|
|
422
487
|
statement_config: "StatementConfig",
|
|
423
488
|
is_many: bool = False,
|
|
424
|
-
prepared_statement:
|
|
489
|
+
prepared_statement: Any | None = None, # pyright: ignore[reportUnusedParameter]
|
|
425
490
|
) -> Any:
|
|
426
491
|
"""Prepare parameters for database driver consumption.
|
|
427
492
|
|
|
@@ -449,6 +514,23 @@ class CommonDriverAttributesMixin:
|
|
|
449
514
|
return [self._format_parameter_set_for_many(parameters, statement_config)]
|
|
450
515
|
return self._format_parameter_set(parameters, statement_config)
|
|
451
516
|
|
|
517
|
+
def _apply_coercion(self, value: Any, statement_config: "StatementConfig") -> Any:
|
|
518
|
+
"""Apply type coercion to a single value.
|
|
519
|
+
|
|
520
|
+
Args:
|
|
521
|
+
value: Value to coerce (may be TypedParameter or raw value)
|
|
522
|
+
statement_config: Statement configuration for type coercion map
|
|
523
|
+
|
|
524
|
+
Returns:
|
|
525
|
+
Coerced value with TypedParameter unwrapped
|
|
526
|
+
"""
|
|
527
|
+
unwrapped_value = value.value if isinstance(value, TypedParameter) else value
|
|
528
|
+
if statement_config.parameter_config.type_coercion_map:
|
|
529
|
+
for type_check, converter in statement_config.parameter_config.type_coercion_map.items():
|
|
530
|
+
if isinstance(unwrapped_value, type_check):
|
|
531
|
+
return converter(unwrapped_value)
|
|
532
|
+
return unwrapped_value
|
|
533
|
+
|
|
452
534
|
def _format_parameter_set_for_many(self, parameters: Any, statement_config: "StatementConfig") -> Any:
|
|
453
535
|
"""Prepare a single parameter set for execute_many operations.
|
|
454
536
|
|
|
@@ -465,27 +547,14 @@ class CommonDriverAttributesMixin:
|
|
|
465
547
|
if not parameters:
|
|
466
548
|
return []
|
|
467
549
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
unwrapped_value = value.value if isinstance(value, TypedParameter) else value
|
|
471
|
-
|
|
472
|
-
if statement_config.parameter_config.type_coercion_map:
|
|
473
|
-
for type_check, converter in statement_config.parameter_config.type_coercion_map.items():
|
|
474
|
-
if type_check in {list, tuple} and isinstance(unwrapped_value, (list, tuple)):
|
|
475
|
-
continue
|
|
476
|
-
if isinstance(unwrapped_value, type_check):
|
|
477
|
-
return converter(unwrapped_value)
|
|
478
|
-
|
|
479
|
-
return unwrapped_value
|
|
550
|
+
if not isinstance(parameters, (dict, list, tuple)):
|
|
551
|
+
return self._apply_coercion(parameters, statement_config)
|
|
480
552
|
|
|
481
553
|
if isinstance(parameters, dict):
|
|
482
|
-
return {k:
|
|
483
|
-
|
|
484
|
-
if isinstance(parameters, (list, tuple)):
|
|
485
|
-
coerced_params = [apply_type_coercion(p) for p in parameters]
|
|
486
|
-
return tuple(coerced_params) if isinstance(parameters, tuple) else coerced_params
|
|
554
|
+
return {k: self._apply_coercion(v, statement_config) for k, v in parameters.items()}
|
|
487
555
|
|
|
488
|
-
|
|
556
|
+
coerced_params = [self._apply_coercion(p, statement_config) for p in parameters]
|
|
557
|
+
return tuple(coerced_params) if isinstance(parameters, tuple) else coerced_params
|
|
489
558
|
|
|
490
559
|
def _format_parameter_set(self, parameters: Any, statement_config: "StatementConfig") -> Any:
|
|
491
560
|
"""Prepare a single parameter set for database driver consumption.
|
|
@@ -500,50 +569,34 @@ class CommonDriverAttributesMixin:
|
|
|
500
569
|
if not parameters:
|
|
501
570
|
return []
|
|
502
571
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
unwrapped_value = value.value if isinstance(value, TypedParameter) else value
|
|
506
|
-
|
|
507
|
-
if statement_config.parameter_config.type_coercion_map:
|
|
508
|
-
for type_check, converter in statement_config.parameter_config.type_coercion_map.items():
|
|
509
|
-
if isinstance(unwrapped_value, type_check):
|
|
510
|
-
return converter(unwrapped_value)
|
|
511
|
-
|
|
512
|
-
return unwrapped_value
|
|
572
|
+
if not isinstance(parameters, (dict, list, tuple)):
|
|
573
|
+
return [self._apply_coercion(parameters, statement_config)]
|
|
513
574
|
|
|
514
575
|
if isinstance(parameters, dict):
|
|
515
|
-
if not parameters:
|
|
516
|
-
return []
|
|
517
576
|
if statement_config.parameter_config.supported_execution_parameter_styles and (
|
|
518
577
|
ParameterStyle.NAMED_PYFORMAT in statement_config.parameter_config.supported_execution_parameter_styles
|
|
519
578
|
or ParameterStyle.NAMED_COLON in statement_config.parameter_config.supported_execution_parameter_styles
|
|
520
579
|
):
|
|
521
|
-
return {k:
|
|
580
|
+
return {k: self._apply_coercion(v, statement_config) for k, v in parameters.items()}
|
|
522
581
|
if statement_config.parameter_config.default_parameter_style in {
|
|
523
582
|
ParameterStyle.NUMERIC,
|
|
524
583
|
ParameterStyle.QMARK,
|
|
525
584
|
ParameterStyle.POSITIONAL_PYFORMAT,
|
|
526
585
|
}:
|
|
527
|
-
ordered_parameters = []
|
|
528
586
|
sorted_items = sorted(
|
|
529
587
|
parameters.items(),
|
|
530
588
|
key=lambda item: int(item[0])
|
|
531
589
|
if item[0].isdigit()
|
|
532
590
|
else (int(item[0][6:]) if item[0].startswith("param_") and item[0][6:].isdigit() else float("inf")),
|
|
533
591
|
)
|
|
534
|
-
for _, value in sorted_items
|
|
535
|
-
ordered_parameters.append(apply_type_coercion(value))
|
|
536
|
-
return ordered_parameters
|
|
537
|
-
|
|
538
|
-
return {k: apply_type_coercion(v) for k, v in parameters.items()}
|
|
592
|
+
return [self._apply_coercion(value, statement_config) for _, value in sorted_items]
|
|
539
593
|
|
|
540
|
-
|
|
541
|
-
coerced_params = [apply_type_coercion(p) for p in parameters]
|
|
542
|
-
if statement_config.parameter_config.preserve_parameter_format and isinstance(parameters, tuple):
|
|
543
|
-
return tuple(coerced_params)
|
|
544
|
-
return coerced_params
|
|
594
|
+
return {k: self._apply_coercion(v, statement_config) for k, v in parameters.items()}
|
|
545
595
|
|
|
546
|
-
|
|
596
|
+
coerced_params = [self._apply_coercion(p, statement_config) for p in parameters]
|
|
597
|
+
if statement_config.parameter_config.preserve_parameter_format and isinstance(parameters, tuple):
|
|
598
|
+
return tuple(coerced_params)
|
|
599
|
+
return coerced_params
|
|
547
600
|
|
|
548
601
|
def _get_compiled_sql(
|
|
549
602
|
self, statement: "SQL", statement_config: "StatementConfig", flatten_single_parameters: bool = False
|
|
@@ -625,26 +678,26 @@ class CommonDriverAttributesMixin:
|
|
|
625
678
|
)
|
|
626
679
|
|
|
627
680
|
params = statement.parameters
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
681
|
+
|
|
682
|
+
if params is None or (isinstance(params, (list, tuple, dict)) and not params):
|
|
683
|
+
return f"compiled:{hash(statement.sql)}:{context_hash}"
|
|
684
|
+
|
|
685
|
+
if isinstance(params, tuple) and all(isinstance(p, (int, str, bytes, bool, type(None))) for p in params):
|
|
686
|
+
try:
|
|
687
|
+
return (
|
|
688
|
+
f"compiled:{hash((statement.sql, params, statement.is_many, statement.is_script))}:{context_hash}"
|
|
689
|
+
)
|
|
690
|
+
except TypeError:
|
|
691
|
+
pass
|
|
639
692
|
|
|
640
693
|
try:
|
|
641
694
|
if isinstance(params, dict):
|
|
642
|
-
params_key =
|
|
695
|
+
params_key = make_cache_key_hashable(params)
|
|
643
696
|
elif isinstance(params, (list, tuple)) and params:
|
|
644
697
|
if isinstance(params[0], dict):
|
|
645
|
-
params_key = tuple(
|
|
698
|
+
params_key = tuple(make_cache_key_hashable(d) for d in params)
|
|
646
699
|
else:
|
|
647
|
-
params_key =
|
|
700
|
+
params_key = make_cache_key_hashable(params)
|
|
648
701
|
elif isinstance(params, (list, tuple)):
|
|
649
702
|
params_key = ()
|
|
650
703
|
else:
|
|
@@ -655,7 +708,7 @@ class CommonDriverAttributesMixin:
|
|
|
655
708
|
base_hash = hash((statement.sql, params_key, statement.is_many, statement.is_script))
|
|
656
709
|
return f"compiled:{base_hash}:{context_hash}"
|
|
657
710
|
|
|
658
|
-
def _get_dominant_parameter_style(self, parameters: "list[Any]") -> "
|
|
711
|
+
def _get_dominant_parameter_style(self, parameters: "list[Any]") -> "ParameterStyle | None":
|
|
659
712
|
"""Determine the dominant parameter style from parameter info list.
|
|
660
713
|
|
|
661
714
|
Args:
|
|
@@ -688,7 +741,7 @@ class CommonDriverAttributesMixin:
|
|
|
688
741
|
def find_filter(
|
|
689
742
|
filter_type: "type[FilterTypeT]",
|
|
690
743
|
filters: "Sequence[StatementFilter | StatementParameters] | Sequence[StatementFilter]",
|
|
691
|
-
) -> "
|
|
744
|
+
) -> "FilterTypeT | None":
|
|
692
745
|
"""Get the filter specified by filter type from the filters.
|
|
693
746
|
|
|
694
747
|
Args:
|