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.

Files changed (185) hide show
  1. sqlspec/__init__.py +71 -8
  2. sqlspec/__main__.py +12 -0
  3. sqlspec/__metadata__.py +1 -3
  4. sqlspec/_serialization.py +1 -2
  5. sqlspec/_sql.py +930 -136
  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 +116 -285
  10. sqlspec/adapters/adbc/driver.py +462 -340
  11. sqlspec/adapters/aiosqlite/__init__.py +18 -3
  12. sqlspec/adapters/aiosqlite/_types.py +13 -0
  13. sqlspec/adapters/aiosqlite/config.py +202 -150
  14. sqlspec/adapters/aiosqlite/driver.py +226 -247
  15. sqlspec/adapters/asyncmy/__init__.py +18 -3
  16. sqlspec/adapters/asyncmy/_types.py +12 -0
  17. sqlspec/adapters/asyncmy/config.py +80 -199
  18. sqlspec/adapters/asyncmy/driver.py +257 -215
  19. sqlspec/adapters/asyncpg/__init__.py +19 -4
  20. sqlspec/adapters/asyncpg/_types.py +17 -0
  21. sqlspec/adapters/asyncpg/config.py +81 -214
  22. sqlspec/adapters/asyncpg/driver.py +284 -359
  23. sqlspec/adapters/bigquery/__init__.py +17 -3
  24. sqlspec/adapters/bigquery/_types.py +12 -0
  25. sqlspec/adapters/bigquery/config.py +191 -299
  26. sqlspec/adapters/bigquery/driver.py +474 -634
  27. sqlspec/adapters/duckdb/__init__.py +14 -3
  28. sqlspec/adapters/duckdb/_types.py +12 -0
  29. sqlspec/adapters/duckdb/config.py +414 -397
  30. sqlspec/adapters/duckdb/driver.py +342 -393
  31. sqlspec/adapters/oracledb/__init__.py +19 -5
  32. sqlspec/adapters/oracledb/_types.py +14 -0
  33. sqlspec/adapters/oracledb/config.py +123 -458
  34. sqlspec/adapters/oracledb/driver.py +505 -531
  35. sqlspec/adapters/psqlpy/__init__.py +13 -3
  36. sqlspec/adapters/psqlpy/_types.py +11 -0
  37. sqlspec/adapters/psqlpy/config.py +93 -307
  38. sqlspec/adapters/psqlpy/driver.py +504 -213
  39. sqlspec/adapters/psycopg/__init__.py +19 -5
  40. sqlspec/adapters/psycopg/_types.py +17 -0
  41. sqlspec/adapters/psycopg/config.py +143 -472
  42. sqlspec/adapters/psycopg/driver.py +704 -825
  43. sqlspec/adapters/sqlite/__init__.py +14 -3
  44. sqlspec/adapters/sqlite/_types.py +11 -0
  45. sqlspec/adapters/sqlite/config.py +208 -142
  46. sqlspec/adapters/sqlite/driver.py +263 -278
  47. sqlspec/base.py +105 -9
  48. sqlspec/{statement/builder → builder}/__init__.py +12 -14
  49. sqlspec/{statement/builder/base.py → builder/_base.py} +184 -86
  50. sqlspec/{statement/builder/column.py → builder/_column.py} +97 -60
  51. sqlspec/{statement/builder/ddl.py → builder/_ddl.py} +61 -131
  52. sqlspec/{statement/builder → builder}/_ddl_utils.py +4 -10
  53. sqlspec/{statement/builder/delete.py → builder/_delete.py} +10 -30
  54. sqlspec/builder/_insert.py +421 -0
  55. sqlspec/builder/_merge.py +71 -0
  56. sqlspec/{statement/builder → builder}/_parsing_utils.py +49 -26
  57. sqlspec/builder/_select.py +170 -0
  58. sqlspec/{statement/builder/update.py → builder/_update.py} +16 -20
  59. sqlspec/builder/mixins/__init__.py +55 -0
  60. sqlspec/builder/mixins/_cte_and_set_ops.py +222 -0
  61. sqlspec/{statement/builder/mixins/_delete_from.py → builder/mixins/_delete_operations.py} +8 -1
  62. sqlspec/builder/mixins/_insert_operations.py +244 -0
  63. sqlspec/{statement/builder/mixins/_join.py → builder/mixins/_join_operations.py} +45 -13
  64. sqlspec/{statement/builder/mixins/_merge_clauses.py → builder/mixins/_merge_operations.py} +188 -30
  65. sqlspec/builder/mixins/_order_limit_operations.py +135 -0
  66. sqlspec/builder/mixins/_pivot_operations.py +153 -0
  67. sqlspec/builder/mixins/_select_operations.py +604 -0
  68. sqlspec/builder/mixins/_update_operations.py +202 -0
  69. sqlspec/builder/mixins/_where_clause.py +644 -0
  70. sqlspec/cli.py +247 -0
  71. sqlspec/config.py +183 -138
  72. sqlspec/core/__init__.py +63 -0
  73. sqlspec/core/cache.py +871 -0
  74. sqlspec/core/compiler.py +417 -0
  75. sqlspec/core/filters.py +830 -0
  76. sqlspec/core/hashing.py +310 -0
  77. sqlspec/core/parameters.py +1237 -0
  78. sqlspec/core/result.py +677 -0
  79. sqlspec/{statement → core}/splitter.py +321 -191
  80. sqlspec/core/statement.py +676 -0
  81. sqlspec/driver/__init__.py +7 -10
  82. sqlspec/driver/_async.py +422 -163
  83. sqlspec/driver/_common.py +545 -287
  84. sqlspec/driver/_sync.py +426 -160
  85. sqlspec/driver/mixins/__init__.py +2 -13
  86. sqlspec/driver/mixins/_result_tools.py +193 -0
  87. sqlspec/driver/mixins/_sql_translator.py +65 -14
  88. sqlspec/exceptions.py +5 -252
  89. sqlspec/extensions/aiosql/adapter.py +93 -96
  90. sqlspec/extensions/litestar/__init__.py +2 -1
  91. sqlspec/extensions/litestar/cli.py +48 -0
  92. sqlspec/extensions/litestar/config.py +0 -1
  93. sqlspec/extensions/litestar/handlers.py +15 -26
  94. sqlspec/extensions/litestar/plugin.py +21 -16
  95. sqlspec/extensions/litestar/providers.py +17 -52
  96. sqlspec/loader.py +423 -104
  97. sqlspec/migrations/__init__.py +35 -0
  98. sqlspec/migrations/base.py +414 -0
  99. sqlspec/migrations/commands.py +443 -0
  100. sqlspec/migrations/loaders.py +402 -0
  101. sqlspec/migrations/runner.py +213 -0
  102. sqlspec/migrations/tracker.py +140 -0
  103. sqlspec/migrations/utils.py +129 -0
  104. sqlspec/protocols.py +51 -186
  105. sqlspec/storage/__init__.py +1 -1
  106. sqlspec/storage/backends/base.py +37 -40
  107. sqlspec/storage/backends/fsspec.py +136 -112
  108. sqlspec/storage/backends/obstore.py +138 -160
  109. sqlspec/storage/capabilities.py +5 -4
  110. sqlspec/storage/registry.py +57 -106
  111. sqlspec/typing.py +136 -115
  112. sqlspec/utils/__init__.py +2 -2
  113. sqlspec/utils/correlation.py +0 -3
  114. sqlspec/utils/deprecation.py +6 -6
  115. sqlspec/utils/fixtures.py +6 -6
  116. sqlspec/utils/logging.py +0 -2
  117. sqlspec/utils/module_loader.py +7 -12
  118. sqlspec/utils/singleton.py +0 -1
  119. sqlspec/utils/sync_tools.py +17 -38
  120. sqlspec/utils/text.py +12 -51
  121. sqlspec/utils/type_guards.py +482 -235
  122. {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/METADATA +7 -2
  123. sqlspec-0.16.2.dist-info/RECORD +134 -0
  124. sqlspec-0.16.2.dist-info/entry_points.txt +2 -0
  125. sqlspec/driver/connection.py +0 -207
  126. sqlspec/driver/mixins/_csv_writer.py +0 -91
  127. sqlspec/driver/mixins/_pipeline.py +0 -512
  128. sqlspec/driver/mixins/_result_utils.py +0 -140
  129. sqlspec/driver/mixins/_storage.py +0 -926
  130. sqlspec/driver/mixins/_type_coercion.py +0 -130
  131. sqlspec/driver/parameters.py +0 -138
  132. sqlspec/service/__init__.py +0 -4
  133. sqlspec/service/_util.py +0 -147
  134. sqlspec/service/base.py +0 -1131
  135. sqlspec/service/pagination.py +0 -26
  136. sqlspec/statement/__init__.py +0 -21
  137. sqlspec/statement/builder/insert.py +0 -288
  138. sqlspec/statement/builder/merge.py +0 -95
  139. sqlspec/statement/builder/mixins/__init__.py +0 -65
  140. sqlspec/statement/builder/mixins/_aggregate_functions.py +0 -250
  141. sqlspec/statement/builder/mixins/_case_builder.py +0 -91
  142. sqlspec/statement/builder/mixins/_common_table_expr.py +0 -90
  143. sqlspec/statement/builder/mixins/_from.py +0 -63
  144. sqlspec/statement/builder/mixins/_group_by.py +0 -118
  145. sqlspec/statement/builder/mixins/_having.py +0 -35
  146. sqlspec/statement/builder/mixins/_insert_from_select.py +0 -47
  147. sqlspec/statement/builder/mixins/_insert_into.py +0 -36
  148. sqlspec/statement/builder/mixins/_insert_values.py +0 -67
  149. sqlspec/statement/builder/mixins/_limit_offset.py +0 -53
  150. sqlspec/statement/builder/mixins/_order_by.py +0 -46
  151. sqlspec/statement/builder/mixins/_pivot.py +0 -79
  152. sqlspec/statement/builder/mixins/_returning.py +0 -37
  153. sqlspec/statement/builder/mixins/_select_columns.py +0 -61
  154. sqlspec/statement/builder/mixins/_set_ops.py +0 -122
  155. sqlspec/statement/builder/mixins/_unpivot.py +0 -77
  156. sqlspec/statement/builder/mixins/_update_from.py +0 -55
  157. sqlspec/statement/builder/mixins/_update_set.py +0 -94
  158. sqlspec/statement/builder/mixins/_update_table.py +0 -29
  159. sqlspec/statement/builder/mixins/_where.py +0 -401
  160. sqlspec/statement/builder/mixins/_window_functions.py +0 -86
  161. sqlspec/statement/builder/select.py +0 -221
  162. sqlspec/statement/filters.py +0 -596
  163. sqlspec/statement/parameter_manager.py +0 -220
  164. sqlspec/statement/parameters.py +0 -867
  165. sqlspec/statement/pipelines/__init__.py +0 -210
  166. sqlspec/statement/pipelines/analyzers/__init__.py +0 -9
  167. sqlspec/statement/pipelines/analyzers/_analyzer.py +0 -646
  168. sqlspec/statement/pipelines/context.py +0 -115
  169. sqlspec/statement/pipelines/transformers/__init__.py +0 -7
  170. sqlspec/statement/pipelines/transformers/_expression_simplifier.py +0 -88
  171. sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +0 -1247
  172. sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +0 -76
  173. sqlspec/statement/pipelines/validators/__init__.py +0 -23
  174. sqlspec/statement/pipelines/validators/_dml_safety.py +0 -290
  175. sqlspec/statement/pipelines/validators/_parameter_style.py +0 -370
  176. sqlspec/statement/pipelines/validators/_performance.py +0 -718
  177. sqlspec/statement/pipelines/validators/_security.py +0 -967
  178. sqlspec/statement/result.py +0 -435
  179. sqlspec/statement/sql.py +0 -1704
  180. sqlspec/statement/sql_compiler.py +0 -140
  181. sqlspec/utils/cached_property.py +0 -25
  182. sqlspec-0.13.1.dist-info/RECORD +0 -150
  183. {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/WHEEL +0 -0
  184. {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/licenses/LICENSE +0 -0
  185. {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)