sqlspec 0.26.0__py3-none-any.whl → 0.28.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 +155 -52
- sqlspec/adapters/adbc/_types.py +1 -1
- sqlspec/adapters/adbc/adk/__init__.py +5 -0
- sqlspec/adapters/adbc/adk/store.py +880 -0
- sqlspec/adapters/adbc/config.py +62 -12
- sqlspec/adapters/adbc/data_dictionary.py +74 -2
- sqlspec/adapters/adbc/driver.py +226 -58
- 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 +536 -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 +503 -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 +460 -0
- sqlspec/adapters/asyncpg/config.py +57 -36
- sqlspec/adapters/asyncpg/data_dictionary.py +48 -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 +585 -0
- sqlspec/adapters/bigquery/config.py +36 -11
- sqlspec/adapters/bigquery/data_dictionary.py +42 -2
- sqlspec/adapters/bigquery/driver.py +489 -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 +563 -0
- sqlspec/adapters/duckdb/config.py +79 -21
- sqlspec/adapters/duckdb/data_dictionary.py +41 -2
- sqlspec/adapters/duckdb/driver.py +225 -44
- 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 +1628 -0
- sqlspec/adapters/oracledb/config.py +120 -36
- sqlspec/adapters/oracledb/data_dictionary.py +87 -20
- sqlspec/adapters/oracledb/driver.py +475 -86
- sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
- sqlspec/adapters/oracledb/litestar/store.py +765 -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 +483 -0
- sqlspec/adapters/psqlpy/config.py +45 -19
- sqlspec/adapters/psqlpy/data_dictionary.py +48 -2
- sqlspec/adapters/psqlpy/driver.py +108 -41
- 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 +962 -0
- sqlspec/adapters/psycopg/config.py +65 -37
- sqlspec/adapters/psycopg/data_dictionary.py +91 -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 +582 -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 +331 -62
- sqlspec/core/__init__.py +5 -4
- sqlspec/core/cache.py +18 -18
- sqlspec/core/compiler.py +6 -8
- sqlspec/core/filters.py +55 -47
- sqlspec/core/hashing.py +9 -9
- sqlspec/core/parameters.py +76 -45
- sqlspec/core/result.py +234 -47
- 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 +183 -160
- sqlspec/driver/_common.py +197 -109
- sqlspec/driver/_sync.py +189 -161
- 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 +69 -61
- sqlspec/extensions/fastapi/__init__.py +21 -0
- sqlspec/extensions/fastapi/extension.py +331 -0
- sqlspec/extensions/fastapi/providers.py +543 -0
- sqlspec/extensions/flask/__init__.py +36 -0
- sqlspec/extensions/flask/_state.py +71 -0
- sqlspec/extensions/flask/_utils.py +40 -0
- sqlspec/extensions/flask/extension.py +389 -0
- sqlspec/extensions/litestar/__init__.py +21 -4
- sqlspec/extensions/litestar/cli.py +54 -10
- sqlspec/extensions/litestar/config.py +56 -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 +349 -224
- sqlspec/extensions/litestar/providers.py +25 -25
- sqlspec/extensions/litestar/store.py +265 -0
- sqlspec/extensions/starlette/__init__.py +10 -0
- sqlspec/extensions/starlette/_state.py +25 -0
- sqlspec/extensions/starlette/_utils.py +52 -0
- sqlspec/extensions/starlette/extension.py +254 -0
- sqlspec/extensions/starlette/middleware.py +154 -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 +106 -36
- sqlspec/storage/_utils.py +85 -0
- sqlspec/storage/backends/fsspec.py +133 -107
- sqlspec/storage/backends/local.py +78 -51
- sqlspec/storage/backends/obstore.py +276 -168
- sqlspec/storage/registry.py +75 -39
- sqlspec/typing.py +30 -84
- sqlspec/utils/__init__.py +25 -4
- sqlspec/utils/arrow_helpers.py +81 -0
- 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 +205 -5
- sqlspec/utils/portal.py +311 -0
- sqlspec/utils/schema.py +288 -0
- sqlspec/utils/serializers.py +113 -4
- sqlspec/utils/sync_tools.py +36 -22
- sqlspec/utils/text.py +1 -2
- sqlspec/utils/type_guards.py +136 -20
- sqlspec/utils/version.py +433 -0
- {sqlspec-0.26.0.dist-info → sqlspec-0.28.0.dist-info}/METADATA +41 -22
- sqlspec-0.28.0.dist-info/RECORD +221 -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.28.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.26.0.dist-info → sqlspec-0.28.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.26.0.dist-info → sqlspec-0.28.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,8 +11,9 @@ 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
|
+
from sqlspec.utils.type_guards import is_statement_filter
|
|
16
17
|
|
|
17
18
|
if TYPE_CHECKING:
|
|
18
19
|
from collections.abc import Sequence
|
|
@@ -31,6 +32,8 @@ __all__ = (
|
|
|
31
32
|
"ExecutionResult",
|
|
32
33
|
"ScriptExecutionResult",
|
|
33
34
|
"VersionInfo",
|
|
35
|
+
"handle_single_row_error",
|
|
36
|
+
"make_cache_key_hashable",
|
|
34
37
|
)
|
|
35
38
|
|
|
36
39
|
|
|
@@ -41,6 +44,69 @@ VERSION_GROUPS_MIN_FOR_MINOR = 1
|
|
|
41
44
|
VERSION_GROUPS_MIN_FOR_PATCH = 2
|
|
42
45
|
|
|
43
46
|
|
|
47
|
+
def make_cache_key_hashable(obj: Any) -> Any:
|
|
48
|
+
"""Recursively convert unhashable types to hashable ones for cache keys.
|
|
49
|
+
|
|
50
|
+
For array-like objects (NumPy arrays, Python arrays, etc.), we use structural
|
|
51
|
+
info (dtype + shape or typecode + length) rather than content for cache keys.
|
|
52
|
+
This ensures high cache hit rates for parameterized queries with different
|
|
53
|
+
vector values while avoiding expensive content hashing.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
obj: Object to make hashable.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
A hashable representation of the object. Collections become tuples,
|
|
60
|
+
arrays become structural tuples like ("ndarray", dtype, shape).
|
|
61
|
+
|
|
62
|
+
Examples:
|
|
63
|
+
>>> make_cache_key_hashable([1, 2, 3])
|
|
64
|
+
(1, 2, 3)
|
|
65
|
+
>>> make_cache_key_hashable({"a": 1, "b": 2})
|
|
66
|
+
(('a', 1), ('b', 2))
|
|
67
|
+
"""
|
|
68
|
+
if isinstance(obj, (list, tuple)):
|
|
69
|
+
return tuple(make_cache_key_hashable(item) for item in obj)
|
|
70
|
+
if isinstance(obj, dict):
|
|
71
|
+
return tuple(sorted((k, make_cache_key_hashable(v)) for k, v in obj.items()))
|
|
72
|
+
if isinstance(obj, set):
|
|
73
|
+
return frozenset(make_cache_key_hashable(item) for item in obj)
|
|
74
|
+
|
|
75
|
+
typecode = getattr(obj, "typecode", None)
|
|
76
|
+
if typecode is not None:
|
|
77
|
+
try:
|
|
78
|
+
length = len(obj)
|
|
79
|
+
except (AttributeError, TypeError):
|
|
80
|
+
return ("array", typecode)
|
|
81
|
+
else:
|
|
82
|
+
return ("array", typecode, length)
|
|
83
|
+
|
|
84
|
+
if hasattr(obj, "__array__"):
|
|
85
|
+
try:
|
|
86
|
+
dtype_str = getattr(obj.dtype, "str", str(type(obj)))
|
|
87
|
+
shape = tuple(int(s) for s in obj.shape)
|
|
88
|
+
except (AttributeError, TypeError):
|
|
89
|
+
try:
|
|
90
|
+
length = len(obj)
|
|
91
|
+
except (AttributeError, TypeError):
|
|
92
|
+
return ("array_like", type(obj).__name__)
|
|
93
|
+
else:
|
|
94
|
+
return ("array_like", type(obj).__name__, length)
|
|
95
|
+
else:
|
|
96
|
+
return ("ndarray", dtype_str, shape)
|
|
97
|
+
return obj
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def handle_single_row_error(error: ValueError) -> "NoReturn":
|
|
101
|
+
"""Normalize single-row selection errors to SQLSpec exceptions."""
|
|
102
|
+
|
|
103
|
+
message = str(error)
|
|
104
|
+
if message.startswith("No result found"):
|
|
105
|
+
msg = "No rows found"
|
|
106
|
+
raise NotFoundError(msg) from error
|
|
107
|
+
raise error
|
|
108
|
+
|
|
109
|
+
|
|
44
110
|
class VersionInfo:
|
|
45
111
|
"""Database version information."""
|
|
46
112
|
|
|
@@ -100,7 +166,7 @@ class VersionInfo:
|
|
|
100
166
|
class DataDictionaryMixin:
|
|
101
167
|
"""Mixin providing common data dictionary functionality."""
|
|
102
168
|
|
|
103
|
-
def parse_version_string(self, version_str: str) -> "
|
|
169
|
+
def parse_version_string(self, version_str: str) -> "VersionInfo | None":
|
|
104
170
|
"""Parse version string into VersionInfo.
|
|
105
171
|
|
|
106
172
|
Args:
|
|
@@ -128,7 +194,7 @@ class DataDictionaryMixin:
|
|
|
128
194
|
|
|
129
195
|
return None
|
|
130
196
|
|
|
131
|
-
def detect_version_with_queries(self, driver: Any, queries: "list[str]") -> "
|
|
197
|
+
def detect_version_with_queries(self, driver: Any, queries: "list[str]") -> "VersionInfo | None":
|
|
132
198
|
"""Try multiple version queries to detect database version.
|
|
133
199
|
|
|
134
200
|
Args:
|
|
@@ -184,7 +250,7 @@ class ScriptExecutionResult(NamedTuple):
|
|
|
184
250
|
"""Result from script execution with statement count information."""
|
|
185
251
|
|
|
186
252
|
cursor_result: Any
|
|
187
|
-
rowcount_override:
|
|
253
|
+
rowcount_override: int | None
|
|
188
254
|
special_data: Any
|
|
189
255
|
statement_count: int
|
|
190
256
|
successful_statements: int
|
|
@@ -194,23 +260,23 @@ class ExecutionResult(NamedTuple):
|
|
|
194
260
|
"""Execution result containing all data needed for SQLResult building."""
|
|
195
261
|
|
|
196
262
|
cursor_result: Any
|
|
197
|
-
rowcount_override:
|
|
263
|
+
rowcount_override: int | None
|
|
198
264
|
special_data: Any
|
|
199
265
|
selected_data: Optional["list[dict[str, Any]]"]
|
|
200
266
|
column_names: Optional["list[str]"]
|
|
201
|
-
data_row_count:
|
|
202
|
-
statement_count:
|
|
203
|
-
successful_statements:
|
|
267
|
+
data_row_count: int | None
|
|
268
|
+
statement_count: int | None
|
|
269
|
+
successful_statements: int | None
|
|
204
270
|
is_script_result: bool
|
|
205
271
|
is_select_result: bool
|
|
206
272
|
is_many_result: bool
|
|
207
|
-
last_inserted_id:
|
|
273
|
+
last_inserted_id: int | str | None = None
|
|
208
274
|
|
|
209
275
|
|
|
210
276
|
EXEC_CURSOR_RESULT: Final[int] = 0
|
|
211
277
|
EXEC_ROWCOUNT_OVERRIDE: Final[int] = 1
|
|
212
278
|
EXEC_SPECIAL_DATA: Final[int] = 2
|
|
213
|
-
DEFAULT_EXECUTION_RESULT: Final[tuple[Any,
|
|
279
|
+
DEFAULT_EXECUTION_RESULT: Final[tuple[Any, int | None, Any]] = (None, None, None)
|
|
214
280
|
|
|
215
281
|
|
|
216
282
|
@trait
|
|
@@ -223,7 +289,7 @@ class CommonDriverAttributesMixin:
|
|
|
223
289
|
driver_features: "dict[str, Any]"
|
|
224
290
|
|
|
225
291
|
def __init__(
|
|
226
|
-
self, connection: "Any", statement_config: "StatementConfig", driver_features: "
|
|
292
|
+
self, connection: "Any", statement_config: "StatementConfig", driver_features: "dict[str, Any] | None" = None
|
|
227
293
|
) -> None:
|
|
228
294
|
"""Initialize driver adapter with connection and configuration.
|
|
229
295
|
|
|
@@ -240,17 +306,17 @@ class CommonDriverAttributesMixin:
|
|
|
240
306
|
self,
|
|
241
307
|
cursor_result: Any,
|
|
242
308
|
*,
|
|
243
|
-
rowcount_override:
|
|
309
|
+
rowcount_override: int | None = None,
|
|
244
310
|
special_data: Any = None,
|
|
245
311
|
selected_data: Optional["list[dict[str, Any]]"] = None,
|
|
246
312
|
column_names: Optional["list[str]"] = None,
|
|
247
|
-
data_row_count:
|
|
248
|
-
statement_count:
|
|
249
|
-
successful_statements:
|
|
313
|
+
data_row_count: int | None = None,
|
|
314
|
+
statement_count: int | None = None,
|
|
315
|
+
successful_statements: int | None = None,
|
|
250
316
|
is_script_result: bool = False,
|
|
251
317
|
is_select_result: bool = False,
|
|
252
318
|
is_many_result: bool = False,
|
|
253
|
-
last_inserted_id:
|
|
319
|
+
last_inserted_id: int | str | None = None,
|
|
254
320
|
) -> ExecutionResult:
|
|
255
321
|
"""Create ExecutionResult with all necessary data for any operation type.
|
|
256
322
|
|
|
@@ -328,11 +394,11 @@ class CommonDriverAttributesMixin:
|
|
|
328
394
|
|
|
329
395
|
def prepare_statement(
|
|
330
396
|
self,
|
|
331
|
-
statement: "
|
|
332
|
-
parameters: "tuple[
|
|
397
|
+
statement: "Statement | QueryBuilder",
|
|
398
|
+
parameters: "tuple[StatementParameters | StatementFilter, ...]" = (),
|
|
333
399
|
*,
|
|
334
400
|
statement_config: "StatementConfig",
|
|
335
|
-
kwargs: "
|
|
401
|
+
kwargs: "dict[str, Any] | None" = None,
|
|
336
402
|
) -> "SQL":
|
|
337
403
|
"""Build SQL statement from various input types.
|
|
338
404
|
|
|
@@ -349,48 +415,82 @@ class CommonDriverAttributesMixin:
|
|
|
349
415
|
"""
|
|
350
416
|
kwargs = kwargs or {}
|
|
351
417
|
|
|
418
|
+
filters: list[StatementFilter] = []
|
|
419
|
+
data_parameters: list[StatementParameters] = []
|
|
420
|
+
|
|
421
|
+
for param in parameters:
|
|
422
|
+
if is_statement_filter(param):
|
|
423
|
+
filters.append(param)
|
|
424
|
+
else:
|
|
425
|
+
data_parameters.append(param)
|
|
426
|
+
|
|
352
427
|
if isinstance(statement, QueryBuilder):
|
|
353
428
|
sql_statement = statement.to_statement(statement_config)
|
|
354
|
-
if
|
|
429
|
+
if data_parameters or kwargs:
|
|
355
430
|
merged_parameters = (
|
|
356
|
-
(*sql_statement.positional_parameters, *
|
|
357
|
-
if
|
|
431
|
+
(*sql_statement.positional_parameters, *tuple(data_parameters))
|
|
432
|
+
if data_parameters
|
|
358
433
|
else sql_statement.positional_parameters
|
|
359
434
|
)
|
|
360
|
-
|
|
435
|
+
sql_statement = SQL(sql_statement.sql, *merged_parameters, statement_config=statement_config, **kwargs)
|
|
436
|
+
|
|
437
|
+
for filter_obj in filters:
|
|
438
|
+
sql_statement = filter_obj.append_to_statement(sql_statement)
|
|
439
|
+
|
|
361
440
|
return sql_statement
|
|
441
|
+
|
|
362
442
|
if isinstance(statement, SQL):
|
|
363
|
-
|
|
443
|
+
sql_statement = statement
|
|
444
|
+
|
|
445
|
+
if data_parameters or kwargs:
|
|
364
446
|
merged_parameters = (
|
|
365
|
-
(*
|
|
447
|
+
(*sql_statement.positional_parameters, *tuple(data_parameters))
|
|
448
|
+
if data_parameters
|
|
449
|
+
else sql_statement.positional_parameters
|
|
366
450
|
)
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
451
|
+
sql_statement = SQL(sql_statement.sql, *merged_parameters, statement_config=statement_config, **kwargs)
|
|
452
|
+
else:
|
|
453
|
+
needs_rebuild = False
|
|
454
|
+
|
|
455
|
+
if statement_config.dialect and (
|
|
456
|
+
not sql_statement.statement_config.dialect
|
|
457
|
+
or sql_statement.statement_config.dialect != statement_config.dialect
|
|
458
|
+
):
|
|
459
|
+
needs_rebuild = True
|
|
460
|
+
|
|
461
|
+
if (
|
|
462
|
+
sql_statement.statement_config.parameter_config.default_execution_parameter_style
|
|
463
|
+
!= statement_config.parameter_config.default_execution_parameter_style
|
|
464
|
+
):
|
|
465
|
+
needs_rebuild = True
|
|
466
|
+
|
|
467
|
+
if needs_rebuild:
|
|
468
|
+
sql_text = sql_statement.raw_sql or sql_statement.sql
|
|
469
|
+
|
|
470
|
+
if sql_statement.is_many and sql_statement.parameters:
|
|
471
|
+
sql_statement = SQL(
|
|
472
|
+
sql_text, sql_statement.parameters, statement_config=statement_config, is_many=True
|
|
473
|
+
)
|
|
474
|
+
elif sql_statement.named_parameters:
|
|
475
|
+
sql_statement = SQL(
|
|
476
|
+
sql_text, statement_config=statement_config, **sql_statement.named_parameters
|
|
477
|
+
)
|
|
478
|
+
else:
|
|
479
|
+
sql_statement = SQL(
|
|
480
|
+
sql_text, *sql_statement.positional_parameters, statement_config=statement_config
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
for filter_obj in filters:
|
|
484
|
+
sql_statement = filter_obj.append_to_statement(sql_statement)
|
|
374
485
|
|
|
375
|
-
|
|
376
|
-
statement.statement_config.parameter_config.default_execution_parameter_style
|
|
377
|
-
!= statement_config.parameter_config.default_execution_parameter_style
|
|
378
|
-
):
|
|
379
|
-
needs_rebuild = True
|
|
486
|
+
return sql_statement
|
|
380
487
|
|
|
381
|
-
|
|
382
|
-
sql_text = statement.raw_sql or statement.sql
|
|
488
|
+
sql_statement = SQL(statement, *tuple(data_parameters), statement_config=statement_config, **kwargs)
|
|
383
489
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
elif statement.named_parameters:
|
|
387
|
-
new_sql = SQL(sql_text, statement_config=statement_config, **statement.named_parameters)
|
|
388
|
-
else:
|
|
389
|
-
new_sql = SQL(sql_text, *statement.positional_parameters, statement_config=statement_config)
|
|
490
|
+
for filter_obj in filters:
|
|
491
|
+
sql_statement = filter_obj.append_to_statement(sql_statement)
|
|
390
492
|
|
|
391
|
-
|
|
392
|
-
return statement
|
|
393
|
-
return SQL(statement, *parameters, statement_config=statement_config, **kwargs)
|
|
493
|
+
return sql_statement
|
|
394
494
|
|
|
395
495
|
def split_script_statements(
|
|
396
496
|
self, script: str, statement_config: "StatementConfig", strip_trailing_semicolon: bool = False
|
|
@@ -421,7 +521,7 @@ class CommonDriverAttributesMixin:
|
|
|
421
521
|
parameters: Any,
|
|
422
522
|
statement_config: "StatementConfig",
|
|
423
523
|
is_many: bool = False,
|
|
424
|
-
prepared_statement:
|
|
524
|
+
prepared_statement: Any | None = None, # pyright: ignore[reportUnusedParameter]
|
|
425
525
|
) -> Any:
|
|
426
526
|
"""Prepare parameters for database driver consumption.
|
|
427
527
|
|
|
@@ -449,6 +549,23 @@ class CommonDriverAttributesMixin:
|
|
|
449
549
|
return [self._format_parameter_set_for_many(parameters, statement_config)]
|
|
450
550
|
return self._format_parameter_set(parameters, statement_config)
|
|
451
551
|
|
|
552
|
+
def _apply_coercion(self, value: Any, statement_config: "StatementConfig") -> Any:
|
|
553
|
+
"""Apply type coercion to a single value.
|
|
554
|
+
|
|
555
|
+
Args:
|
|
556
|
+
value: Value to coerce (may be TypedParameter or raw value)
|
|
557
|
+
statement_config: Statement configuration for type coercion map
|
|
558
|
+
|
|
559
|
+
Returns:
|
|
560
|
+
Coerced value with TypedParameter unwrapped
|
|
561
|
+
"""
|
|
562
|
+
unwrapped_value = value.value if isinstance(value, TypedParameter) else value
|
|
563
|
+
if statement_config.parameter_config.type_coercion_map:
|
|
564
|
+
for type_check, converter in statement_config.parameter_config.type_coercion_map.items():
|
|
565
|
+
if isinstance(unwrapped_value, type_check):
|
|
566
|
+
return converter(unwrapped_value)
|
|
567
|
+
return unwrapped_value
|
|
568
|
+
|
|
452
569
|
def _format_parameter_set_for_many(self, parameters: Any, statement_config: "StatementConfig") -> Any:
|
|
453
570
|
"""Prepare a single parameter set for execute_many operations.
|
|
454
571
|
|
|
@@ -465,27 +582,14 @@ class CommonDriverAttributesMixin:
|
|
|
465
582
|
if not parameters:
|
|
466
583
|
return []
|
|
467
584
|
|
|
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
|
|
585
|
+
if not isinstance(parameters, (dict, list, tuple)):
|
|
586
|
+
return self._apply_coercion(parameters, statement_config)
|
|
480
587
|
|
|
481
588
|
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
|
|
589
|
+
return {k: self._apply_coercion(v, statement_config) for k, v in parameters.items()}
|
|
487
590
|
|
|
488
|
-
|
|
591
|
+
coerced_params = [self._apply_coercion(p, statement_config) for p in parameters]
|
|
592
|
+
return tuple(coerced_params) if isinstance(parameters, tuple) else coerced_params
|
|
489
593
|
|
|
490
594
|
def _format_parameter_set(self, parameters: Any, statement_config: "StatementConfig") -> Any:
|
|
491
595
|
"""Prepare a single parameter set for database driver consumption.
|
|
@@ -500,50 +604,34 @@ class CommonDriverAttributesMixin:
|
|
|
500
604
|
if not parameters:
|
|
501
605
|
return []
|
|
502
606
|
|
|
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
|
|
607
|
+
if not isinstance(parameters, (dict, list, tuple)):
|
|
608
|
+
return [self._apply_coercion(parameters, statement_config)]
|
|
513
609
|
|
|
514
610
|
if isinstance(parameters, dict):
|
|
515
|
-
if not parameters:
|
|
516
|
-
return []
|
|
517
611
|
if statement_config.parameter_config.supported_execution_parameter_styles and (
|
|
518
612
|
ParameterStyle.NAMED_PYFORMAT in statement_config.parameter_config.supported_execution_parameter_styles
|
|
519
613
|
or ParameterStyle.NAMED_COLON in statement_config.parameter_config.supported_execution_parameter_styles
|
|
520
614
|
):
|
|
521
|
-
return {k:
|
|
615
|
+
return {k: self._apply_coercion(v, statement_config) for k, v in parameters.items()}
|
|
522
616
|
if statement_config.parameter_config.default_parameter_style in {
|
|
523
617
|
ParameterStyle.NUMERIC,
|
|
524
618
|
ParameterStyle.QMARK,
|
|
525
619
|
ParameterStyle.POSITIONAL_PYFORMAT,
|
|
526
620
|
}:
|
|
527
|
-
ordered_parameters = []
|
|
528
621
|
sorted_items = sorted(
|
|
529
622
|
parameters.items(),
|
|
530
623
|
key=lambda item: int(item[0])
|
|
531
624
|
if item[0].isdigit()
|
|
532
625
|
else (int(item[0][6:]) if item[0].startswith("param_") and item[0][6:].isdigit() else float("inf")),
|
|
533
626
|
)
|
|
534
|
-
for _, value in sorted_items
|
|
535
|
-
ordered_parameters.append(apply_type_coercion(value))
|
|
536
|
-
return ordered_parameters
|
|
627
|
+
return [self._apply_coercion(value, statement_config) for _, value in sorted_items]
|
|
537
628
|
|
|
538
|
-
return {k:
|
|
629
|
+
return {k: self._apply_coercion(v, statement_config) for k, v in parameters.items()}
|
|
539
630
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
return coerced_params
|
|
545
|
-
|
|
546
|
-
return [apply_type_coercion(parameters)]
|
|
631
|
+
coerced_params = [self._apply_coercion(p, statement_config) for p in parameters]
|
|
632
|
+
if statement_config.parameter_config.preserve_parameter_format and isinstance(parameters, tuple):
|
|
633
|
+
return tuple(coerced_params)
|
|
634
|
+
return coerced_params
|
|
547
635
|
|
|
548
636
|
def _get_compiled_sql(
|
|
549
637
|
self, statement: "SQL", statement_config: "StatementConfig", flatten_single_parameters: bool = False
|
|
@@ -625,26 +713,26 @@ class CommonDriverAttributesMixin:
|
|
|
625
713
|
)
|
|
626
714
|
|
|
627
715
|
params = statement.parameters
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
716
|
+
|
|
717
|
+
if params is None or (isinstance(params, (list, tuple, dict)) and not params):
|
|
718
|
+
return f"compiled:{hash(statement.sql)}:{context_hash}"
|
|
719
|
+
|
|
720
|
+
if isinstance(params, tuple) and all(isinstance(p, (int, str, bytes, bool, type(None))) for p in params):
|
|
721
|
+
try:
|
|
722
|
+
return (
|
|
723
|
+
f"compiled:{hash((statement.sql, params, statement.is_many, statement.is_script))}:{context_hash}"
|
|
724
|
+
)
|
|
725
|
+
except TypeError:
|
|
726
|
+
pass
|
|
639
727
|
|
|
640
728
|
try:
|
|
641
729
|
if isinstance(params, dict):
|
|
642
|
-
params_key =
|
|
730
|
+
params_key = make_cache_key_hashable(params)
|
|
643
731
|
elif isinstance(params, (list, tuple)) and params:
|
|
644
732
|
if isinstance(params[0], dict):
|
|
645
|
-
params_key = tuple(
|
|
733
|
+
params_key = tuple(make_cache_key_hashable(d) for d in params)
|
|
646
734
|
else:
|
|
647
|
-
params_key =
|
|
735
|
+
params_key = make_cache_key_hashable(params)
|
|
648
736
|
elif isinstance(params, (list, tuple)):
|
|
649
737
|
params_key = ()
|
|
650
738
|
else:
|
|
@@ -655,7 +743,7 @@ class CommonDriverAttributesMixin:
|
|
|
655
743
|
base_hash = hash((statement.sql, params_key, statement.is_many, statement.is_script))
|
|
656
744
|
return f"compiled:{base_hash}:{context_hash}"
|
|
657
745
|
|
|
658
|
-
def _get_dominant_parameter_style(self, parameters: "list[Any]") -> "
|
|
746
|
+
def _get_dominant_parameter_style(self, parameters: "list[Any]") -> "ParameterStyle | None":
|
|
659
747
|
"""Determine the dominant parameter style from parameter info list.
|
|
660
748
|
|
|
661
749
|
Args:
|
|
@@ -688,7 +776,7 @@ class CommonDriverAttributesMixin:
|
|
|
688
776
|
def find_filter(
|
|
689
777
|
filter_type: "type[FilterTypeT]",
|
|
690
778
|
filters: "Sequence[StatementFilter | StatementParameters] | Sequence[StatementFilter]",
|
|
691
|
-
) -> "
|
|
779
|
+
) -> "FilterTypeT | None":
|
|
692
780
|
"""Get the filter specified by filter type from the filters.
|
|
693
781
|
|
|
694
782
|
Args:
|