sqlspec 0.13.0__py3-none-any.whl → 0.14.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 +39 -1
- sqlspec/adapters/adbc/config.py +4 -40
- sqlspec/adapters/adbc/driver.py +29 -16
- sqlspec/adapters/aiosqlite/config.py +15 -20
- sqlspec/adapters/aiosqlite/driver.py +36 -18
- sqlspec/adapters/asyncmy/config.py +16 -33
- sqlspec/adapters/asyncmy/driver.py +23 -16
- sqlspec/adapters/asyncpg/config.py +19 -61
- sqlspec/adapters/asyncpg/driver.py +41 -18
- sqlspec/adapters/bigquery/config.py +2 -43
- sqlspec/adapters/bigquery/driver.py +26 -14
- sqlspec/adapters/duckdb/config.py +2 -49
- sqlspec/adapters/duckdb/driver.py +35 -16
- sqlspec/adapters/oracledb/config.py +30 -83
- sqlspec/adapters/oracledb/driver.py +54 -27
- sqlspec/adapters/psqlpy/config.py +17 -57
- sqlspec/adapters/psqlpy/driver.py +28 -8
- sqlspec/adapters/psycopg/config.py +30 -73
- sqlspec/adapters/psycopg/driver.py +69 -24
- sqlspec/adapters/sqlite/config.py +3 -21
- sqlspec/adapters/sqlite/driver.py +50 -26
- sqlspec/cli.py +248 -0
- sqlspec/config.py +18 -20
- sqlspec/driver/_async.py +28 -10
- sqlspec/driver/_common.py +5 -4
- sqlspec/driver/_sync.py +28 -10
- sqlspec/driver/mixins/__init__.py +6 -0
- sqlspec/driver/mixins/_cache.py +114 -0
- sqlspec/driver/mixins/_pipeline.py +0 -4
- sqlspec/{service/base.py → driver/mixins/_query_tools.py} +86 -421
- sqlspec/driver/mixins/_result_utils.py +0 -2
- sqlspec/driver/mixins/_sql_translator.py +0 -2
- sqlspec/driver/mixins/_storage.py +4 -18
- sqlspec/driver/mixins/_type_coercion.py +0 -2
- sqlspec/driver/parameters.py +4 -4
- sqlspec/extensions/aiosql/adapter.py +4 -4
- sqlspec/extensions/litestar/__init__.py +2 -1
- sqlspec/extensions/litestar/cli.py +48 -0
- sqlspec/extensions/litestar/plugin.py +3 -0
- sqlspec/loader.py +1 -1
- sqlspec/migrations/__init__.py +23 -0
- sqlspec/migrations/base.py +390 -0
- sqlspec/migrations/commands.py +525 -0
- sqlspec/migrations/runner.py +215 -0
- sqlspec/migrations/tracker.py +153 -0
- sqlspec/migrations/utils.py +89 -0
- sqlspec/protocols.py +37 -3
- sqlspec/statement/builder/__init__.py +8 -8
- sqlspec/statement/builder/{column.py → _column.py} +82 -52
- sqlspec/statement/builder/{ddl.py → _ddl.py} +5 -5
- sqlspec/statement/builder/_ddl_utils.py +1 -1
- sqlspec/statement/builder/{delete.py → _delete.py} +1 -1
- sqlspec/statement/builder/{insert.py → _insert.py} +1 -1
- sqlspec/statement/builder/{merge.py → _merge.py} +1 -1
- sqlspec/statement/builder/_parsing_utils.py +5 -3
- sqlspec/statement/builder/{select.py → _select.py} +59 -61
- sqlspec/statement/builder/{update.py → _update.py} +2 -2
- sqlspec/statement/builder/mixins/__init__.py +24 -30
- sqlspec/statement/builder/mixins/{_set_ops.py → _cte_and_set_ops.py} +86 -2
- sqlspec/statement/builder/mixins/{_delete_from.py → _delete_operations.py} +2 -0
- sqlspec/statement/builder/mixins/{_insert_values.py → _insert_operations.py} +70 -1
- sqlspec/statement/builder/mixins/{_merge_clauses.py → _merge_operations.py} +2 -0
- sqlspec/statement/builder/mixins/_order_limit_operations.py +123 -0
- sqlspec/statement/builder/mixins/{_pivot.py → _pivot_operations.py} +71 -2
- sqlspec/statement/builder/mixins/_select_operations.py +612 -0
- sqlspec/statement/builder/mixins/{_update_set.py → _update_operations.py} +73 -2
- sqlspec/statement/builder/mixins/_where_clause.py +536 -0
- sqlspec/statement/cache.py +50 -0
- sqlspec/statement/filters.py +37 -8
- sqlspec/statement/parameters.py +154 -25
- sqlspec/statement/pipelines/__init__.py +1 -1
- sqlspec/statement/pipelines/context.py +4 -4
- sqlspec/statement/pipelines/transformers/_expression_simplifier.py +3 -3
- sqlspec/statement/pipelines/validators/_parameter_style.py +22 -22
- sqlspec/statement/pipelines/validators/_performance.py +1 -5
- sqlspec/statement/sql.py +246 -176
- sqlspec/utils/__init__.py +2 -1
- sqlspec/utils/statement_hashing.py +203 -0
- sqlspec/utils/type_guards.py +32 -0
- {sqlspec-0.13.0.dist-info → sqlspec-0.14.0.dist-info}/METADATA +1 -1
- sqlspec-0.14.0.dist-info/RECORD +143 -0
- sqlspec-0.14.0.dist-info/entry_points.txt +2 -0
- sqlspec/service/__init__.py +0 -4
- sqlspec/service/_util.py +0 -147
- sqlspec/service/pagination.py +0 -26
- sqlspec/statement/builder/mixins/_aggregate_functions.py +0 -250
- sqlspec/statement/builder/mixins/_case_builder.py +0 -91
- sqlspec/statement/builder/mixins/_common_table_expr.py +0 -90
- sqlspec/statement/builder/mixins/_from.py +0 -63
- sqlspec/statement/builder/mixins/_group_by.py +0 -118
- sqlspec/statement/builder/mixins/_having.py +0 -35
- sqlspec/statement/builder/mixins/_insert_from_select.py +0 -47
- sqlspec/statement/builder/mixins/_insert_into.py +0 -36
- sqlspec/statement/builder/mixins/_limit_offset.py +0 -53
- sqlspec/statement/builder/mixins/_order_by.py +0 -46
- sqlspec/statement/builder/mixins/_returning.py +0 -37
- sqlspec/statement/builder/mixins/_select_columns.py +0 -61
- sqlspec/statement/builder/mixins/_unpivot.py +0 -77
- sqlspec/statement/builder/mixins/_update_from.py +0 -55
- sqlspec/statement/builder/mixins/_update_table.py +0 -29
- sqlspec/statement/builder/mixins/_where.py +0 -401
- sqlspec/statement/builder/mixins/_window_functions.py +0 -86
- sqlspec/statement/parameter_manager.py +0 -220
- sqlspec/statement/sql_compiler.py +0 -140
- sqlspec-0.13.0.dist-info/RECORD +0 -150
- /sqlspec/statement/builder/{base.py → _base.py} +0 -0
- /sqlspec/statement/builder/mixins/{_join.py → _join_operations.py} +0 -0
- {sqlspec-0.13.0.dist-info → sqlspec-0.14.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.13.0.dist-info → sqlspec-0.14.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.13.0.dist-info → sqlspec-0.14.0.dist-info}/licenses/NOTICE +0 -0
sqlspec/statement/sql.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import operator
|
|
4
4
|
from dataclasses import dataclass, field
|
|
5
|
-
from typing import TYPE_CHECKING, Any, Optional, Union
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Callable, Optional, Union
|
|
6
6
|
|
|
7
7
|
import sqlglot
|
|
8
8
|
import sqlglot.expressions as exp
|
|
@@ -10,6 +10,7 @@ from sqlglot.errors import ParseError
|
|
|
10
10
|
from typing_extensions import TypeAlias
|
|
11
11
|
|
|
12
12
|
from sqlspec.exceptions import RiskLevel, SQLParsingError, SQLValidationError
|
|
13
|
+
from sqlspec.statement.cache import sql_cache
|
|
13
14
|
from sqlspec.statement.filters import StatementFilter
|
|
14
15
|
from sqlspec.statement.parameters import (
|
|
15
16
|
SQLGLOT_INCOMPATIBLE_STYLES,
|
|
@@ -19,8 +20,9 @@ from sqlspec.statement.parameters import (
|
|
|
19
20
|
)
|
|
20
21
|
from sqlspec.statement.pipelines import SQLProcessingContext, StatementPipeline
|
|
21
22
|
from sqlspec.statement.pipelines.transformers import CommentAndHintRemover, ParameterizeLiterals
|
|
22
|
-
from sqlspec.statement.pipelines.validators import DMLSafetyValidator, ParameterStyleValidator
|
|
23
|
+
from sqlspec.statement.pipelines.validators import DMLSafetyValidator, ParameterStyleValidator, SecurityValidator
|
|
23
24
|
from sqlspec.utils.logging import get_logger
|
|
25
|
+
from sqlspec.utils.statement_hashing import hash_sql_statement
|
|
24
26
|
from sqlspec.utils.type_guards import (
|
|
25
27
|
can_append_to_statement,
|
|
26
28
|
can_extract_parameters,
|
|
@@ -29,16 +31,13 @@ from sqlspec.utils.type_guards import (
|
|
|
29
31
|
is_dict,
|
|
30
32
|
is_expression,
|
|
31
33
|
is_statement_filter,
|
|
32
|
-
supports_limit,
|
|
33
|
-
supports_offset,
|
|
34
|
-
supports_order_by,
|
|
35
34
|
supports_where,
|
|
36
35
|
)
|
|
37
36
|
|
|
38
37
|
if TYPE_CHECKING:
|
|
39
38
|
from sqlglot.dialects.dialect import DialectType
|
|
40
39
|
|
|
41
|
-
from sqlspec.statement.parameters import
|
|
40
|
+
from sqlspec.statement.parameters import ParameterStyleTransformationState
|
|
42
41
|
|
|
43
42
|
__all__ = ("SQL", "SQLConfig", "Statement")
|
|
44
43
|
|
|
@@ -76,25 +75,33 @@ class _ProcessedState:
|
|
|
76
75
|
class SQLConfig:
|
|
77
76
|
"""Configuration for SQL statement behavior.
|
|
78
77
|
|
|
79
|
-
Uses conservative defaults that prioritize compatibility and robustness
|
|
80
|
-
|
|
81
|
-
and complex queries.
|
|
78
|
+
Uses conservative defaults that prioritize compatibility and robustness,
|
|
79
|
+
making it easier to work with diverse SQL dialects and complex queries.
|
|
82
80
|
|
|
83
|
-
|
|
81
|
+
Pipeline Configuration:
|
|
82
|
+
enable_parsing: Parse SQL strings using sqlglot (default: True)
|
|
83
|
+
enable_validation: Run SQL validators to check for safety issues (default: True)
|
|
84
|
+
enable_transformations: Apply SQL transformers like literal parameterization (default: True)
|
|
85
|
+
enable_analysis: Run SQL analyzers for metadata extraction (default: False)
|
|
86
|
+
enable_expression_simplification: Apply expression simplification transformer (default: False)
|
|
87
|
+
enable_parameter_type_wrapping: Wrap parameters with type information (default: True)
|
|
88
|
+
parse_errors_as_warnings: Treat parse errors as warnings instead of failures (default: True)
|
|
89
|
+
enable_caching: Cache processed SQL statements (default: True)
|
|
90
|
+
|
|
91
|
+
Component Lists (Advanced):
|
|
84
92
|
transformers: Optional list of SQL transformers for explicit staging
|
|
85
93
|
validators: Optional list of SQL validators for explicit staging
|
|
86
94
|
analyzers: Optional list of SQL analyzers for explicit staging
|
|
87
95
|
|
|
88
|
-
Configuration
|
|
96
|
+
Internal Configuration:
|
|
89
97
|
parameter_converter: Handles parameter style conversions
|
|
90
98
|
parameter_validator: Validates parameter usage and styles
|
|
91
|
-
analysis_cache_size: Cache size for analysis results
|
|
92
99
|
input_sql_had_placeholders: Populated by SQL.__init__ to track original SQL state
|
|
93
100
|
dialect: SQL dialect to use for parsing and generation
|
|
94
101
|
|
|
95
102
|
Parameter Style Configuration:
|
|
96
103
|
allowed_parameter_styles: Allowed parameter styles (e.g., ('qmark', 'named_colon'))
|
|
97
|
-
|
|
104
|
+
default_parameter_style: Target parameter style for SQL generation
|
|
98
105
|
allow_mixed_parameter_styles: Whether to allow mixing parameter styles in same query
|
|
99
106
|
"""
|
|
100
107
|
|
|
@@ -102,10 +109,10 @@ class SQLConfig:
|
|
|
102
109
|
enable_validation: bool = True
|
|
103
110
|
enable_transformations: bool = True
|
|
104
111
|
enable_analysis: bool = False
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
cache_parsed_expression: bool = True
|
|
112
|
+
enable_expression_simplification: bool = False
|
|
113
|
+
enable_parameter_type_wrapping: bool = True
|
|
108
114
|
parse_errors_as_warnings: bool = True
|
|
115
|
+
enable_caching: bool = True
|
|
109
116
|
|
|
110
117
|
transformers: "Optional[list[Any]]" = None
|
|
111
118
|
validators: "Optional[list[Any]]" = None
|
|
@@ -113,13 +120,13 @@ class SQLConfig:
|
|
|
113
120
|
|
|
114
121
|
parameter_converter: ParameterConverter = field(default_factory=ParameterConverter)
|
|
115
122
|
parameter_validator: ParameterValidator = field(default_factory=ParameterValidator)
|
|
116
|
-
analysis_cache_size: int = 1000
|
|
117
123
|
input_sql_had_placeholders: bool = False
|
|
118
124
|
dialect: "Optional[DialectType]" = None
|
|
119
125
|
|
|
120
126
|
allowed_parameter_styles: "Optional[tuple[str, ...]]" = None
|
|
121
|
-
|
|
127
|
+
default_parameter_style: "Optional[str]" = None
|
|
122
128
|
allow_mixed_parameter_styles: bool = False
|
|
129
|
+
analyzer_output_handler: "Optional[Callable[[Any], None]]" = None
|
|
123
130
|
|
|
124
131
|
def validate_parameter_style(self, style: "Union[ParameterStyle, str]") -> bool:
|
|
125
132
|
"""Check if a parameter style is allowed.
|
|
@@ -145,22 +152,37 @@ class SQLConfig:
|
|
|
145
152
|
if self.transformers is not None:
|
|
146
153
|
transformers = list(self.transformers)
|
|
147
154
|
elif self.enable_transformations:
|
|
148
|
-
placeholder_style = self.
|
|
155
|
+
placeholder_style = self.default_parameter_style or "?"
|
|
149
156
|
transformers = [CommentAndHintRemover(), ParameterizeLiterals(placeholder_style=placeholder_style)]
|
|
157
|
+
if self.enable_expression_simplification:
|
|
158
|
+
from sqlspec.statement.pipelines.transformers import ExpressionSimplifier
|
|
159
|
+
|
|
160
|
+
transformers.append(ExpressionSimplifier())
|
|
150
161
|
|
|
151
162
|
validators = []
|
|
152
163
|
if self.validators is not None:
|
|
153
164
|
validators = list(self.validators)
|
|
154
165
|
elif self.enable_validation:
|
|
155
|
-
validators = [
|
|
166
|
+
validators = [
|
|
167
|
+
ParameterStyleValidator(fail_on_violation=not self.parse_errors_as_warnings),
|
|
168
|
+
DMLSafetyValidator(),
|
|
169
|
+
SecurityValidator(),
|
|
170
|
+
]
|
|
156
171
|
|
|
157
172
|
analyzers = []
|
|
158
173
|
if self.analyzers is not None:
|
|
159
174
|
analyzers = list(self.analyzers)
|
|
160
175
|
elif self.enable_analysis:
|
|
161
|
-
analyzers
|
|
176
|
+
from sqlspec.statement.pipelines.analyzers import StatementAnalyzer
|
|
177
|
+
|
|
178
|
+
analyzers = [StatementAnalyzer()]
|
|
179
|
+
|
|
180
|
+
return StatementPipeline(transformers=transformers, validators=validators, analyzers=analyzers) # pyright: ignore
|
|
162
181
|
|
|
163
|
-
|
|
182
|
+
|
|
183
|
+
def default_analysis_handler(analysis: Any) -> None:
|
|
184
|
+
"""Default handler that logs analysis to debug."""
|
|
185
|
+
logger.debug("SQL Analysis: %s", analysis)
|
|
164
186
|
|
|
165
187
|
|
|
166
188
|
class SQL:
|
|
@@ -185,7 +207,7 @@ class SQL:
|
|
|
185
207
|
"_named_params",
|
|
186
208
|
"_original_parameters",
|
|
187
209
|
"_original_sql",
|
|
188
|
-
"
|
|
210
|
+
"_parameter_conversion_state",
|
|
189
211
|
"_placeholder_mapping",
|
|
190
212
|
"_positional_params",
|
|
191
213
|
"_processed_state",
|
|
@@ -208,7 +230,7 @@ class SQL:
|
|
|
208
230
|
if "config" in kwargs and _config is None:
|
|
209
231
|
_config = kwargs.pop("config")
|
|
210
232
|
self._config = _config or SQLConfig()
|
|
211
|
-
self._dialect = _dialect or
|
|
233
|
+
self._dialect = _dialect or self._config.dialect
|
|
212
234
|
self._builder_result_type = _builder_result_type
|
|
213
235
|
self._processed_state: Optional[_ProcessedState] = None
|
|
214
236
|
self._processing_context: Optional[SQLProcessingContext] = None
|
|
@@ -220,12 +242,12 @@ class SQL:
|
|
|
220
242
|
self._original_parameters: Any = None
|
|
221
243
|
self._original_sql: str = ""
|
|
222
244
|
self._placeholder_mapping: dict[str, Union[str, int]] = {}
|
|
223
|
-
self.
|
|
245
|
+
self._parameter_conversion_state: Optional[ParameterStyleTransformationState] = None
|
|
224
246
|
self._is_many: bool = False
|
|
225
247
|
self._is_script: bool = False
|
|
226
248
|
|
|
227
249
|
if isinstance(statement, SQL):
|
|
228
|
-
self._init_from_sql_object(statement, _dialect, _config, _builder_result_type)
|
|
250
|
+
self._init_from_sql_object(statement, _dialect, _config or SQLConfig(), _builder_result_type)
|
|
229
251
|
else:
|
|
230
252
|
self._init_from_str_or_expression(statement)
|
|
231
253
|
|
|
@@ -238,13 +260,9 @@ class SQL:
|
|
|
238
260
|
self._process_parameters(*parameters, **kwargs)
|
|
239
261
|
|
|
240
262
|
def _init_from_sql_object(
|
|
241
|
-
self,
|
|
242
|
-
statement: "SQL",
|
|
243
|
-
dialect: "DialectType",
|
|
244
|
-
config: "Optional[SQLConfig]",
|
|
245
|
-
builder_result_type: "Optional[type]",
|
|
263
|
+
self, statement: "SQL", dialect: "DialectType", config: "SQLConfig", builder_result_type: "Optional[type]"
|
|
246
264
|
) -> None:
|
|
247
|
-
"""Initialize
|
|
265
|
+
"""Initialize from an existing SQL object."""
|
|
248
266
|
self._statement = statement._statement
|
|
249
267
|
self._dialect = dialect or statement._dialect
|
|
250
268
|
self._config = config or statement._config
|
|
@@ -255,18 +273,18 @@ class SQL:
|
|
|
255
273
|
self._original_parameters = statement._original_parameters
|
|
256
274
|
self._original_sql = statement._original_sql
|
|
257
275
|
self._placeholder_mapping = statement._placeholder_mapping.copy()
|
|
258
|
-
self.
|
|
276
|
+
self._parameter_conversion_state = statement._parameter_conversion_state
|
|
259
277
|
self._positional_params.extend(statement._positional_params)
|
|
260
278
|
self._named_params.update(statement._named_params)
|
|
261
279
|
self._filters.extend(statement._filters)
|
|
262
280
|
|
|
263
281
|
def _init_from_str_or_expression(self, statement: "Union[str, exp.Expression]") -> None:
|
|
264
|
-
"""Initialize
|
|
282
|
+
"""Initialize from a string or expression."""
|
|
265
283
|
if isinstance(statement, str):
|
|
266
284
|
self._raw_sql = statement
|
|
267
285
|
self._statement = self._to_expression(statement)
|
|
268
286
|
else:
|
|
269
|
-
self._raw_sql = statement.sql(dialect=self._dialect)
|
|
287
|
+
self._raw_sql = statement.sql(dialect=self._dialect)
|
|
270
288
|
self._statement = statement
|
|
271
289
|
|
|
272
290
|
def _load_from_existing_state(self, existing_state: "dict[str, Any]") -> None:
|
|
@@ -280,8 +298,8 @@ class SQL:
|
|
|
280
298
|
self._original_parameters = existing_state.get("original_parameters", self._original_parameters)
|
|
281
299
|
|
|
282
300
|
def _set_original_parameters(self, *parameters: Any) -> None:
|
|
283
|
-
"""
|
|
284
|
-
if
|
|
301
|
+
"""Set the original parameters."""
|
|
302
|
+
if not parameters or (len(parameters) == 1 and is_statement_filter(parameters[0])):
|
|
285
303
|
self._original_parameters = None
|
|
286
304
|
elif len(parameters) == 1 and isinstance(parameters[0], (list, tuple)):
|
|
287
305
|
self._original_parameters = parameters[0]
|
|
@@ -289,7 +307,7 @@ class SQL:
|
|
|
289
307
|
self._original_parameters = parameters
|
|
290
308
|
|
|
291
309
|
def _process_parameters(self, *parameters: Any, **kwargs: Any) -> None:
|
|
292
|
-
"""Process
|
|
310
|
+
"""Process and categorize parameters."""
|
|
293
311
|
for param in parameters:
|
|
294
312
|
self._process_parameter_item(param)
|
|
295
313
|
|
|
@@ -302,9 +320,11 @@ class SQL:
|
|
|
302
320
|
else:
|
|
303
321
|
self._positional_params.append(param_value)
|
|
304
322
|
|
|
305
|
-
for
|
|
306
|
-
|
|
307
|
-
|
|
323
|
+
self._named_params.update({k: v for k, v in kwargs.items() if not k.startswith("_")})
|
|
324
|
+
|
|
325
|
+
def _cache_key(self) -> str:
|
|
326
|
+
"""Generate a cache key for the current SQL state."""
|
|
327
|
+
return hash_sql_statement(self)
|
|
308
328
|
|
|
309
329
|
def _process_parameter_item(self, item: Any) -> None:
|
|
310
330
|
"""Process a single item from the parameters list."""
|
|
@@ -332,6 +352,16 @@ class SQL:
|
|
|
332
352
|
if self._processed_state is not None:
|
|
333
353
|
return
|
|
334
354
|
|
|
355
|
+
# Check cache first if caching is enabled
|
|
356
|
+
cache_key = None
|
|
357
|
+
if self._config.enable_caching:
|
|
358
|
+
cache_key = self._cache_key()
|
|
359
|
+
cached_state = sql_cache.get(cache_key)
|
|
360
|
+
|
|
361
|
+
if cached_state is not None:
|
|
362
|
+
self._processed_state = cached_state
|
|
363
|
+
return
|
|
364
|
+
|
|
335
365
|
final_expr, final_params = self._build_final_state()
|
|
336
366
|
has_placeholders = self._detect_placeholders()
|
|
337
367
|
initial_sql_for_context, final_params = self._prepare_context_sql(final_expr, final_params)
|
|
@@ -343,6 +373,10 @@ class SQL:
|
|
|
343
373
|
|
|
344
374
|
self._finalize_processed_state(result, processed_sql, merged_params)
|
|
345
375
|
|
|
376
|
+
# Store in cache if caching is enabled
|
|
377
|
+
if self._config.enable_caching and cache_key is not None and self._processed_state is not None:
|
|
378
|
+
sql_cache.set(cache_key, self._processed_state)
|
|
379
|
+
|
|
346
380
|
def _detect_placeholders(self) -> bool:
|
|
347
381
|
"""Detect if the raw SQL has placeholders."""
|
|
348
382
|
if self._raw_sql:
|
|
@@ -361,24 +395,24 @@ class SQL:
|
|
|
361
395
|
if is_expression(final_expr) and self._placeholder_mapping:
|
|
362
396
|
initial_sql_for_context = final_expr.sql(dialect=self._dialect or self._config.dialect)
|
|
363
397
|
if self._placeholder_mapping:
|
|
364
|
-
final_params = self.
|
|
398
|
+
final_params = self._convert_parameters(final_params)
|
|
365
399
|
|
|
366
400
|
return initial_sql_for_context, final_params
|
|
367
401
|
|
|
368
|
-
def
|
|
369
|
-
"""
|
|
402
|
+
def _convert_parameters(self, final_params: Any) -> Any:
|
|
403
|
+
"""Convert parameters based on placeholder mapping."""
|
|
370
404
|
if is_dict(final_params):
|
|
371
|
-
|
|
405
|
+
converted_params = {}
|
|
372
406
|
for placeholder_key, original_name in self._placeholder_mapping.items():
|
|
373
407
|
if str(original_name) in final_params:
|
|
374
|
-
|
|
408
|
+
converted_params[placeholder_key] = final_params[str(original_name)]
|
|
375
409
|
non_oracle_params = {
|
|
376
410
|
key: value
|
|
377
411
|
for key, value in final_params.items()
|
|
378
412
|
if key not in {str(name) for name in self._placeholder_mapping.values()}
|
|
379
413
|
}
|
|
380
|
-
|
|
381
|
-
return
|
|
414
|
+
converted_params.update(non_oracle_params)
|
|
415
|
+
return converted_params
|
|
382
416
|
if isinstance(final_params, (list, tuple)):
|
|
383
417
|
validator = self._config.parameter_validator
|
|
384
418
|
param_info = validator.extract_parameters(self._raw_sql)
|
|
@@ -386,21 +420,21 @@ class SQL:
|
|
|
386
420
|
all_numeric = all(p.name and p.name.isdigit() for p in param_info)
|
|
387
421
|
|
|
388
422
|
if all_numeric:
|
|
389
|
-
|
|
423
|
+
converted_params = {}
|
|
390
424
|
|
|
391
425
|
min_param_num = min(int(p.name) for p in param_info if p.name)
|
|
392
426
|
|
|
393
427
|
for i, param in enumerate(final_params):
|
|
394
428
|
param_num = str(i + min_param_num)
|
|
395
|
-
|
|
429
|
+
converted_params[param_num] = param
|
|
396
430
|
|
|
397
|
-
return
|
|
398
|
-
|
|
431
|
+
return converted_params
|
|
432
|
+
converted_params = {}
|
|
399
433
|
for i, param in enumerate(final_params):
|
|
400
434
|
if i < len(param_info):
|
|
401
435
|
placeholder_key = f"{PARAM_PREFIX}{param_info[i].ordinal}"
|
|
402
|
-
|
|
403
|
-
return
|
|
436
|
+
converted_params[placeholder_key] = param
|
|
437
|
+
return converted_params
|
|
404
438
|
return final_params
|
|
405
439
|
|
|
406
440
|
def _create_processing_context(
|
|
@@ -420,9 +454,9 @@ class SQL:
|
|
|
420
454
|
if self._placeholder_mapping:
|
|
421
455
|
context.extra_info["placeholder_map"] = self._placeholder_mapping
|
|
422
456
|
|
|
423
|
-
# Set
|
|
424
|
-
if self.
|
|
425
|
-
context.
|
|
457
|
+
# Set conversion state if available
|
|
458
|
+
if self._parameter_conversion_state:
|
|
459
|
+
context.parameter_conversion = self._parameter_conversion_state
|
|
426
460
|
|
|
427
461
|
validator = self._config.parameter_validator
|
|
428
462
|
context.parameter_info = validator.extract_parameters(context.initial_sql_string)
|
|
@@ -445,9 +479,26 @@ class SQL:
|
|
|
445
479
|
if isinstance(processed_expr, exp.Anonymous):
|
|
446
480
|
processed_sql = self._raw_sql or context.initial_sql_string
|
|
447
481
|
else:
|
|
448
|
-
|
|
449
|
-
|
|
482
|
+
# Use the initial expression that includes filters, not the processed one
|
|
483
|
+
# The processed expression may have lost LIMIT/OFFSET during pipeline processing
|
|
484
|
+
if hasattr(context, "initial_expression") and context.initial_expression != processed_expr:
|
|
485
|
+
# Check if LIMIT/OFFSET was stripped during processing
|
|
486
|
+
has_limit_in_initial = (
|
|
487
|
+
context.initial_expression is not None
|
|
488
|
+
and hasattr(context.initial_expression, "args")
|
|
489
|
+
and "limit" in context.initial_expression.args
|
|
490
|
+
)
|
|
491
|
+
has_limit_in_processed = hasattr(processed_expr, "args") and "limit" in processed_expr.args
|
|
492
|
+
|
|
493
|
+
if has_limit_in_initial and not has_limit_in_processed:
|
|
494
|
+
# Restore LIMIT/OFFSET from initial expression
|
|
495
|
+
processed_expr = context.initial_expression
|
|
450
496
|
|
|
497
|
+
processed_sql = (
|
|
498
|
+
processed_expr.sql(dialect=self._dialect or self._config.dialect, comments=False)
|
|
499
|
+
if processed_expr
|
|
500
|
+
else ""
|
|
501
|
+
)
|
|
451
502
|
if self._placeholder_mapping and self._original_sql:
|
|
452
503
|
processed_sql, result = self._denormalize_sql(processed_sql, result)
|
|
453
504
|
|
|
@@ -461,22 +512,14 @@ class SQL:
|
|
|
461
512
|
original_sql = self._original_sql
|
|
462
513
|
param_info = self._config.parameter_validator.extract_parameters(original_sql)
|
|
463
514
|
target_styles = {p.style for p in param_info}
|
|
464
|
-
|
|
465
|
-
logger.debug(
|
|
466
|
-
"Denormalizing SQL: before='%s', original='%s', styles=%s", processed_sql, original_sql, target_styles
|
|
467
|
-
)
|
|
468
|
-
|
|
469
515
|
if ParameterStyle.POSITIONAL_PYFORMAT in target_styles:
|
|
470
516
|
processed_sql = self._config.parameter_converter._convert_sql_placeholders(
|
|
471
517
|
processed_sql, param_info, ParameterStyle.POSITIONAL_PYFORMAT
|
|
472
518
|
)
|
|
473
|
-
logger.debug("Denormalized SQL to: '%s'", processed_sql)
|
|
474
519
|
elif ParameterStyle.NAMED_PYFORMAT in target_styles:
|
|
475
520
|
processed_sql = self._config.parameter_converter._convert_sql_placeholders(
|
|
476
521
|
processed_sql, param_info, ParameterStyle.NAMED_PYFORMAT
|
|
477
522
|
)
|
|
478
|
-
logger.debug("Denormalized SQL to: '%s'", processed_sql)
|
|
479
|
-
# Also denormalize the parameters back to their original names
|
|
480
523
|
if (
|
|
481
524
|
self._placeholder_mapping
|
|
482
525
|
and result.context.merged_parameters
|
|
@@ -487,25 +530,16 @@ class SQL:
|
|
|
487
530
|
processed_param_info = self._config.parameter_validator.extract_parameters(processed_sql)
|
|
488
531
|
has_param_placeholders = any(p.name and p.name.startswith(PARAM_PREFIX) for p in processed_param_info)
|
|
489
532
|
|
|
490
|
-
if has_param_placeholders:
|
|
491
|
-
logger.debug("Skipping denormalization for param_N placeholders")
|
|
492
|
-
else:
|
|
533
|
+
if not has_param_placeholders:
|
|
493
534
|
processed_sql = self._config.parameter_converter._convert_sql_placeholders(
|
|
494
535
|
processed_sql, param_info, ParameterStyle.POSITIONAL_COLON
|
|
495
536
|
)
|
|
496
|
-
logger.debug("Denormalized SQL to: '%s'", processed_sql)
|
|
497
537
|
if (
|
|
498
538
|
self._placeholder_mapping
|
|
499
539
|
and result.context.merged_parameters
|
|
500
540
|
and is_dict(result.context.merged_parameters)
|
|
501
541
|
):
|
|
502
542
|
result.context.merged_parameters = self._denormalize_colon_params(result.context.merged_parameters)
|
|
503
|
-
else:
|
|
504
|
-
logger.debug(
|
|
505
|
-
"No denormalization needed: mapping=%s, original=%s",
|
|
506
|
-
bool(self._placeholder_mapping),
|
|
507
|
-
bool(self._original_sql),
|
|
508
|
-
)
|
|
509
543
|
|
|
510
544
|
return processed_sql, result
|
|
511
545
|
|
|
@@ -524,15 +558,15 @@ class SQL:
|
|
|
524
558
|
|
|
525
559
|
def _denormalize_pyformat_params(self, params: "dict[str, Any]") -> "dict[str, Any]":
|
|
526
560
|
"""Denormalize pyformat parameters back to their original names."""
|
|
527
|
-
|
|
561
|
+
deconverted_params = {}
|
|
528
562
|
for placeholder_key, original_name in self._placeholder_mapping.items():
|
|
529
563
|
if placeholder_key in params:
|
|
530
564
|
# For pyformat, the original_name is the actual parameter name (e.g., 'max_value')
|
|
531
|
-
|
|
532
|
-
# Include any parameters that weren't
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
return
|
|
565
|
+
deconverted_params[str(original_name)] = params[placeholder_key]
|
|
566
|
+
# Include any parameters that weren't converted
|
|
567
|
+
non_converted_params = {key: value for key, value in params.items() if not key.startswith(PARAM_PREFIX)}
|
|
568
|
+
deconverted_params.update(non_converted_params)
|
|
569
|
+
return deconverted_params
|
|
536
570
|
|
|
537
571
|
def _merge_pipeline_parameters(self, result: Any, final_params: Any) -> Any:
|
|
538
572
|
"""Merge parameters from the pipeline processing."""
|
|
@@ -569,16 +603,51 @@ class SQL:
|
|
|
569
603
|
|
|
570
604
|
def _finalize_processed_state(self, result: Any, processed_sql: str, merged_params: Any) -> None:
|
|
571
605
|
"""Finalize the processed state."""
|
|
606
|
+
# Wrap parameters with type information if enabled
|
|
607
|
+
if self._config.enable_parameter_type_wrapping and merged_params is not None:
|
|
608
|
+
# Get parameter info from the processed SQL
|
|
609
|
+
validator = self._config.parameter_validator
|
|
610
|
+
param_info = validator.extract_parameters(processed_sql)
|
|
611
|
+
|
|
612
|
+
# Wrap parameters with type information
|
|
613
|
+
converter = self._config.parameter_converter
|
|
614
|
+
merged_params = converter.wrap_parameters_with_types(merged_params, param_info)
|
|
615
|
+
|
|
616
|
+
# Extract analyzer results from context metadata
|
|
617
|
+
analysis_results = (
|
|
618
|
+
{key: value for key, value in result.context.metadata.items() if key.endswith("Analyzer")}
|
|
619
|
+
if result.context.metadata
|
|
620
|
+
else {}
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
# If analyzer output handler is configured, call it with the analysis
|
|
624
|
+
if self._config.analyzer_output_handler and analysis_results:
|
|
625
|
+
# Create a structured analysis object from the metadata
|
|
626
|
+
|
|
627
|
+
# Extract the main analyzer results
|
|
628
|
+
analyzer_metadata = analysis_results.get("StatementAnalyzer", {})
|
|
629
|
+
if analyzer_metadata:
|
|
630
|
+
# Create a simplified analysis object for the handler
|
|
631
|
+
analysis = {
|
|
632
|
+
"statement_type": analyzer_metadata.get("statement_type"),
|
|
633
|
+
"complexity_score": analyzer_metadata.get("complexity_score"),
|
|
634
|
+
"table_count": analyzer_metadata.get("table_count"),
|
|
635
|
+
"has_subqueries": analyzer_metadata.get("has_subqueries"),
|
|
636
|
+
"join_count": analyzer_metadata.get("join_count"),
|
|
637
|
+
"duration_ms": analyzer_metadata.get("duration_ms"),
|
|
638
|
+
}
|
|
639
|
+
self._config.analyzer_output_handler(analysis)
|
|
640
|
+
|
|
572
641
|
self._processed_state = _ProcessedState(
|
|
573
642
|
processed_expression=result.expression,
|
|
574
643
|
processed_sql=processed_sql,
|
|
575
644
|
merged_parameters=merged_params,
|
|
576
645
|
validation_errors=list(result.context.validation_errors),
|
|
577
|
-
analysis_results=
|
|
646
|
+
analysis_results=analysis_results,
|
|
578
647
|
transformation_results={},
|
|
579
648
|
)
|
|
580
649
|
|
|
581
|
-
if self._config.
|
|
650
|
+
if not self._config.parse_errors_as_warnings and self._processed_state.validation_errors:
|
|
582
651
|
highest_risk_error = max(
|
|
583
652
|
self._processed_state.validation_errors, key=lambda e: e.risk_level.value if has_risk_level(e) else 0
|
|
584
653
|
)
|
|
@@ -604,33 +673,33 @@ class SQL:
|
|
|
604
673
|
validator = self._config.parameter_validator
|
|
605
674
|
param_info = validator.extract_parameters(statement)
|
|
606
675
|
|
|
607
|
-
# Check if
|
|
608
|
-
|
|
676
|
+
# Check if conversion is needed
|
|
677
|
+
needs_conversion = any(p.style in SQLGLOT_INCOMPATIBLE_STYLES for p in param_info)
|
|
609
678
|
|
|
610
|
-
|
|
679
|
+
converted_sql = statement
|
|
611
680
|
placeholder_mapping: dict[str, Any] = {}
|
|
612
681
|
|
|
613
|
-
if
|
|
682
|
+
if needs_conversion:
|
|
614
683
|
converter = self._config.parameter_converter
|
|
615
|
-
|
|
684
|
+
converted_sql, placeholder_mapping = converter._transform_sql_for_parsing(statement, param_info)
|
|
616
685
|
self._original_sql = statement
|
|
617
686
|
self._placeholder_mapping = placeholder_mapping
|
|
618
687
|
|
|
619
|
-
# Create
|
|
620
|
-
from sqlspec.statement.parameters import
|
|
688
|
+
# Create conversion state
|
|
689
|
+
from sqlspec.statement.parameters import ParameterStyleTransformationState
|
|
621
690
|
|
|
622
|
-
self.
|
|
623
|
-
|
|
691
|
+
self._parameter_conversion_state = ParameterStyleTransformationState(
|
|
692
|
+
was_transformed=True,
|
|
624
693
|
original_styles=list({p.style for p in param_info}),
|
|
625
|
-
|
|
694
|
+
transformation_style=ParameterStyle.NAMED_COLON,
|
|
626
695
|
placeholder_map=placeholder_mapping,
|
|
627
696
|
original_param_info=param_info,
|
|
628
697
|
)
|
|
629
698
|
else:
|
|
630
|
-
self.
|
|
699
|
+
self._parameter_conversion_state = None
|
|
631
700
|
|
|
632
701
|
try:
|
|
633
|
-
expressions = sqlglot.parse(
|
|
702
|
+
expressions = sqlglot.parse(converted_sql, dialect=self._dialect) # pyright: ignore
|
|
634
703
|
if not expressions:
|
|
635
704
|
return exp.Anonymous(this=statement)
|
|
636
705
|
first_expr = expressions[0]
|
|
@@ -779,22 +848,34 @@ class SQL:
|
|
|
779
848
|
"""Build final expression and parameters after applying filters."""
|
|
780
849
|
final_expr = self._statement
|
|
781
850
|
|
|
851
|
+
# Accumulate parameters from both the original SQL and filters
|
|
852
|
+
accumulated_positional = list(self._positional_params)
|
|
853
|
+
accumulated_named = dict(self._named_params)
|
|
854
|
+
|
|
782
855
|
for filter_obj in self._filters:
|
|
783
856
|
if can_append_to_statement(filter_obj):
|
|
784
857
|
temp_sql = SQL(final_expr, config=self._config, dialect=self._dialect)
|
|
785
|
-
temp_sql._positional_params = list(
|
|
786
|
-
temp_sql._named_params = dict(
|
|
858
|
+
temp_sql._positional_params = list(accumulated_positional)
|
|
859
|
+
temp_sql._named_params = dict(accumulated_named)
|
|
787
860
|
result = filter_obj.append_to_statement(temp_sql)
|
|
788
|
-
|
|
861
|
+
|
|
862
|
+
if isinstance(result, SQL):
|
|
863
|
+
# Extract the modified expression
|
|
864
|
+
final_expr = result._statement
|
|
865
|
+
# Also preserve any parameters added by the filter
|
|
866
|
+
accumulated_positional = list(result._positional_params)
|
|
867
|
+
accumulated_named = dict(result._named_params)
|
|
868
|
+
else:
|
|
869
|
+
final_expr = result
|
|
789
870
|
|
|
790
871
|
final_params: Any
|
|
791
|
-
if
|
|
792
|
-
final_params = dict(
|
|
793
|
-
elif
|
|
794
|
-
final_params = list(
|
|
795
|
-
elif
|
|
796
|
-
final_params = dict(
|
|
797
|
-
for i, param in enumerate(
|
|
872
|
+
if accumulated_named and not accumulated_positional:
|
|
873
|
+
final_params = dict(accumulated_named)
|
|
874
|
+
elif accumulated_positional and not accumulated_named:
|
|
875
|
+
final_params = list(accumulated_positional)
|
|
876
|
+
elif accumulated_positional and accumulated_named:
|
|
877
|
+
final_params = dict(accumulated_named)
|
|
878
|
+
for i, param in enumerate(accumulated_positional):
|
|
798
879
|
param_name = f"arg_{i}"
|
|
799
880
|
while param_name in final_params:
|
|
800
881
|
param_name = f"arg_{i}_{id(param)}"
|
|
@@ -821,6 +902,11 @@ class SQL:
|
|
|
821
902
|
raise RuntimeError(msg)
|
|
822
903
|
return self._processed_state.processed_sql
|
|
823
904
|
|
|
905
|
+
@property
|
|
906
|
+
def config(self) -> "SQLConfig":
|
|
907
|
+
"""Get the SQL configuration."""
|
|
908
|
+
return self._config
|
|
909
|
+
|
|
824
910
|
@property
|
|
825
911
|
def expression(self) -> "Optional[exp.Expression]":
|
|
826
912
|
"""Get the final expression."""
|
|
@@ -882,22 +968,48 @@ class SQL:
|
|
|
882
968
|
return params
|
|
883
969
|
|
|
884
970
|
def _compile_execute_many(self, placeholder_style: "Optional[str]") -> "tuple[str, Any]":
|
|
885
|
-
"""
|
|
886
|
-
sql = self.sql
|
|
971
|
+
"""Compile for execute_many operations.
|
|
887
972
|
|
|
973
|
+
The pipeline processed the first parameter set to extract literals.
|
|
974
|
+
Now we need to apply those extracted literals to all parameter sets.
|
|
975
|
+
"""
|
|
976
|
+
sql = self.sql
|
|
888
977
|
self._ensure_processed()
|
|
889
978
|
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
if
|
|
895
|
-
|
|
979
|
+
# Get the original parameter sets
|
|
980
|
+
param_sets = self._original_parameters or []
|
|
981
|
+
|
|
982
|
+
# Get any literals extracted during pipeline processing
|
|
983
|
+
if self._processed_state and self._processing_context:
|
|
984
|
+
extracted_literals = self._processing_context.extracted_parameters_from_pipeline
|
|
985
|
+
|
|
986
|
+
if extracted_literals:
|
|
987
|
+
# Apply extracted literals to each parameter set
|
|
988
|
+
enhanced_params: list[Any] = []
|
|
989
|
+
for param_set in param_sets:
|
|
990
|
+
if isinstance(param_set, (list, tuple)):
|
|
991
|
+
# Add extracted literals to the parameter tuple
|
|
992
|
+
enhanced_set = list(param_set) + [
|
|
993
|
+
p.value if hasattr(p, "value") else p for p in extracted_literals
|
|
994
|
+
]
|
|
995
|
+
enhanced_params.append(tuple(enhanced_set))
|
|
996
|
+
elif isinstance(param_set, dict):
|
|
997
|
+
# For dict params, add extracted literals with generated names
|
|
998
|
+
enhanced_dict = dict(param_set)
|
|
999
|
+
for i, literal in enumerate(extracted_literals):
|
|
1000
|
+
param_name = f"_literal_{i}"
|
|
1001
|
+
enhanced_dict[param_name] = literal.value if hasattr(literal, "value") else literal
|
|
1002
|
+
enhanced_params.append(enhanced_dict)
|
|
1003
|
+
else:
|
|
1004
|
+
# Single parameter - convert to tuple with literals
|
|
1005
|
+
literals = [p.value if hasattr(p, "value") else p for p in extracted_literals]
|
|
1006
|
+
enhanced_params.append((param_set, *literals))
|
|
1007
|
+
param_sets = enhanced_params
|
|
896
1008
|
|
|
897
1009
|
if placeholder_style:
|
|
898
|
-
sql,
|
|
1010
|
+
sql, param_sets = self._convert_placeholder_style(sql, param_sets, placeholder_style)
|
|
899
1011
|
|
|
900
|
-
return sql,
|
|
1012
|
+
return sql, param_sets
|
|
901
1013
|
|
|
902
1014
|
def _get_extracted_parameters(self) -> "list[Any]":
|
|
903
1015
|
"""Get extracted parameters from pipeline processing."""
|
|
@@ -958,13 +1070,13 @@ class SQL:
|
|
|
958
1070
|
if parameter_mapping:
|
|
959
1071
|
params = self._reorder_parameters(params, parameter_mapping)
|
|
960
1072
|
|
|
961
|
-
# Handle
|
|
962
|
-
if self._processing_context and self._processing_context.
|
|
963
|
-
norm_state = self._processing_context.
|
|
1073
|
+
# Handle deconversion if needed
|
|
1074
|
+
if self._processing_context and self._processing_context.parameter_conversion:
|
|
1075
|
+
norm_state = self._processing_context.parameter_conversion
|
|
964
1076
|
|
|
965
1077
|
# If original SQL had incompatible styles, denormalize back to the original style
|
|
966
1078
|
# when no specific style requested OR when the requested style matches the original
|
|
967
|
-
if norm_state.
|
|
1079
|
+
if norm_state.was_transformed and norm_state.original_styles:
|
|
968
1080
|
original_style = norm_state.original_styles[0]
|
|
969
1081
|
should_denormalize = placeholder_style is None or (
|
|
970
1082
|
placeholder_style and ParameterStyle(placeholder_style) == original_style
|
|
@@ -975,7 +1087,7 @@ class SQL:
|
|
|
975
1087
|
sql = self._config.parameter_converter._convert_sql_placeholders(
|
|
976
1088
|
sql, norm_state.original_param_info, original_style
|
|
977
1089
|
)
|
|
978
|
-
# Also
|
|
1090
|
+
# Also deConvert parameters if needed
|
|
979
1091
|
if original_style == ParameterStyle.POSITIONAL_COLON and is_dict(params):
|
|
980
1092
|
params = self._denormalize_colon_params(params)
|
|
981
1093
|
|
|
@@ -1103,10 +1215,10 @@ class SQL:
|
|
|
1103
1215
|
if (
|
|
1104
1216
|
target_style == ParameterStyle.POSITIONAL_COLON
|
|
1105
1217
|
and self._processing_context
|
|
1106
|
-
and self._processing_context.
|
|
1107
|
-
and self._processing_context.
|
|
1218
|
+
and self._processing_context.parameter_conversion
|
|
1219
|
+
and self._processing_context.parameter_conversion.original_param_info
|
|
1108
1220
|
):
|
|
1109
|
-
param_info = self._processing_context.
|
|
1221
|
+
param_info = self._processing_context.parameter_conversion.original_param_info
|
|
1110
1222
|
else:
|
|
1111
1223
|
param_info = converter.validator.extract_parameters(sql)
|
|
1112
1224
|
|
|
@@ -1310,7 +1422,7 @@ class SQL:
|
|
|
1310
1422
|
return result_dict
|
|
1311
1423
|
|
|
1312
1424
|
def _process_mixed_colon_params(self, params: "dict[str, Any]", param_info: "list[Any]") -> "dict[str, Any]":
|
|
1313
|
-
"""Process mixed colon-style numeric and
|
|
1425
|
+
"""Process mixed colon-style numeric and converted parameters."""
|
|
1314
1426
|
result_dict: dict[str, Any] = {}
|
|
1315
1427
|
|
|
1316
1428
|
# When we have mixed parameters (extracted literals + user oracle params),
|
|
@@ -1393,22 +1505,22 @@ class SQL:
|
|
|
1393
1505
|
if all(key.startswith("param_") for key in params):
|
|
1394
1506
|
param_result_dict: dict[str, Any] = {}
|
|
1395
1507
|
for p in sorted(param_info, key=lambda x: x.ordinal):
|
|
1396
|
-
# Use the parameter's ordinal to find the
|
|
1397
|
-
|
|
1398
|
-
if
|
|
1508
|
+
# Use the parameter's ordinal to find the converted key
|
|
1509
|
+
converted_key = f"param_{p.ordinal}"
|
|
1510
|
+
if converted_key in params:
|
|
1399
1511
|
if p.name and p.name.isdigit():
|
|
1400
1512
|
# For Oracle numeric parameters, preserve the original number
|
|
1401
|
-
param_result_dict[p.name] = params[
|
|
1513
|
+
param_result_dict[p.name] = params[converted_key]
|
|
1402
1514
|
else:
|
|
1403
1515
|
# For other cases, use sequential numbering
|
|
1404
|
-
param_result_dict[str(p.ordinal + 1)] = params[
|
|
1516
|
+
param_result_dict[str(p.ordinal + 1)] = params[converted_key]
|
|
1405
1517
|
return param_result_dict
|
|
1406
1518
|
|
|
1407
1519
|
has_oracle_numeric = any(key.isdigit() for key in params)
|
|
1408
|
-
|
|
1520
|
+
has_param_converted = any(key.startswith("param_") for key in params)
|
|
1409
1521
|
has_typed_params = any(has_parameter_value(v) for v in params.values())
|
|
1410
1522
|
|
|
1411
|
-
if (has_oracle_numeric and
|
|
1523
|
+
if (has_oracle_numeric and has_param_converted) or has_typed_params:
|
|
1412
1524
|
return self._process_mixed_colon_params(params, param_info)
|
|
1413
1525
|
|
|
1414
1526
|
result_dict: dict[str, Any] = {}
|
|
@@ -1628,7 +1740,7 @@ class SQL:
|
|
|
1628
1740
|
def parameter_info(self) -> list[Any]:
|
|
1629
1741
|
"""Get parameter information from the SQL statement.
|
|
1630
1742
|
|
|
1631
|
-
Returns the original parameter info before any
|
|
1743
|
+
Returns the original parameter info before any conversion.
|
|
1632
1744
|
"""
|
|
1633
1745
|
validator = self._config.parameter_validator
|
|
1634
1746
|
if self._raw_sql:
|
|
@@ -1660,45 +1772,3 @@ class SQL:
|
|
|
1660
1772
|
def statement(self) -> exp.Expression:
|
|
1661
1773
|
"""Get statement for compatibility."""
|
|
1662
1774
|
return self._statement
|
|
1663
|
-
|
|
1664
|
-
def limit(self, count: int, use_parameter: bool = False) -> "SQL":
|
|
1665
|
-
"""Add LIMIT clause."""
|
|
1666
|
-
if use_parameter:
|
|
1667
|
-
param_name = self.get_unique_parameter_name("limit")
|
|
1668
|
-
result = self
|
|
1669
|
-
result = result.add_named_parameter(param_name, count)
|
|
1670
|
-
if supports_limit(result._statement):
|
|
1671
|
-
new_statement = result._statement.limit(exp.Placeholder(this=param_name)) # pyright: ignore
|
|
1672
|
-
else:
|
|
1673
|
-
new_statement = exp.Select().from_(result._statement).limit(exp.Placeholder(this=param_name)) # pyright: ignore
|
|
1674
|
-
return result.copy(statement=new_statement)
|
|
1675
|
-
if supports_limit(self._statement):
|
|
1676
|
-
new_statement = self._statement.limit(count) # pyright: ignore
|
|
1677
|
-
else:
|
|
1678
|
-
new_statement = exp.Select().from_(self._statement).limit(count) # pyright: ignore
|
|
1679
|
-
return self.copy(statement=new_statement)
|
|
1680
|
-
|
|
1681
|
-
def offset(self, count: int, use_parameter: bool = False) -> "SQL":
|
|
1682
|
-
"""Add OFFSET clause."""
|
|
1683
|
-
if use_parameter:
|
|
1684
|
-
param_name = self.get_unique_parameter_name("offset")
|
|
1685
|
-
result = self
|
|
1686
|
-
result = result.add_named_parameter(param_name, count)
|
|
1687
|
-
if supports_offset(result._statement):
|
|
1688
|
-
new_statement = result._statement.offset(exp.Placeholder(this=param_name)) # pyright: ignore
|
|
1689
|
-
else:
|
|
1690
|
-
new_statement = exp.Select().from_(result._statement).offset(exp.Placeholder(this=param_name)) # pyright: ignore
|
|
1691
|
-
return result.copy(statement=new_statement)
|
|
1692
|
-
if supports_offset(self._statement):
|
|
1693
|
-
new_statement = self._statement.offset(count) # pyright: ignore
|
|
1694
|
-
else:
|
|
1695
|
-
new_statement = exp.Select().from_(self._statement).offset(count) # pyright: ignore
|
|
1696
|
-
return self.copy(statement=new_statement)
|
|
1697
|
-
|
|
1698
|
-
def order_by(self, expression: exp.Expression) -> "SQL":
|
|
1699
|
-
"""Add ORDER BY clause."""
|
|
1700
|
-
if supports_order_by(self._statement):
|
|
1701
|
-
new_statement = self._statement.order_by(expression) # pyright: ignore
|
|
1702
|
-
else:
|
|
1703
|
-
new_statement = exp.Select().from_(self._statement).order_by(expression) # pyright: ignore
|
|
1704
|
-
return self.copy(statement=new_statement)
|