sqlspec 0.14.1__py3-none-any.whl → 0.16.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.

Files changed (159) hide show
  1. sqlspec/__init__.py +50 -25
  2. sqlspec/__main__.py +1 -1
  3. sqlspec/__metadata__.py +1 -3
  4. sqlspec/_serialization.py +1 -2
  5. sqlspec/_sql.py +480 -121
  6. sqlspec/_typing.py +278 -142
  7. sqlspec/adapters/adbc/__init__.py +4 -3
  8. sqlspec/adapters/adbc/_types.py +12 -0
  9. sqlspec/adapters/adbc/config.py +115 -260
  10. sqlspec/adapters/adbc/driver.py +462 -367
  11. sqlspec/adapters/aiosqlite/__init__.py +18 -3
  12. sqlspec/adapters/aiosqlite/_types.py +13 -0
  13. sqlspec/adapters/aiosqlite/config.py +199 -129
  14. sqlspec/adapters/aiosqlite/driver.py +230 -269
  15. sqlspec/adapters/asyncmy/__init__.py +18 -3
  16. sqlspec/adapters/asyncmy/_types.py +12 -0
  17. sqlspec/adapters/asyncmy/config.py +80 -168
  18. sqlspec/adapters/asyncmy/driver.py +260 -225
  19. sqlspec/adapters/asyncpg/__init__.py +19 -4
  20. sqlspec/adapters/asyncpg/_types.py +17 -0
  21. sqlspec/adapters/asyncpg/config.py +82 -181
  22. sqlspec/adapters/asyncpg/driver.py +285 -383
  23. sqlspec/adapters/bigquery/__init__.py +17 -3
  24. sqlspec/adapters/bigquery/_types.py +12 -0
  25. sqlspec/adapters/bigquery/config.py +191 -258
  26. sqlspec/adapters/bigquery/driver.py +474 -646
  27. sqlspec/adapters/duckdb/__init__.py +14 -3
  28. sqlspec/adapters/duckdb/_types.py +12 -0
  29. sqlspec/adapters/duckdb/config.py +415 -351
  30. sqlspec/adapters/duckdb/driver.py +343 -413
  31. sqlspec/adapters/oracledb/__init__.py +19 -5
  32. sqlspec/adapters/oracledb/_types.py +14 -0
  33. sqlspec/adapters/oracledb/config.py +123 -379
  34. sqlspec/adapters/oracledb/driver.py +507 -560
  35. sqlspec/adapters/psqlpy/__init__.py +13 -3
  36. sqlspec/adapters/psqlpy/_types.py +11 -0
  37. sqlspec/adapters/psqlpy/config.py +93 -254
  38. sqlspec/adapters/psqlpy/driver.py +505 -234
  39. sqlspec/adapters/psycopg/__init__.py +19 -5
  40. sqlspec/adapters/psycopg/_types.py +17 -0
  41. sqlspec/adapters/psycopg/config.py +143 -403
  42. sqlspec/adapters/psycopg/driver.py +706 -872
  43. sqlspec/adapters/sqlite/__init__.py +14 -3
  44. sqlspec/adapters/sqlite/_types.py +11 -0
  45. sqlspec/adapters/sqlite/config.py +202 -118
  46. sqlspec/adapters/sqlite/driver.py +264 -303
  47. sqlspec/base.py +105 -9
  48. sqlspec/{statement/builder → builder}/__init__.py +12 -14
  49. sqlspec/{statement/builder → builder}/_base.py +120 -55
  50. sqlspec/{statement/builder → builder}/_column.py +17 -6
  51. sqlspec/{statement/builder → builder}/_ddl.py +46 -79
  52. sqlspec/{statement/builder → builder}/_ddl_utils.py +5 -10
  53. sqlspec/{statement/builder → builder}/_delete.py +6 -25
  54. sqlspec/{statement/builder → builder}/_insert.py +18 -65
  55. sqlspec/builder/_merge.py +56 -0
  56. sqlspec/{statement/builder → builder}/_parsing_utils.py +8 -11
  57. sqlspec/{statement/builder → builder}/_select.py +11 -56
  58. sqlspec/{statement/builder → builder}/_update.py +12 -18
  59. sqlspec/{statement/builder → builder}/mixins/__init__.py +10 -14
  60. sqlspec/{statement/builder → builder}/mixins/_cte_and_set_ops.py +48 -59
  61. sqlspec/{statement/builder → builder}/mixins/_insert_operations.py +34 -18
  62. sqlspec/{statement/builder → builder}/mixins/_join_operations.py +1 -3
  63. sqlspec/{statement/builder → builder}/mixins/_merge_operations.py +19 -9
  64. sqlspec/{statement/builder → builder}/mixins/_order_limit_operations.py +3 -3
  65. sqlspec/{statement/builder → builder}/mixins/_pivot_operations.py +4 -8
  66. sqlspec/{statement/builder → builder}/mixins/_select_operations.py +25 -38
  67. sqlspec/{statement/builder → builder}/mixins/_update_operations.py +15 -16
  68. sqlspec/{statement/builder → builder}/mixins/_where_clause.py +210 -137
  69. sqlspec/cli.py +4 -5
  70. sqlspec/config.py +180 -133
  71. sqlspec/core/__init__.py +63 -0
  72. sqlspec/core/cache.py +873 -0
  73. sqlspec/core/compiler.py +396 -0
  74. sqlspec/core/filters.py +830 -0
  75. sqlspec/core/hashing.py +310 -0
  76. sqlspec/core/parameters.py +1209 -0
  77. sqlspec/core/result.py +664 -0
  78. sqlspec/{statement → core}/splitter.py +321 -191
  79. sqlspec/core/statement.py +666 -0
  80. sqlspec/driver/__init__.py +7 -10
  81. sqlspec/driver/_async.py +387 -176
  82. sqlspec/driver/_common.py +527 -289
  83. sqlspec/driver/_sync.py +390 -172
  84. sqlspec/driver/mixins/__init__.py +2 -19
  85. sqlspec/driver/mixins/_result_tools.py +164 -0
  86. sqlspec/driver/mixins/_sql_translator.py +6 -3
  87. sqlspec/exceptions.py +5 -252
  88. sqlspec/extensions/aiosql/adapter.py +93 -96
  89. sqlspec/extensions/litestar/cli.py +1 -1
  90. sqlspec/extensions/litestar/config.py +0 -1
  91. sqlspec/extensions/litestar/handlers.py +15 -26
  92. sqlspec/extensions/litestar/plugin.py +18 -16
  93. sqlspec/extensions/litestar/providers.py +17 -52
  94. sqlspec/loader.py +424 -105
  95. sqlspec/migrations/__init__.py +12 -0
  96. sqlspec/migrations/base.py +92 -68
  97. sqlspec/migrations/commands.py +24 -106
  98. sqlspec/migrations/loaders.py +402 -0
  99. sqlspec/migrations/runner.py +49 -51
  100. sqlspec/migrations/tracker.py +31 -44
  101. sqlspec/migrations/utils.py +64 -24
  102. sqlspec/protocols.py +7 -183
  103. sqlspec/storage/__init__.py +1 -1
  104. sqlspec/storage/backends/base.py +37 -40
  105. sqlspec/storage/backends/fsspec.py +136 -112
  106. sqlspec/storage/backends/obstore.py +138 -160
  107. sqlspec/storage/capabilities.py +5 -4
  108. sqlspec/storage/registry.py +57 -106
  109. sqlspec/typing.py +136 -115
  110. sqlspec/utils/__init__.py +2 -3
  111. sqlspec/utils/correlation.py +0 -3
  112. sqlspec/utils/deprecation.py +6 -6
  113. sqlspec/utils/fixtures.py +6 -6
  114. sqlspec/utils/logging.py +0 -2
  115. sqlspec/utils/module_loader.py +7 -12
  116. sqlspec/utils/singleton.py +0 -1
  117. sqlspec/utils/sync_tools.py +17 -38
  118. sqlspec/utils/text.py +12 -51
  119. sqlspec/utils/type_guards.py +443 -232
  120. {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/METADATA +7 -2
  121. sqlspec-0.16.0.dist-info/RECORD +134 -0
  122. sqlspec/adapters/adbc/transformers.py +0 -108
  123. sqlspec/driver/connection.py +0 -207
  124. sqlspec/driver/mixins/_cache.py +0 -114
  125. sqlspec/driver/mixins/_csv_writer.py +0 -91
  126. sqlspec/driver/mixins/_pipeline.py +0 -508
  127. sqlspec/driver/mixins/_query_tools.py +0 -796
  128. sqlspec/driver/mixins/_result_utils.py +0 -138
  129. sqlspec/driver/mixins/_storage.py +0 -912
  130. sqlspec/driver/mixins/_type_coercion.py +0 -128
  131. sqlspec/driver/parameters.py +0 -138
  132. sqlspec/statement/__init__.py +0 -21
  133. sqlspec/statement/builder/_merge.py +0 -95
  134. sqlspec/statement/cache.py +0 -50
  135. sqlspec/statement/filters.py +0 -625
  136. sqlspec/statement/parameters.py +0 -956
  137. sqlspec/statement/pipelines/__init__.py +0 -210
  138. sqlspec/statement/pipelines/analyzers/__init__.py +0 -9
  139. sqlspec/statement/pipelines/analyzers/_analyzer.py +0 -646
  140. sqlspec/statement/pipelines/context.py +0 -109
  141. sqlspec/statement/pipelines/transformers/__init__.py +0 -7
  142. sqlspec/statement/pipelines/transformers/_expression_simplifier.py +0 -88
  143. sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +0 -1247
  144. sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +0 -76
  145. sqlspec/statement/pipelines/validators/__init__.py +0 -23
  146. sqlspec/statement/pipelines/validators/_dml_safety.py +0 -290
  147. sqlspec/statement/pipelines/validators/_parameter_style.py +0 -370
  148. sqlspec/statement/pipelines/validators/_performance.py +0 -714
  149. sqlspec/statement/pipelines/validators/_security.py +0 -967
  150. sqlspec/statement/result.py +0 -435
  151. sqlspec/statement/sql.py +0 -1774
  152. sqlspec/utils/cached_property.py +0 -25
  153. sqlspec/utils/statement_hashing.py +0 -203
  154. sqlspec-0.14.1.dist-info/RECORD +0 -145
  155. /sqlspec/{statement/builder → builder}/mixins/_delete_operations.py +0 -0
  156. {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/WHEEL +0 -0
  157. {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/entry_points.txt +0 -0
  158. {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.dist-info}/licenses/LICENSE +0 -0
  159. {sqlspec-0.14.1.dist-info → sqlspec-0.16.0.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 converted by looking for param_ placeholders
77
- # This happens when Oracle numeric parameters (:1, :2) are converted
78
- is_converted = 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 converted)
81
- has_style_errors = False
82
- if not is_converted 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 converted parameters (e.g., param_0)
283
- is_converted = any(p.name and p.name.startswith("param_") for p in param_info)
284
-
285
- if is_converted and hasattr(context, "extra_info"):
286
- # For converted 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 in range(len(param_info)):
295
- converted_name = f"param_{i}"
296
- original_key = placeholder_map.get(converted_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 in range(len(param_info)):
313
- converted_name = f"param_{i}"
314
- if converted_name not in merged_params or merged_params[converted_name] is None:
315
- # Get original parameter style from placeholder map
316
- original_key = placeholder_map.get(converted_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 in range(len(param_info)):
326
- converted_name = f"param_{i}"
327
- original_key = placeholder_map.get(converted_name)
328
-
329
- if original_key is not None:
330
- # For mixed params, check both converted and original keys
331
- original_key_str = str(original_key)
332
-
333
- # First check with converted name
334
- found = converted_name in merged_params and merged_params[converted_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)