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,835 @@
|
|
|
1
|
+
"""SQL statement and configuration management."""
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Final, Optional, TypeAlias
|
|
4
|
+
|
|
5
|
+
import sqlglot
|
|
6
|
+
from mypy_extensions import mypyc_attr
|
|
7
|
+
from sqlglot import exp
|
|
8
|
+
from sqlglot.errors import ParseError
|
|
9
|
+
|
|
10
|
+
import sqlspec.exceptions
|
|
11
|
+
from sqlspec.core.compiler import OperationProfile, OperationType
|
|
12
|
+
from sqlspec.core.parameters import (
|
|
13
|
+
ParameterConverter,
|
|
14
|
+
ParameterProfile,
|
|
15
|
+
ParameterStyle,
|
|
16
|
+
ParameterStyleConfig,
|
|
17
|
+
ParameterValidator,
|
|
18
|
+
)
|
|
19
|
+
from sqlspec.core.pipeline import compile_with_shared_pipeline
|
|
20
|
+
from sqlspec.typing import Empty, EmptyEnum
|
|
21
|
+
from sqlspec.utils.logging import get_logger
|
|
22
|
+
from sqlspec.utils.type_guards import is_statement_filter, supports_where
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from collections.abc import Callable
|
|
26
|
+
|
|
27
|
+
from sqlglot.dialects.dialect import DialectType
|
|
28
|
+
|
|
29
|
+
from sqlspec.core.cache import FiltersView
|
|
30
|
+
from sqlspec.core.filters import StatementFilter
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
__all__ = (
|
|
34
|
+
"SQL",
|
|
35
|
+
"ProcessedState",
|
|
36
|
+
"Statement",
|
|
37
|
+
"StatementConfig",
|
|
38
|
+
"get_default_config",
|
|
39
|
+
"get_default_parameter_config",
|
|
40
|
+
)
|
|
41
|
+
logger = get_logger("sqlspec.core.statement")
|
|
42
|
+
|
|
43
|
+
RETURNS_ROWS_OPERATIONS: Final = {"SELECT", "WITH", "VALUES", "TABLE", "SHOW", "DESCRIBE", "PRAGMA"}
|
|
44
|
+
MODIFYING_OPERATIONS: Final = {"INSERT", "UPDATE", "DELETE", "MERGE", "UPSERT"}
|
|
45
|
+
|
|
46
|
+
SQL_CONFIG_SLOTS: Final = (
|
|
47
|
+
"pre_process_steps",
|
|
48
|
+
"post_process_steps",
|
|
49
|
+
"dialect",
|
|
50
|
+
"enable_analysis",
|
|
51
|
+
"enable_caching",
|
|
52
|
+
"enable_expression_simplification",
|
|
53
|
+
"enable_parameter_type_wrapping",
|
|
54
|
+
"enable_parsing",
|
|
55
|
+
"enable_transformations",
|
|
56
|
+
"enable_validation",
|
|
57
|
+
"execution_mode",
|
|
58
|
+
"execution_args",
|
|
59
|
+
"output_transformer",
|
|
60
|
+
"parameter_config",
|
|
61
|
+
"parameter_converter",
|
|
62
|
+
"parameter_validator",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
PROCESSED_STATE_SLOTS: Final = (
|
|
66
|
+
"compiled_sql",
|
|
67
|
+
"execution_parameters",
|
|
68
|
+
"parsed_expression",
|
|
69
|
+
"operation_type",
|
|
70
|
+
"parameter_casts",
|
|
71
|
+
"parameter_profile",
|
|
72
|
+
"operation_profile",
|
|
73
|
+
"validation_errors",
|
|
74
|
+
"is_many",
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@mypyc_attr(allow_interpreted_subclasses=False)
|
|
79
|
+
class ProcessedState:
|
|
80
|
+
"""Processing results for SQL statements.
|
|
81
|
+
|
|
82
|
+
Contains the compiled SQL, execution parameters, parsed expression,
|
|
83
|
+
operation type, and validation errors for a processed SQL statement.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
__slots__ = PROCESSED_STATE_SLOTS
|
|
87
|
+
operation_type: "OperationType"
|
|
88
|
+
|
|
89
|
+
def __init__(
|
|
90
|
+
self,
|
|
91
|
+
compiled_sql: str,
|
|
92
|
+
execution_parameters: Any,
|
|
93
|
+
parsed_expression: "exp.Expression | None" = None,
|
|
94
|
+
operation_type: "OperationType" = "UNKNOWN",
|
|
95
|
+
parameter_casts: "dict[int, str] | None" = None,
|
|
96
|
+
validation_errors: "list[str] | None" = None,
|
|
97
|
+
parameter_profile: "ParameterProfile | None" = None,
|
|
98
|
+
operation_profile: "OperationProfile | None" = None,
|
|
99
|
+
is_many: bool = False,
|
|
100
|
+
) -> None:
|
|
101
|
+
self.compiled_sql = compiled_sql
|
|
102
|
+
self.execution_parameters = execution_parameters
|
|
103
|
+
self.parsed_expression = parsed_expression
|
|
104
|
+
self.operation_type = operation_type
|
|
105
|
+
self.parameter_casts = parameter_casts or {}
|
|
106
|
+
self.validation_errors = validation_errors or []
|
|
107
|
+
self.parameter_profile = parameter_profile or ParameterProfile.empty()
|
|
108
|
+
self.operation_profile = operation_profile or OperationProfile.empty()
|
|
109
|
+
self.is_many = is_many
|
|
110
|
+
|
|
111
|
+
def __hash__(self) -> int:
|
|
112
|
+
return hash((self.compiled_sql, str(self.execution_parameters), self.operation_type))
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@mypyc_attr(allow_interpreted_subclasses=False)
|
|
116
|
+
class SQL:
|
|
117
|
+
"""SQL statement with parameter and filter support.
|
|
118
|
+
|
|
119
|
+
Represents a SQL statement that can be compiled with parameters and filters.
|
|
120
|
+
Supports both positional and named parameters, statement filtering,
|
|
121
|
+
and various execution modes including batch operations.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
__slots__ = (
|
|
125
|
+
"_dialect",
|
|
126
|
+
"_filters",
|
|
127
|
+
"_hash",
|
|
128
|
+
"_is_many",
|
|
129
|
+
"_is_script",
|
|
130
|
+
"_named_parameters",
|
|
131
|
+
"_original_parameters",
|
|
132
|
+
"_positional_parameters",
|
|
133
|
+
"_processed_state",
|
|
134
|
+
"_raw_sql",
|
|
135
|
+
"_statement_config",
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def __init__(
|
|
139
|
+
self,
|
|
140
|
+
statement: "str | exp.Expression | 'SQL'",
|
|
141
|
+
*parameters: "Any | StatementFilter | list[Any | StatementFilter]",
|
|
142
|
+
statement_config: Optional["StatementConfig"] = None,
|
|
143
|
+
is_many: bool | None = None,
|
|
144
|
+
**kwargs: Any,
|
|
145
|
+
) -> None:
|
|
146
|
+
"""Initialize SQL statement.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
statement: SQL string, expression, or existing SQL object
|
|
150
|
+
*parameters: Parameters and filters
|
|
151
|
+
statement_config: Configuration
|
|
152
|
+
is_many: Mark as execute_many operation
|
|
153
|
+
**kwargs: Additional parameters
|
|
154
|
+
"""
|
|
155
|
+
config = statement_config or self._create_auto_config(statement, parameters, kwargs)
|
|
156
|
+
self._statement_config = config
|
|
157
|
+
self._dialect = self._normalize_dialect(config.dialect)
|
|
158
|
+
self._processed_state: EmptyEnum | ProcessedState = Empty
|
|
159
|
+
self._hash: int | None = None
|
|
160
|
+
self._filters: list[StatementFilter] = []
|
|
161
|
+
self._named_parameters: dict[str, Any] = {}
|
|
162
|
+
self._positional_parameters: list[Any] = []
|
|
163
|
+
self._is_script = False
|
|
164
|
+
|
|
165
|
+
if isinstance(statement, SQL):
|
|
166
|
+
self._init_from_sql_object(statement)
|
|
167
|
+
if is_many is not None:
|
|
168
|
+
self._is_many = is_many
|
|
169
|
+
else:
|
|
170
|
+
if isinstance(statement, str):
|
|
171
|
+
self._raw_sql = statement
|
|
172
|
+
else:
|
|
173
|
+
dialect = self._dialect
|
|
174
|
+
self._raw_sql = statement.sql(dialect=str(dialect) if dialect else None)
|
|
175
|
+
|
|
176
|
+
self._is_many = is_many if is_many is not None else self._should_auto_detect_many(parameters)
|
|
177
|
+
|
|
178
|
+
self._original_parameters = parameters
|
|
179
|
+
self._process_parameters(*parameters, **kwargs)
|
|
180
|
+
|
|
181
|
+
def _create_auto_config(
|
|
182
|
+
self, _statement: "str | exp.Expression | 'SQL'", _parameters: tuple, _kwargs: dict[str, Any]
|
|
183
|
+
) -> "StatementConfig":
|
|
184
|
+
"""Create default StatementConfig when none provided.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
_statement: The SQL statement (unused)
|
|
188
|
+
_parameters: Statement parameters (unused)
|
|
189
|
+
_kwargs: Additional keyword arguments (unused)
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
Default StatementConfig instance
|
|
193
|
+
"""
|
|
194
|
+
return get_default_config()
|
|
195
|
+
|
|
196
|
+
def _normalize_dialect(self, dialect: "DialectType | None") -> "str | None":
|
|
197
|
+
"""Convert dialect to string representation.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
dialect: Dialect type or string
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
String representation of the dialect or None
|
|
204
|
+
"""
|
|
205
|
+
if dialect is None:
|
|
206
|
+
return None
|
|
207
|
+
if isinstance(dialect, str):
|
|
208
|
+
return dialect
|
|
209
|
+
return dialect.__class__.__name__.lower()
|
|
210
|
+
|
|
211
|
+
def _init_from_sql_object(self, sql_obj: "SQL") -> None:
|
|
212
|
+
"""Initialize instance attributes from existing SQL object.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
sql_obj: Existing SQL object to copy from
|
|
216
|
+
"""
|
|
217
|
+
self._raw_sql = sql_obj.raw_sql
|
|
218
|
+
self._filters = sql_obj.filters.copy()
|
|
219
|
+
self._named_parameters = sql_obj.named_parameters.copy()
|
|
220
|
+
self._positional_parameters = sql_obj.positional_parameters.copy()
|
|
221
|
+
self._is_many = sql_obj.is_many
|
|
222
|
+
self._is_script = sql_obj.is_script
|
|
223
|
+
if sql_obj.is_processed:
|
|
224
|
+
self._processed_state = sql_obj.get_processed_state()
|
|
225
|
+
|
|
226
|
+
def _should_auto_detect_many(self, parameters: tuple) -> bool:
|
|
227
|
+
"""Detect execute_many mode from parameter structure.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
parameters: Parameter tuple to analyze
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
True if parameters indicate batch execution
|
|
234
|
+
"""
|
|
235
|
+
if len(parameters) == 1 and isinstance(parameters[0], list):
|
|
236
|
+
param_list = parameters[0]
|
|
237
|
+
if param_list and all(isinstance(item, (tuple, list)) for item in param_list):
|
|
238
|
+
return len(param_list) > 1
|
|
239
|
+
return False
|
|
240
|
+
|
|
241
|
+
def _process_parameters(self, *parameters: Any, dialect: str | None = None, **kwargs: Any) -> None:
|
|
242
|
+
"""Process and organize parameters and filters.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
*parameters: Variable parameters and filters
|
|
246
|
+
dialect: SQL dialect override
|
|
247
|
+
**kwargs: Additional named parameters
|
|
248
|
+
"""
|
|
249
|
+
if dialect is not None:
|
|
250
|
+
self._dialect = self._normalize_dialect(dialect)
|
|
251
|
+
|
|
252
|
+
if "is_script" in kwargs:
|
|
253
|
+
self._is_script = bool(kwargs.pop("is_script"))
|
|
254
|
+
|
|
255
|
+
filters: list[StatementFilter] = []
|
|
256
|
+
actual_params: list[Any] = []
|
|
257
|
+
for p in parameters:
|
|
258
|
+
if is_statement_filter(p):
|
|
259
|
+
filters.append(p)
|
|
260
|
+
else:
|
|
261
|
+
actual_params.append(p)
|
|
262
|
+
|
|
263
|
+
self._filters.extend(filters)
|
|
264
|
+
|
|
265
|
+
if actual_params:
|
|
266
|
+
param_count = len(actual_params)
|
|
267
|
+
if param_count == 1:
|
|
268
|
+
param = actual_params[0]
|
|
269
|
+
if isinstance(param, dict):
|
|
270
|
+
self._named_parameters.update(param)
|
|
271
|
+
elif isinstance(param, (list, tuple)):
|
|
272
|
+
if self._is_many:
|
|
273
|
+
self._positional_parameters = list(param)
|
|
274
|
+
else:
|
|
275
|
+
# For drivers with native list expansion support, each item in the tuple/list
|
|
276
|
+
# should be treated as a separate parameter (but preserve inner lists/arrays)
|
|
277
|
+
# This allows passing arrays/lists as single JSONB parameters
|
|
278
|
+
self._positional_parameters.extend(param)
|
|
279
|
+
else:
|
|
280
|
+
self._positional_parameters.append(param)
|
|
281
|
+
else:
|
|
282
|
+
self._positional_parameters.extend(actual_params)
|
|
283
|
+
|
|
284
|
+
self._named_parameters.update(kwargs)
|
|
285
|
+
|
|
286
|
+
@property
|
|
287
|
+
def sql(self) -> str:
|
|
288
|
+
"""Get the raw SQL string."""
|
|
289
|
+
return self._raw_sql
|
|
290
|
+
|
|
291
|
+
@property
|
|
292
|
+
def raw_sql(self) -> str:
|
|
293
|
+
"""Get raw SQL string (public API).
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
The raw SQL string
|
|
297
|
+
"""
|
|
298
|
+
return self._raw_sql
|
|
299
|
+
|
|
300
|
+
@property
|
|
301
|
+
def parameters(self) -> Any:
|
|
302
|
+
"""Get the original parameters."""
|
|
303
|
+
if self._named_parameters:
|
|
304
|
+
return self._named_parameters
|
|
305
|
+
return self._positional_parameters or []
|
|
306
|
+
|
|
307
|
+
@property
|
|
308
|
+
def positional_parameters(self) -> "list[Any]":
|
|
309
|
+
"""Get positional parameters (public API)."""
|
|
310
|
+
return self._positional_parameters or []
|
|
311
|
+
|
|
312
|
+
@property
|
|
313
|
+
def named_parameters(self) -> "dict[str, Any]":
|
|
314
|
+
"""Get named parameters (public API)."""
|
|
315
|
+
return self._named_parameters
|
|
316
|
+
|
|
317
|
+
@property
|
|
318
|
+
def original_parameters(self) -> Any:
|
|
319
|
+
"""Get original parameters (public API)."""
|
|
320
|
+
return self._original_parameters
|
|
321
|
+
|
|
322
|
+
@property
|
|
323
|
+
def operation_type(self) -> "OperationType":
|
|
324
|
+
"""SQL operation type."""
|
|
325
|
+
if self._processed_state is Empty:
|
|
326
|
+
return "UNKNOWN"
|
|
327
|
+
return self._processed_state.operation_type
|
|
328
|
+
|
|
329
|
+
@property
|
|
330
|
+
def statement_config(self) -> "StatementConfig":
|
|
331
|
+
"""Statement configuration."""
|
|
332
|
+
return self._statement_config
|
|
333
|
+
|
|
334
|
+
@property
|
|
335
|
+
def expression(self) -> "exp.Expression | None":
|
|
336
|
+
"""SQLGlot expression."""
|
|
337
|
+
if self._processed_state is not Empty:
|
|
338
|
+
return self._processed_state.parsed_expression
|
|
339
|
+
return None
|
|
340
|
+
|
|
341
|
+
@property
|
|
342
|
+
def filters(self) -> "list[StatementFilter]":
|
|
343
|
+
"""Applied filters."""
|
|
344
|
+
return self._filters.copy()
|
|
345
|
+
|
|
346
|
+
def get_filters_view(self) -> "FiltersView":
|
|
347
|
+
"""Get zero-copy filters view (public API).
|
|
348
|
+
|
|
349
|
+
Returns:
|
|
350
|
+
Read-only view of filters without copying
|
|
351
|
+
"""
|
|
352
|
+
from sqlspec.core.cache import FiltersView
|
|
353
|
+
|
|
354
|
+
return FiltersView(self._filters)
|
|
355
|
+
|
|
356
|
+
@property
|
|
357
|
+
def is_processed(self) -> bool:
|
|
358
|
+
"""Check if SQL has been processed (public API)."""
|
|
359
|
+
return self._processed_state is not Empty
|
|
360
|
+
|
|
361
|
+
def get_processed_state(self) -> Any:
|
|
362
|
+
"""Get processed state (public API)."""
|
|
363
|
+
return self._processed_state
|
|
364
|
+
|
|
365
|
+
@property
|
|
366
|
+
def dialect(self) -> "str | None":
|
|
367
|
+
"""SQL dialect."""
|
|
368
|
+
return self._dialect
|
|
369
|
+
|
|
370
|
+
@property
|
|
371
|
+
def _statement(self) -> "exp.Expression | None":
|
|
372
|
+
"""Internal SQLGlot expression."""
|
|
373
|
+
return self.expression
|
|
374
|
+
|
|
375
|
+
@property
|
|
376
|
+
def statement_expression(self) -> "exp.Expression | None":
|
|
377
|
+
"""Get parsed statement expression (public API).
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
Parsed SQLGlot expression or None if not parsed
|
|
381
|
+
"""
|
|
382
|
+
if self._processed_state is not Empty:
|
|
383
|
+
return self._processed_state.parsed_expression
|
|
384
|
+
return None
|
|
385
|
+
|
|
386
|
+
@property
|
|
387
|
+
def is_many(self) -> bool:
|
|
388
|
+
"""Check if this is execute_many."""
|
|
389
|
+
return self._is_many
|
|
390
|
+
|
|
391
|
+
@property
|
|
392
|
+
def is_script(self) -> bool:
|
|
393
|
+
"""Check if this is script execution."""
|
|
394
|
+
return self._is_script
|
|
395
|
+
|
|
396
|
+
@property
|
|
397
|
+
def validation_errors(self) -> "list[str]":
|
|
398
|
+
"""Validation errors."""
|
|
399
|
+
if self._processed_state is Empty:
|
|
400
|
+
return []
|
|
401
|
+
return self._processed_state.validation_errors.copy()
|
|
402
|
+
|
|
403
|
+
@property
|
|
404
|
+
def has_errors(self) -> bool:
|
|
405
|
+
"""Check if there are validation errors."""
|
|
406
|
+
return len(self.validation_errors) > 0
|
|
407
|
+
|
|
408
|
+
def returns_rows(self) -> bool:
|
|
409
|
+
"""Check if statement returns rows.
|
|
410
|
+
|
|
411
|
+
Returns:
|
|
412
|
+
True if the SQL statement returns result rows
|
|
413
|
+
"""
|
|
414
|
+
if self._processed_state is Empty:
|
|
415
|
+
self.compile()
|
|
416
|
+
if self._processed_state is Empty:
|
|
417
|
+
return False
|
|
418
|
+
|
|
419
|
+
profile = getattr(self._processed_state, "operation_profile", None)
|
|
420
|
+
if profile and profile.returns_rows:
|
|
421
|
+
return True
|
|
422
|
+
|
|
423
|
+
op_type = self._processed_state.operation_type
|
|
424
|
+
if op_type in RETURNS_ROWS_OPERATIONS:
|
|
425
|
+
return True
|
|
426
|
+
|
|
427
|
+
if self._processed_state.parsed_expression:
|
|
428
|
+
expr = self._processed_state.parsed_expression
|
|
429
|
+
if isinstance(expr, (exp.Insert, exp.Update, exp.Delete)) and expr.args.get("returning"):
|
|
430
|
+
return True
|
|
431
|
+
|
|
432
|
+
return False
|
|
433
|
+
|
|
434
|
+
def is_modifying_operation(self) -> bool:
|
|
435
|
+
"""Check if the SQL statement is a modifying operation.
|
|
436
|
+
|
|
437
|
+
Returns:
|
|
438
|
+
True if the operation modifies data (INSERT/UPDATE/DELETE)
|
|
439
|
+
"""
|
|
440
|
+
if self._processed_state is Empty:
|
|
441
|
+
return False
|
|
442
|
+
|
|
443
|
+
profile = getattr(self._processed_state, "operation_profile", None)
|
|
444
|
+
if profile and profile.modifies_rows:
|
|
445
|
+
return True
|
|
446
|
+
|
|
447
|
+
op_type = self._processed_state.operation_type
|
|
448
|
+
if op_type in MODIFYING_OPERATIONS:
|
|
449
|
+
return True
|
|
450
|
+
|
|
451
|
+
if self._processed_state.parsed_expression:
|
|
452
|
+
return isinstance(self._processed_state.parsed_expression, (exp.Insert, exp.Update, exp.Delete, exp.Merge))
|
|
453
|
+
|
|
454
|
+
return False
|
|
455
|
+
|
|
456
|
+
def compile(self) -> tuple[str, Any]:
|
|
457
|
+
"""Compile SQL statement with parameters.
|
|
458
|
+
|
|
459
|
+
Returns:
|
|
460
|
+
Tuple of compiled SQL string and execution parameters
|
|
461
|
+
"""
|
|
462
|
+
if self._processed_state is Empty:
|
|
463
|
+
try:
|
|
464
|
+
config = self._statement_config
|
|
465
|
+
raw_sql = self._raw_sql
|
|
466
|
+
params = self._named_parameters or self._positional_parameters
|
|
467
|
+
is_many = self._is_many
|
|
468
|
+
compiled_result = compile_with_shared_pipeline(config, raw_sql, params, is_many=is_many)
|
|
469
|
+
|
|
470
|
+
self._processed_state = ProcessedState(
|
|
471
|
+
compiled_sql=compiled_result.compiled_sql,
|
|
472
|
+
execution_parameters=compiled_result.execution_parameters,
|
|
473
|
+
parsed_expression=compiled_result.expression,
|
|
474
|
+
operation_type=compiled_result.operation_type,
|
|
475
|
+
parameter_casts=compiled_result.parameter_casts,
|
|
476
|
+
parameter_profile=compiled_result.parameter_profile,
|
|
477
|
+
operation_profile=compiled_result.operation_profile,
|
|
478
|
+
validation_errors=[],
|
|
479
|
+
is_many=self._is_many,
|
|
480
|
+
)
|
|
481
|
+
except sqlspec.exceptions.SQLSpecError:
|
|
482
|
+
raise
|
|
483
|
+
except Exception as e:
|
|
484
|
+
self._processed_state = self._handle_compile_failure(e)
|
|
485
|
+
|
|
486
|
+
return self._processed_state.compiled_sql, self._processed_state.execution_parameters
|
|
487
|
+
|
|
488
|
+
def as_script(self) -> "SQL":
|
|
489
|
+
"""Create copy marked for script execution.
|
|
490
|
+
|
|
491
|
+
Returns:
|
|
492
|
+
New SQL instance configured for script execution
|
|
493
|
+
"""
|
|
494
|
+
original_params = self._original_parameters
|
|
495
|
+
config = self._statement_config
|
|
496
|
+
is_many = self._is_many
|
|
497
|
+
new_sql = SQL(self._raw_sql, *original_params, statement_config=config, is_many=is_many)
|
|
498
|
+
new_sql._named_parameters.update(self._named_parameters)
|
|
499
|
+
new_sql._positional_parameters = self._positional_parameters.copy()
|
|
500
|
+
new_sql._filters = self._filters.copy()
|
|
501
|
+
new_sql._is_script = True
|
|
502
|
+
return new_sql
|
|
503
|
+
|
|
504
|
+
def copy(
|
|
505
|
+
self, statement: "str | exp.Expression | None" = None, parameters: Any | None = None, **kwargs: Any
|
|
506
|
+
) -> "SQL":
|
|
507
|
+
"""Create copy with modifications.
|
|
508
|
+
|
|
509
|
+
Args:
|
|
510
|
+
statement: New SQL statement to use
|
|
511
|
+
parameters: New parameters to use
|
|
512
|
+
**kwargs: Additional modifications
|
|
513
|
+
|
|
514
|
+
Returns:
|
|
515
|
+
New SQL instance with modifications applied
|
|
516
|
+
"""
|
|
517
|
+
new_sql = SQL(
|
|
518
|
+
statement or self._raw_sql,
|
|
519
|
+
*(parameters if parameters is not None else self._original_parameters),
|
|
520
|
+
statement_config=self._statement_config,
|
|
521
|
+
is_many=self._is_many,
|
|
522
|
+
**kwargs,
|
|
523
|
+
)
|
|
524
|
+
if parameters is None:
|
|
525
|
+
new_sql._named_parameters.update(self._named_parameters)
|
|
526
|
+
new_sql._positional_parameters = self._positional_parameters.copy()
|
|
527
|
+
new_sql._filters = self._filters.copy()
|
|
528
|
+
return new_sql
|
|
529
|
+
|
|
530
|
+
def _handle_compile_failure(self, error: Exception) -> ProcessedState:
|
|
531
|
+
logger.warning("Processing failed, using fallback: %s", error)
|
|
532
|
+
return ProcessedState(
|
|
533
|
+
compiled_sql=self._raw_sql,
|
|
534
|
+
execution_parameters=self._named_parameters or self._positional_parameters,
|
|
535
|
+
operation_type="UNKNOWN",
|
|
536
|
+
parameter_casts={},
|
|
537
|
+
parameter_profile=ParameterProfile.empty(),
|
|
538
|
+
operation_profile=OperationProfile.empty(),
|
|
539
|
+
is_many=self._is_many,
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
def add_named_parameter(self, name: str, value: Any) -> "SQL":
|
|
543
|
+
"""Add a named parameter and return a new SQL instance.
|
|
544
|
+
|
|
545
|
+
Args:
|
|
546
|
+
name: Parameter name
|
|
547
|
+
value: Parameter value
|
|
548
|
+
|
|
549
|
+
Returns:
|
|
550
|
+
New SQL instance with the added parameter
|
|
551
|
+
"""
|
|
552
|
+
original_params = self._original_parameters
|
|
553
|
+
config = self._statement_config
|
|
554
|
+
is_many = self._is_many
|
|
555
|
+
new_sql = SQL(self._raw_sql, *original_params, statement_config=config, is_many=is_many)
|
|
556
|
+
new_sql._named_parameters.update(self._named_parameters)
|
|
557
|
+
new_sql._named_parameters[name] = value
|
|
558
|
+
new_sql._positional_parameters = self._positional_parameters.copy()
|
|
559
|
+
new_sql._filters = self._filters.copy()
|
|
560
|
+
return new_sql
|
|
561
|
+
|
|
562
|
+
def where(self, condition: "str | exp.Expression") -> "SQL":
|
|
563
|
+
"""Add WHERE condition to the SQL statement.
|
|
564
|
+
|
|
565
|
+
Args:
|
|
566
|
+
condition: WHERE condition as string or SQLGlot expression
|
|
567
|
+
|
|
568
|
+
Returns:
|
|
569
|
+
New SQL instance with the WHERE condition applied
|
|
570
|
+
"""
|
|
571
|
+
try:
|
|
572
|
+
current_expr = sqlglot.parse_one(self._raw_sql, dialect=self._dialect)
|
|
573
|
+
except ParseError:
|
|
574
|
+
subquery_sql = f"SELECT * FROM ({self._raw_sql}) AS subquery"
|
|
575
|
+
current_expr = sqlglot.parse_one(subquery_sql, dialect=self._dialect)
|
|
576
|
+
|
|
577
|
+
condition_expr: exp.Expression
|
|
578
|
+
if isinstance(condition, str):
|
|
579
|
+
try:
|
|
580
|
+
condition_expr = sqlglot.parse_one(condition, dialect=self._dialect, into=exp.Condition)
|
|
581
|
+
except ParseError:
|
|
582
|
+
condition_expr = exp.Condition(this=condition)
|
|
583
|
+
else:
|
|
584
|
+
condition_expr = condition
|
|
585
|
+
|
|
586
|
+
if isinstance(current_expr, exp.Select) or supports_where(current_expr):
|
|
587
|
+
new_expr = current_expr.where(condition_expr, copy=False)
|
|
588
|
+
else:
|
|
589
|
+
new_expr = exp.Select().from_(current_expr).where(condition_expr, copy=False)
|
|
590
|
+
|
|
591
|
+
original_params = self._original_parameters
|
|
592
|
+
config = self._statement_config
|
|
593
|
+
is_many = self._is_many
|
|
594
|
+
new_sql = SQL(new_expr, *original_params, statement_config=config, is_many=is_many)
|
|
595
|
+
|
|
596
|
+
new_sql._named_parameters.update(self._named_parameters)
|
|
597
|
+
new_sql._positional_parameters = self._positional_parameters.copy()
|
|
598
|
+
new_sql._filters = self._filters.copy()
|
|
599
|
+
return new_sql
|
|
600
|
+
|
|
601
|
+
def __hash__(self) -> int:
|
|
602
|
+
"""Hash value computation."""
|
|
603
|
+
if self._hash is None:
|
|
604
|
+
positional_tuple = tuple(self._positional_parameters)
|
|
605
|
+
named_tuple = tuple(sorted(self._named_parameters.items())) if self._named_parameters else ()
|
|
606
|
+
raw_sql = self._raw_sql
|
|
607
|
+
is_many = self._is_many
|
|
608
|
+
is_script = self._is_script
|
|
609
|
+
self._hash = hash((raw_sql, positional_tuple, named_tuple, is_many, is_script))
|
|
610
|
+
return self._hash
|
|
611
|
+
|
|
612
|
+
def __eq__(self, other: object) -> bool:
|
|
613
|
+
"""Equality comparison."""
|
|
614
|
+
if not isinstance(other, SQL):
|
|
615
|
+
return False
|
|
616
|
+
return (
|
|
617
|
+
self._raw_sql == other._raw_sql
|
|
618
|
+
and self._positional_parameters == other._positional_parameters
|
|
619
|
+
and self._named_parameters == other._named_parameters
|
|
620
|
+
and self._is_many == other._is_many
|
|
621
|
+
and self._is_script == other._is_script
|
|
622
|
+
)
|
|
623
|
+
|
|
624
|
+
def __repr__(self) -> str:
|
|
625
|
+
"""String representation."""
|
|
626
|
+
params_parts = []
|
|
627
|
+
if self._positional_parameters:
|
|
628
|
+
params_parts.append(f"params={self._positional_parameters}")
|
|
629
|
+
if self._named_parameters:
|
|
630
|
+
params_parts.append(f"named_params={self._named_parameters}")
|
|
631
|
+
params_str = f", {', '.join(params_parts)}" if params_parts else ""
|
|
632
|
+
|
|
633
|
+
flags = []
|
|
634
|
+
if self._is_many:
|
|
635
|
+
flags.append("is_many")
|
|
636
|
+
if self._is_script:
|
|
637
|
+
flags.append("is_script")
|
|
638
|
+
flags_str = f", {', '.join(flags)}" if flags else ""
|
|
639
|
+
|
|
640
|
+
return f"SQL({self._raw_sql!r}{params_str}{flags_str})"
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
@mypyc_attr(allow_interpreted_subclasses=False)
|
|
644
|
+
class StatementConfig:
|
|
645
|
+
"""Configuration for SQL statement processing.
|
|
646
|
+
|
|
647
|
+
Controls SQL parsing, validation, transformations, parameter handling,
|
|
648
|
+
and other processing options for SQL statements.
|
|
649
|
+
"""
|
|
650
|
+
|
|
651
|
+
__slots__ = SQL_CONFIG_SLOTS
|
|
652
|
+
|
|
653
|
+
def __init__(
|
|
654
|
+
self,
|
|
655
|
+
parameter_config: "ParameterStyleConfig | None" = None,
|
|
656
|
+
enable_parsing: bool = True,
|
|
657
|
+
enable_validation: bool = True,
|
|
658
|
+
enable_transformations: bool = True,
|
|
659
|
+
enable_analysis: bool = False,
|
|
660
|
+
enable_expression_simplification: bool = False,
|
|
661
|
+
enable_parameter_type_wrapping: bool = True,
|
|
662
|
+
enable_caching: bool = True,
|
|
663
|
+
parameter_converter: "ParameterConverter | None" = None,
|
|
664
|
+
parameter_validator: "ParameterValidator | None" = None,
|
|
665
|
+
dialect: "DialectType | None" = None,
|
|
666
|
+
pre_process_steps: "list[Any] | None" = None,
|
|
667
|
+
post_process_steps: "list[Any] | None" = None,
|
|
668
|
+
execution_mode: "str | None" = None,
|
|
669
|
+
execution_args: "dict[str, Any] | None" = None,
|
|
670
|
+
output_transformer: "Callable[[str, Any], tuple[str, Any]] | None" = None,
|
|
671
|
+
) -> None:
|
|
672
|
+
"""Initialize StatementConfig.
|
|
673
|
+
|
|
674
|
+
Args:
|
|
675
|
+
parameter_config: Parameter style configuration
|
|
676
|
+
enable_parsing: Enable SQL parsing
|
|
677
|
+
enable_validation: Run SQL validators
|
|
678
|
+
enable_transformations: Apply SQL transformers
|
|
679
|
+
enable_analysis: Run SQL analyzers
|
|
680
|
+
enable_expression_simplification: Apply expression simplification
|
|
681
|
+
enable_parameter_type_wrapping: Wrap parameters with type information
|
|
682
|
+
enable_caching: Cache processed SQL statements
|
|
683
|
+
parameter_converter: Handles parameter style conversions
|
|
684
|
+
parameter_validator: Validates parameter usage and styles
|
|
685
|
+
dialect: SQL dialect
|
|
686
|
+
pre_process_steps: Optional list of preprocessing steps
|
|
687
|
+
post_process_steps: Optional list of postprocessing steps
|
|
688
|
+
execution_mode: Special execution mode
|
|
689
|
+
execution_args: Arguments for special execution modes
|
|
690
|
+
output_transformer: Optional output transformation function
|
|
691
|
+
"""
|
|
692
|
+
self.enable_parsing = enable_parsing
|
|
693
|
+
self.enable_validation = enable_validation
|
|
694
|
+
self.enable_transformations = enable_transformations
|
|
695
|
+
self.enable_analysis = enable_analysis
|
|
696
|
+
self.enable_expression_simplification = enable_expression_simplification
|
|
697
|
+
self.enable_parameter_type_wrapping = enable_parameter_type_wrapping
|
|
698
|
+
self.enable_caching = enable_caching
|
|
699
|
+
self.parameter_converter = parameter_converter or ParameterConverter()
|
|
700
|
+
self.parameter_validator = parameter_validator or ParameterValidator()
|
|
701
|
+
self.parameter_config = parameter_config or ParameterStyleConfig(
|
|
702
|
+
default_parameter_style=ParameterStyle.QMARK, supported_parameter_styles={ParameterStyle.QMARK}
|
|
703
|
+
)
|
|
704
|
+
|
|
705
|
+
self.dialect = dialect
|
|
706
|
+
self.pre_process_steps = pre_process_steps
|
|
707
|
+
self.post_process_steps = post_process_steps
|
|
708
|
+
self.execution_mode = execution_mode
|
|
709
|
+
self.execution_args = execution_args
|
|
710
|
+
self.output_transformer = output_transformer
|
|
711
|
+
|
|
712
|
+
def replace(self, **kwargs: Any) -> "StatementConfig":
|
|
713
|
+
"""Immutable update pattern.
|
|
714
|
+
|
|
715
|
+
Args:
|
|
716
|
+
**kwargs: Attributes to update
|
|
717
|
+
|
|
718
|
+
Returns:
|
|
719
|
+
New StatementConfig instance with updated attributes
|
|
720
|
+
"""
|
|
721
|
+
for key in kwargs:
|
|
722
|
+
if key not in SQL_CONFIG_SLOTS:
|
|
723
|
+
msg = f"{key!r} is not a field in {type(self).__name__}"
|
|
724
|
+
raise TypeError(msg)
|
|
725
|
+
|
|
726
|
+
current_kwargs: dict[str, Any] = {
|
|
727
|
+
"parameter_config": self.parameter_config,
|
|
728
|
+
"enable_parsing": self.enable_parsing,
|
|
729
|
+
"enable_validation": self.enable_validation,
|
|
730
|
+
"enable_transformations": self.enable_transformations,
|
|
731
|
+
"enable_analysis": self.enable_analysis,
|
|
732
|
+
"enable_expression_simplification": self.enable_expression_simplification,
|
|
733
|
+
"enable_parameter_type_wrapping": self.enable_parameter_type_wrapping,
|
|
734
|
+
"enable_caching": self.enable_caching,
|
|
735
|
+
"parameter_converter": self.parameter_converter,
|
|
736
|
+
"parameter_validator": self.parameter_validator,
|
|
737
|
+
"dialect": self.dialect,
|
|
738
|
+
"pre_process_steps": self.pre_process_steps,
|
|
739
|
+
"post_process_steps": self.post_process_steps,
|
|
740
|
+
"execution_mode": self.execution_mode,
|
|
741
|
+
"execution_args": self.execution_args,
|
|
742
|
+
"output_transformer": self.output_transformer,
|
|
743
|
+
}
|
|
744
|
+
current_kwargs.update(kwargs)
|
|
745
|
+
return type(self)(**current_kwargs)
|
|
746
|
+
|
|
747
|
+
def __hash__(self) -> int:
|
|
748
|
+
"""Hash based on configuration settings."""
|
|
749
|
+
return hash((
|
|
750
|
+
self.enable_parsing,
|
|
751
|
+
self.enable_validation,
|
|
752
|
+
self.enable_transformations,
|
|
753
|
+
self.enable_analysis,
|
|
754
|
+
self.enable_expression_simplification,
|
|
755
|
+
self.enable_parameter_type_wrapping,
|
|
756
|
+
self.enable_caching,
|
|
757
|
+
str(self.dialect),
|
|
758
|
+
))
|
|
759
|
+
|
|
760
|
+
def __repr__(self) -> str:
|
|
761
|
+
"""String representation of the StatementConfig instance."""
|
|
762
|
+
field_strs = [
|
|
763
|
+
f"parameter_config={self.parameter_config!r}",
|
|
764
|
+
f"enable_parsing={self.enable_parsing!r}",
|
|
765
|
+
f"enable_validation={self.enable_validation!r}",
|
|
766
|
+
f"enable_transformations={self.enable_transformations!r}",
|
|
767
|
+
f"enable_analysis={self.enable_analysis!r}",
|
|
768
|
+
f"enable_expression_simplification={self.enable_expression_simplification!r}",
|
|
769
|
+
f"enable_parameter_type_wrapping={self.enable_parameter_type_wrapping!r}",
|
|
770
|
+
f"enable_caching={self.enable_caching!r}",
|
|
771
|
+
f"parameter_converter={self.parameter_converter!r}",
|
|
772
|
+
f"parameter_validator={self.parameter_validator!r}",
|
|
773
|
+
f"dialect={self.dialect!r}",
|
|
774
|
+
f"pre_process_steps={self.pre_process_steps!r}",
|
|
775
|
+
f"post_process_steps={self.post_process_steps!r}",
|
|
776
|
+
f"execution_mode={self.execution_mode!r}",
|
|
777
|
+
f"execution_args={self.execution_args!r}",
|
|
778
|
+
f"output_transformer={self.output_transformer!r}",
|
|
779
|
+
]
|
|
780
|
+
return f"{self.__class__.__name__}({', '.join(field_strs)})"
|
|
781
|
+
|
|
782
|
+
def __eq__(self, other: object) -> bool:
|
|
783
|
+
"""Equality comparison."""
|
|
784
|
+
if not isinstance(other, type(self)):
|
|
785
|
+
return False
|
|
786
|
+
|
|
787
|
+
if not self._compare_parameter_configs(self.parameter_config, other.parameter_config):
|
|
788
|
+
return False
|
|
789
|
+
|
|
790
|
+
return (
|
|
791
|
+
self.enable_parsing == other.enable_parsing
|
|
792
|
+
and self.enable_validation == other.enable_validation
|
|
793
|
+
and self.enable_transformations == other.enable_transformations
|
|
794
|
+
and self.enable_analysis == other.enable_analysis
|
|
795
|
+
and self.enable_expression_simplification == other.enable_expression_simplification
|
|
796
|
+
and self.enable_parameter_type_wrapping == other.enable_parameter_type_wrapping
|
|
797
|
+
and self.enable_caching == other.enable_caching
|
|
798
|
+
and self.dialect == other.dialect
|
|
799
|
+
and self.pre_process_steps == other.pre_process_steps
|
|
800
|
+
and self.post_process_steps == other.post_process_steps
|
|
801
|
+
and self.execution_mode == other.execution_mode
|
|
802
|
+
and self.execution_args == other.execution_args
|
|
803
|
+
and self.output_transformer == other.output_transformer
|
|
804
|
+
)
|
|
805
|
+
|
|
806
|
+
def _compare_parameter_configs(self, config1: Any, config2: Any) -> bool:
|
|
807
|
+
"""Compare parameter configs."""
|
|
808
|
+
return bool(
|
|
809
|
+
config1.default_parameter_style == config2.default_parameter_style
|
|
810
|
+
and config1.supported_parameter_styles == config2.supported_parameter_styles
|
|
811
|
+
and config1.supported_execution_parameter_styles == config2.supported_execution_parameter_styles
|
|
812
|
+
)
|
|
813
|
+
|
|
814
|
+
|
|
815
|
+
def get_default_config() -> StatementConfig:
|
|
816
|
+
"""Get default statement configuration.
|
|
817
|
+
|
|
818
|
+
Returns:
|
|
819
|
+
StatementConfig with default settings
|
|
820
|
+
"""
|
|
821
|
+
return StatementConfig()
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
def get_default_parameter_config() -> ParameterStyleConfig:
|
|
825
|
+
"""Get default parameter configuration.
|
|
826
|
+
|
|
827
|
+
Returns:
|
|
828
|
+
ParameterStyleConfig with QMARK style as default
|
|
829
|
+
"""
|
|
830
|
+
return ParameterStyleConfig(
|
|
831
|
+
default_parameter_style=ParameterStyle.QMARK, supported_parameter_styles={ParameterStyle.QMARK}
|
|
832
|
+
)
|
|
833
|
+
|
|
834
|
+
|
|
835
|
+
Statement: TypeAlias = str | exp.Expression | SQL
|