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,499 @@
1
+ """Frontend ViewModels.
2
+
3
+ These adapters transform backend domain objects (TranspileResult,
4
+ SemanticDiff, NL2SQLResult, etc.) into presentation-friendly structures
5
+ that the UI layer consumes. The UI must never reference backend field
6
+ names directly — all access goes through the ViewModel.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass, field
11
+ from typing import Any, Literal
12
+
13
+ from backend.core.semantic_diff import SemanticDiff, StructuredSemanticDifference
14
+
15
+ # ── Conversion ViewModel ───────────────────────────────────────────────
16
+
17
+ StatusKind = Literal["valid", "warning", "error", "info", "neutral"]
18
+ SyntaxStatus = Literal["ok", "error", "pending"]
19
+ ASTStatus = Literal["ok", "error", "pending"]
20
+ SemanticStatus = Literal["equivalent", "different", "unknown", "parse_error", "pending"]
21
+ RuntimeStatus = Literal["not_executed", "ok", "error", "pending"]
22
+
23
+
24
+ @dataclass
25
+ class ConversionViewModel:
26
+ """Presentation model for a single SQL conversion result."""
27
+
28
+ status: StatusKind
29
+ source_dialect: str
30
+ target_dialect: str
31
+ source_sql: str
32
+ target_sql: str | None
33
+ syntax_status: SyntaxStatus
34
+ ast_status: ASTStatus
35
+ semantic_status: SemanticStatus
36
+ semantic_label: str = ""
37
+ runtime_status: RuntimeStatus = "not_executed"
38
+ target_validation_state: str = "target_valid"
39
+ warnings: list[str] = field(default_factory=list)
40
+ transformations: list[str] = field(default_factory=list)
41
+ compatibility_notes: list[str] = field(default_factory=list)
42
+ error_message: str | None = None
43
+ semantic_findings: list[StructuredSemanticDifference] = field(default_factory=list)
44
+
45
+ @classmethod
46
+ def from_transpile_result(cls, result: Any) -> ConversionViewModel:
47
+ """Build a ViewModel from a backend TranspileResult."""
48
+ if result.success:
49
+ status: StatusKind = "valid"
50
+ error_message = None
51
+ semantic_status: SemanticStatus = "pending"
52
+ semantic_label = ""
53
+ findings: list[StructuredSemanticDifference] = []
54
+ else:
55
+ status = "error"
56
+ error_message = result.error or "Conversion failed"
57
+ semantic_status = "parse_error"
58
+ semantic_label = "Parse Error"
59
+ findings = []
60
+
61
+ return cls(
62
+ status=status,
63
+ source_dialect=result.source_dialect,
64
+ target_dialect=result.target_dialect,
65
+ source_sql=result.source_sql,
66
+ target_sql=result.target_sql,
67
+ syntax_status="ok" if result.success else "error",
68
+ ast_status="ok" if result.success else "error",
69
+ semantic_status=semantic_status,
70
+ semantic_label=semantic_label,
71
+ target_validation_state=getattr(result, "target_validation_state", "target_valid"),
72
+ warnings=list(result.warnings),
73
+ transformations=list(result.transformations),
74
+ compatibility_notes=list(result.compatibility_notes),
75
+ error_message=error_message,
76
+ semantic_findings=findings,
77
+ )
78
+
79
+ @classmethod
80
+ def from_semantic_diff(cls, base: ConversionViewModel, diff: SemanticDiff) -> ConversionViewModel:
81
+ """Enrich a ConversionViewModel with semantic diff results."""
82
+ from frontend.core.design_tokens import (
83
+ SEMANTIC_STATUS_COLOR,
84
+ SEMANTIC_STATUS_LABELS,
85
+ )
86
+
87
+ semantic_status_map: dict[str, SemanticStatus] = {
88
+ "equivalent": "equivalent",
89
+ "structurally_equivalent": "equivalent",
90
+ "different": "different",
91
+ "potentially_different": "unknown",
92
+ "definitely_different": "different",
93
+ "parse_error": "parse_error",
94
+ "unknown": "unknown",
95
+ }
96
+ new_semantic = semantic_status_map.get(diff.semantic_classification, "unknown")
97
+ label = SEMANTIC_STATUS_LABELS.get(diff.semantic_classification, diff.semantic_classification)
98
+ color_key = SEMANTIC_STATUS_COLOR.get(diff.semantic_classification, "neutral")
99
+ status: StatusKind = color_key # type: ignore[assignment]
100
+
101
+ enriched = cls(
102
+ status=status,
103
+ source_dialect=base.source_dialect,
104
+ target_dialect=base.target_dialect,
105
+ source_sql=base.source_sql,
106
+ target_sql=base.target_sql,
107
+ syntax_status=base.syntax_status,
108
+ ast_status=base.ast_status,
109
+ semantic_status=new_semantic,
110
+ semantic_label=label,
111
+ target_validation_state=base.target_validation_state,
112
+ warnings=base.warnings,
113
+ transformations=base.transformations,
114
+ compatibility_notes=base.compatibility_notes,
115
+ error_message=base.error_message,
116
+ semantic_findings=list(diff.structured_differences),
117
+ )
118
+ return enriched
119
+
120
+
121
+ @property
122
+ def success(self) -> bool:
123
+ """Whether the conversion produced a usable result."""
124
+ return self.status == "valid"
125
+
126
+
127
+ # ── Batch Conversion ViewModel ─────────────────────────────────────────
128
+
129
+ @dataclass
130
+ class BatchResultItem:
131
+ statement_index: int
132
+ original_sql: str
133
+ converted_sql: str | None
134
+ success: bool
135
+ error: str | None = None
136
+ warnings: list[str] = field(default_factory=list)
137
+
138
+
139
+ @dataclass
140
+ class BatchConversionViewModel:
141
+ total: int
142
+ succeeded: int
143
+ failed: int
144
+ items: list[BatchResultItem] = field(default_factory=list)
145
+
146
+ @property
147
+ def success_rate(self) -> float:
148
+ return self.succeeded / self.total if self.total else 0.0
149
+
150
+ @classmethod
151
+ def from_results(cls, statements: list[str], results: list[Any]) -> BatchConversionViewModel:
152
+ items = []
153
+ succeeded = 0
154
+ for i, r in enumerate(results):
155
+ ok = getattr(r, "success", False)
156
+ if ok:
157
+ succeeded += 1
158
+ items.append(BatchResultItem(
159
+ statement_index=i + 1,
160
+ original_sql=statements[i] if i < len(statements) else "",
161
+ converted_sql=r.target_sql if ok else None,
162
+ success=ok,
163
+ error=r.error if not ok else None,
164
+ warnings=list(r.warnings) if ok else [],
165
+ ))
166
+ return cls(
167
+ total=len(results),
168
+ succeeded=succeeded,
169
+ failed=len(results) - succeeded,
170
+ items=items,
171
+ )
172
+
173
+
174
+ # ── NL2SQL ViewModel ───────────────────────────────────────────────────
175
+
176
+ ConfidenceLevel = Literal["high", "review", "low"]
177
+
178
+
179
+ @dataclass
180
+ class NL2SQLViewModel:
181
+ success: bool
182
+ input_text: str
183
+ sql: str | None
184
+ dialect: str
185
+ explanation: str
186
+ confidence: float
187
+ suggestions: list[str]
188
+ parsed_elements: dict[str, Any]
189
+ # Structured evidence trail backing the confidence score.
190
+ # Each entry is a dict with keys: evidence_id, label, weight, detail.
191
+ evidence_items: list[dict[str, Any]] = field(default_factory=list)
192
+
193
+ @property
194
+ def status(self) -> StatusKind:
195
+ return "valid" if self.success else "error"
196
+
197
+ @property
198
+ def confidence_level(self) -> ConfidenceLevel:
199
+ """Map overall confidence to a display level."""
200
+ if self.confidence >= 0.8:
201
+ return "high"
202
+ elif self.confidence >= 0.5:
203
+ return "review"
204
+ return "low"
205
+
206
+ @property
207
+ def confidence_label(self) -> str:
208
+ return {
209
+ "high": "High confidence",
210
+ "review": "Review recommended",
211
+ "low": "Low confidence",
212
+ }[self.confidence_level]
213
+
214
+ @property
215
+ def evidence_summary(self) -> str:
216
+ """Human-readable one-line summary of the evidence trail."""
217
+ if not self.evidence_items:
218
+ return ""
219
+ parts = [item["label"] for item in self.evidence_items if item.get("weight", 0.0) != 0.0]
220
+ return "; ".join(parts) if parts else ""
221
+
222
+ @property
223
+ def tables(self) -> list[str]:
224
+ """Extract table names from parsed_elements."""
225
+ pe = self.parsed_elements
226
+ return list({t for t in pe.get("tables", []) if t})
227
+
228
+ @property
229
+ def columns(self) -> list[str]:
230
+ """Extract column names from parsed_elements."""
231
+ pe = self.parsed_elements
232
+ return list({c for c in pe.get("columns", []) if c})
233
+
234
+ @property
235
+ def values(self) -> list[str]:
236
+ """Extract numeric/string values from parsed_elements."""
237
+ pe = self.parsed_elements
238
+ return list({str(v) for v in pe.get("numbers", []) if v})
239
+
240
+ @property
241
+ def is_chinese(self) -> bool:
242
+ return bool(self.parsed_elements.get("is_chinese"))
243
+
244
+ @property
245
+ def token_count(self) -> int:
246
+ return int(self.parsed_elements.get("token_count", 0))
247
+
248
+ @classmethod
249
+ def from_nl2sql_result(cls, result: Any) -> NL2SQLViewModel:
250
+ evidence = getattr(result, "evidence", None)
251
+ evidence_items = []
252
+ if evidence is not None:
253
+ evidence_items = [
254
+ {
255
+ "evidence_id": item.evidence_id,
256
+ "label": item.label,
257
+ "weight": item.weight,
258
+ "detail": item.detail,
259
+ }
260
+ for item in evidence.items
261
+ ]
262
+ return cls(
263
+ success=result.success,
264
+ input_text=result.input_text,
265
+ sql=result.sql,
266
+ dialect=result.dialect,
267
+ explanation=result.explanation,
268
+ confidence=result.confidence,
269
+ suggestions=list(result.suggestions),
270
+ parsed_elements=dict(result.parsed_elements),
271
+ evidence_items=evidence_items,
272
+ )
273
+
274
+ def clean_sql(self) -> str:
275
+ """Return SQL without the backend's specific comment prefix.
276
+
277
+ The backend prefixes generated SQL with '-- Generated for DIALECT\\n'.
278
+ We only strip that exact prefix to avoid removing legitimate comments.
279
+ """
280
+ if not self.sql:
281
+ return ""
282
+ lines = self.sql.splitlines()
283
+ # Remove only the backend-generated header comment
284
+ if lines and lines[0].startswith("-- Generated for "):
285
+ lines = lines[1:]
286
+ return "\n".join(lines).strip() or self.sql
287
+
288
+
289
+ # ── Semantic Diff ViewModel ────────────────────────────────────────────
290
+
291
+ FindingSeverity = Literal["error", "warning"]
292
+
293
+
294
+ @dataclass
295
+ class SemanticFindingViewModel:
296
+ """Presentation model for one structured semantic difference finding."""
297
+
298
+ index: int
299
+ category: str
300
+ severity: FindingSeverity
301
+ source_fragment: str
302
+ target_fragment: str
303
+ explanation: str
304
+ confidence: float
305
+ evidence: str | None
306
+
307
+ @classmethod
308
+ def from_backend(cls, index: int, diff: StructuredSemanticDifference) -> SemanticFindingViewModel:
309
+ return cls(
310
+ index=index,
311
+ category=diff.category,
312
+ severity=diff.severity, # type: ignore[arg-type]
313
+ source_fragment=diff.source_fragment or "",
314
+ target_fragment=diff.target_fragment or "",
315
+ explanation=diff.explanation or "",
316
+ confidence=diff.confidence,
317
+ evidence=diff.evidence,
318
+ )
319
+
320
+ @property
321
+ def severity_label(self) -> str:
322
+ return "HIGH" if self.severity == "error" else "MEDIUM"
323
+
324
+ @property
325
+ def category_label(self) -> str:
326
+ return self.category.replace("_", " ").title()
327
+
328
+
329
+ SemanticClassification = Literal[
330
+ "equivalent",
331
+ "structurally_equivalent",
332
+ "potentially_different",
333
+ "definitely_different",
334
+ "unknown",
335
+ "parse_error",
336
+ ]
337
+
338
+
339
+ @dataclass
340
+ class SemanticDiffViewModel:
341
+ """Presentation model for a full semantic diff comparison."""
342
+
343
+ classification: SemanticClassification
344
+ classification_label: str
345
+ overall_status: StatusKind
346
+ confidence: float
347
+ source_sql: str
348
+ target_sql: str
349
+ source_dialect: str
350
+ target_dialect: str
351
+ parse_error: str | None
352
+ differences: list[str]
353
+ findings: list[SemanticFindingViewModel]
354
+ evidence: str | None
355
+
356
+ @classmethod
357
+ def from_backend(
358
+ cls,
359
+ source_sql: str,
360
+ target_sql: str,
361
+ source_dialect: str,
362
+ target_dialect: str,
363
+ diff: SemanticDiff,
364
+ ) -> SemanticDiffViewModel:
365
+ from frontend.core.design_tokens import (
366
+ SEMANTIC_STATUS_COLOR,
367
+ SEMANTIC_STATUS_LABELS,
368
+ )
369
+
370
+ label = SEMANTIC_STATUS_LABELS.get(diff.semantic_classification, diff.semantic_classification)
371
+ color_key = SEMANTIC_STATUS_COLOR.get(diff.semantic_classification, "neutral")
372
+ status: StatusKind = color_key # type: ignore[assignment]
373
+
374
+ findings = [
375
+ SemanticFindingViewModel.from_backend(i, sd)
376
+ for i, sd in enumerate(diff.structured_differences)
377
+ ]
378
+
379
+ return cls(
380
+ classification=diff.semantic_classification, # type: ignore[arg-type]
381
+ classification_label=label,
382
+ overall_status=status,
383
+ confidence=diff.confidence,
384
+ source_sql=source_sql,
385
+ target_sql=target_sql,
386
+ source_dialect=source_dialect,
387
+ target_dialect=target_dialect,
388
+ parse_error=diff.parse_error,
389
+ differences=list(diff.differences),
390
+ findings=findings,
391
+ evidence=diff.evidence,
392
+ )
393
+
394
+ @property
395
+ def finding_count(self) -> int:
396
+ return len(self.findings)
397
+
398
+ @property
399
+ def error_count(self) -> int:
400
+ return sum(1 for f in self.findings if f.severity == "error")
401
+
402
+ @property
403
+ def warning_count(self) -> int:
404
+ return sum(1 for f in self.findings if f.severity == "warning")
405
+
406
+
407
+ # ── Lineage ViewModel ──────────────────────────────────────────────────
408
+
409
+ @dataclass
410
+ class LineageTable:
411
+ name: str
412
+ alias: str | None = None
413
+
414
+
415
+ @dataclass
416
+ class LineageOutputColumn:
417
+ name: str
418
+ expression: str = ""
419
+ source_table: str | None = None
420
+ source_column: str | None = None
421
+
422
+
423
+ @dataclass
424
+ class LineageViewModel:
425
+ dialect: str
426
+ sql: str
427
+ tables: list[LineageTable] = field(default_factory=list)
428
+ output_columns: list[LineageOutputColumn] = field(default_factory=list)
429
+ column_references: dict[str, list[str]] = field(default_factory=dict)
430
+ mermaid_safe: str = ""
431
+
432
+ @classmethod
433
+ def empty(cls, dialect: str, sql: str) -> LineageViewModel:
434
+ return cls(dialect=dialect, sql=sql)
435
+
436
+
437
+ # ── Query Analysis ViewModel ───────────────────────────────────────────
438
+
439
+ @dataclass
440
+ class QueryAnalysisResult:
441
+ dialect: str
442
+ query_type: str = "SELECT"
443
+ tables: list[str] = field(default_factory=list)
444
+ join_count: int = 0
445
+ has_aggregation: bool = False
446
+ has_sorting: bool = False
447
+ limit: str | None = None
448
+ hints: list[str] = field(default_factory=list)
449
+ error: str | None = None
450
+
451
+
452
+ # ── Helper: sanitize Mermaid identifiers ──────────────────────────────
453
+
454
+ _MERMAID_SAFE_REPLACEMENTS = {
455
+ '"': "",
456
+ "'": "",
457
+ "`": "",
458
+ "\\": "",
459
+ "/": "_",
460
+ " ": "_",
461
+ "-": "_",
462
+ ".": "_",
463
+ ",": "",
464
+ "(": "",
465
+ ")": "",
466
+ "[": "",
467
+ "]": "",
468
+ "{": "",
469
+ "}": "",
470
+ "<": "",
471
+ ">": "",
472
+ "|": "_",
473
+ "\n": "",
474
+ "\r": "",
475
+ "\t": "",
476
+ }
477
+
478
+
479
+ def sanitize_mermaid_label(text: str) -> str:
480
+ """Sanitize a string for use as a Mermaid node label or identifier."""
481
+ result = str(text)
482
+ for bad, repl in _MERMAID_SAFE_REPLACEMENTS.items():
483
+ result = result.replace(bad, repl)
484
+ # Collapse underscores
485
+ while "__" in result:
486
+ result = result.replace("__", "_")
487
+ result = result.strip("_")
488
+ return result or "node"
489
+
490
+
491
+ def sanitize_mermaid_id(text: str) -> str:
492
+ """Sanitize a string for use as a Mermaid node ID (alphanumeric + underscore)."""
493
+ import re
494
+ result = sanitize_mermaid_label(text)
495
+ result = re.sub(r"[^a-zA-Z0-9_]", "_", result)
496
+ # Must start with a letter
497
+ if result and not result[0].isalpha():
498
+ result = "n_" + result
499
+ return result or "node"
File without changes