sqlspec 0.32.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sqlspec/__init__.py +104 -0
- sqlspec/__main__.py +12 -0
- sqlspec/__metadata__.py +14 -0
- sqlspec/_serialization.py +312 -0
- sqlspec/_typing.py +784 -0
- sqlspec/adapters/__init__.py +0 -0
- sqlspec/adapters/adbc/__init__.py +5 -0
- sqlspec/adapters/adbc/_types.py +12 -0
- sqlspec/adapters/adbc/adk/__init__.py +5 -0
- sqlspec/adapters/adbc/adk/store.py +880 -0
- sqlspec/adapters/adbc/config.py +436 -0
- sqlspec/adapters/adbc/data_dictionary.py +537 -0
- sqlspec/adapters/adbc/driver.py +841 -0
- sqlspec/adapters/adbc/litestar/__init__.py +5 -0
- sqlspec/adapters/adbc/litestar/store.py +504 -0
- sqlspec/adapters/adbc/type_converter.py +153 -0
- sqlspec/adapters/aiosqlite/__init__.py +29 -0
- sqlspec/adapters/aiosqlite/_types.py +13 -0
- sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/adk/store.py +536 -0
- sqlspec/adapters/aiosqlite/config.py +310 -0
- sqlspec/adapters/aiosqlite/data_dictionary.py +260 -0
- sqlspec/adapters/aiosqlite/driver.py +463 -0
- sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/litestar/store.py +281 -0
- sqlspec/adapters/aiosqlite/pool.py +500 -0
- sqlspec/adapters/asyncmy/__init__.py +25 -0
- sqlspec/adapters/asyncmy/_types.py +12 -0
- sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
- sqlspec/adapters/asyncmy/adk/store.py +503 -0
- sqlspec/adapters/asyncmy/config.py +246 -0
- sqlspec/adapters/asyncmy/data_dictionary.py +241 -0
- sqlspec/adapters/asyncmy/driver.py +632 -0
- sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncmy/litestar/store.py +296 -0
- sqlspec/adapters/asyncpg/__init__.py +23 -0
- sqlspec/adapters/asyncpg/_type_handlers.py +76 -0
- sqlspec/adapters/asyncpg/_types.py +23 -0
- sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
- sqlspec/adapters/asyncpg/adk/store.py +460 -0
- sqlspec/adapters/asyncpg/config.py +464 -0
- sqlspec/adapters/asyncpg/data_dictionary.py +321 -0
- sqlspec/adapters/asyncpg/driver.py +720 -0
- sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncpg/litestar/store.py +253 -0
- sqlspec/adapters/bigquery/__init__.py +18 -0
- sqlspec/adapters/bigquery/_types.py +12 -0
- sqlspec/adapters/bigquery/adk/__init__.py +5 -0
- sqlspec/adapters/bigquery/adk/store.py +585 -0
- sqlspec/adapters/bigquery/config.py +298 -0
- sqlspec/adapters/bigquery/data_dictionary.py +256 -0
- sqlspec/adapters/bigquery/driver.py +1073 -0
- sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
- sqlspec/adapters/bigquery/litestar/store.py +327 -0
- sqlspec/adapters/bigquery/type_converter.py +125 -0
- sqlspec/adapters/duckdb/__init__.py +24 -0
- sqlspec/adapters/duckdb/_types.py +12 -0
- sqlspec/adapters/duckdb/adk/__init__.py +14 -0
- sqlspec/adapters/duckdb/adk/store.py +563 -0
- sqlspec/adapters/duckdb/config.py +396 -0
- sqlspec/adapters/duckdb/data_dictionary.py +264 -0
- sqlspec/adapters/duckdb/driver.py +604 -0
- sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
- sqlspec/adapters/duckdb/litestar/store.py +332 -0
- sqlspec/adapters/duckdb/pool.py +273 -0
- sqlspec/adapters/duckdb/type_converter.py +133 -0
- sqlspec/adapters/oracledb/__init__.py +32 -0
- sqlspec/adapters/oracledb/_numpy_handlers.py +133 -0
- sqlspec/adapters/oracledb/_types.py +39 -0
- sqlspec/adapters/oracledb/_uuid_handlers.py +130 -0
- sqlspec/adapters/oracledb/adk/__init__.py +5 -0
- sqlspec/adapters/oracledb/adk/store.py +1632 -0
- sqlspec/adapters/oracledb/config.py +469 -0
- sqlspec/adapters/oracledb/data_dictionary.py +717 -0
- sqlspec/adapters/oracledb/driver.py +1493 -0
- sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
- sqlspec/adapters/oracledb/litestar/store.py +765 -0
- sqlspec/adapters/oracledb/migrations.py +532 -0
- sqlspec/adapters/oracledb/type_converter.py +207 -0
- sqlspec/adapters/psqlpy/__init__.py +16 -0
- sqlspec/adapters/psqlpy/_type_handlers.py +44 -0
- sqlspec/adapters/psqlpy/_types.py +12 -0
- sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
- sqlspec/adapters/psqlpy/adk/store.py +483 -0
- sqlspec/adapters/psqlpy/config.py +271 -0
- sqlspec/adapters/psqlpy/data_dictionary.py +179 -0
- sqlspec/adapters/psqlpy/driver.py +892 -0
- sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
- sqlspec/adapters/psqlpy/litestar/store.py +272 -0
- sqlspec/adapters/psqlpy/type_converter.py +102 -0
- sqlspec/adapters/psycopg/__init__.py +32 -0
- sqlspec/adapters/psycopg/_type_handlers.py +90 -0
- sqlspec/adapters/psycopg/_types.py +18 -0
- sqlspec/adapters/psycopg/adk/__init__.py +5 -0
- sqlspec/adapters/psycopg/adk/store.py +962 -0
- sqlspec/adapters/psycopg/config.py +487 -0
- sqlspec/adapters/psycopg/data_dictionary.py +630 -0
- sqlspec/adapters/psycopg/driver.py +1336 -0
- sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
- sqlspec/adapters/psycopg/litestar/store.py +554 -0
- sqlspec/adapters/spanner/__init__.py +38 -0
- sqlspec/adapters/spanner/_type_handlers.py +186 -0
- sqlspec/adapters/spanner/_types.py +12 -0
- sqlspec/adapters/spanner/adk/__init__.py +5 -0
- sqlspec/adapters/spanner/adk/store.py +435 -0
- sqlspec/adapters/spanner/config.py +241 -0
- sqlspec/adapters/spanner/data_dictionary.py +95 -0
- sqlspec/adapters/spanner/dialect/__init__.py +6 -0
- sqlspec/adapters/spanner/dialect/_spangres.py +52 -0
- sqlspec/adapters/spanner/dialect/_spanner.py +123 -0
- sqlspec/adapters/spanner/driver.py +366 -0
- sqlspec/adapters/spanner/litestar/__init__.py +5 -0
- sqlspec/adapters/spanner/litestar/store.py +266 -0
- sqlspec/adapters/spanner/type_converter.py +46 -0
- sqlspec/adapters/sqlite/__init__.py +18 -0
- sqlspec/adapters/sqlite/_type_handlers.py +86 -0
- sqlspec/adapters/sqlite/_types.py +11 -0
- sqlspec/adapters/sqlite/adk/__init__.py +5 -0
- sqlspec/adapters/sqlite/adk/store.py +582 -0
- sqlspec/adapters/sqlite/config.py +221 -0
- sqlspec/adapters/sqlite/data_dictionary.py +256 -0
- sqlspec/adapters/sqlite/driver.py +527 -0
- sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/sqlite/litestar/store.py +318 -0
- sqlspec/adapters/sqlite/pool.py +140 -0
- sqlspec/base.py +811 -0
- sqlspec/builder/__init__.py +146 -0
- sqlspec/builder/_base.py +900 -0
- sqlspec/builder/_column.py +517 -0
- sqlspec/builder/_ddl.py +1642 -0
- sqlspec/builder/_delete.py +84 -0
- sqlspec/builder/_dml.py +381 -0
- sqlspec/builder/_expression_wrappers.py +46 -0
- sqlspec/builder/_factory.py +1537 -0
- sqlspec/builder/_insert.py +315 -0
- sqlspec/builder/_join.py +375 -0
- sqlspec/builder/_merge.py +848 -0
- sqlspec/builder/_parsing_utils.py +297 -0
- sqlspec/builder/_select.py +1615 -0
- sqlspec/builder/_update.py +161 -0
- sqlspec/builder/_vector_expressions.py +259 -0
- sqlspec/cli.py +764 -0
- sqlspec/config.py +1540 -0
- sqlspec/core/__init__.py +305 -0
- sqlspec/core/cache.py +785 -0
- sqlspec/core/compiler.py +603 -0
- sqlspec/core/filters.py +872 -0
- sqlspec/core/hashing.py +274 -0
- sqlspec/core/metrics.py +83 -0
- sqlspec/core/parameters/__init__.py +64 -0
- sqlspec/core/parameters/_alignment.py +266 -0
- sqlspec/core/parameters/_converter.py +413 -0
- sqlspec/core/parameters/_processor.py +341 -0
- sqlspec/core/parameters/_registry.py +201 -0
- sqlspec/core/parameters/_transformers.py +226 -0
- sqlspec/core/parameters/_types.py +430 -0
- sqlspec/core/parameters/_validator.py +123 -0
- sqlspec/core/pipeline.py +187 -0
- sqlspec/core/result.py +1124 -0
- sqlspec/core/splitter.py +940 -0
- sqlspec/core/stack.py +163 -0
- sqlspec/core/statement.py +835 -0
- sqlspec/core/type_conversion.py +235 -0
- sqlspec/driver/__init__.py +36 -0
- sqlspec/driver/_async.py +1027 -0
- sqlspec/driver/_common.py +1236 -0
- sqlspec/driver/_sync.py +1025 -0
- sqlspec/driver/mixins/__init__.py +7 -0
- sqlspec/driver/mixins/_result_tools.py +61 -0
- sqlspec/driver/mixins/_sql_translator.py +122 -0
- sqlspec/driver/mixins/_storage.py +311 -0
- sqlspec/exceptions.py +321 -0
- sqlspec/extensions/__init__.py +0 -0
- sqlspec/extensions/adk/__init__.py +53 -0
- sqlspec/extensions/adk/_types.py +51 -0
- sqlspec/extensions/adk/converters.py +172 -0
- sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +144 -0
- sqlspec/extensions/adk/migrations/__init__.py +0 -0
- sqlspec/extensions/adk/service.py +181 -0
- sqlspec/extensions/adk/store.py +536 -0
- sqlspec/extensions/aiosql/__init__.py +10 -0
- sqlspec/extensions/aiosql/adapter.py +471 -0
- sqlspec/extensions/fastapi/__init__.py +19 -0
- sqlspec/extensions/fastapi/extension.py +341 -0
- sqlspec/extensions/fastapi/providers.py +543 -0
- sqlspec/extensions/flask/__init__.py +36 -0
- sqlspec/extensions/flask/_state.py +72 -0
- sqlspec/extensions/flask/_utils.py +40 -0
- sqlspec/extensions/flask/extension.py +402 -0
- sqlspec/extensions/litestar/__init__.py +23 -0
- sqlspec/extensions/litestar/_utils.py +52 -0
- sqlspec/extensions/litestar/cli.py +92 -0
- sqlspec/extensions/litestar/config.py +90 -0
- sqlspec/extensions/litestar/handlers.py +316 -0
- sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
- sqlspec/extensions/litestar/migrations/__init__.py +3 -0
- sqlspec/extensions/litestar/plugin.py +638 -0
- sqlspec/extensions/litestar/providers.py +454 -0
- sqlspec/extensions/litestar/store.py +265 -0
- sqlspec/extensions/otel/__init__.py +58 -0
- sqlspec/extensions/prometheus/__init__.py +107 -0
- sqlspec/extensions/starlette/__init__.py +10 -0
- sqlspec/extensions/starlette/_state.py +26 -0
- sqlspec/extensions/starlette/_utils.py +52 -0
- sqlspec/extensions/starlette/extension.py +257 -0
- sqlspec/extensions/starlette/middleware.py +154 -0
- sqlspec/loader.py +716 -0
- sqlspec/migrations/__init__.py +36 -0
- sqlspec/migrations/base.py +728 -0
- sqlspec/migrations/commands.py +1140 -0
- sqlspec/migrations/context.py +142 -0
- sqlspec/migrations/fix.py +203 -0
- sqlspec/migrations/loaders.py +450 -0
- sqlspec/migrations/runner.py +1024 -0
- sqlspec/migrations/templates.py +234 -0
- sqlspec/migrations/tracker.py +403 -0
- sqlspec/migrations/utils.py +256 -0
- sqlspec/migrations/validation.py +203 -0
- sqlspec/observability/__init__.py +22 -0
- sqlspec/observability/_config.py +228 -0
- sqlspec/observability/_diagnostics.py +67 -0
- sqlspec/observability/_dispatcher.py +151 -0
- sqlspec/observability/_observer.py +180 -0
- sqlspec/observability/_runtime.py +381 -0
- sqlspec/observability/_spans.py +158 -0
- sqlspec/protocols.py +530 -0
- sqlspec/py.typed +0 -0
- sqlspec/storage/__init__.py +46 -0
- sqlspec/storage/_utils.py +104 -0
- sqlspec/storage/backends/__init__.py +1 -0
- sqlspec/storage/backends/base.py +163 -0
- sqlspec/storage/backends/fsspec.py +398 -0
- sqlspec/storage/backends/local.py +377 -0
- sqlspec/storage/backends/obstore.py +580 -0
- sqlspec/storage/errors.py +104 -0
- sqlspec/storage/pipeline.py +604 -0
- sqlspec/storage/registry.py +289 -0
- sqlspec/typing.py +219 -0
- sqlspec/utils/__init__.py +31 -0
- sqlspec/utils/arrow_helpers.py +95 -0
- sqlspec/utils/config_resolver.py +153 -0
- sqlspec/utils/correlation.py +132 -0
- sqlspec/utils/data_transformation.py +114 -0
- sqlspec/utils/dependencies.py +79 -0
- sqlspec/utils/deprecation.py +113 -0
- sqlspec/utils/fixtures.py +250 -0
- sqlspec/utils/logging.py +172 -0
- sqlspec/utils/module_loader.py +273 -0
- sqlspec/utils/portal.py +325 -0
- sqlspec/utils/schema.py +288 -0
- sqlspec/utils/serializers.py +396 -0
- sqlspec/utils/singleton.py +41 -0
- sqlspec/utils/sync_tools.py +277 -0
- sqlspec/utils/text.py +108 -0
- sqlspec/utils/type_converters.py +99 -0
- sqlspec/utils/type_guards.py +1324 -0
- sqlspec/utils/version.py +444 -0
- sqlspec-0.32.0.dist-info/METADATA +202 -0
- sqlspec-0.32.0.dist-info/RECORD +262 -0
- sqlspec-0.32.0.dist-info/WHEEL +4 -0
- sqlspec-0.32.0.dist-info/entry_points.txt +2 -0
- sqlspec-0.32.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Parameter extraction utilities."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from collections import OrderedDict
|
|
5
|
+
|
|
6
|
+
from mypy_extensions import mypyc_attr
|
|
7
|
+
|
|
8
|
+
from sqlspec.core.parameters._types import ParameterInfo, ParameterStyle
|
|
9
|
+
|
|
10
|
+
__all__ = ("PARAMETER_REGEX", "ParameterValidator")
|
|
11
|
+
|
|
12
|
+
PARAMETER_REGEX = re.compile(
|
|
13
|
+
r"""
|
|
14
|
+
(?P<dquote>"(?:[^"\\]|\\.)*") |
|
|
15
|
+
(?P<squote>'(?:[^'\\]|\\.)*') |
|
|
16
|
+
(?P<dollar_quoted_string>\$(?P<dollar_quote_tag_inner>\w*)?\$[\s\S]*?\$\4\$) |
|
|
17
|
+
(?P<line_comment>--[^\r\n]*) |
|
|
18
|
+
(?P<block_comment>/\*(?:[^*]|\*(?!/))*\*/) |
|
|
19
|
+
(?P<pg_q_operator>\?\?|\?\||\?&) |
|
|
20
|
+
(?P<pg_cast>::(?P<cast_type>\w+)) |
|
|
21
|
+
(?P<sql_server_global>@@(?P<global_var_name>\w+)) |
|
|
22
|
+
(?P<pyformat_named>%\((?P<pyformat_name>\w+)\)s) |
|
|
23
|
+
(?P<pyformat_pos>%s) |
|
|
24
|
+
(?P<positional_colon>(?<![A-Za-z0-9_]):(?P<colon_num>\d+)) |
|
|
25
|
+
(?P<named_colon>(?<![A-Za-z0-9_]):(?P<colon_name>\w+)) |
|
|
26
|
+
(?P<named_at>(?<![A-Za-z0-9_])@(?P<at_name>\w+)) |
|
|
27
|
+
(?P<numeric>(?<![A-Za-z0-9_])\$(?P<numeric_num>\d+)) |
|
|
28
|
+
(?P<named_dollar_param>(?<![A-Za-z0-9_])\$(?P<dollar_param_name>\w+)) |
|
|
29
|
+
(?P<qmark>\?)
|
|
30
|
+
""",
|
|
31
|
+
re.VERBOSE | re.IGNORECASE | re.MULTILINE | re.DOTALL,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@mypyc_attr(allow_interpreted_subclasses=False)
|
|
36
|
+
class ParameterValidator:
|
|
37
|
+
"""Extracts placeholder metadata and dialect compatibility information."""
|
|
38
|
+
|
|
39
|
+
__slots__ = ("_cache_max_size", "_parameter_cache")
|
|
40
|
+
|
|
41
|
+
def __init__(self, cache_max_size: int = 5000) -> None:
|
|
42
|
+
self._parameter_cache: OrderedDict[str, list[ParameterInfo]] = OrderedDict()
|
|
43
|
+
self._cache_max_size = cache_max_size
|
|
44
|
+
|
|
45
|
+
def _extract_parameter_style(self, match: re.Match[str]) -> "tuple[ParameterStyle | None, str | None]":
|
|
46
|
+
"""Map a regex match to a placeholder style and optional name."""
|
|
47
|
+
if match.group("qmark"):
|
|
48
|
+
return ParameterStyle.QMARK, None
|
|
49
|
+
if match.group("named_colon"):
|
|
50
|
+
return ParameterStyle.NAMED_COLON, match.group("colon_name")
|
|
51
|
+
if match.group("numeric"):
|
|
52
|
+
return ParameterStyle.NUMERIC, match.group("numeric_num")
|
|
53
|
+
if match.group("named_at"):
|
|
54
|
+
return ParameterStyle.NAMED_AT, match.group("at_name")
|
|
55
|
+
if match.group("pyformat_named"):
|
|
56
|
+
return ParameterStyle.NAMED_PYFORMAT, match.group("pyformat_name")
|
|
57
|
+
if match.group("pyformat_pos"):
|
|
58
|
+
return ParameterStyle.POSITIONAL_PYFORMAT, None
|
|
59
|
+
if match.group("positional_colon"):
|
|
60
|
+
return ParameterStyle.POSITIONAL_COLON, match.group("colon_num")
|
|
61
|
+
if match.group("named_dollar_param"):
|
|
62
|
+
return ParameterStyle.NAMED_DOLLAR, match.group("dollar_param_name")
|
|
63
|
+
return None, None
|
|
64
|
+
|
|
65
|
+
def extract_parameters(self, sql: str) -> "list[ParameterInfo]":
|
|
66
|
+
"""Extract ordered parameter metadata from SQL text."""
|
|
67
|
+
cached_result = self._parameter_cache.get(sql)
|
|
68
|
+
if cached_result is not None:
|
|
69
|
+
self._parameter_cache.move_to_end(sql)
|
|
70
|
+
return cached_result
|
|
71
|
+
|
|
72
|
+
if not any(c in sql for c in ("?", "%", ":", "@", "$")):
|
|
73
|
+
if len(self._parameter_cache) >= self._cache_max_size:
|
|
74
|
+
self._parameter_cache.popitem(last=False)
|
|
75
|
+
self._parameter_cache[sql] = []
|
|
76
|
+
return []
|
|
77
|
+
|
|
78
|
+
parameters: list[ParameterInfo] = []
|
|
79
|
+
ordinal = 0
|
|
80
|
+
|
|
81
|
+
skip_groups = (
|
|
82
|
+
"dquote",
|
|
83
|
+
"squote",
|
|
84
|
+
"dollar_quoted_string",
|
|
85
|
+
"line_comment",
|
|
86
|
+
"block_comment",
|
|
87
|
+
"pg_q_operator",
|
|
88
|
+
"pg_cast",
|
|
89
|
+
"sql_server_global",
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
for match in PARAMETER_REGEX.finditer(sql):
|
|
93
|
+
if any(match.group(group) for group in skip_groups):
|
|
94
|
+
continue
|
|
95
|
+
style, name = self._extract_parameter_style(match)
|
|
96
|
+
if style is None:
|
|
97
|
+
continue
|
|
98
|
+
placeholder_text = match.group(0)
|
|
99
|
+
parameters.append(ParameterInfo(name, style, match.start(), ordinal, placeholder_text))
|
|
100
|
+
ordinal += 1
|
|
101
|
+
|
|
102
|
+
if len(self._parameter_cache) >= self._cache_max_size:
|
|
103
|
+
self._parameter_cache.popitem(last=False)
|
|
104
|
+
self._parameter_cache[sql] = parameters
|
|
105
|
+
return parameters
|
|
106
|
+
|
|
107
|
+
def get_sqlglot_incompatible_styles(self, dialect: str | None = None) -> "set[ParameterStyle]":
|
|
108
|
+
"""Return placeholder styles incompatible with SQLGlot for the dialect."""
|
|
109
|
+
base_incompatible = {
|
|
110
|
+
ParameterStyle.NAMED_PYFORMAT,
|
|
111
|
+
ParameterStyle.POSITIONAL_PYFORMAT,
|
|
112
|
+
ParameterStyle.POSITIONAL_COLON,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if dialect and dialect.lower() in {"mysql", "mariadb"}:
|
|
116
|
+
return base_incompatible
|
|
117
|
+
if dialect and dialect.lower() in {"postgres", "postgresql"}:
|
|
118
|
+
return {ParameterStyle.POSITIONAL_COLON}
|
|
119
|
+
if dialect and dialect.lower() == "sqlite":
|
|
120
|
+
return {ParameterStyle.POSITIONAL_COLON}
|
|
121
|
+
if dialect and dialect.lower() in {"oracle", "bigquery"}:
|
|
122
|
+
return base_incompatible
|
|
123
|
+
return base_incompatible
|
sqlspec/core/pipeline.py
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""Shared statement pipeline registry and instrumentation."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from collections import OrderedDict
|
|
5
|
+
from typing import Any, Final
|
|
6
|
+
|
|
7
|
+
from mypy_extensions import mypyc_attr
|
|
8
|
+
|
|
9
|
+
from sqlspec.core.compiler import CompiledSQL, SQLProcessor
|
|
10
|
+
|
|
11
|
+
DEBUG_ENV_FLAG: Final[str] = "SQLSPEC_DEBUG_PIPELINE_CACHE"
|
|
12
|
+
DEFAULT_PIPELINE_CACHE_SIZE: Final[int] = 1000
|
|
13
|
+
DEFAULT_PIPELINE_COUNT: Final[int] = 32
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _is_truthy(value: "str | None") -> bool:
|
|
17
|
+
if value is None:
|
|
18
|
+
return False
|
|
19
|
+
normalized = value.strip().lower()
|
|
20
|
+
return normalized in {"1", "true", "yes", "on"}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@mypyc_attr(allow_interpreted_subclasses=False)
|
|
24
|
+
class _PipelineMetrics:
|
|
25
|
+
__slots__ = ("hits", "max_size", "misses", "size")
|
|
26
|
+
|
|
27
|
+
def __init__(self) -> None:
|
|
28
|
+
self.hits = 0
|
|
29
|
+
self.misses = 0
|
|
30
|
+
self.size = 0
|
|
31
|
+
self.max_size = 0
|
|
32
|
+
|
|
33
|
+
def update(self, stats: "dict[str, int]") -> None:
|
|
34
|
+
self.hits = stats.get("hits", 0)
|
|
35
|
+
self.misses = stats.get("misses", 0)
|
|
36
|
+
self.size = stats.get("size", 0)
|
|
37
|
+
self.max_size = stats.get("max_size", 0)
|
|
38
|
+
|
|
39
|
+
def snapshot(self) -> "dict[str, int]":
|
|
40
|
+
return {"hits": self.hits, "misses": self.misses, "size": self.size, "max_size": self.max_size}
|
|
41
|
+
|
|
42
|
+
def reset(self) -> None:
|
|
43
|
+
self.hits = 0
|
|
44
|
+
self.misses = 0
|
|
45
|
+
self.size = 0
|
|
46
|
+
self.max_size = 0
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@mypyc_attr(allow_interpreted_subclasses=False)
|
|
50
|
+
class _StatementPipeline:
|
|
51
|
+
__slots__ = ("_metrics", "_processor", "dialect", "parameter_style")
|
|
52
|
+
|
|
53
|
+
def __init__(self, config: "Any", cache_size: int, record_metrics: bool) -> None:
|
|
54
|
+
self._processor = SQLProcessor(config, max_cache_size=cache_size)
|
|
55
|
+
self.dialect = str(config.dialect) if getattr(config, "dialect", None) else "default"
|
|
56
|
+
parameter_style = config.parameter_config.default_parameter_style
|
|
57
|
+
self.parameter_style = parameter_style.value if parameter_style else "unknown"
|
|
58
|
+
self._metrics = _PipelineMetrics() if record_metrics else None
|
|
59
|
+
|
|
60
|
+
def compile(self, sql: str, parameters: Any, is_many: bool, record_metrics: bool) -> "CompiledSQL":
|
|
61
|
+
result = self._processor.compile(sql, parameters, is_many=is_many)
|
|
62
|
+
if record_metrics and self._metrics is not None:
|
|
63
|
+
self._metrics.update(self._processor.cache_stats)
|
|
64
|
+
return result
|
|
65
|
+
|
|
66
|
+
def reset(self) -> None:
|
|
67
|
+
self._processor.clear_cache()
|
|
68
|
+
if self._metrics is not None:
|
|
69
|
+
self._metrics.reset()
|
|
70
|
+
|
|
71
|
+
def metrics(self) -> "dict[str, int] | None":
|
|
72
|
+
if self._metrics is None:
|
|
73
|
+
return None
|
|
74
|
+
return self._metrics.snapshot()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@mypyc_attr(allow_interpreted_subclasses=False)
|
|
78
|
+
class StatementPipelineRegistry:
|
|
79
|
+
__slots__ = ("_max_pipelines", "_pipeline_cache_size", "_pipelines")
|
|
80
|
+
|
|
81
|
+
def __init__(
|
|
82
|
+
self, max_pipelines: int = DEFAULT_PIPELINE_COUNT, cache_size: int = DEFAULT_PIPELINE_CACHE_SIZE
|
|
83
|
+
) -> None:
|
|
84
|
+
self._pipelines: OrderedDict[str, _StatementPipeline] = OrderedDict()
|
|
85
|
+
self._max_pipelines = max_pipelines
|
|
86
|
+
self._pipeline_cache_size = cache_size
|
|
87
|
+
|
|
88
|
+
def compile(self, config: "Any", sql: str, parameters: Any, is_many: bool = False) -> "CompiledSQL":
|
|
89
|
+
key = self._fingerprint_config(config)
|
|
90
|
+
pipeline = self._pipelines.get(key)
|
|
91
|
+
record_metrics = _is_truthy(os.getenv(DEBUG_ENV_FLAG))
|
|
92
|
+
|
|
93
|
+
if pipeline is not None:
|
|
94
|
+
self._pipelines.move_to_end(key)
|
|
95
|
+
else:
|
|
96
|
+
pipeline = _StatementPipeline(config, self._pipeline_cache_size, record_metrics)
|
|
97
|
+
if len(self._pipelines) >= self._max_pipelines:
|
|
98
|
+
self._pipelines.popitem(last=False)
|
|
99
|
+
self._pipelines[key] = pipeline
|
|
100
|
+
|
|
101
|
+
return pipeline.compile(sql, parameters, is_many, record_metrics)
|
|
102
|
+
|
|
103
|
+
def reset(self) -> None:
|
|
104
|
+
for pipeline in self._pipelines.values():
|
|
105
|
+
pipeline.reset()
|
|
106
|
+
self._pipelines.clear()
|
|
107
|
+
|
|
108
|
+
def metrics(self) -> "list[dict[str, Any]]":
|
|
109
|
+
if not _is_truthy(os.getenv(DEBUG_ENV_FLAG)):
|
|
110
|
+
return []
|
|
111
|
+
|
|
112
|
+
snapshots: list[dict[str, Any]] = []
|
|
113
|
+
for key, pipeline in self._pipelines.items():
|
|
114
|
+
metrics = pipeline.metrics()
|
|
115
|
+
if metrics is None:
|
|
116
|
+
continue
|
|
117
|
+
entry = {"config": key, "dialect": pipeline.dialect, "parameter_style": pipeline.parameter_style}
|
|
118
|
+
entry.update(metrics)
|
|
119
|
+
snapshots.append(entry)
|
|
120
|
+
return snapshots
|
|
121
|
+
|
|
122
|
+
def _fingerprint_config(self, config: "Any") -> str:
|
|
123
|
+
param_config = config.parameter_config
|
|
124
|
+
supported_styles = sorted(style.value for style in param_config.supported_parameter_styles)
|
|
125
|
+
exec_styles = (
|
|
126
|
+
sorted(style.value for style in param_config.supported_execution_parameter_styles)
|
|
127
|
+
if param_config.supported_execution_parameter_styles
|
|
128
|
+
else None
|
|
129
|
+
)
|
|
130
|
+
converter_name = type(config.parameter_converter).__name__ if config.parameter_converter else "None"
|
|
131
|
+
validator_name = type(config.parameter_validator).__name__ if config.parameter_validator else "None"
|
|
132
|
+
pre_steps = tuple(type(step).__name__ for step in config.pre_process_steps) if config.pre_process_steps else ()
|
|
133
|
+
post_steps = (
|
|
134
|
+
tuple(type(step).__name__ for step in config.post_process_steps) if config.post_process_steps else ()
|
|
135
|
+
)
|
|
136
|
+
output_name = type(config.output_transformer).__name__ if config.output_transformer else "None"
|
|
137
|
+
finger_components = (
|
|
138
|
+
bool(config.enable_parsing),
|
|
139
|
+
bool(config.enable_validation),
|
|
140
|
+
bool(config.enable_transformations),
|
|
141
|
+
bool(config.enable_analysis),
|
|
142
|
+
bool(config.enable_expression_simplification),
|
|
143
|
+
bool(config.enable_parameter_type_wrapping),
|
|
144
|
+
bool(config.enable_caching),
|
|
145
|
+
str(config.dialect),
|
|
146
|
+
param_config.default_parameter_style.value,
|
|
147
|
+
param_config.default_execution_parameter_style.value,
|
|
148
|
+
param_config.hash(),
|
|
149
|
+
tuple(supported_styles),
|
|
150
|
+
tuple(exec_styles) if exec_styles else None,
|
|
151
|
+
converter_name,
|
|
152
|
+
validator_name,
|
|
153
|
+
pre_steps,
|
|
154
|
+
post_steps,
|
|
155
|
+
output_name,
|
|
156
|
+
bool(param_config.output_transformer),
|
|
157
|
+
bool(param_config.ast_transformer),
|
|
158
|
+
param_config.has_native_list_expansion,
|
|
159
|
+
param_config.allow_mixed_parameter_styles,
|
|
160
|
+
param_config.preserve_parameter_format,
|
|
161
|
+
param_config.preserve_original_params_for_many,
|
|
162
|
+
)
|
|
163
|
+
fingerprint = hash(finger_components)
|
|
164
|
+
return f"pipeline::{fingerprint}"
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
_PIPELINE_REGISTRY: "StatementPipelineRegistry" = StatementPipelineRegistry()
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def compile_with_shared_pipeline(config: "Any", sql: str, parameters: Any, is_many: bool = False) -> "CompiledSQL":
|
|
171
|
+
return _PIPELINE_REGISTRY.compile(config, sql, parameters, is_many=is_many)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def reset_statement_pipeline_cache() -> None:
|
|
175
|
+
_PIPELINE_REGISTRY.reset()
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def get_statement_pipeline_metrics() -> "list[dict[str, Any]]":
|
|
179
|
+
return _PIPELINE_REGISTRY.metrics()
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
__all__ = (
|
|
183
|
+
"StatementPipelineRegistry",
|
|
184
|
+
"compile_with_shared_pipeline",
|
|
185
|
+
"get_statement_pipeline_metrics",
|
|
186
|
+
"reset_statement_pipeline_cache",
|
|
187
|
+
)
|