sql-dialect-master 1.1.1__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.
Files changed (86) hide show
  1. backend/__init__.py +1 -0
  2. backend/api/__init__.py +1 -0
  3. backend/api/main.py +732 -0
  4. backend/api/middleware.py +274 -0
  5. backend/api/rate_limit_store.py +105 -0
  6. backend/api/readiness.py +178 -0
  7. backend/core/__init__.py +57 -0
  8. backend/core/cache.py +261 -0
  9. backend/core/column_hint_validation.py +22 -0
  10. backend/core/config.py +546 -0
  11. backend/core/constants.py +84 -0
  12. backend/core/exceptions.py +124 -0
  13. backend/core/function_call_scanner.py +64 -0
  14. backend/core/function_semantics.py +148 -0
  15. backend/core/functions_db.json +601 -0
  16. backend/core/functions_lookup.py +332 -0
  17. backend/core/metadata.py +71 -0
  18. backend/core/nl2sql.py +85 -0
  19. backend/core/nl2sql_components/__init__.py +72 -0
  20. backend/core/nl2sql_components/aggregations.py +35 -0
  21. backend/core/nl2sql_components/boolean_conditions.py +279 -0
  22. backend/core/nl2sql_components/core_extract.py +106 -0
  23. backend/core/nl2sql_components/evidence.py +122 -0
  24. backend/core/nl2sql_components/mappings.py +185 -0
  25. backend/core/nl2sql_components/relations.py +180 -0
  26. backend/core/nl2sql_components/templates.py +130 -0
  27. backend/core/nl2sql_components/tokenizer.py +187 -0
  28. backend/core/nl2sql_legacy.py +22 -0
  29. backend/core/nl2sql_pipeline/__init__.py +13 -0
  30. backend/core/nl2sql_pipeline/aggregations.py +26 -0
  31. backend/core/nl2sql_pipeline/clause_extract.py +97 -0
  32. backend/core/nl2sql_pipeline/dialect.py +139 -0
  33. backend/core/nl2sql_pipeline/intent.py +85 -0
  34. backend/core/nl2sql_pipeline/ir.py +70 -0
  35. backend/core/nl2sql_pipeline/models.py +30 -0
  36. backend/core/nl2sql_pipeline/orchestrator.py +576 -0
  37. backend/core/nl2sql_pipeline/predicates.py +182 -0
  38. backend/core/nl2sql_pipeline/renderer.py +248 -0
  39. backend/core/nl2sql_pipeline/resolution.py +69 -0
  40. backend/core/nl2sql_pipeline/suggestions.py +30 -0
  41. backend/core/nl2sql_pipeline/templates.py +371 -0
  42. backend/core/nl2sql_pipeline/validation.py +28 -0
  43. backend/core/p1_sql_scanner.py +196 -0
  44. backend/core/p2_data_validation.py +72 -0
  45. backend/core/parser.py +280 -0
  46. backend/core/post_processor.py +129 -0
  47. backend/core/rules.py +808 -0
  48. backend/core/semantic_diff.py +385 -0
  49. backend/core/transpiler.py +791 -0
  50. backend/core/type_mapping.json +593 -0
  51. backend/core/type_mapping.py +765 -0
  52. backend/utils/__init__.py +2 -0
  53. backend/utils/validators.py +407 -0
  54. frontend/__init__.py +10 -0
  55. frontend/app_context_v2.py +201 -0
  56. frontend/core/__init__.py +33 -0
  57. frontend/core/design_tokens.py +127 -0
  58. frontend/core/escaping.py +13 -0
  59. frontend/core/navigation.py +135 -0
  60. frontend/core/state.py +612 -0
  61. frontend/core/styles.py +523 -0
  62. frontend/core/themes.py +69 -0
  63. frontend/core/viewmodels.py +499 -0
  64. frontend/pages/__init__.py +0 -0
  65. frontend/pages/convert.py +283 -0
  66. frontend/pages/diff.py +157 -0
  67. frontend/pages/functions.py +100 -0
  68. frontend/pages/history.py +144 -0
  69. frontend/pages/lineage.py +168 -0
  70. frontend/pages/nl2sql.py +305 -0
  71. frontend/pages/query_analysis.py +120 -0
  72. frontend/pages/runtime.py +66 -0
  73. frontend/pages/settings.py +60 -0
  74. frontend/pages/templates.py +67 -0
  75. frontend/pages/types.py +110 -0
  76. frontend/templates_v2.py +53 -0
  77. frontend/ui/__init__.py +0 -0
  78. frontend/ui/command_palette.py +153 -0
  79. frontend/ui/diff.py +275 -0
  80. frontend/ui/editor.py +102 -0
  81. frontend/ui/status.py +255 -0
  82. sql_dialect_master-1.1.1.dist-info/METADATA +360 -0
  83. sql_dialect_master-1.1.1.dist-info/RECORD +86 -0
  84. sql_dialect_master-1.1.1.dist-info/WHEEL +5 -0
  85. sql_dialect_master-1.1.1.dist-info/licenses/LICENSE +21 -0
  86. sql_dialect_master-1.1.1.dist-info/top_level.txt +2 -0
@@ -0,0 +1,576 @@
1
+ #!/usr/bin/env python3
2
+ """P8 NL2SQL pipeline: orchestrator (moved from nl2sql_legacy).
3
+
4
+ ``PipelineNL2SQLGenerator`` is the canonical base class the facade
5
+ (``backend.core.nl2sql``) extends. It owns the three-code-path
6
+ ``generate()`` dispatch:
7
+
8
+ 1. boolean/null fast path
9
+ 2. template path (relational-keyword bypass)
10
+ 3. enhanced fallback (G1 named-tables fail-closed gate)
11
+
12
+ All per-stage logic delegates to the pure stage modules
13
+ (``intent``, ``resolution``, ``predicates``, ``aggregations``,
14
+ ``clause_extract``, ``templates``, ``ir``, ``renderer``, ``dialect``,
15
+ ``validation``, ``suggestions``). Instance-method signatures are
16
+ unchanged so the facade's ``super()`` overrides resolve exactly as they
17
+ did against the legacy class.
18
+ """
19
+ import logging
20
+ import re
21
+ from typing import Any, Dict, List, Optional, Tuple
22
+
23
+ from ..column_hint_validation import validate_column_hints
24
+ from ..nl2sql_components.boolean_conditions import extract_boolean_conditions
25
+ from ..nl2sql_components.evidence import GenerationEvidence
26
+ from ..nl2sql_components.mappings import (
27
+ COLUMN_PATTERNS as MAPPING_COLUMN_PATTERNS,
28
+ KEYWORDS as MAPPING_KEYWORDS,
29
+ TABLE_PATTERNS as MAPPING_TABLE_PATTERNS,
30
+ )
31
+ from ..nl2sql_components.templates import DEFAULT_QUERY_TEMPLATES, QueryTemplate
32
+ from ..nl2sql_components.tokenizer import Tokenizer
33
+ from . import ir as _nl2sql_ir
34
+ from .aggregations import extract_aggregations as _pipeline_extract_aggregations
35
+ from .clause_extract import (
36
+ check_distinct as _pipeline_check_distinct,
37
+ extract_group_by as _pipeline_extract_group_by,
38
+ extract_limit as _pipeline_extract_limit,
39
+ extract_ordering as _pipeline_extract_ordering,
40
+ )
41
+ from .dialect import apply_dialect_adjustments as _apply_dialect_adjustments_impl
42
+ from .intent import (
43
+ analyze_intent as _analyze_intent,
44
+ detect_operation as _detect_operation,
45
+ parse_text as _parse_text_impl,
46
+ )
47
+ from .models import NL2SQLResult
48
+ from .predicates import extract_conditions as _extract_conditions_enhanced_impl
49
+ from .renderer import add_limit as _add_limit_impl
50
+ from .renderer import join_sql_kind as _join_sql_kind_impl
51
+ from .renderer import render_sql as _render_sql_impl
52
+ from .resolution import (
53
+ extract_columns as _pipeline_extract_columns,
54
+ extract_joins as _pipeline_extract_joins,
55
+ extract_table as _pipeline_extract_table,
56
+ extract_table_subject as _pipeline_extract_table_subject,
57
+ guess_join_key as _pipeline_guess_join_key,
58
+ has_relational_keyword as _pipeline_has_relational_keyword,
59
+ named_tables as _pipeline_named_tables,
60
+ )
61
+ from .suggestions import generate_suggestions as _pipeline_generate_suggestions
62
+ from .templates import (
63
+ generate_from_template as _pipeline_generate_from_template,
64
+ template_suggestions as _pipeline_template_suggestions,
65
+ )
66
+ from .validation import validate_sql as _validate_sql_impl
67
+
68
+ logger = logging.getLogger(__name__)
69
+
70
+
71
+ class PipelineNL2SQLGenerator:
72
+ """NL2SQL pipeline base class.
73
+
74
+ Identical in behaviour to the legacy ``NL2SQLGenerator`` — it merely
75
+ delegates every per-stage concern to a pure function in the
76
+ ``nl2sql_pipeline`` package so the class body reads as an explicit
77
+ orchestration of the target stages:
78
+
79
+ Intent → Entity Resolution → Relation Resolution →
80
+ Predicate Resolution → Query IR → Dialect Renderer →
81
+ AST Validation → Evidence/Confidence
82
+ """
83
+
84
+ # Canonical mappings live in nl2sql_components.mappings.
85
+ KEYWORDS = MAPPING_KEYWORDS
86
+ TABLE_PATTERNS = MAPPING_TABLE_PATTERNS
87
+ COLUMN_PATTERNS = MAPPING_COLUMN_PATTERNS
88
+
89
+ def __init__(self, default_dialect: str = "hive"):
90
+ """Initialize generator with default dialect and template system."""
91
+ self.default_dialect = default_dialect
92
+ self.tokenizer = Tokenizer()
93
+ self._init_query_templates()
94
+
95
+ def _init_query_templates(self) -> None:
96
+ """Initialize prioritized query templates from the canonical module."""
97
+ self.query_templates: List[QueryTemplate] = list(DEFAULT_QUERY_TEMPLATES)
98
+ self.query_templates.sort(key=lambda template: template.priority, reverse=True)
99
+
100
+ def _match_templates(
101
+ self, text: str
102
+ ) -> Tuple[Optional[QueryTemplate], Optional[Dict[str, Any]]]:
103
+ """Try to match input text against query templates.
104
+
105
+ Explicit INSERT/UPDATE/DELETE requests bypass template matching and
106
+ flow through the enhanced semantic builder instead.
107
+ """
108
+ operation = self._detect_operation(text)
109
+ if operation in {"INSERT", "UPDATE", "DELETE"}:
110
+ return None, None
111
+ for template in self.query_templates:
112
+ match_result = template.match(text)
113
+ if match_result:
114
+ logger.debug(
115
+ "Matched template '%s' with priority %s",
116
+ template.name,
117
+ template.priority,
118
+ )
119
+ return template, match_result
120
+ return None, None
121
+
122
+ def _tokenize_and_analyze(self, text: str) -> Dict[str, Any]:
123
+ """Tokenize text and perform semantic analysis.
124
+
125
+ Delegates to ``nl2sql_pipeline.intent.analyze_intent``.
126
+ """
127
+ return _analyze_intent(text)
128
+
129
+ def _generate_from_template(
130
+ self,
131
+ template: QueryTemplate,
132
+ match_groups: Dict[str, Any],
133
+ analysis: Dict[str, Any],
134
+ dialect: str,
135
+ table_hint: Optional[str],
136
+ column_hints: Optional[List[str]],
137
+ text_lower: str = "",
138
+ text_original: str = "",
139
+ ) -> NL2SQLResult:
140
+ """Generate SQL from a matched template.
141
+
142
+ Delegates to ``nl2sql_pipeline.templates.generate_from_template``.
143
+ """
144
+ return _pipeline_generate_from_template(
145
+ template, match_groups, analysis, dialect,
146
+ table_hint, column_hints,
147
+ text_lower, text_original,
148
+ table_patterns=self.TABLE_PATTERNS,
149
+ column_patterns=self.COLUMN_PATTERNS,
150
+ )
151
+
152
+ def _generate_suggestions_for_template(
153
+ self,
154
+ template: QueryTemplate,
155
+ table: str,
156
+ columns: List[str],
157
+ dialect: str,
158
+ ) -> List[str]:
159
+ """Generate suggestions specific to template-based generation.
160
+
161
+ Delegates to ``nl2sql_pipeline.templates.template_suggestions``.
162
+ """
163
+ return _pipeline_template_suggestions(template, table, columns, dialect)
164
+
165
+ def generate(
166
+ self,
167
+ text: str,
168
+ dialect: str = None,
169
+ table_hint: str = None,
170
+ column_hints: List[str] = None,
171
+ ) -> NL2SQLResult:
172
+ """Generate SQL from natural language text."""
173
+ # Validate inputs
174
+ if not isinstance(text, str):
175
+ raise TypeError("text must be a string")
176
+ normalized_dialect = (dialect or self.default_dialect).strip().lower()
177
+ from ..config import SUPPORTED_DIALECTS
178
+ if normalized_dialect not in SUPPORTED_DIALECTS:
179
+ raise ValueError(
180
+ f"Unsupported dialect: {dialect}. "
181
+ f"Supported: {', '.join(SUPPORTED_DIALECTS)}"
182
+ )
183
+ _SAFE_TABLE_HINT = re.compile(
184
+ r"[A-Za-z_][A-Za-z0-9_$]*(?:\.[A-Za-z_][A-Za-z0-9_$]*)*"
185
+ )
186
+ normalized_table_hint = (
187
+ table_hint.strip() if isinstance(table_hint, str) else table_hint
188
+ )
189
+ if normalized_table_hint and not _SAFE_TABLE_HINT.fullmatch(normalized_table_hint):
190
+ raise ValueError(
191
+ "table_hint must be a simple SQL identifier or dotted identifier"
192
+ )
193
+ column_hints_error = validate_column_hints(column_hints)
194
+ if column_hints_error:
195
+ return NL2SQLResult(
196
+ success=False,
197
+ input_text=text,
198
+ dialect=normalized_dialect,
199
+ explanation=column_hints_error,
200
+ confidence=0.0,
201
+ evidence=GenerationEvidence(),
202
+ )
203
+ # Length guard (from production_hardening)
204
+ from ..config import settings
205
+ max_input_length = getattr(settings, "nl2sql_max_input_length", 8192)
206
+ if len(text) > max_input_length:
207
+ return NL2SQLResult(
208
+ success=False,
209
+ input_text=text[:200] + "...",
210
+ dialect=normalized_dialect,
211
+ explanation=(
212
+ f"Input text exceeds maximum length of "
213
+ f"{max_input_length} characters"
214
+ ),
215
+ suggestions=["Please shorten the natural-language query and try again."],
216
+ evidence=GenerationEvidence(),
217
+ )
218
+ dialect = normalized_dialect
219
+ table_hint = normalized_table_hint
220
+ if column_hints_error:
221
+ return NL2SQLResult(
222
+ success=False,
223
+ input_text=text if isinstance(text, str) else "",
224
+ dialect=dialect,
225
+ explanation=column_hints_error,
226
+ confidence=0.0,
227
+ evidence=GenerationEvidence(),
228
+ )
229
+ text_lower = text.lower()
230
+
231
+ log_text = text[:50].replace("\r", "\\r").replace("\n", "\\n")
232
+ logger.info("NL2SQL: Processing '%s...' for dialect %s", log_text, dialect)
233
+ analysis = self._tokenize_and_analyze(text)
234
+ logger.debug(
235
+ "Token analysis: %d tokens, %d tables, %d columns",
236
+ len(analysis["tokens"]),
237
+ len(analysis["tables"]),
238
+ len(analysis["columns"]),
239
+ )
240
+
241
+ boolean_conditions = extract_boolean_conditions(text_lower)
242
+ has_explicit_null_predicate = any(
243
+ keyword in text_lower
244
+ for keyword in [
245
+ "为空",
246
+ "空值",
247
+ "is null",
248
+ "null",
249
+ "empty",
250
+ "非空",
251
+ "不为空",
252
+ "is not null",
253
+ "not null",
254
+ "not empty",
255
+ ]
256
+ )
257
+ if boolean_conditions or has_explicit_null_predicate:
258
+ parsed = self._parse_text(text_lower, text)
259
+ parsed.update(analysis)
260
+ operation = self._detect_operation(text_lower)
261
+ if self._has_relational_keyword(text_lower):
262
+ # Relational subject: 'users who have orders where price = 5'
263
+ # must yield FROM users, not FROM orders.
264
+ table = (
265
+ table_hint
266
+ or self._extract_table_subject(text_lower)
267
+ or self._extract_table(text_lower)
268
+ )
269
+ else:
270
+ table = table_hint or self._extract_table(text_lower)
271
+ columns = column_hints if column_hints else self._extract_columns(text_lower)
272
+ conditions = self._extract_conditions_enhanced(text_lower, text)
273
+ aggregations = self._extract_aggregations(text_lower)
274
+ group_by = self._extract_group_by(text_lower)
275
+ ordering = self._extract_ordering(text_lower)
276
+ limit = self._extract_limit(text_lower)
277
+ joins = self._extract_joins(text_lower)
278
+ distinct = self._check_distinct(text_lower)
279
+ sql, explanation, confidence, evidence = self._build_sql_enhanced(
280
+ operation, table, columns, conditions, aggregations,
281
+ group_by, ordering, limit, joins, distinct, dialect,
282
+ )
283
+ if sql is None:
284
+ suggestions = [
285
+ "Specify the relationship between tables, e.g., "
286
+ "'users.id = invoices.user_id'",
287
+ ]
288
+ else:
289
+ suggestions = self._generate_suggestions(text, sql, dialect)
290
+ return NL2SQLResult(
291
+ success=sql is not None,
292
+ input_text=text,
293
+ sql=sql,
294
+ dialect=dialect,
295
+ explanation=explanation,
296
+ confidence=confidence,
297
+ evidence=evidence,
298
+ suggestions=suggestions,
299
+ parsed_elements=parsed,
300
+ )
301
+
302
+ template, match_groups = self._match_templates(text_lower)
303
+ # If relational keywords detected (D2), bypass templates and use
304
+ # the enhanced path to ensure EXISTS/NOT EXISTS structure instead
305
+ # of a physical JOIN.
306
+ if template and self._has_relational_keyword(text_lower):
307
+ template = None
308
+ logger.debug("NL2SQL: Relational keyword detected, bypassing template matching")
309
+ if template:
310
+ result = self._generate_from_template(
311
+ template, match_groups, analysis, dialect,
312
+ table_hint, column_hints, text_lower, text,
313
+ )
314
+ if result.success:
315
+ logger.info(
316
+ "NL2SQL: Template '%s' matched with confidence %.2f",
317
+ template.name,
318
+ result.confidence,
319
+ )
320
+ return result
321
+
322
+ logger.debug("NL2SQL: Falling back to keyword-based extraction")
323
+ parsed = self._parse_text(text_lower, text)
324
+ parsed.update(analysis)
325
+ operation = self._detect_operation(text_lower)
326
+ columns = column_hints if column_hints else self._extract_columns(text_lower)
327
+ conditions = self._extract_conditions_enhanced(text_lower, text)
328
+ aggregations = self._extract_aggregations(text_lower)
329
+ group_by = self._extract_group_by(text_lower)
330
+ ordering = self._extract_ordering(text_lower)
331
+ limit = self._extract_limit(text_lower)
332
+ joins = self._extract_joins(text_lower)
333
+ distinct = self._check_distinct(text_lower)
334
+
335
+ # Table-name ownership (G1): a request that mentions two or more
336
+ # tables is a relational/join request; it only succeeds when every
337
+ # pair between the mentioned tables has a canonical relationship.
338
+ named_tables = self._named_tables(text_lower)
339
+ if len(named_tables) >= 2:
340
+ table = table_hint or named_tables[0]
341
+ unresolved = [j for j in joins if j.get("condition") is None]
342
+ if unresolved:
343
+ pair_desc = " ⟷ ".join(
344
+ [j.get("subject") or table for j in unresolved]
345
+ + [j["table"] for j in unresolved]
346
+ )
347
+ return NL2SQLResult(
348
+ success=False,
349
+ input_text=text,
350
+ sql=None,
351
+ dialect=dialect,
352
+ explanation=(
353
+ "Unable to safely infer the relationship between the "
354
+ f"table(s) involved ({pair_desc}). Please provide the "
355
+ "join condition or schema relationship."
356
+ ),
357
+ confidence=0.0,
358
+ evidence=GenerationEvidence(),
359
+ suggestions=[
360
+ "Specify the relationship between tables, e.g., "
361
+ "'users.id = invoices.user_id'",
362
+ ],
363
+ )
364
+ else:
365
+ if self._has_relational_keyword(text_lower):
366
+ table = table_hint or self._extract_table_subject(text_lower) or "table_name"
367
+ else:
368
+ table = table_hint or self._extract_table(text_lower)
369
+
370
+ sql, explanation, confidence, evidence = self._build_sql_enhanced(
371
+ operation, table, columns, conditions, aggregations,
372
+ group_by, ordering, limit, joins, distinct, dialect,
373
+ )
374
+ if sql is None:
375
+ suggestions = [
376
+ "Specify the relationship between tables, e.g., "
377
+ "'users.id = invoices.user_id'",
378
+ ]
379
+ else:
380
+ suggestions = self._generate_suggestions(text, sql, dialect)
381
+
382
+ return NL2SQLResult(
383
+ success=sql is not None,
384
+ input_text=text,
385
+ sql=sql,
386
+ dialect=dialect,
387
+ explanation=explanation,
388
+ confidence=confidence,
389
+ evidence=evidence,
390
+ suggestions=suggestions,
391
+ parsed_elements=parsed,
392
+ )
393
+
394
+ def _parse_text(self, text_lower: str, original: str) -> dict:
395
+ """Parse and extract all elements from text.
396
+
397
+ Delegates to ``nl2sql_pipeline.intent.parse_text``.
398
+ """
399
+ return _parse_text_impl(text_lower, original)
400
+
401
+ def _detect_operation(self, text: str) -> str:
402
+ """Detect SQL operation type from text.
403
+
404
+ Delegates to ``nl2sql_pipeline.intent.detect_operation``.
405
+ """
406
+ return _detect_operation(text)
407
+
408
+ def _extract_table(self, text: str) -> str:
409
+ """Extract table name from text.
410
+
411
+ Delegates to ``nl2sql_pipeline.resolution.extract_table``.
412
+ """
413
+ return _pipeline_extract_table(text, self.TABLE_PATTERNS)
414
+
415
+ def _extract_table_subject(self, text: str) -> Optional[str]:
416
+ """Extract the subject table (first appearing in text) for D2 relational queries.
417
+
418
+ Delegates to ``nl2sql_pipeline.resolution.extract_table_subject``.
419
+ """
420
+ return _pipeline_extract_table_subject(text, self.TABLE_PATTERNS)
421
+
422
+ def _named_tables(self, text: str) -> list:
423
+ """Return the distinct table names the text mentions.
424
+
425
+ Delegates to ``nl2sql_pipeline.resolution.named_tables``.
426
+ """
427
+ return _pipeline_named_tables(text, self.TABLE_PATTERNS)
428
+
429
+ def _extract_columns(self, text: str) -> list:
430
+ """Extract column names from text.
431
+
432
+ Delegates to ``nl2sql_pipeline.resolution.extract_columns``.
433
+ """
434
+ return _pipeline_extract_columns(text, self.COLUMN_PATTERNS)
435
+
436
+ def _extract_conditions_enhanced(self, text: str, original: str) -> list:
437
+ """Extract WHERE conditions with enhanced parsing including boolean expressions.
438
+
439
+ Delegates to ``nl2sql_pipeline.predicates.extract_conditions``.
440
+ """
441
+ return _extract_conditions_enhanced_impl(text, original, self.COLUMN_PATTERNS)
442
+
443
+ def _extract_aggregations(self, text: str) -> list:
444
+ """Extract aggregation functions from text.
445
+
446
+ Delegates to ``nl2sql_pipeline.aggregations.extract_aggregations``.
447
+ """
448
+ return _pipeline_extract_aggregations(text, self.COLUMN_PATTERNS)
449
+
450
+ def _extract_group_by(self, text: str) -> list:
451
+ """Extract GROUP BY columns from text.
452
+
453
+ Delegates to ``nl2sql_pipeline.clause_extract.extract_group_by``.
454
+ """
455
+ return _pipeline_extract_group_by(text, self.COLUMN_PATTERNS, self.TABLE_PATTERNS)
456
+
457
+ def _extract_ordering(self, text: str) -> Optional[Tuple[str, str]]:
458
+ """Extract ORDER BY clause from text.
459
+
460
+ Delegates to ``nl2sql_pipeline.clause_extract.extract_ordering``.
461
+ """
462
+ return _pipeline_extract_ordering(text, self.COLUMN_PATTERNS)
463
+
464
+ def _extract_limit(self, text: str) -> Optional[int]:
465
+ """Extract LIMIT value from text.
466
+
467
+ Delegates to ``nl2sql_pipeline.clause_extract.extract_limit``.
468
+ """
469
+ return _pipeline_extract_limit(text)
470
+
471
+ def _extract_joins(self, text: str) -> list:
472
+ """Extract JOIN information from text.
473
+
474
+ Delegates to ``nl2sql_pipeline.resolution.extract_joins``.
475
+ Detects relational existence patterns (e.g., "users who have orders")
476
+ and returns structured join contexts for EXISTS-based SQL generation.
477
+ """
478
+ return _pipeline_extract_joins(text, self.TABLE_PATTERNS)
479
+
480
+ def _guess_join_key(self, table1: str, table2: str) -> Optional[str]:
481
+ """Guess the join key between two tables.
482
+
483
+ Delegates to ``nl2sql_pipeline.resolution.guess_join_key``.
484
+ Returns None for unknown relationships instead of guessing.
485
+ """
486
+ return _pipeline_guess_join_key(table1, table2)
487
+
488
+ def _check_distinct(self, text: str) -> bool:
489
+ """Check if DISTINCT is needed.
490
+
491
+ Delegates to ``nl2sql_pipeline.clause_extract.check_distinct``.
492
+ """
493
+ return _pipeline_check_distinct(text)
494
+
495
+ def _has_relational_keyword(self, text: str) -> bool:
496
+ """Check if text contains relational existence keywords (D2).
497
+
498
+ Delegates to ``nl2sql_pipeline.resolution.has_relational_keyword``.
499
+ """
500
+ return _pipeline_has_relational_keyword(text)
501
+
502
+ def _build_sql_enhanced(
503
+ self,
504
+ operation,
505
+ table,
506
+ columns,
507
+ conditions,
508
+ aggregations,
509
+ group_by,
510
+ ordering,
511
+ limit,
512
+ joins,
513
+ distinct,
514
+ dialect,
515
+ ) -> tuple:
516
+ """Build SQL statement from extracted components.
517
+
518
+ Delegates to ``nl2sql_pipeline.renderer.render_sql``.
519
+ Fail-closed behaviour (unresolved join → success=False) is
520
+ enforced by ``render_sql``.
521
+ """
522
+ ir = _nl2sql_ir.build_ir(
523
+ operation, table, columns, conditions, aggregations,
524
+ group_by, ordering, limit, joins, distinct, dialect,
525
+ )
526
+ return _render_sql_impl(ir)
527
+
528
+ def _validate_generated_sql(self, sql: str, dialect: str) -> float:
529
+ """Validate generated SQL syntax and return confidence adjustment.
530
+
531
+ Delegates to ``nl2sql_pipeline.validation.validate_sql``.
532
+ """
533
+ return _validate_sql_impl(sql, dialect)
534
+
535
+ def _add_limit(self, sql: str, limit: int, dialect: str) -> str:
536
+ """Add LIMIT clause with dialect-specific syntax.
537
+
538
+ Delegates to ``nl2sql_pipeline.renderer.add_limit``.
539
+ """
540
+ return _add_limit_impl(sql, limit, dialect)
541
+
542
+ def _apply_dialect_adjustments(self, sql: str, dialect: str) -> str:
543
+ """Apply dialect-specific syntax adjustments.
544
+
545
+ Delegates to ``nl2sql_pipeline.dialect.apply_dialect_adjustments``
546
+ (moved unchanged — executable-region-aware regex rewriter).
547
+ """
548
+ return _apply_dialect_adjustments_impl(sql, dialect)
549
+
550
+ def _generate_suggestions(self, text: str, sql: str, dialect: str) -> list:
551
+ """Generate improvement suggestions.
552
+
553
+ Delegates to ``nl2sql_pipeline.suggestions.generate_suggestions``.
554
+ """
555
+ return _pipeline_generate_suggestions(text, sql, dialect)
556
+
557
+ def _extract_conditions(self, text: str) -> list:
558
+ """Backward-compatible alias for enhanced condition extraction."""
559
+ return self._extract_conditions_enhanced(text, text)
560
+
561
+ def _build_sql(
562
+ self,
563
+ operation,
564
+ table,
565
+ columns,
566
+ conditions,
567
+ aggregations,
568
+ ordering,
569
+ limit,
570
+ dialect,
571
+ ) -> tuple:
572
+ """Backward-compatible alias for enhanced SQL building."""
573
+ return self._build_sql_enhanced(
574
+ operation, table, columns, conditions, aggregations,
575
+ [], ordering, limit, [], False, dialect,
576
+ )