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.
- backend/__init__.py +1 -0
- backend/api/__init__.py +1 -0
- backend/api/main.py +732 -0
- backend/api/middleware.py +274 -0
- backend/api/rate_limit_store.py +105 -0
- backend/api/readiness.py +178 -0
- backend/core/__init__.py +57 -0
- backend/core/cache.py +261 -0
- backend/core/column_hint_validation.py +22 -0
- backend/core/config.py +546 -0
- backend/core/constants.py +84 -0
- backend/core/exceptions.py +124 -0
- backend/core/function_call_scanner.py +64 -0
- backend/core/function_semantics.py +148 -0
- backend/core/functions_db.json +601 -0
- backend/core/functions_lookup.py +332 -0
- backend/core/metadata.py +71 -0
- backend/core/nl2sql.py +85 -0
- backend/core/nl2sql_components/__init__.py +72 -0
- backend/core/nl2sql_components/aggregations.py +35 -0
- backend/core/nl2sql_components/boolean_conditions.py +279 -0
- backend/core/nl2sql_components/core_extract.py +106 -0
- backend/core/nl2sql_components/evidence.py +122 -0
- backend/core/nl2sql_components/mappings.py +185 -0
- backend/core/nl2sql_components/relations.py +180 -0
- backend/core/nl2sql_components/templates.py +130 -0
- backend/core/nl2sql_components/tokenizer.py +187 -0
- backend/core/nl2sql_legacy.py +22 -0
- backend/core/nl2sql_pipeline/__init__.py +13 -0
- backend/core/nl2sql_pipeline/aggregations.py +26 -0
- backend/core/nl2sql_pipeline/clause_extract.py +97 -0
- backend/core/nl2sql_pipeline/dialect.py +139 -0
- backend/core/nl2sql_pipeline/intent.py +85 -0
- backend/core/nl2sql_pipeline/ir.py +70 -0
- backend/core/nl2sql_pipeline/models.py +30 -0
- backend/core/nl2sql_pipeline/orchestrator.py +576 -0
- backend/core/nl2sql_pipeline/predicates.py +182 -0
- backend/core/nl2sql_pipeline/renderer.py +248 -0
- backend/core/nl2sql_pipeline/resolution.py +69 -0
- backend/core/nl2sql_pipeline/suggestions.py +30 -0
- backend/core/nl2sql_pipeline/templates.py +371 -0
- backend/core/nl2sql_pipeline/validation.py +28 -0
- backend/core/p1_sql_scanner.py +196 -0
- backend/core/p2_data_validation.py +72 -0
- backend/core/parser.py +280 -0
- backend/core/post_processor.py +129 -0
- backend/core/rules.py +808 -0
- backend/core/semantic_diff.py +385 -0
- backend/core/transpiler.py +791 -0
- backend/core/type_mapping.json +593 -0
- backend/core/type_mapping.py +765 -0
- backend/utils/__init__.py +2 -0
- backend/utils/validators.py +407 -0
- frontend/__init__.py +10 -0
- frontend/app_context_v2.py +201 -0
- frontend/core/__init__.py +33 -0
- frontend/core/design_tokens.py +127 -0
- frontend/core/escaping.py +13 -0
- frontend/core/navigation.py +135 -0
- frontend/core/state.py +612 -0
- frontend/core/styles.py +523 -0
- frontend/core/themes.py +69 -0
- frontend/core/viewmodels.py +499 -0
- frontend/pages/__init__.py +0 -0
- frontend/pages/convert.py +283 -0
- frontend/pages/diff.py +157 -0
- frontend/pages/functions.py +100 -0
- frontend/pages/history.py +144 -0
- frontend/pages/lineage.py +168 -0
- frontend/pages/nl2sql.py +305 -0
- frontend/pages/query_analysis.py +120 -0
- frontend/pages/runtime.py +66 -0
- frontend/pages/settings.py +60 -0
- frontend/pages/templates.py +67 -0
- frontend/pages/types.py +110 -0
- frontend/templates_v2.py +53 -0
- frontend/ui/__init__.py +0 -0
- frontend/ui/command_palette.py +153 -0
- frontend/ui/diff.py +275 -0
- frontend/ui/editor.py +102 -0
- frontend/ui/status.py +255 -0
- sql_dialect_master-1.1.1.dist-info/METADATA +360 -0
- sql_dialect_master-1.1.1.dist-info/RECORD +86 -0
- sql_dialect_master-1.1.1.dist-info/WHEEL +5 -0
- sql_dialect_master-1.1.1.dist-info/licenses/LICENSE +21 -0
- sql_dialect_master-1.1.1.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Custom exceptions for SQL Dialect Master.
|
|
2
|
+
|
|
3
|
+
Provides a hierarchy of exceptions for better error handling and debugging.
|
|
4
|
+
"""
|
|
5
|
+
from enum import Enum
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ErrorCode(str, Enum):
|
|
9
|
+
"""Stable machine-readable error categories."""
|
|
10
|
+
UNSUPPORTED_DIALECT = "UNSUPPORTED_DIALECT"
|
|
11
|
+
TRANSPILE_FAILED = "TRANSPILE_FAILED"
|
|
12
|
+
PARSE_FAILED = "PARSE_FAILED"
|
|
13
|
+
SECURITY_VIOLATION = "SECURITY_VIOLATION"
|
|
14
|
+
VALIDATION_FAILED = "VALIDATION_FAILED"
|
|
15
|
+
BAD_REQUEST = "BAD_REQUEST"
|
|
16
|
+
NOT_FOUND = "NOT_FOUND"
|
|
17
|
+
METHOD_NOT_ALLOWED = "METHOD_NOT_ALLOWED"
|
|
18
|
+
PAYLOAD_TOO_LARGE = "PAYLOAD_TOO_LARGE"
|
|
19
|
+
RATE_LIMITED = "RATE_LIMITED"
|
|
20
|
+
CACHE_FAILED = "CACHE_FAILED"
|
|
21
|
+
CONFIGURATION_INVALID = "CONFIGURATION_INVALID"
|
|
22
|
+
RULE_CONFLICT = "RULE_CONFLICT"
|
|
23
|
+
INTERNAL_ERROR = "INTERNAL_ERROR"
|
|
24
|
+
UNKNOWN = "UNKNOWN"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SDMException(Exception):
|
|
28
|
+
"""Base exception for SQL Dialect Master."""
|
|
29
|
+
|
|
30
|
+
error_code = ErrorCode.UNKNOWN
|
|
31
|
+
|
|
32
|
+
def __init__(self, message: str, details: dict = None):
|
|
33
|
+
super().__init__(message)
|
|
34
|
+
self.message = message
|
|
35
|
+
self.details = details or {}
|
|
36
|
+
|
|
37
|
+
def to_dict(self) -> dict:
|
|
38
|
+
"""Convert exception to a backward-compatible API error dictionary."""
|
|
39
|
+
code = self.error_code.value
|
|
40
|
+
return {
|
|
41
|
+
"error_type": self.__class__.__name__,
|
|
42
|
+
"error_code": code,
|
|
43
|
+
"code": code,
|
|
44
|
+
"message": self.message,
|
|
45
|
+
"details": self.details
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class UnsupportedDialectError(SDMException):
|
|
50
|
+
"""Raised when an unsupported SQL dialect is specified."""
|
|
51
|
+
|
|
52
|
+
error_code = ErrorCode.UNSUPPORTED_DIALECT
|
|
53
|
+
|
|
54
|
+
def __init__(self, dialect: str, supported: list = None):
|
|
55
|
+
message = f"Unsupported dialect: {dialect}"
|
|
56
|
+
if supported:
|
|
57
|
+
message += f". Supported: {', '.join(supported)}"
|
|
58
|
+
super().__init__(message, {"dialect": dialect, "supported": supported})
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class TranspileError(SDMException):
|
|
62
|
+
"""Raised when SQL transpilation fails."""
|
|
63
|
+
|
|
64
|
+
error_code = ErrorCode.TRANSPILE_FAILED
|
|
65
|
+
|
|
66
|
+
def __init__(self, message: str, source_sql: str = None, source_dialect: str = None, target_dialect: str = None):
|
|
67
|
+
details = {
|
|
68
|
+
"source_sql": source_sql[:200] if source_sql else None,
|
|
69
|
+
"source_dialect": source_dialect,
|
|
70
|
+
"target_dialect": target_dialect
|
|
71
|
+
}
|
|
72
|
+
super().__init__(message, details)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class ParseError(SDMException):
|
|
76
|
+
"""Raised when SQL parsing fails."""
|
|
77
|
+
|
|
78
|
+
error_code = ErrorCode.PARSE_FAILED
|
|
79
|
+
|
|
80
|
+
def __init__(self, message: str, sql: str = None, dialect: str = None):
|
|
81
|
+
details = {"sql": sql[:200] if sql else None, "dialect": dialect}
|
|
82
|
+
super().__init__(message, details)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class SecurityViolationError(SDMException):
|
|
86
|
+
"""Raised when SQL contains potentially dangerous patterns."""
|
|
87
|
+
|
|
88
|
+
error_code = ErrorCode.SECURITY_VIOLATION
|
|
89
|
+
|
|
90
|
+
def __init__(self, message: str, pattern: str = None):
|
|
91
|
+
details = {"pattern": pattern}
|
|
92
|
+
super().__init__(message, details)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class ValidationError(SDMException):
|
|
96
|
+
"""Raised when input validation fails."""
|
|
97
|
+
|
|
98
|
+
error_code = ErrorCode.VALIDATION_FAILED
|
|
99
|
+
|
|
100
|
+
def __init__(self, message: str, field: str = None, value: str = None):
|
|
101
|
+
details = {"field": field, "value": value}
|
|
102
|
+
super().__init__(message, details)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class CacheError(SDMException):
|
|
106
|
+
"""Raised when cache operations fail."""
|
|
107
|
+
|
|
108
|
+
error_code = ErrorCode.CACHE_FAILED
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class ConfigurationError(SDMException):
|
|
112
|
+
"""Raised when configuration is invalid."""
|
|
113
|
+
|
|
114
|
+
error_code = ErrorCode.CONFIGURATION_INVALID
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class RuleConflictError(SDMException):
|
|
118
|
+
"""Raised when transformation rules conflict."""
|
|
119
|
+
|
|
120
|
+
error_code = ErrorCode.RULE_CONFLICT
|
|
121
|
+
|
|
122
|
+
def __init__(self, message: str, rule1: str = None, rule2: str = None):
|
|
123
|
+
details = {"rule1": rule1, "rule2": rule2}
|
|
124
|
+
super().__init__(message, details)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Scanner-backed function-call replacement shared by custom SQL rewrites."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Callable
|
|
5
|
+
|
|
6
|
+
from .p1_sql_scanner import mask_non_executable
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def replace_function_calls(
|
|
10
|
+
sql: str,
|
|
11
|
+
function_name: str,
|
|
12
|
+
replacer: Callable[[str, str], str],
|
|
13
|
+
) -> str:
|
|
14
|
+
"""Replace complete calls without interpreting literals or comments as SQL."""
|
|
15
|
+
upper_name = function_name.upper()
|
|
16
|
+
name_len = len(function_name)
|
|
17
|
+
masked = mask_non_executable(sql)
|
|
18
|
+
result = []
|
|
19
|
+
i = 0
|
|
20
|
+
length = len(sql)
|
|
21
|
+
|
|
22
|
+
while i < length:
|
|
23
|
+
if masked[i:i + name_len].upper() != upper_name:
|
|
24
|
+
result.append(sql[i])
|
|
25
|
+
i += 1
|
|
26
|
+
continue
|
|
27
|
+
|
|
28
|
+
name_end = i + name_len
|
|
29
|
+
if i > 0 and (masked[i - 1].isalnum() or masked[i - 1] == "_"):
|
|
30
|
+
result.append(sql[i])
|
|
31
|
+
i += 1
|
|
32
|
+
continue
|
|
33
|
+
|
|
34
|
+
j = name_end
|
|
35
|
+
while j < length and masked[j].isspace():
|
|
36
|
+
j += 1
|
|
37
|
+
if j >= length or masked[j] != "(":
|
|
38
|
+
result.append(sql[i])
|
|
39
|
+
i += 1
|
|
40
|
+
continue
|
|
41
|
+
|
|
42
|
+
depth = 0
|
|
43
|
+
k = j
|
|
44
|
+
while k < length:
|
|
45
|
+
char = masked[k]
|
|
46
|
+
if char == "(":
|
|
47
|
+
depth += 1
|
|
48
|
+
elif char == ")":
|
|
49
|
+
depth -= 1
|
|
50
|
+
if depth == 0:
|
|
51
|
+
original_call = sql[i:k + 1]
|
|
52
|
+
args = sql[j + 1:k]
|
|
53
|
+
result.append(replacer(args, original_call))
|
|
54
|
+
i = k + 1
|
|
55
|
+
break
|
|
56
|
+
k += 1
|
|
57
|
+
else:
|
|
58
|
+
result.append(sql[i:])
|
|
59
|
+
break
|
|
60
|
+
|
|
61
|
+
return "".join(result)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
__all__ = ["replace_function_calls"]
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Semantics-aware function equivalence metadata for SQL dialect conversion.
|
|
3
|
+
|
|
4
|
+
The existing function catalog answers "is there syntax for this function?".
|
|
5
|
+
This module answers the stronger question "can this function be treated as
|
|
6
|
+
semantically equivalent for this source/target dialect pair?".
|
|
7
|
+
|
|
8
|
+
Only explicitly-reviewed functions and dialect pairs are classified as
|
|
9
|
+
semantic equivalence. Unreviewed pairs remain ``unknown`` instead of being
|
|
10
|
+
inferred from syntax availability or fuzzy matching.
|
|
11
|
+
"""
|
|
12
|
+
from typing import Any, Dict, Tuple
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
FunctionKey = Tuple[str, str, str]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
_SEMANTIC_STATUS = {
|
|
19
|
+
"equivalent": "equivalent",
|
|
20
|
+
"supported-but-different": "supported-but-different",
|
|
21
|
+
"unsupported": "unsupported",
|
|
22
|
+
"unknown": "unknown",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# Explicit metadata for the initial high-risk cross-dialect corpus.
|
|
27
|
+
# Keep this registry intentionally small: additions require semantic review and
|
|
28
|
+
# a regression test rather than being inferred from functions_db.json syntax.
|
|
29
|
+
_FUNCTION_SEMANTICS: Dict[str, Dict[str, Any]] = {
|
|
30
|
+
"DATEDIFF": {
|
|
31
|
+
"argument_semantics": "calendar-day difference; argument order and/or unit syntax is dialect-sensitive",
|
|
32
|
+
"return_shape": "scalar integer-like value",
|
|
33
|
+
"null_behavior": "NULL-sensitive; follows source/target function semantics",
|
|
34
|
+
"notes": "Dialect implementations differ in argument order and unit handling.",
|
|
35
|
+
"pairs": {
|
|
36
|
+
("mysql", "tsql"): "supported-but-different",
|
|
37
|
+
("mysql", "postgres"): "supported-but-different",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
"DATE_FORMAT": {
|
|
41
|
+
"argument_semantics": "format a date/time value using dialect-specific format tokens",
|
|
42
|
+
"return_shape": "string",
|
|
43
|
+
"null_behavior": "NULL input produces NULL",
|
|
44
|
+
"notes": "Formatting token languages differ across dialects.",
|
|
45
|
+
"pairs": {
|
|
46
|
+
("mysql", "postgres"): "supported-but-different",
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
"LENGTH": {
|
|
50
|
+
"argument_semantics": "string length; byte-vs-character semantics may differ",
|
|
51
|
+
"return_shape": "scalar integer-like value",
|
|
52
|
+
"null_behavior": "NULL input produces NULL",
|
|
53
|
+
"notes": "MySQL LENGTH is byte-oriented while Oracle LENGTH is character-oriented.",
|
|
54
|
+
"pairs": {
|
|
55
|
+
("mysql", "oracle"): "supported-but-different",
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
"CONCAT": {
|
|
59
|
+
"argument_semantics": "variadic string concatenation",
|
|
60
|
+
"return_shape": "string",
|
|
61
|
+
"null_behavior": "NULL propagation semantics differ by dialect",
|
|
62
|
+
"notes": "MySQL CONCAT propagates NULL arguments; PostgreSQL CONCAT ignores NULL arguments.",
|
|
63
|
+
"pairs": {
|
|
64
|
+
("mysql", "postgres"): "supported-but-different",
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
"GET_JSON_OBJECT": {
|
|
68
|
+
"argument_semantics": "extract a JSON path value with dialect-specific JSON path/operator semantics",
|
|
69
|
+
"return_shape": "scalar text-like JSON value",
|
|
70
|
+
"null_behavior": "path/input failures are dialect-sensitive",
|
|
71
|
+
"notes": "Hive GET_JSON_OBJECT is not a semantic synonym for PostgreSQL JSON operators.",
|
|
72
|
+
"pairs": {
|
|
73
|
+
("hive", "postgres"): "supported-but-different",
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
"COLLECT_LIST": {
|
|
77
|
+
"argument_semantics": "aggregate input values into a collection; ordering is not implied unless explicitly specified",
|
|
78
|
+
"return_shape": "array/list-like aggregate",
|
|
79
|
+
"null_behavior": "NULL inclusion/filtering is dialect-sensitive",
|
|
80
|
+
"notes": "Spark COLLECT_LIST and PostgreSQL ARRAY_AGG differ in output type and ordering guarantees.",
|
|
81
|
+
"pairs": {
|
|
82
|
+
("spark", "postgres"): "supported-but-different",
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
"ROW_NUMBER": {
|
|
86
|
+
"argument_semantics": "assign sequential row numbers within an optional window specification",
|
|
87
|
+
"return_shape": "integer-like value",
|
|
88
|
+
"null_behavior": "window ordering determines numbering; NULL ordering remains dialect-sensitive",
|
|
89
|
+
"notes": "Core window semantics are equivalent for the reviewed pair when the same ORDER BY semantics are preserved.",
|
|
90
|
+
"pairs": {
|
|
91
|
+
("postgres", "mysql"): "equivalent",
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
"REGEXP_REPLACE": {
|
|
95
|
+
"argument_semantics": "replace regex matches using dialect-specific regex engines and replacement rules",
|
|
96
|
+
"return_shape": "string",
|
|
97
|
+
"null_behavior": "NULL input produces NULL",
|
|
98
|
+
"notes": "T-SQL has no native REGEXP_REPLACE equivalent in the reviewed baseline.",
|
|
99
|
+
"pairs": {},
|
|
100
|
+
},
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class FunctionSemanticRegistry:
|
|
105
|
+
"""Resolve explicit semantic equivalence for reviewed SQL functions."""
|
|
106
|
+
|
|
107
|
+
def __init__(self, semantics: Dict[str, Dict[str, Any]] = None) -> None:
|
|
108
|
+
self._semantics = semantics if semantics is not None else _FUNCTION_SEMANTICS
|
|
109
|
+
|
|
110
|
+
def resolve(self, function: str, source: str, target: str) -> Dict[str, Any]:
|
|
111
|
+
"""Return an explicit semantic classification for a function pair."""
|
|
112
|
+
function_name = function.upper().strip()
|
|
113
|
+
source_dialect = source.lower().strip()
|
|
114
|
+
target_dialect = target.lower().strip()
|
|
115
|
+
metadata = self._semantics.get(function_name)
|
|
116
|
+
|
|
117
|
+
if metadata is None:
|
|
118
|
+
return {
|
|
119
|
+
"function": function_name,
|
|
120
|
+
"source_dialect": source_dialect,
|
|
121
|
+
"target_dialect": target_dialect,
|
|
122
|
+
"status": _SEMANTIC_STATUS["unknown"],
|
|
123
|
+
"equivalent": False,
|
|
124
|
+
"reason": "Function is not present in the reviewed semantic registry.",
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
pair_status = metadata.get("pairs", {}).get((source_dialect, target_dialect))
|
|
128
|
+
if pair_status is None:
|
|
129
|
+
if function_name == "REGEXP_REPLACE" and target_dialect == "tsql":
|
|
130
|
+
pair_status = _SEMANTIC_STATUS["unsupported"]
|
|
131
|
+
else:
|
|
132
|
+
pair_status = _SEMANTIC_STATUS["unknown"]
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
"function": function_name,
|
|
136
|
+
"source_dialect": source_dialect,
|
|
137
|
+
"target_dialect": target_dialect,
|
|
138
|
+
"status": pair_status,
|
|
139
|
+
"equivalent": pair_status == _SEMANTIC_STATUS["equivalent"],
|
|
140
|
+
"argument_semantics": metadata.get("argument_semantics", ""),
|
|
141
|
+
"return_shape": metadata.get("return_shape", ""),
|
|
142
|
+
"null_behavior": metadata.get("null_behavior", ""),
|
|
143
|
+
"notes": metadata.get("notes", ""),
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
def get_metadata(self, function: str) -> Dict[str, Any]:
|
|
147
|
+
"""Return reviewed metadata without inferring semantic equivalence."""
|
|
148
|
+
return self._semantics.get(function.upper().strip(), {})
|