sql-dialect-master 1.5.0__tar.gz → 1.7.0__tar.gz
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.
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/PKG-INFO +2 -2
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/README.md +1 -1
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/metadata.py +2 -2
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_pipeline/orchestrator.py +537 -7
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/pyproject.toml +1 -1
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/sql_dialect_master.egg-info/PKG-INFO +2 -2
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/LICENSE +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/__init__.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/api/__init__.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/api/auth.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/api/client_identity.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/api/main.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/api/metrics.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/api/middleware.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/api/rate_limit_store.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/api/readiness.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/api/workers.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/__init__.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/cache.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/column_hint_validation.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/config.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/constants.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/exceptions.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/function_call_scanner.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/function_semantics.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/functions_db.json +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/functions_lookup.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_components/__init__.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_components/aggregations.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_components/boolean_conditions.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_components/core_extract.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_components/evidence.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_components/mappings.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_components/relations.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_components/templates.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_components/tokenizer.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_legacy.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_llm.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_pipeline/__init__.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_pipeline/aggregations.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_pipeline/clause_extract.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_pipeline/dialect.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_pipeline/intent.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_pipeline/ir.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_pipeline/models.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_pipeline/predicates.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_pipeline/renderer.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_pipeline/resolution.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_pipeline/suggestions.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_pipeline/templates.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_pipeline/validation.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_schema.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/p1_sql_scanner.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/p2_data_validation.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/parser.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/post_processor.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/rules.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/semantic_diff.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/sql_statements.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/transpiler.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/type_mapping.json +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/type_mapping.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/utils/__init__.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/utils/validators.py +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/setup.cfg +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/sql_dialect_master.egg-info/SOURCES.txt +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/sql_dialect_master.egg-info/dependency_links.txt +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/sql_dialect_master.egg-info/requires.txt +0 -0
- {sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/sql_dialect_master.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sql-dialect-master
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7.0
|
|
4
4
|
Summary: SQL Dialect Master - Multi-database SQL conversion engine
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -38,7 +38,7 @@ Dynamic: license-file
|
|
|
38
38
|
|
|
39
39
|
Enterprise-grade multi-database SQL conversion engine supporting 12 database dialects.
|
|
40
40
|
|
|
41
|
-
Current release: 1.
|
|
41
|
+
Current release: 1.7.0
|
|
42
42
|
|
|
43
43
|
[](https://www.python.org/downloads/)
|
|
44
44
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Enterprise-grade multi-database SQL conversion engine supporting 12 database dialects.
|
|
4
4
|
|
|
5
|
-
Current release: 1.
|
|
5
|
+
Current release: 1.7.0
|
|
6
6
|
|
|
7
7
|
[](https://www.python.org/downloads/)
|
|
8
8
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -17,8 +17,8 @@ Design rules:
|
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
19
19
|
# ── Release version facts (sole source of truth) ────────────────────────
|
|
20
|
-
PROJECT_VERSION = "1.
|
|
21
|
-
API_VERSION = "1.
|
|
20
|
+
PROJECT_VERSION = "1.7.0"
|
|
21
|
+
API_VERSION = "1.7.0"
|
|
22
22
|
# Frontend v2 architecture — independent of the product release version.
|
|
23
23
|
FRONTEND_ARCHITECTURE_VERSION = 2
|
|
24
24
|
|
{sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_pipeline/orchestrator.py
RENAMED
|
@@ -98,6 +98,45 @@ _DATE_INTERVAL = re.compile(
|
|
|
98
98
|
re.IGNORECASE,
|
|
99
99
|
)
|
|
100
100
|
MAX_NL2SQL_DATE_INTERVAL_DAYS = 36525
|
|
101
|
+
_SCHEMA_WHOSE_PREFIX = re.compile(
|
|
102
|
+
r"\bwhose\s+"
|
|
103
|
+
r"([A-Za-z_][A-Za-z0-9_$]*|"
|
|
104
|
+
r"[A-Za-z_][A-Za-z0-9_$]*\.[A-Za-z_][A-Za-z0-9_$]*|"
|
|
105
|
+
r"[A-Za-z_][A-Za-z0-9_$]*\.[A-Za-z_][A-Za-z0-9_$]*\.[A-Za-z_][A-Za-z0-9_$]*)\s+"
|
|
106
|
+
r"(is\s+not|isn't|is|equals|<>|!=|>=|<=|=|>|<)\s*",
|
|
107
|
+
re.IGNORECASE,
|
|
108
|
+
)
|
|
109
|
+
_SCHEMA_WHOSE_REJECTED_VALUES = {
|
|
110
|
+
"and", "or", "not", "is", "null", "true", "false", "unknown",
|
|
111
|
+
"select", "from", "where", "group", "order", "by", "limit",
|
|
112
|
+
"current_date", "current_timestamp", "now",
|
|
113
|
+
}
|
|
114
|
+
_SCHEMA_WHOSE_CJK_CONNECTORS = {"和", "或", "且", "并", "与", "跟", "及"}
|
|
115
|
+
_SCHEMA_JOINED_ON_PREFIX = re.compile(
|
|
116
|
+
r"\b(?:(inner|left|right)\s+)?joined\s+on\b",
|
|
117
|
+
re.IGNORECASE,
|
|
118
|
+
)
|
|
119
|
+
_SCHEMA_JOINED_ON_OPERAND = re.compile(
|
|
120
|
+
r"[A-Za-z_][A-Za-z0-9_$]*\.[A-Za-z_][A-Za-z0-9_$]*"
|
|
121
|
+
r"(?:\.[A-Za-z_][A-Za-z0-9_$]*)?"
|
|
122
|
+
)
|
|
123
|
+
_SCHEMA_JOINED_WITH_PREFIX = re.compile(
|
|
124
|
+
r"\b(?:(inner|left|right)\s+)?joined\s+with\s+"
|
|
125
|
+
r"([A-Za-z_][A-Za-z0-9_$]*(?:\.[A-Za-z_][A-Za-z0-9_$]*)?)\b",
|
|
126
|
+
re.IGNORECASE,
|
|
127
|
+
)
|
|
128
|
+
_SCHEMA_DIAGNOSTIC_VALUES = {
|
|
129
|
+
"active", "inactive", "enabled", "disabled", "valid", "invalid",
|
|
130
|
+
"completed", "pending", "paid", "unpaid", "done", "true", "false",
|
|
131
|
+
}
|
|
132
|
+
_SCHEMA_DIAGNOSTIC_KEYWORDS = {
|
|
133
|
+
"and", "or", "not", "is", "where", "with", "by", "group", "grouped",
|
|
134
|
+
"order", "ordered", "sort", "sorted", "asc", "desc", "ascending",
|
|
135
|
+
"descending", "users", "user", "show", "list", "find", "display",
|
|
136
|
+
}
|
|
137
|
+
_SCHEMA_DIAGNOSTIC_COLUMN_ALLOWLIST = {
|
|
138
|
+
"password", "nickname", "token", "hash", "bio", "avatar",
|
|
139
|
+
}
|
|
101
140
|
_SCHEMA_GROUP_LABEL_COLUMNS = {
|
|
102
141
|
"部门": ("department", "dept"),
|
|
103
142
|
"状态": ("status",),
|
|
@@ -1135,6 +1174,453 @@ class PipelineNL2SQLGenerator:
|
|
|
1135
1174
|
resolved.append(rewritten)
|
|
1136
1175
|
return resolved
|
|
1137
1176
|
|
|
1177
|
+
@staticmethod
|
|
1178
|
+
def _schema_mask_span(
|
|
1179
|
+
text: str, span: tuple[int, int]
|
|
1180
|
+
) -> str:
|
|
1181
|
+
start, end = span
|
|
1182
|
+
return text[:start] + " " * (end - start) + text[end:]
|
|
1183
|
+
|
|
1184
|
+
def _schema_resolve_whose(
|
|
1185
|
+
self,
|
|
1186
|
+
text: str,
|
|
1187
|
+
table: SchemaTableContext,
|
|
1188
|
+
schema: SchemaContext,
|
|
1189
|
+
) -> tuple[str, tuple[int, int]] | None:
|
|
1190
|
+
"""Resolve one schema-aware ``whose`` predicate in linear time."""
|
|
1191
|
+
executable = mask_non_executable(text)
|
|
1192
|
+
match = _SCHEMA_WHOSE_PREFIX.search(executable)
|
|
1193
|
+
if match is None:
|
|
1194
|
+
return None
|
|
1195
|
+
whose_matches = list(_SCHEMA_WHOSE_PREFIX.finditer(executable))
|
|
1196
|
+
if len(whose_matches) != 1:
|
|
1197
|
+
raise SchemaValidationError("multiple whose predicates are unsupported")
|
|
1198
|
+
original_match = _SCHEMA_WHOSE_PREFIX.match(text, match.start())
|
|
1199
|
+
if original_match is None:
|
|
1200
|
+
raise SchemaValidationError("whose predicate is not executable")
|
|
1201
|
+
match = original_match
|
|
1202
|
+
|
|
1203
|
+
raw_column, raw_operator = match.groups()
|
|
1204
|
+
parts = raw_column.split(".")
|
|
1205
|
+
column_name = parts[-1]
|
|
1206
|
+
if len(parts) > 1:
|
|
1207
|
+
candidates = schema.table_candidates(".".join(parts[:-1]))
|
|
1208
|
+
if len(candidates) != 1 or candidates[0].key != table.key:
|
|
1209
|
+
raise SchemaValidationError("whose column table is not the selected table")
|
|
1210
|
+
canonical = schema.canonical_column(table, column_name)
|
|
1211
|
+
if canonical is None:
|
|
1212
|
+
if table.has_column_metadata:
|
|
1213
|
+
raise SchemaValidationError("whose column is outside the schema")
|
|
1214
|
+
canonical = column_name
|
|
1215
|
+
|
|
1216
|
+
position = match.end()
|
|
1217
|
+
if position >= len(text):
|
|
1218
|
+
raise SchemaValidationError("whose predicate value is missing")
|
|
1219
|
+
quote = text[position] if text[position] in "'\"" else None
|
|
1220
|
+
value_end = position
|
|
1221
|
+
if quote is not None:
|
|
1222
|
+
position += 1
|
|
1223
|
+
value_chars: list[str] = []
|
|
1224
|
+
closed = False
|
|
1225
|
+
while position < len(text):
|
|
1226
|
+
character = text[position]
|
|
1227
|
+
if character == "\\" and position + 1 < len(text):
|
|
1228
|
+
value_chars.extend((character, text[position + 1]))
|
|
1229
|
+
position += 2
|
|
1230
|
+
continue
|
|
1231
|
+
if character == quote:
|
|
1232
|
+
if position + 1 < len(text) and text[position + 1] == quote:
|
|
1233
|
+
value_chars.append(quote)
|
|
1234
|
+
position += 2
|
|
1235
|
+
continue
|
|
1236
|
+
closed = True
|
|
1237
|
+
position += 1
|
|
1238
|
+
break
|
|
1239
|
+
value_chars.append(character)
|
|
1240
|
+
position += 1
|
|
1241
|
+
if not closed:
|
|
1242
|
+
raise SchemaValidationError("whose quoted value is not closed")
|
|
1243
|
+
value = "".join(value_chars)
|
|
1244
|
+
literal = "'" + value.replace("'", "''") + "'"
|
|
1245
|
+
value_end = position
|
|
1246
|
+
else:
|
|
1247
|
+
numeric = re.match(
|
|
1248
|
+
r"-?(?:\d+(?:\.\d*)?|\.\d+)(?![\w$])",
|
|
1249
|
+
text[position:],
|
|
1250
|
+
)
|
|
1251
|
+
if numeric is not None:
|
|
1252
|
+
literal = numeric.group(0)
|
|
1253
|
+
value_end = position + numeric.end()
|
|
1254
|
+
else:
|
|
1255
|
+
null_match = re.match(r"null(?![\w$])", text[position:], re.IGNORECASE)
|
|
1256
|
+
if null_match is not None:
|
|
1257
|
+
literal = "NULL"
|
|
1258
|
+
value_end = position + null_match.end()
|
|
1259
|
+
else:
|
|
1260
|
+
cjk_token = re.match(r"[\u4e00-\u9fff]+", text[position:])
|
|
1261
|
+
if cjk_token is not None:
|
|
1262
|
+
value = cjk_token.group(0)
|
|
1263
|
+
if any(connector in value for connector in _SCHEMA_WHOSE_CJK_CONNECTORS):
|
|
1264
|
+
raise SchemaValidationError("whose CJK value contains a connector")
|
|
1265
|
+
literal = "'" + value.replace("'", "''") + "'"
|
|
1266
|
+
value_end = position + cjk_token.end()
|
|
1267
|
+
else:
|
|
1268
|
+
token = re.match(r"[A-Za-z_][A-Za-z0-9_$]*(?![\w$])", text[position:])
|
|
1269
|
+
if token is None:
|
|
1270
|
+
raise SchemaValidationError("whose value is not a supported literal")
|
|
1271
|
+
value = token.group(0)
|
|
1272
|
+
if value.casefold() in _SCHEMA_WHOSE_REJECTED_VALUES:
|
|
1273
|
+
raise SchemaValidationError("whose value is a reserved word")
|
|
1274
|
+
after_token = text[position + token.end():].lstrip()
|
|
1275
|
+
if after_token.startswith("("):
|
|
1276
|
+
raise SchemaValidationError("whose function values are unsupported")
|
|
1277
|
+
literal = "'" + value.replace("'", "''") + "'"
|
|
1278
|
+
value_end = position + token.end()
|
|
1279
|
+
|
|
1280
|
+
operator = raw_operator.casefold()
|
|
1281
|
+
if literal == "NULL":
|
|
1282
|
+
if operator in {"is", "is not", "isn't"}:
|
|
1283
|
+
predicate = f"{canonical} IS {'NOT ' if operator != 'is' else ''}NULL"
|
|
1284
|
+
else:
|
|
1285
|
+
raise SchemaValidationError("comparison with NULL is unsupported")
|
|
1286
|
+
else:
|
|
1287
|
+
normalized = {
|
|
1288
|
+
"is": "=", "equals": "=", "is not": "!=", "isn't": "!=",
|
|
1289
|
+
"<>": "!=",
|
|
1290
|
+
}.get(operator, raw_operator)
|
|
1291
|
+
predicate = f"{canonical} {normalized} {literal}"
|
|
1292
|
+
|
|
1293
|
+
tail = executable[value_end:]
|
|
1294
|
+
if re.search(r"\bor\b|\bwhose\b|[()]", tail, re.IGNORECASE):
|
|
1295
|
+
raise SchemaValidationError("complex whose predicates are unsupported")
|
|
1296
|
+
stripped_tail = tail.strip()
|
|
1297
|
+
if stripped_tail and not re.match(r"^and\b", stripped_tail, re.IGNORECASE):
|
|
1298
|
+
raise SchemaValidationError("unexpected text after whose predicate")
|
|
1299
|
+
span = (match.start(), value_end)
|
|
1300
|
+
return predicate, span
|
|
1301
|
+
|
|
1302
|
+
def _schema_resolve_joined_on(
|
|
1303
|
+
self,
|
|
1304
|
+
text: str,
|
|
1305
|
+
table_hint: Optional[str],
|
|
1306
|
+
schema: SchemaContext,
|
|
1307
|
+
) -> tuple[SchemaTableContext, list[dict], tuple[int, int]] | None:
|
|
1308
|
+
"""Resolve one schema-aware qualified equality after ``joined on``."""
|
|
1309
|
+
executable = mask_non_executable(text)
|
|
1310
|
+
prefixes = list(_SCHEMA_JOINED_ON_PREFIX.finditer(executable))
|
|
1311
|
+
if not prefixes:
|
|
1312
|
+
return None
|
|
1313
|
+
if re.search(r"\bjoined\s+with\b", executable, re.IGNORECASE):
|
|
1314
|
+
raise SchemaValidationError("mixed joined-on and joined-with clauses are unsupported")
|
|
1315
|
+
if len(prefixes) != 1:
|
|
1316
|
+
raise SchemaValidationError("multiple joined-on clauses are unsupported")
|
|
1317
|
+
if re.search(r"\b(?:per|each)\b|每个|各个|每位", executable, re.IGNORECASE):
|
|
1318
|
+
raise SchemaValidationError("joined-on with group labels is unsupported")
|
|
1319
|
+
prefix = prefixes[0]
|
|
1320
|
+
original_prefix = _SCHEMA_JOINED_ON_PREFIX.match(text, prefix.start())
|
|
1321
|
+
if original_prefix is None:
|
|
1322
|
+
raise SchemaValidationError("joined-on clause is not executable")
|
|
1323
|
+
prefix = original_prefix
|
|
1324
|
+
kind = (prefix.group(1) or "inner").upper()
|
|
1325
|
+
before_mentions = self._schema_table_mentions(text[:prefix.start()], schema)
|
|
1326
|
+
if table_hint:
|
|
1327
|
+
subject_candidates = schema.table_candidates(table_hint)
|
|
1328
|
+
if len(subject_candidates) != 1:
|
|
1329
|
+
raise SchemaValidationError("joined-on subject hint is ambiguous")
|
|
1330
|
+
subject = subject_candidates[0]
|
|
1331
|
+
elif before_mentions:
|
|
1332
|
+
subject = before_mentions[0]
|
|
1333
|
+
else:
|
|
1334
|
+
raise SchemaValidationError("joined-on subject is missing")
|
|
1335
|
+
|
|
1336
|
+
remainder = executable[prefix.end():]
|
|
1337
|
+
left_match = _SCHEMA_JOINED_ON_OPERAND.match(remainder.lstrip())
|
|
1338
|
+
if left_match is None:
|
|
1339
|
+
raise SchemaValidationError("joined-on left endpoint is invalid")
|
|
1340
|
+
left_start = prefix.end() + (len(remainder) - len(remainder.lstrip()))
|
|
1341
|
+
left_raw = left_match.group(0)
|
|
1342
|
+
after_left = remainder.lstrip()[left_match.end():]
|
|
1343
|
+
equals = re.match(r"\s*=\s*", after_left)
|
|
1344
|
+
if equals is None:
|
|
1345
|
+
raise SchemaValidationError("joined-on requires one equality")
|
|
1346
|
+
right_offset = left_start + left_match.end() + equals.end()
|
|
1347
|
+
right_match = _SCHEMA_JOINED_ON_OPERAND.match(executable[right_offset:])
|
|
1348
|
+
if right_match is None:
|
|
1349
|
+
raise SchemaValidationError("joined-on right endpoint is invalid")
|
|
1350
|
+
right_raw = right_match.group(0)
|
|
1351
|
+
value_end = right_offset + right_match.end()
|
|
1352
|
+
|
|
1353
|
+
tail = executable[value_end:]
|
|
1354
|
+
stripped_tail = tail.strip()
|
|
1355
|
+
if stripped_tail and not re.match(r"^(?:where|whose)\b", stripped_tail, re.IGNORECASE):
|
|
1356
|
+
raise SchemaValidationError("unexpected text after joined-on equality")
|
|
1357
|
+
|
|
1358
|
+
def resolve_endpoint(raw: str) -> tuple[SchemaTableContext, str]:
|
|
1359
|
+
parts = raw.split(".")
|
|
1360
|
+
table_name = ".".join(parts[:-1])
|
|
1361
|
+
candidates = schema.table_candidates(table_name)
|
|
1362
|
+
if len(candidates) != 1:
|
|
1363
|
+
raise SchemaValidationError("joined-on table is not unique")
|
|
1364
|
+
table = candidates[0]
|
|
1365
|
+
column = schema.canonical_column(table, parts[-1])
|
|
1366
|
+
if column is None:
|
|
1367
|
+
raise SchemaValidationError("joined-on column is outside the schema")
|
|
1368
|
+
return table, column
|
|
1369
|
+
|
|
1370
|
+
left_table, left_column = resolve_endpoint(left_raw)
|
|
1371
|
+
right_table, right_column = resolve_endpoint(right_raw)
|
|
1372
|
+
if left_table.key == right_table.key:
|
|
1373
|
+
raise SchemaValidationError("self joined-on is unsupported")
|
|
1374
|
+
endpoint_tables = {left_table.key, right_table.key}
|
|
1375
|
+
if subject.key not in endpoint_tables:
|
|
1376
|
+
raise SchemaValidationError("joined-on subject is not an endpoint")
|
|
1377
|
+
target_keys = endpoint_tables - {subject.key}
|
|
1378
|
+
if len(target_keys) != 1:
|
|
1379
|
+
raise SchemaValidationError("joined-on target is ambiguous")
|
|
1380
|
+
target = right_table if right_table.key in target_keys else left_table
|
|
1381
|
+
mentions = self._schema_table_mentions(text, schema)
|
|
1382
|
+
if {table.key for table in mentions} - endpoint_tables:
|
|
1383
|
+
raise SchemaValidationError("joined-on contains an extra table")
|
|
1384
|
+
|
|
1385
|
+
raw_relationships = [
|
|
1386
|
+
relationship for relationship in schema.relationships
|
|
1387
|
+
if {
|
|
1388
|
+
relationship.from_table.casefold(), relationship.to_table.casefold()
|
|
1389
|
+
} == endpoint_tables
|
|
1390
|
+
]
|
|
1391
|
+
if raw_relationships:
|
|
1392
|
+
def matches_relationship(relationship) -> bool:
|
|
1393
|
+
declared = {
|
|
1394
|
+
(relationship.from_table.casefold(), relationship.from_column.casefold()),
|
|
1395
|
+
(relationship.to_table.casefold(), relationship.to_column.casefold()),
|
|
1396
|
+
}
|
|
1397
|
+
supplied = {
|
|
1398
|
+
(left_table.key, left_column.casefold()),
|
|
1399
|
+
(right_table.key, right_column.casefold()),
|
|
1400
|
+
}
|
|
1401
|
+
return declared == supplied
|
|
1402
|
+
|
|
1403
|
+
if sum(matches_relationship(item) for item in raw_relationships) != 1:
|
|
1404
|
+
raise SchemaValidationError("joined-on does not match a unique relationship")
|
|
1405
|
+
|
|
1406
|
+
condition = (
|
|
1407
|
+
f"{left_table.name}.{left_column} = "
|
|
1408
|
+
f"{right_table.name}.{right_column}"
|
|
1409
|
+
)
|
|
1410
|
+
span = (prefix.start(), value_end)
|
|
1411
|
+
return subject, [{
|
|
1412
|
+
"type": kind,
|
|
1413
|
+
"table": target.name,
|
|
1414
|
+
"subject": subject.name,
|
|
1415
|
+
"condition": condition,
|
|
1416
|
+
"relational": False,
|
|
1417
|
+
"join_spec": JoinSpec(kind=kind, matched_text="joined on"),
|
|
1418
|
+
}], span
|
|
1419
|
+
|
|
1420
|
+
def _schema_resolve_joined_with(
|
|
1421
|
+
self,
|
|
1422
|
+
text: str,
|
|
1423
|
+
table_hint: Optional[str],
|
|
1424
|
+
schema: SchemaContext,
|
|
1425
|
+
) -> tuple[SchemaTableContext, list[dict], tuple[int, int]] | None:
|
|
1426
|
+
"""Resolve one relationship-backed physical ``joined with`` clause."""
|
|
1427
|
+
executable = mask_non_executable(text)
|
|
1428
|
+
matches = list(_SCHEMA_JOINED_WITH_PREFIX.finditer(executable))
|
|
1429
|
+
if not matches:
|
|
1430
|
+
return None
|
|
1431
|
+
if len(matches) != 1 or re.search(r"\bjoined\s+on\b", executable, re.IGNORECASE):
|
|
1432
|
+
raise SchemaValidationError("multiple or mixed joined clauses are unsupported")
|
|
1433
|
+
match = matches[0]
|
|
1434
|
+
original_match = _SCHEMA_JOINED_WITH_PREFIX.match(text, match.start())
|
|
1435
|
+
if original_match is None:
|
|
1436
|
+
raise SchemaValidationError("joined-with clause is not executable")
|
|
1437
|
+
match = original_match
|
|
1438
|
+
if re.search(
|
|
1439
|
+
r"\b(?:per|each|group\s+by|grouped\s+by)\b|每个|各个|每位",
|
|
1440
|
+
executable,
|
|
1441
|
+
re.IGNORECASE,
|
|
1442
|
+
):
|
|
1443
|
+
raise SchemaValidationError("joined-with grouping is unsupported")
|
|
1444
|
+
|
|
1445
|
+
kind = (match.group(1) or "inner").upper()
|
|
1446
|
+
target_name = match.group(2)
|
|
1447
|
+
target_candidates = schema.table_candidates(target_name)
|
|
1448
|
+
if len(target_candidates) != 1:
|
|
1449
|
+
raise SchemaValidationError("joined-with target is not unique")
|
|
1450
|
+
target = target_candidates[0]
|
|
1451
|
+
before_mentions = self._schema_table_mentions(text[:match.start()], schema)
|
|
1452
|
+
if table_hint:
|
|
1453
|
+
subject_candidates = schema.table_candidates(table_hint)
|
|
1454
|
+
if len(subject_candidates) != 1:
|
|
1455
|
+
raise SchemaValidationError("joined-with subject hint is ambiguous")
|
|
1456
|
+
subject = subject_candidates[0]
|
|
1457
|
+
elif before_mentions:
|
|
1458
|
+
subject = before_mentions[0]
|
|
1459
|
+
else:
|
|
1460
|
+
raise SchemaValidationError("joined-with subject is missing")
|
|
1461
|
+
if self._schema_text_match(text[:match.start()], target_name):
|
|
1462
|
+
raise SchemaValidationError("joined-with target is repeated before the clause")
|
|
1463
|
+
before_keys = {table.key for table in before_mentions}
|
|
1464
|
+
if before_keys - {subject.key, target.key}:
|
|
1465
|
+
raise SchemaValidationError("joined-with contains an extra table")
|
|
1466
|
+
if subject.key == target.key:
|
|
1467
|
+
raise SchemaValidationError("self joined-with is unsupported")
|
|
1468
|
+
|
|
1469
|
+
tail = executable[match.end():]
|
|
1470
|
+
stripped_tail = tail.strip()
|
|
1471
|
+
if stripped_tail and not re.match(r"^(?:where|whose)\b", stripped_tail, re.IGNORECASE):
|
|
1472
|
+
raise SchemaValidationError("unexpected text after joined-with target")
|
|
1473
|
+
relationships = [
|
|
1474
|
+
relationship for relationship in schema.relationships
|
|
1475
|
+
if {
|
|
1476
|
+
relationship.from_table.casefold(), relationship.to_table.casefold()
|
|
1477
|
+
} == {subject.key, target.key}
|
|
1478
|
+
]
|
|
1479
|
+
if len(relationships) != 1:
|
|
1480
|
+
raise SchemaValidationError("joined-with relationship is not unique")
|
|
1481
|
+
relationship = relationships[0]
|
|
1482
|
+
condition = (
|
|
1483
|
+
f"{relationship.from_table}.{relationship.from_column} = "
|
|
1484
|
+
f"{relationship.to_table}.{relationship.to_column}"
|
|
1485
|
+
)
|
|
1486
|
+
span = (match.start(), match.end())
|
|
1487
|
+
return subject, [{
|
|
1488
|
+
"type": kind,
|
|
1489
|
+
"table": target.name,
|
|
1490
|
+
"subject": subject.name,
|
|
1491
|
+
"condition": condition,
|
|
1492
|
+
"relational": False,
|
|
1493
|
+
"join_spec": JoinSpec(kind=kind, matched_text="joined with"),
|
|
1494
|
+
}], span
|
|
1495
|
+
|
|
1496
|
+
def _schema_diagnostic_suggestions(
|
|
1497
|
+
self,
|
|
1498
|
+
text: str,
|
|
1499
|
+
table: SchemaTableContext,
|
|
1500
|
+
schema: SchemaContext,
|
|
1501
|
+
columns: list[str],
|
|
1502
|
+
existing: list,
|
|
1503
|
+
) -> list:
|
|
1504
|
+
"""Return high-confidence unmapped-column suggestions for schema success."""
|
|
1505
|
+
if not table.has_column_metadata:
|
|
1506
|
+
return list(existing)
|
|
1507
|
+
masked = mask_non_executable(text)
|
|
1508
|
+
schema_tables = {candidate.key for candidate in schema.tables}
|
|
1509
|
+
table_aliases = {
|
|
1510
|
+
pattern.casefold() for pattern in self.TABLE_PATTERNS
|
|
1511
|
+
} | {
|
|
1512
|
+
mapped.casefold() for mapped in self.TABLE_PATTERNS.values()
|
|
1513
|
+
}
|
|
1514
|
+
schema_columns = {column.casefold() for column in table.columns}
|
|
1515
|
+
mapped_aliases = {
|
|
1516
|
+
alias.casefold(): canonical
|
|
1517
|
+
for alias, canonical in self.COLUMN_PATTERNS.items()
|
|
1518
|
+
}
|
|
1519
|
+
warnings: list[tuple[int, str]] = []
|
|
1520
|
+
seen: set[str] = set()
|
|
1521
|
+
|
|
1522
|
+
def add_warning(position: int, token: str, *, projection: bool = False) -> None:
|
|
1523
|
+
key = token.casefold()
|
|
1524
|
+
if key in seen or key in schema_columns:
|
|
1525
|
+
return
|
|
1526
|
+
canonical = mapped_aliases.get(key)
|
|
1527
|
+
if canonical is not None and canonical.casefold() in schema_columns:
|
|
1528
|
+
return
|
|
1529
|
+
message = (
|
|
1530
|
+
f'Possible projection term "{token}" was not resolved against the selected schema table.'
|
|
1531
|
+
if projection else
|
|
1532
|
+
f'Possible unmapped column reference: "{token}". '
|
|
1533
|
+
"Provide it in column_hints if it is a schema column."
|
|
1534
|
+
)
|
|
1535
|
+
seen.add(key)
|
|
1536
|
+
warnings.append((position, message))
|
|
1537
|
+
|
|
1538
|
+
# Strong identifier contexts: `with token` and `by snake_case`.
|
|
1539
|
+
for match in re.finditer(
|
|
1540
|
+
r"\b(?:with|by)\s+([A-Za-z_][A-Za-z0-9_$]*)",
|
|
1541
|
+
masked,
|
|
1542
|
+
re.IGNORECASE,
|
|
1543
|
+
):
|
|
1544
|
+
token = match.group(1)
|
|
1545
|
+
lowered = token.casefold()
|
|
1546
|
+
if lowered in _SCHEMA_DIAGNOSTIC_KEYWORDS | _SCHEMA_DIAGNOSTIC_VALUES:
|
|
1547
|
+
continue
|
|
1548
|
+
if schema.table_candidates(token) or lowered in schema_tables:
|
|
1549
|
+
continue
|
|
1550
|
+
if lowered in table_aliases:
|
|
1551
|
+
continue
|
|
1552
|
+
if schema.canonical_column(table, token) is not None:
|
|
1553
|
+
continue
|
|
1554
|
+
if lowered in mapped_aliases and mapped_aliases[lowered].casefold() in schema_columns:
|
|
1555
|
+
continue
|
|
1556
|
+
if match.group(0).casefold().startswith("by ") and "_" not in token:
|
|
1557
|
+
continue
|
|
1558
|
+
add_warning(match.start(1), token)
|
|
1559
|
+
|
|
1560
|
+
# Explicit projection conjunctions: only mapped aliases after a
|
|
1561
|
+
# show/list/display marker, before any predicate/clause boundary.
|
|
1562
|
+
projection_marker = re.search(r"\b(?:show|list|display|select)\b", masked, re.IGNORECASE)
|
|
1563
|
+
if projection_marker:
|
|
1564
|
+
boundary = re.search(
|
|
1565
|
+
r"\b(?:where|whose|order(?:ed)?\s+by|sort(?:ed)?\s+by|group(?:ed)?\s+by)\b",
|
|
1566
|
+
masked[projection_marker.end():],
|
|
1567
|
+
re.IGNORECASE,
|
|
1568
|
+
)
|
|
1569
|
+
projection_end = (
|
|
1570
|
+
projection_marker.end() + boundary.start()
|
|
1571
|
+
if boundary is not None else len(masked)
|
|
1572
|
+
)
|
|
1573
|
+
for alias, canonical in sorted(self.COLUMN_PATTERNS.items(), key=lambda item: -len(item[0])):
|
|
1574
|
+
if canonical.casefold() in schema_columns:
|
|
1575
|
+
continue
|
|
1576
|
+
aliases = [alias]
|
|
1577
|
+
if alias.isascii() and not alias.casefold().endswith("s"):
|
|
1578
|
+
aliases.append(alias + "s")
|
|
1579
|
+
for candidate_alias in aliases:
|
|
1580
|
+
expression = re.escape(candidate_alias)
|
|
1581
|
+
if not re.search(r"[\u4e00-\u9fff]", candidate_alias):
|
|
1582
|
+
expression = rf"(?<![\w$]){expression}(?![\w$])"
|
|
1583
|
+
for match in re.finditer(expression, masked[projection_marker.end():projection_end], re.IGNORECASE):
|
|
1584
|
+
add_warning(
|
|
1585
|
+
projection_marker.end() + match.start(),
|
|
1586
|
+
alias,
|
|
1587
|
+
projection=True,
|
|
1588
|
+
)
|
|
1589
|
+
projection_slice = masked[projection_marker.end():projection_end]
|
|
1590
|
+
for token_match in re.finditer(
|
|
1591
|
+
r"[A-Za-z_][A-Za-z0-9_$]*", projection_slice
|
|
1592
|
+
):
|
|
1593
|
+
token = token_match.group(0)
|
|
1594
|
+
lowered = token.casefold()
|
|
1595
|
+
if (
|
|
1596
|
+
lowered in _SCHEMA_DIAGNOSTIC_KEYWORDS
|
|
1597
|
+
or lowered in _SCHEMA_DIAGNOSTIC_VALUES
|
|
1598
|
+
or lowered in table_aliases
|
|
1599
|
+
or lowered in schema_tables
|
|
1600
|
+
or lowered in schema_columns
|
|
1601
|
+
):
|
|
1602
|
+
continue
|
|
1603
|
+
canonical = mapped_aliases.get(lowered)
|
|
1604
|
+
if canonical is not None and canonical.casefold() in schema_columns:
|
|
1605
|
+
continue
|
|
1606
|
+
if canonical is None and (
|
|
1607
|
+
"_" not in token and lowered not in _SCHEMA_DIAGNOSTIC_COLUMN_ALLOWLIST
|
|
1608
|
+
):
|
|
1609
|
+
continue
|
|
1610
|
+
add_warning(
|
|
1611
|
+
projection_marker.end() + token_match.start(),
|
|
1612
|
+
canonical or token,
|
|
1613
|
+
projection=True,
|
|
1614
|
+
)
|
|
1615
|
+
|
|
1616
|
+
warnings.sort(key=lambda item: item[0])
|
|
1617
|
+
diagnostic_messages = [message for _, message in warnings[:3]]
|
|
1618
|
+
combined: list = []
|
|
1619
|
+
for suggestion in [*existing, *diagnostic_messages]:
|
|
1620
|
+
if suggestion not in combined:
|
|
1621
|
+
combined.append(suggestion)
|
|
1622
|
+
return combined[:3]
|
|
1623
|
+
|
|
1138
1624
|
def _schema_resolve_joins(
|
|
1139
1625
|
self,
|
|
1140
1626
|
text: str,
|
|
@@ -1267,9 +1753,19 @@ class PipelineNL2SQLGenerator:
|
|
|
1267
1753
|
"""Generate through the enhanced path with strict schema resolution."""
|
|
1268
1754
|
try:
|
|
1269
1755
|
executable_text = mask_non_executable(text)
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1756
|
+
joined_on_resolution = self._schema_resolve_joined_on(
|
|
1757
|
+
text, table_hint, schema
|
|
1758
|
+
)
|
|
1759
|
+
joined_with_resolution = None
|
|
1760
|
+
if joined_on_resolution is None:
|
|
1761
|
+
joined_with_resolution = self._schema_resolve_joined_with(
|
|
1762
|
+
text, table_hint, schema
|
|
1763
|
+
)
|
|
1764
|
+
label_resolution = None
|
|
1765
|
+
if joined_on_resolution is None and joined_with_resolution is None:
|
|
1766
|
+
label_resolution = self._schema_resolve_english_group_label(executable_text, schema)
|
|
1767
|
+
if label_resolution is None:
|
|
1768
|
+
label_resolution = self._schema_resolve_chinese_group_label(executable_text, schema)
|
|
1273
1769
|
ignored_label_spans = (label_resolution[2],) if label_resolution else ()
|
|
1274
1770
|
unknown_tables = self._schema_unknown_table_patterns(
|
|
1275
1771
|
text, schema, ignored_spans=ignored_label_spans
|
|
@@ -1280,7 +1776,15 @@ class PipelineNL2SQLGenerator:
|
|
|
1280
1776
|
dialect,
|
|
1281
1777
|
"The request references tables outside the provided schema.",
|
|
1282
1778
|
)
|
|
1283
|
-
|
|
1779
|
+
joined_on_span = None
|
|
1780
|
+
joined_with_span = None
|
|
1781
|
+
if joined_on_resolution is not None:
|
|
1782
|
+
table_context, joins, joined_on_span = joined_on_resolution
|
|
1783
|
+
metric_text = self._schema_mask_span(text_lower, joined_on_span)
|
|
1784
|
+
elif joined_with_resolution is not None:
|
|
1785
|
+
table_context, joins, joined_with_span = joined_with_resolution
|
|
1786
|
+
metric_text = self._schema_mask_span(text_lower, joined_with_span)
|
|
1787
|
+
elif label_resolution is not None:
|
|
1284
1788
|
table_context = label_resolution[0]
|
|
1285
1789
|
label_group_column = label_resolution[1]
|
|
1286
1790
|
joins = []
|
|
@@ -1289,6 +1793,21 @@ class PipelineNL2SQLGenerator:
|
|
|
1289
1793
|
else:
|
|
1290
1794
|
table_context, joins = self._schema_resolve_joins(text, table_hint, schema)
|
|
1291
1795
|
metric_text = text_lower
|
|
1796
|
+
whose_resolution = self._schema_resolve_whose(text, table_context, schema)
|
|
1797
|
+
whose_condition = None
|
|
1798
|
+
condition_text = text_lower
|
|
1799
|
+
condition_original = text
|
|
1800
|
+
if joined_on_span is not None:
|
|
1801
|
+
condition_text = self._schema_mask_span(condition_text, joined_on_span)
|
|
1802
|
+
condition_original = self._schema_mask_span(condition_original, joined_on_span)
|
|
1803
|
+
elif joined_with_span is not None:
|
|
1804
|
+
condition_text = self._schema_mask_span(condition_text, joined_with_span)
|
|
1805
|
+
condition_original = self._schema_mask_span(condition_original, joined_with_span)
|
|
1806
|
+
if whose_resolution is not None:
|
|
1807
|
+
whose_condition, whose_span = whose_resolution
|
|
1808
|
+
metric_text = self._schema_mask_span(metric_text, whose_span)
|
|
1809
|
+
condition_text = self._schema_mask_span(condition_text, whose_span)
|
|
1810
|
+
condition_original = self._schema_mask_span(condition_original, whose_span)
|
|
1292
1811
|
count_metric = self._schema_resolve_count_metric(
|
|
1293
1812
|
text_lower, table_context, schema
|
|
1294
1813
|
)
|
|
@@ -1300,11 +1819,13 @@ class PipelineNL2SQLGenerator:
|
|
|
1300
1819
|
suppress_quantity_token=count_metric is not None,
|
|
1301
1820
|
)
|
|
1302
1821
|
conditions = self._schema_resolve_conditions(
|
|
1303
|
-
|
|
1304
|
-
self._extract_conditions_enhanced(
|
|
1822
|
+
condition_text,
|
|
1823
|
+
self._extract_conditions_enhanced(condition_text, condition_original),
|
|
1305
1824
|
table_context,
|
|
1306
1825
|
schema,
|
|
1307
1826
|
)
|
|
1827
|
+
if whose_condition is not None:
|
|
1828
|
+
conditions = [*conditions, whose_condition]
|
|
1308
1829
|
parsed = self._parse_text(text_lower, text)
|
|
1309
1830
|
parsed.update(analysis)
|
|
1310
1831
|
operation = self._detect_operation(text_lower)
|
|
@@ -1353,7 +1874,16 @@ class PipelineNL2SQLGenerator:
|
|
|
1353
1874
|
),
|
|
1354
1875
|
parsed_elements=parsed,
|
|
1355
1876
|
))
|
|
1356
|
-
|
|
1877
|
+
finalized = self._schema_finalize(result, schema)
|
|
1878
|
+
if finalized.success and finalized.schema_used:
|
|
1879
|
+
finalized.suggestions = self._schema_diagnostic_suggestions(
|
|
1880
|
+
text,
|
|
1881
|
+
table_context,
|
|
1882
|
+
schema,
|
|
1883
|
+
columns,
|
|
1884
|
+
list(finalized.suggestions),
|
|
1885
|
+
)
|
|
1886
|
+
return finalized
|
|
1357
1887
|
except (SchemaValidationError, ValueError):
|
|
1358
1888
|
return self._schema_failure(
|
|
1359
1889
|
text,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sql-dialect-master
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7.0
|
|
4
4
|
Summary: SQL Dialect Master - Multi-database SQL conversion engine
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -38,7 +38,7 @@ Dynamic: license-file
|
|
|
38
38
|
|
|
39
39
|
Enterprise-grade multi-database SQL conversion engine supporting 12 database dialects.
|
|
40
40
|
|
|
41
|
-
Current release: 1.
|
|
41
|
+
Current release: 1.7.0
|
|
42
42
|
|
|
43
43
|
[](https://www.python.org/downloads/)
|
|
44
44
|
[](https://opensource.org/licenses/MIT)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/column_hint_validation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_components/__init__.py
RENAMED
|
File without changes
|
{sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_components/aggregations.py
RENAMED
|
File without changes
|
|
File without changes
|
{sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_components/core_extract.py
RENAMED
|
File without changes
|
{sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_components/evidence.py
RENAMED
|
File without changes
|
{sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_components/mappings.py
RENAMED
|
File without changes
|
{sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_components/relations.py
RENAMED
|
File without changes
|
{sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_components/templates.py
RENAMED
|
File without changes
|
{sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_components/tokenizer.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_pipeline/__init__.py
RENAMED
|
File without changes
|
{sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_pipeline/aggregations.py
RENAMED
|
File without changes
|
{sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_pipeline/clause_extract.py
RENAMED
|
File without changes
|
{sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_pipeline/dialect.py
RENAMED
|
File without changes
|
{sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_pipeline/intent.py
RENAMED
|
File without changes
|
|
File without changes
|
{sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_pipeline/models.py
RENAMED
|
File without changes
|
{sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_pipeline/predicates.py
RENAMED
|
File without changes
|
{sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_pipeline/renderer.py
RENAMED
|
File without changes
|
{sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_pipeline/resolution.py
RENAMED
|
File without changes
|
{sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_pipeline/suggestions.py
RENAMED
|
File without changes
|
{sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_pipeline/templates.py
RENAMED
|
File without changes
|
{sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/backend/core/nl2sql_pipeline/validation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/sql_dialect_master.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/sql_dialect_master.egg-info/requires.txt
RENAMED
|
File without changes
|
{sql_dialect_master-1.5.0 → sql_dialect_master-1.7.0}/sql_dialect_master.egg-info/top_level.txt
RENAMED
|
File without changes
|