sqlspec 0.13.1__py3-none-any.whl → 0.16.2__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 +71 -8
- sqlspec/__main__.py +12 -0
- sqlspec/__metadata__.py +1 -3
- sqlspec/_serialization.py +1 -2
- sqlspec/_sql.py +930 -136
- sqlspec/_typing.py +278 -142
- sqlspec/adapters/adbc/__init__.py +4 -3
- sqlspec/adapters/adbc/_types.py +12 -0
- sqlspec/adapters/adbc/config.py +116 -285
- sqlspec/adapters/adbc/driver.py +462 -340
- sqlspec/adapters/aiosqlite/__init__.py +18 -3
- sqlspec/adapters/aiosqlite/_types.py +13 -0
- sqlspec/adapters/aiosqlite/config.py +202 -150
- sqlspec/adapters/aiosqlite/driver.py +226 -247
- sqlspec/adapters/asyncmy/__init__.py +18 -3
- sqlspec/adapters/asyncmy/_types.py +12 -0
- sqlspec/adapters/asyncmy/config.py +80 -199
- sqlspec/adapters/asyncmy/driver.py +257 -215
- sqlspec/adapters/asyncpg/__init__.py +19 -4
- sqlspec/adapters/asyncpg/_types.py +17 -0
- sqlspec/adapters/asyncpg/config.py +81 -214
- sqlspec/adapters/asyncpg/driver.py +284 -359
- sqlspec/adapters/bigquery/__init__.py +17 -3
- sqlspec/adapters/bigquery/_types.py +12 -0
- sqlspec/adapters/bigquery/config.py +191 -299
- sqlspec/adapters/bigquery/driver.py +474 -634
- sqlspec/adapters/duckdb/__init__.py +14 -3
- sqlspec/adapters/duckdb/_types.py +12 -0
- sqlspec/adapters/duckdb/config.py +414 -397
- sqlspec/adapters/duckdb/driver.py +342 -393
- sqlspec/adapters/oracledb/__init__.py +19 -5
- sqlspec/adapters/oracledb/_types.py +14 -0
- sqlspec/adapters/oracledb/config.py +123 -458
- sqlspec/adapters/oracledb/driver.py +505 -531
- sqlspec/adapters/psqlpy/__init__.py +13 -3
- sqlspec/adapters/psqlpy/_types.py +11 -0
- sqlspec/adapters/psqlpy/config.py +93 -307
- sqlspec/adapters/psqlpy/driver.py +504 -213
- sqlspec/adapters/psycopg/__init__.py +19 -5
- sqlspec/adapters/psycopg/_types.py +17 -0
- sqlspec/adapters/psycopg/config.py +143 -472
- sqlspec/adapters/psycopg/driver.py +704 -825
- sqlspec/adapters/sqlite/__init__.py +14 -3
- sqlspec/adapters/sqlite/_types.py +11 -0
- sqlspec/adapters/sqlite/config.py +208 -142
- sqlspec/adapters/sqlite/driver.py +263 -278
- sqlspec/base.py +105 -9
- sqlspec/{statement/builder → builder}/__init__.py +12 -14
- sqlspec/{statement/builder/base.py → builder/_base.py} +184 -86
- sqlspec/{statement/builder/column.py → builder/_column.py} +97 -60
- sqlspec/{statement/builder/ddl.py → builder/_ddl.py} +61 -131
- sqlspec/{statement/builder → builder}/_ddl_utils.py +4 -10
- sqlspec/{statement/builder/delete.py → builder/_delete.py} +10 -30
- sqlspec/builder/_insert.py +421 -0
- sqlspec/builder/_merge.py +71 -0
- sqlspec/{statement/builder → builder}/_parsing_utils.py +49 -26
- sqlspec/builder/_select.py +170 -0
- sqlspec/{statement/builder/update.py → builder/_update.py} +16 -20
- sqlspec/builder/mixins/__init__.py +55 -0
- sqlspec/builder/mixins/_cte_and_set_ops.py +222 -0
- sqlspec/{statement/builder/mixins/_delete_from.py → builder/mixins/_delete_operations.py} +8 -1
- sqlspec/builder/mixins/_insert_operations.py +244 -0
- sqlspec/{statement/builder/mixins/_join.py → builder/mixins/_join_operations.py} +45 -13
- sqlspec/{statement/builder/mixins/_merge_clauses.py → builder/mixins/_merge_operations.py} +188 -30
- sqlspec/builder/mixins/_order_limit_operations.py +135 -0
- sqlspec/builder/mixins/_pivot_operations.py +153 -0
- sqlspec/builder/mixins/_select_operations.py +604 -0
- sqlspec/builder/mixins/_update_operations.py +202 -0
- sqlspec/builder/mixins/_where_clause.py +644 -0
- sqlspec/cli.py +247 -0
- sqlspec/config.py +183 -138
- sqlspec/core/__init__.py +63 -0
- sqlspec/core/cache.py +871 -0
- sqlspec/core/compiler.py +417 -0
- sqlspec/core/filters.py +830 -0
- sqlspec/core/hashing.py +310 -0
- sqlspec/core/parameters.py +1237 -0
- sqlspec/core/result.py +677 -0
- sqlspec/{statement → core}/splitter.py +321 -191
- sqlspec/core/statement.py +676 -0
- sqlspec/driver/__init__.py +7 -10
- sqlspec/driver/_async.py +422 -163
- sqlspec/driver/_common.py +545 -287
- sqlspec/driver/_sync.py +426 -160
- sqlspec/driver/mixins/__init__.py +2 -13
- sqlspec/driver/mixins/_result_tools.py +193 -0
- sqlspec/driver/mixins/_sql_translator.py +65 -14
- sqlspec/exceptions.py +5 -252
- sqlspec/extensions/aiosql/adapter.py +93 -96
- sqlspec/extensions/litestar/__init__.py +2 -1
- sqlspec/extensions/litestar/cli.py +48 -0
- sqlspec/extensions/litestar/config.py +0 -1
- sqlspec/extensions/litestar/handlers.py +15 -26
- sqlspec/extensions/litestar/plugin.py +21 -16
- sqlspec/extensions/litestar/providers.py +17 -52
- sqlspec/loader.py +423 -104
- sqlspec/migrations/__init__.py +35 -0
- sqlspec/migrations/base.py +414 -0
- sqlspec/migrations/commands.py +443 -0
- sqlspec/migrations/loaders.py +402 -0
- sqlspec/migrations/runner.py +213 -0
- sqlspec/migrations/tracker.py +140 -0
- sqlspec/migrations/utils.py +129 -0
- sqlspec/protocols.py +51 -186
- sqlspec/storage/__init__.py +1 -1
- sqlspec/storage/backends/base.py +37 -40
- sqlspec/storage/backends/fsspec.py +136 -112
- sqlspec/storage/backends/obstore.py +138 -160
- sqlspec/storage/capabilities.py +5 -4
- sqlspec/storage/registry.py +57 -106
- sqlspec/typing.py +136 -115
- sqlspec/utils/__init__.py +2 -2
- sqlspec/utils/correlation.py +0 -3
- sqlspec/utils/deprecation.py +6 -6
- sqlspec/utils/fixtures.py +6 -6
- sqlspec/utils/logging.py +0 -2
- sqlspec/utils/module_loader.py +7 -12
- sqlspec/utils/singleton.py +0 -1
- sqlspec/utils/sync_tools.py +17 -38
- sqlspec/utils/text.py +12 -51
- sqlspec/utils/type_guards.py +482 -235
- {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/METADATA +7 -2
- sqlspec-0.16.2.dist-info/RECORD +134 -0
- sqlspec-0.16.2.dist-info/entry_points.txt +2 -0
- sqlspec/driver/connection.py +0 -207
- sqlspec/driver/mixins/_csv_writer.py +0 -91
- sqlspec/driver/mixins/_pipeline.py +0 -512
- sqlspec/driver/mixins/_result_utils.py +0 -140
- sqlspec/driver/mixins/_storage.py +0 -926
- sqlspec/driver/mixins/_type_coercion.py +0 -130
- sqlspec/driver/parameters.py +0 -138
- sqlspec/service/__init__.py +0 -4
- sqlspec/service/_util.py +0 -147
- sqlspec/service/base.py +0 -1131
- sqlspec/service/pagination.py +0 -26
- sqlspec/statement/__init__.py +0 -21
- sqlspec/statement/builder/insert.py +0 -288
- sqlspec/statement/builder/merge.py +0 -95
- sqlspec/statement/builder/mixins/__init__.py +0 -65
- 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/_insert_values.py +0 -67
- sqlspec/statement/builder/mixins/_limit_offset.py +0 -53
- sqlspec/statement/builder/mixins/_order_by.py +0 -46
- sqlspec/statement/builder/mixins/_pivot.py +0 -79
- sqlspec/statement/builder/mixins/_returning.py +0 -37
- sqlspec/statement/builder/mixins/_select_columns.py +0 -61
- sqlspec/statement/builder/mixins/_set_ops.py +0 -122
- sqlspec/statement/builder/mixins/_unpivot.py +0 -77
- sqlspec/statement/builder/mixins/_update_from.py +0 -55
- sqlspec/statement/builder/mixins/_update_set.py +0 -94
- 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/builder/select.py +0 -221
- sqlspec/statement/filters.py +0 -596
- sqlspec/statement/parameter_manager.py +0 -220
- sqlspec/statement/parameters.py +0 -867
- sqlspec/statement/pipelines/__init__.py +0 -210
- sqlspec/statement/pipelines/analyzers/__init__.py +0 -9
- sqlspec/statement/pipelines/analyzers/_analyzer.py +0 -646
- sqlspec/statement/pipelines/context.py +0 -115
- sqlspec/statement/pipelines/transformers/__init__.py +0 -7
- sqlspec/statement/pipelines/transformers/_expression_simplifier.py +0 -88
- sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +0 -1247
- sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +0 -76
- sqlspec/statement/pipelines/validators/__init__.py +0 -23
- sqlspec/statement/pipelines/validators/_dml_safety.py +0 -290
- sqlspec/statement/pipelines/validators/_parameter_style.py +0 -370
- sqlspec/statement/pipelines/validators/_performance.py +0 -718
- sqlspec/statement/pipelines/validators/_security.py +0 -967
- sqlspec/statement/result.py +0 -435
- sqlspec/statement/sql.py +0 -1704
- sqlspec/statement/sql_compiler.py +0 -140
- sqlspec/utils/cached_property.py +0 -25
- sqlspec-0.13.1.dist-info/RECORD +0 -150
- {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/WHEEL +0 -0
- {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,370 +0,0 @@
|
|
|
1
|
-
"""Parameter style validation for SQL statements."""
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
|
-
from typing import TYPE_CHECKING, Any, Optional, Union
|
|
5
|
-
|
|
6
|
-
from sqlglot import exp
|
|
7
|
-
|
|
8
|
-
from sqlspec.exceptions import MissingParameterError, RiskLevel, SQLValidationError
|
|
9
|
-
from sqlspec.protocols import ProcessorProtocol
|
|
10
|
-
from sqlspec.statement.pipelines.context import ValidationError
|
|
11
|
-
from sqlspec.utils.type_guards import is_dict
|
|
12
|
-
|
|
13
|
-
if TYPE_CHECKING:
|
|
14
|
-
from sqlspec.statement.pipelines.context import SQLProcessingContext
|
|
15
|
-
|
|
16
|
-
logger = logging.getLogger("sqlspec.validators.parameter_style")
|
|
17
|
-
|
|
18
|
-
__all__ = ("ParameterStyleValidator",)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class UnsupportedParameterStyleError(SQLValidationError):
|
|
22
|
-
"""Raised when a parameter style is not supported by the current database."""
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class MixedParameterStyleError(SQLValidationError):
|
|
26
|
-
"""Raised when mixed parameter styles are detected but not allowed."""
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
class ParameterStyleValidator(ProcessorProtocol):
|
|
30
|
-
"""Validates that parameter styles are supported by the database configuration.
|
|
31
|
-
|
|
32
|
-
This validator checks:
|
|
33
|
-
1. Whether detected parameter styles are in the allowed list
|
|
34
|
-
2. Whether mixed parameter styles are used when not allowed
|
|
35
|
-
3. Provides helpful error messages about supported styles
|
|
36
|
-
"""
|
|
37
|
-
|
|
38
|
-
def __init__(self, risk_level: "RiskLevel" = RiskLevel.HIGH, fail_on_violation: bool = True) -> None:
|
|
39
|
-
"""Initialize the parameter style validator.
|
|
40
|
-
|
|
41
|
-
Args:
|
|
42
|
-
risk_level: Risk level for unsupported parameter styles
|
|
43
|
-
fail_on_violation: Whether to raise exception on violation
|
|
44
|
-
"""
|
|
45
|
-
self.risk_level = risk_level
|
|
46
|
-
self.fail_on_violation = fail_on_violation
|
|
47
|
-
|
|
48
|
-
def process(self, expression: "Optional[exp.Expression]", context: "SQLProcessingContext") -> None:
|
|
49
|
-
"""Validate parameter styles in SQL.
|
|
50
|
-
|
|
51
|
-
Args:
|
|
52
|
-
expression: The SQL expression being validated
|
|
53
|
-
context: SQL processing context with config
|
|
54
|
-
|
|
55
|
-
Returns:
|
|
56
|
-
A ProcessorResult with the outcome of the validation.
|
|
57
|
-
"""
|
|
58
|
-
if expression is None:
|
|
59
|
-
return
|
|
60
|
-
|
|
61
|
-
if context.current_expression is None:
|
|
62
|
-
error = ValidationError(
|
|
63
|
-
message="ParameterStyleValidator received no expression.",
|
|
64
|
-
code="no-expression",
|
|
65
|
-
risk_level=RiskLevel.CRITICAL,
|
|
66
|
-
processor="ParameterStyleValidator",
|
|
67
|
-
expression=None,
|
|
68
|
-
)
|
|
69
|
-
context.validation_errors.append(error)
|
|
70
|
-
return
|
|
71
|
-
|
|
72
|
-
try:
|
|
73
|
-
config = context.config
|
|
74
|
-
param_info = context.parameter_info
|
|
75
|
-
|
|
76
|
-
# Check if parameters were normalized by looking for param_ placeholders
|
|
77
|
-
# This happens when Oracle numeric parameters (:1, :2) are normalized
|
|
78
|
-
is_normalized = param_info and any(p.name and p.name.startswith("param_") for p in param_info)
|
|
79
|
-
|
|
80
|
-
# First check parameter styles if configured (skip if normalized)
|
|
81
|
-
has_style_errors = False
|
|
82
|
-
if not is_normalized and config.allowed_parameter_styles is not None and param_info:
|
|
83
|
-
unique_styles = {p.style for p in param_info}
|
|
84
|
-
|
|
85
|
-
if len(unique_styles) > 1 and not config.allow_mixed_parameter_styles:
|
|
86
|
-
detected_style_strs = [str(s) for s in unique_styles]
|
|
87
|
-
detected_styles = ", ".join(sorted(detected_style_strs))
|
|
88
|
-
msg = f"Mixed parameter styles detected ({detected_styles}) but not allowed."
|
|
89
|
-
if self.fail_on_violation:
|
|
90
|
-
self._raise_mixed_style_error(msg)
|
|
91
|
-
error = ValidationError(
|
|
92
|
-
message=msg,
|
|
93
|
-
code="mixed-parameter-styles",
|
|
94
|
-
risk_level=self.risk_level,
|
|
95
|
-
processor="ParameterStyleValidator",
|
|
96
|
-
expression=expression,
|
|
97
|
-
)
|
|
98
|
-
context.validation_errors.append(error)
|
|
99
|
-
has_style_errors = True
|
|
100
|
-
|
|
101
|
-
disallowed_styles = {str(s) for s in unique_styles if not config.validate_parameter_style(s)}
|
|
102
|
-
if disallowed_styles:
|
|
103
|
-
disallowed_str = ", ".join(sorted(disallowed_styles))
|
|
104
|
-
# Defensive handling to avoid "expected str instance, NoneType found"
|
|
105
|
-
if config.allowed_parameter_styles:
|
|
106
|
-
allowed_styles_strs = [str(s) for s in config.allowed_parameter_styles]
|
|
107
|
-
allowed_str = ", ".join(allowed_styles_strs)
|
|
108
|
-
msg = f"Parameter style(s) {disallowed_str} not supported. Allowed: {allowed_str}"
|
|
109
|
-
else:
|
|
110
|
-
msg = f"Parameter style(s) {disallowed_str} not supported."
|
|
111
|
-
|
|
112
|
-
if self.fail_on_violation:
|
|
113
|
-
self._raise_unsupported_style_error(msg)
|
|
114
|
-
error = ValidationError(
|
|
115
|
-
message=msg,
|
|
116
|
-
code="unsupported-parameter-style",
|
|
117
|
-
risk_level=self.risk_level,
|
|
118
|
-
processor="ParameterStyleValidator",
|
|
119
|
-
expression=expression,
|
|
120
|
-
)
|
|
121
|
-
context.validation_errors.append(error)
|
|
122
|
-
has_style_errors = True
|
|
123
|
-
|
|
124
|
-
# Check for missing parameters if:
|
|
125
|
-
# 1. We have parameter info
|
|
126
|
-
# 2. Style validation is enabled (allowed_parameter_styles is not None)
|
|
127
|
-
# 3. No style errors were found
|
|
128
|
-
# 4. We have merged parameters OR the original SQL had placeholders
|
|
129
|
-
logger.debug(
|
|
130
|
-
"Checking missing parameters: param_info=%s, extracted=%s, had_placeholders=%s, merged=%s",
|
|
131
|
-
len(param_info) if param_info else 0,
|
|
132
|
-
len(context.extracted_parameters_from_pipeline) if context.extracted_parameters_from_pipeline else 0,
|
|
133
|
-
context.input_sql_had_placeholders,
|
|
134
|
-
context.merged_parameters is not None,
|
|
135
|
-
)
|
|
136
|
-
# Skip validation if we have no merged parameters and the SQL didn't originally have placeholders
|
|
137
|
-
# This handles the case where literals were parameterized by transformers
|
|
138
|
-
if (
|
|
139
|
-
param_info
|
|
140
|
-
and config.allowed_parameter_styles is not None
|
|
141
|
-
and not has_style_errors
|
|
142
|
-
and (context.merged_parameters is not None or context.input_sql_had_placeholders)
|
|
143
|
-
):
|
|
144
|
-
self._validate_missing_parameters(context, expression)
|
|
145
|
-
|
|
146
|
-
except (UnsupportedParameterStyleError, MixedParameterStyleError, MissingParameterError):
|
|
147
|
-
raise
|
|
148
|
-
except Exception as e:
|
|
149
|
-
logger.warning("Parameter style validation failed: %s", e)
|
|
150
|
-
error = ValidationError(
|
|
151
|
-
message=f"Parameter style validation failed: {e}",
|
|
152
|
-
code="validation-error",
|
|
153
|
-
risk_level=RiskLevel.LOW,
|
|
154
|
-
processor="ParameterStyleValidator",
|
|
155
|
-
expression=expression,
|
|
156
|
-
)
|
|
157
|
-
context.validation_errors.append(error)
|
|
158
|
-
|
|
159
|
-
@staticmethod
|
|
160
|
-
def _raise_mixed_style_error(msg: "str") -> "None":
|
|
161
|
-
"""Raise MixedParameterStyleError with the given message."""
|
|
162
|
-
raise MixedParameterStyleError(msg)
|
|
163
|
-
|
|
164
|
-
@staticmethod
|
|
165
|
-
def _raise_unsupported_style_error(msg: "str") -> "None":
|
|
166
|
-
"""Raise UnsupportedParameterStyleError with the given message."""
|
|
167
|
-
raise UnsupportedParameterStyleError(msg)
|
|
168
|
-
|
|
169
|
-
def _validate_missing_parameters(self, context: "SQLProcessingContext", expression: exp.Expression) -> None:
|
|
170
|
-
"""Validate that all required parameters have values provided."""
|
|
171
|
-
param_info = context.parameter_info
|
|
172
|
-
if not param_info:
|
|
173
|
-
return
|
|
174
|
-
|
|
175
|
-
merged_params = self._prepare_merged_parameters(context, param_info)
|
|
176
|
-
|
|
177
|
-
if merged_params is None:
|
|
178
|
-
self._handle_no_parameters(context, expression, param_info)
|
|
179
|
-
elif isinstance(merged_params, (list, tuple)):
|
|
180
|
-
self._handle_positional_parameters(context, expression, param_info, merged_params)
|
|
181
|
-
elif is_dict(merged_params):
|
|
182
|
-
self._handle_named_parameters(context, expression, param_info, merged_params)
|
|
183
|
-
elif len(param_info) > 1:
|
|
184
|
-
self._handle_single_value_multiple_params(context, expression, param_info)
|
|
185
|
-
|
|
186
|
-
@staticmethod
|
|
187
|
-
def _prepare_merged_parameters(context: "SQLProcessingContext", param_info: list[Any]) -> Any:
|
|
188
|
-
"""Prepare merged parameters for validation."""
|
|
189
|
-
merged_params = context.merged_parameters
|
|
190
|
-
|
|
191
|
-
# If we have extracted parameters from transformers (like ParameterizeLiterals),
|
|
192
|
-
# use those for validation instead of the original merged_parameters
|
|
193
|
-
if context.extracted_parameters_from_pipeline and not context.input_sql_had_placeholders:
|
|
194
|
-
# Use extracted parameters as they represent the actual values to be used
|
|
195
|
-
merged_params = context.extracted_parameters_from_pipeline
|
|
196
|
-
has_positional_colon = any(p.style.value == "positional_colon" for p in param_info)
|
|
197
|
-
if has_positional_colon and not isinstance(merged_params, (list, tuple, dict)) and merged_params is not None:
|
|
198
|
-
return [merged_params]
|
|
199
|
-
return merged_params
|
|
200
|
-
|
|
201
|
-
def _report_error(self, context: "SQLProcessingContext", expression: exp.Expression, message: str) -> None:
|
|
202
|
-
"""Report a missing parameter error."""
|
|
203
|
-
if self.fail_on_violation:
|
|
204
|
-
raise MissingParameterError(message)
|
|
205
|
-
error = ValidationError(
|
|
206
|
-
message=message,
|
|
207
|
-
code="missing-parameters",
|
|
208
|
-
risk_level=self.risk_level,
|
|
209
|
-
processor="ParameterStyleValidator",
|
|
210
|
-
expression=expression,
|
|
211
|
-
)
|
|
212
|
-
context.validation_errors.append(error)
|
|
213
|
-
|
|
214
|
-
def _handle_no_parameters(
|
|
215
|
-
self, context: "SQLProcessingContext", expression: exp.Expression, param_info: list[Any]
|
|
216
|
-
) -> None:
|
|
217
|
-
"""Handle validation when no parameters are provided."""
|
|
218
|
-
if context.extracted_parameters_from_pipeline:
|
|
219
|
-
return
|
|
220
|
-
missing = [p.name or p.placeholder_text or f"param_{p.ordinal}" for p in param_info]
|
|
221
|
-
msg = f"Missing required parameters: {', '.join(str(m) for m in missing)}"
|
|
222
|
-
self._report_error(context, expression, msg)
|
|
223
|
-
|
|
224
|
-
def _handle_positional_parameters(
|
|
225
|
-
self,
|
|
226
|
-
context: "SQLProcessingContext",
|
|
227
|
-
expression: exp.Expression,
|
|
228
|
-
param_info: list[Any],
|
|
229
|
-
merged_params: "Union[list[Any], tuple[Any, ...]]",
|
|
230
|
-
) -> None:
|
|
231
|
-
"""Handle validation for positional parameters."""
|
|
232
|
-
has_named = any(p.style.value in {"named_colon", "named_at"} for p in param_info)
|
|
233
|
-
if has_named:
|
|
234
|
-
missing_named = [
|
|
235
|
-
p.name or p.placeholder_text for p in param_info if p.style.value in {"named_colon", "named_at"}
|
|
236
|
-
]
|
|
237
|
-
if missing_named:
|
|
238
|
-
msg = f"Missing required parameters: {', '.join(str(m) for m in missing_named if m)}"
|
|
239
|
-
self._report_error(context, expression, msg)
|
|
240
|
-
return
|
|
241
|
-
|
|
242
|
-
has_positional_colon = any(p.style.value == "positional_colon" for p in param_info)
|
|
243
|
-
if has_positional_colon:
|
|
244
|
-
self._validate_oracle_numeric_params(context, expression, param_info, merged_params)
|
|
245
|
-
elif len(merged_params) < len(param_info):
|
|
246
|
-
msg = f"Expected {len(param_info)} parameters but got {len(merged_params)}"
|
|
247
|
-
self._report_error(context, expression, msg)
|
|
248
|
-
|
|
249
|
-
def _validate_oracle_numeric_params(
|
|
250
|
-
self,
|
|
251
|
-
context: "SQLProcessingContext",
|
|
252
|
-
expression: exp.Expression,
|
|
253
|
-
param_info: list[Any],
|
|
254
|
-
merged_params: "Union[list[Any], tuple[Any, ...]]",
|
|
255
|
-
) -> None:
|
|
256
|
-
"""Validate Oracle-style numeric parameters."""
|
|
257
|
-
missing_indices: list[str] = []
|
|
258
|
-
provided_count = len(merged_params)
|
|
259
|
-
for p in param_info:
|
|
260
|
-
if p.style.value != "positional_colon" or not p.name:
|
|
261
|
-
continue
|
|
262
|
-
try:
|
|
263
|
-
idx = int(p.name)
|
|
264
|
-
if not (idx < provided_count or (idx > 0 and (idx - 1) < provided_count)):
|
|
265
|
-
missing_indices.append(p.name)
|
|
266
|
-
except (ValueError, TypeError):
|
|
267
|
-
pass
|
|
268
|
-
if missing_indices:
|
|
269
|
-
msg = f"Missing required parameters: :{', :'.join(missing_indices)}"
|
|
270
|
-
self._report_error(context, expression, msg)
|
|
271
|
-
|
|
272
|
-
def _handle_named_parameters(
|
|
273
|
-
self,
|
|
274
|
-
context: "SQLProcessingContext",
|
|
275
|
-
expression: exp.Expression,
|
|
276
|
-
param_info: list[Any],
|
|
277
|
-
merged_params: dict[str, Any],
|
|
278
|
-
) -> None:
|
|
279
|
-
"""Handle validation for named parameters."""
|
|
280
|
-
missing: list[str] = []
|
|
281
|
-
|
|
282
|
-
# Check if we have normalized parameters (e.g., param_0)
|
|
283
|
-
is_normalized = any(p.name and p.name.startswith("param_") for p in param_info)
|
|
284
|
-
|
|
285
|
-
if is_normalized and hasattr(context, "extra_info"):
|
|
286
|
-
# For normalized parameters, we need to check against the original placeholder mapping
|
|
287
|
-
placeholder_map = context.extra_info.get("placeholder_map", {})
|
|
288
|
-
|
|
289
|
-
# Check if we have Oracle numeric keys in merged_params
|
|
290
|
-
all_numeric_keys = all(key.isdigit() for key in merged_params)
|
|
291
|
-
|
|
292
|
-
if all_numeric_keys:
|
|
293
|
-
# Parameters were provided as list and converted to Oracle numeric dict {"1": val1, "2": val2}
|
|
294
|
-
for i, _p in enumerate(param_info):
|
|
295
|
-
normalized_name = f"param_{i}"
|
|
296
|
-
original_key = placeholder_map.get(normalized_name)
|
|
297
|
-
|
|
298
|
-
if original_key is not None:
|
|
299
|
-
# Check using the original key (e.g., "1", "2" for Oracle)
|
|
300
|
-
original_key_str = str(original_key)
|
|
301
|
-
if original_key_str not in merged_params or merged_params[original_key_str] is None:
|
|
302
|
-
if original_key_str.isdigit():
|
|
303
|
-
missing.append(f":{original_key}")
|
|
304
|
-
else:
|
|
305
|
-
missing.append(f":{original_key}")
|
|
306
|
-
else:
|
|
307
|
-
# Check if all params follow param_N pattern
|
|
308
|
-
all_param_keys = all(key.startswith("param_") and key[6:].isdigit() for key in merged_params)
|
|
309
|
-
|
|
310
|
-
if all_param_keys:
|
|
311
|
-
# This was originally a list converted to dict with param_N keys
|
|
312
|
-
for i, _p in enumerate(param_info):
|
|
313
|
-
normalized_name = f"param_{i}"
|
|
314
|
-
if normalized_name not in merged_params or merged_params[normalized_name] is None:
|
|
315
|
-
# Get original parameter style from placeholder map
|
|
316
|
-
original_key = placeholder_map.get(normalized_name)
|
|
317
|
-
if original_key is not None:
|
|
318
|
-
original_key_str = str(original_key)
|
|
319
|
-
if original_key_str.isdigit():
|
|
320
|
-
missing.append(f":{original_key}")
|
|
321
|
-
else:
|
|
322
|
-
missing.append(f":{original_key}")
|
|
323
|
-
else:
|
|
324
|
-
# Mixed parameter names, check using placeholder map
|
|
325
|
-
for i, _p in enumerate(param_info):
|
|
326
|
-
normalized_name = f"param_{i}"
|
|
327
|
-
original_key = placeholder_map.get(normalized_name)
|
|
328
|
-
|
|
329
|
-
if original_key is not None:
|
|
330
|
-
# For mixed params, check both normalized and original keys
|
|
331
|
-
original_key_str = str(original_key)
|
|
332
|
-
|
|
333
|
-
# First check with normalized name
|
|
334
|
-
found = normalized_name in merged_params and merged_params[normalized_name] is not None
|
|
335
|
-
|
|
336
|
-
# If not found, check with original key
|
|
337
|
-
if not found:
|
|
338
|
-
found = (
|
|
339
|
-
original_key_str in merged_params and merged_params[original_key_str] is not None
|
|
340
|
-
)
|
|
341
|
-
|
|
342
|
-
if not found:
|
|
343
|
-
# Format the missing parameter based on original style
|
|
344
|
-
if original_key_str.isdigit():
|
|
345
|
-
# It was an Oracle numeric parameter (e.g., :1)
|
|
346
|
-
missing.append(f":{original_key}")
|
|
347
|
-
else:
|
|
348
|
-
# It was a named parameter (e.g., :status)
|
|
349
|
-
missing.append(f":{original_key}")
|
|
350
|
-
else:
|
|
351
|
-
# Regular parameter validation
|
|
352
|
-
for p in param_info:
|
|
353
|
-
param_name = p.name
|
|
354
|
-
if param_name not in merged_params or merged_params.get(param_name) is None:
|
|
355
|
-
is_synthetic = any(key.startswith(("arg_", "param_")) for key in merged_params)
|
|
356
|
-
is_named_style = p.style.value not in {"qmark", "numeric"}
|
|
357
|
-
if (not is_synthetic or is_named_style) and param_name:
|
|
358
|
-
missing.append(param_name)
|
|
359
|
-
|
|
360
|
-
if missing:
|
|
361
|
-
msg = f"Missing required parameters: {', '.join(missing)}"
|
|
362
|
-
self._report_error(context, expression, msg)
|
|
363
|
-
|
|
364
|
-
def _handle_single_value_multiple_params(
|
|
365
|
-
self, context: "SQLProcessingContext", expression: exp.Expression, param_info: list[Any]
|
|
366
|
-
) -> None:
|
|
367
|
-
"""Handle validation for a single value provided for multiple parameters."""
|
|
368
|
-
missing = [p.name or p.placeholder_text or f"param_{p.ordinal}" for p in param_info[1:]]
|
|
369
|
-
msg = f"Missing required parameters: {', '.join(str(m) for m in missing)}"
|
|
370
|
-
self._report_error(context, expression, msg)
|