sql-dialect-master 1.4.0__tar.gz → 1.4.1__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.4.0 → sql_dialect_master-1.4.1}/PKG-INFO +2 -2
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/README.md +1 -1
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/metadata.py +2 -2
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_components/boolean_conditions.py +118 -14
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_components/core_extract.py +9 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_pipeline/predicates.py +131 -52
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_pipeline/templates.py +0 -11
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/pyproject.toml +1 -1
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/sql_dialect_master.egg-info/PKG-INFO +2 -2
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/LICENSE +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/__init__.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/api/__init__.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/api/auth.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/api/client_identity.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/api/main.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/api/metrics.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/api/middleware.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/api/rate_limit_store.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/api/readiness.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/api/workers.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/__init__.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/cache.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/column_hint_validation.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/config.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/constants.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/exceptions.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/function_call_scanner.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/function_semantics.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/functions_db.json +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/functions_lookup.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_components/__init__.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_components/aggregations.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_components/evidence.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_components/mappings.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_components/relations.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_components/templates.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_components/tokenizer.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_legacy.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_llm.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_pipeline/__init__.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_pipeline/aggregations.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_pipeline/clause_extract.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_pipeline/dialect.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_pipeline/intent.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_pipeline/ir.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_pipeline/models.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_pipeline/orchestrator.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_pipeline/renderer.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_pipeline/resolution.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_pipeline/suggestions.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_pipeline/validation.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_schema.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/p1_sql_scanner.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/p2_data_validation.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/parser.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/post_processor.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/rules.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/semantic_diff.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/sql_statements.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/transpiler.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/type_mapping.json +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/type_mapping.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/utils/__init__.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/utils/validators.py +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/setup.cfg +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/sql_dialect_master.egg-info/SOURCES.txt +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/sql_dialect_master.egg-info/dependency_links.txt +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/sql_dialect_master.egg-info/requires.txt +0 -0
- {sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/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.4.
|
|
3
|
+
Version: 1.4.1
|
|
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.4.
|
|
41
|
+
Current release: 1.4.1
|
|
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.4.
|
|
5
|
+
Current release: 1.4.1
|
|
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.4.
|
|
21
|
-
API_VERSION = "1.4.
|
|
20
|
+
PROJECT_VERSION = "1.4.1"
|
|
21
|
+
API_VERSION = "1.4.1"
|
|
22
22
|
# Frontend v2 architecture — independent of the product release version.
|
|
23
23
|
FRONTEND_ARCHITECTURE_VERSION = 2
|
|
24
24
|
|
|
@@ -7,39 +7,41 @@ user-supplied parentheses.
|
|
|
7
7
|
import re
|
|
8
8
|
from typing import List, Optional, Tuple
|
|
9
9
|
|
|
10
|
+
from ..p1_sql_scanner import mask_non_executable
|
|
11
|
+
|
|
10
12
|
|
|
11
13
|
_COMPARISON_PATTERNS = (
|
|
12
14
|
re.compile(
|
|
13
15
|
r"(?<![A-Za-z0-9_])(price|quantity|amount|age|score|rating|views|clicks)\s+"
|
|
14
16
|
r"(greater(?:\s+than)?|more\s+than|above|over|less(?:\s+than)?|"
|
|
15
17
|
r"below|under|equal(?:\s+to)?|equals)\s+"
|
|
16
|
-
r"([0-9]+(?:\.[0-9]+)?)",
|
|
18
|
+
r"(-?[0-9]+(?:\.[0-9]+)?)",
|
|
17
19
|
re.IGNORECASE,
|
|
18
20
|
),
|
|
19
21
|
re.compile(
|
|
20
22
|
r"(?<![A-Za-z0-9_])(price|quantity|amount|age|score|rating|views|clicks)\s*"
|
|
21
|
-
r"(
|
|
23
|
+
r"(>=|<=|!=|<>|=|>|<)\s*(-?[0-9]+(?:\.[0-9]+)?)",
|
|
22
24
|
re.IGNORECASE,
|
|
23
25
|
),
|
|
24
26
|
re.compile(
|
|
25
27
|
r"(价格|数量|金额|年龄|得分|评分|浏览量|点击量)\s*"
|
|
26
28
|
r"(大于等于|小于等于|不等于|大于|小于|超过|低于|等于)\s*"
|
|
27
|
-
r"([0-9]+(?:\.[0-9]+)?)"
|
|
29
|
+
r"(-?[0-9]+(?:\.[0-9]+)?)"
|
|
28
30
|
),
|
|
29
31
|
)
|
|
30
32
|
|
|
31
33
|
_RANGE_PATTERN = re.compile(
|
|
32
34
|
r"(?<![A-Za-z0-9_])(price|quantity|amount|age|score|rating|views|clicks)\s+"
|
|
33
|
-
r"(?:between\s+([0-9]+(?:\.[0-9]+)?)\s+and\s+([0-9]+(?:\.[0-9]+)?)|"
|
|
34
|
-
r"in\s+(?:the\s+)?range\s+([0-9]+(?:\.[0-9]+)?)\s*(?:to|-)\s*"
|
|
35
|
-
r"([0-9]+(?:\.[0-9]+)?))",
|
|
35
|
+
r"(?:between\s+(-?[0-9]+(?:\.[0-9]+)?)\s+and\s+(-?[0-9]+(?:\.[0-9]+)?)|"
|
|
36
|
+
r"in\s+(?:the\s+)?range\s+(-?[0-9]+(?:\.[0-9]+)?)\s*(?:to|-)\s*"
|
|
37
|
+
r"(-?[0-9]+(?:\.[0-9]+)?))",
|
|
36
38
|
re.IGNORECASE,
|
|
37
39
|
)
|
|
38
40
|
|
|
39
41
|
_CN_RANGE_PATTERN = re.compile(
|
|
40
42
|
r"(价格|数量|金额|年龄|得分|评分|浏览量|点击量)\s*"
|
|
41
|
-
r"(?:在|介于)\s*([0-9]+(?:\.[0-9]+)?)\s*(?:和|到|至|-)\s*"
|
|
42
|
-
r"([0-9]+(?:\.[0-9]+)?)\s*(?:之间|范围内|以内)?"
|
|
43
|
+
r"(?:在|介于)\s*(-?[0-9]+(?:\.[0-9]+)?)\s*(?:和|到|至|-)\s*"
|
|
44
|
+
r"(-?[0-9]+(?:\.[0-9]+)?)\s*(?:之间|范围内|以内)?"
|
|
43
45
|
)
|
|
44
46
|
|
|
45
47
|
_TEXT_PATTERN = re.compile(
|
|
@@ -101,6 +103,29 @@ _STATUS_CONDITIONS = {
|
|
|
101
103
|
"未支付": "status = 'unpaid'",
|
|
102
104
|
}
|
|
103
105
|
|
|
106
|
+
_STATUS_VALUES = {
|
|
107
|
+
"active": "active",
|
|
108
|
+
"inactive": "inactive",
|
|
109
|
+
"enabled": "active",
|
|
110
|
+
"disabled": "inactive",
|
|
111
|
+
"valid": "active",
|
|
112
|
+
"invalid": "inactive",
|
|
113
|
+
"completed": "completed",
|
|
114
|
+
"done": "completed",
|
|
115
|
+
"pending": "pending",
|
|
116
|
+
"paid": "paid",
|
|
117
|
+
"unpaid": "unpaid",
|
|
118
|
+
"有效": "active",
|
|
119
|
+
"无效": "inactive",
|
|
120
|
+
"已完成": "completed",
|
|
121
|
+
"未完成": "pending",
|
|
122
|
+
"已支付": "paid",
|
|
123
|
+
"未支付": "unpaid",
|
|
124
|
+
}
|
|
125
|
+
_STATUS_VALUE_PATTERN = "(?:" + "|".join(
|
|
126
|
+
sorted((re.escape(value) for value in _STATUS_VALUES), key=len, reverse=True)
|
|
127
|
+
) + ")"
|
|
128
|
+
|
|
104
129
|
_CN_COLUMNS = {
|
|
105
130
|
"价格": "price",
|
|
106
131
|
"数量": "quantity",
|
|
@@ -168,15 +193,16 @@ def _format_set_values(raw_values: str) -> str:
|
|
|
168
193
|
|
|
169
194
|
def _comparison_matches(text: str) -> List[Tuple[int, int, str]]:
|
|
170
195
|
matches: List[Tuple[int, int, str]] = []
|
|
196
|
+
executable = mask_non_executable(text)
|
|
171
197
|
|
|
172
198
|
for pattern in _COMPARISON_PATTERNS:
|
|
173
|
-
for match in pattern.finditer(
|
|
199
|
+
for match in pattern.finditer(executable):
|
|
174
200
|
first, raw_operator, value = match.groups()
|
|
175
201
|
column = _CN_COLUMNS.get(first, first)
|
|
176
202
|
matches.append((match.start(), match.end(), f"{column} {_normalize_operator(raw_operator)} {value}"))
|
|
177
203
|
|
|
178
204
|
for pattern in (_RANGE_PATTERN, _CN_RANGE_PATTERN):
|
|
179
|
-
for match in pattern.finditer(
|
|
205
|
+
for match in pattern.finditer(executable):
|
|
180
206
|
groups = match.groups()
|
|
181
207
|
if len(groups) == 5:
|
|
182
208
|
first, low1, high1, low2, high2 = groups
|
|
@@ -193,7 +219,7 @@ def _comparison_matches(text: str) -> List[Tuple[int, int, str]]:
|
|
|
193
219
|
matches.append((match.start(), match.end(), _text_sql(column, value)))
|
|
194
220
|
|
|
195
221
|
for pattern in (_NULL_PATTERN, _CN_NULL_PATTERN):
|
|
196
|
-
for match in pattern.finditer(
|
|
222
|
+
for match in pattern.finditer(executable):
|
|
197
223
|
first, raw_predicate = match.groups()
|
|
198
224
|
column = _CN_COLUMNS.get(first, first)
|
|
199
225
|
normalized = raw_predicate.replace(" ", "").lower()
|
|
@@ -229,9 +255,7 @@ def _comparison_matches(text: str) -> List[Tuple[int, int, str]]:
|
|
|
229
255
|
operator = "NOT IN" if operator_text == "不在" else "IN"
|
|
230
256
|
matches.append((match.start(), value_end, f"{_CN_SET_COLUMNS[column]} {operator} ({values})"))
|
|
231
257
|
|
|
232
|
-
|
|
233
|
-
status = match.group(1).lower()
|
|
234
|
-
matches.append((match.start(), match.end(), _STATUS_CONDITIONS[status]))
|
|
258
|
+
matches.extend(_status_matches(text))
|
|
235
259
|
|
|
236
260
|
matches.sort(key=lambda item: (item[0], item[1]))
|
|
237
261
|
deduped: List[Tuple[int, int, str]] = []
|
|
@@ -242,6 +266,86 @@ def _comparison_matches(text: str) -> List[Tuple[int, int, str]]:
|
|
|
242
266
|
return deduped
|
|
243
267
|
|
|
244
268
|
|
|
269
|
+
def _status_matches(text: str) -> List[Tuple[int, int, str]]:
|
|
270
|
+
"""Extract status predicates without matching quoted values."""
|
|
271
|
+
executable = mask_non_executable(text)
|
|
272
|
+
matches: list[Tuple[int, int, str]] = []
|
|
273
|
+
explicit_patterns = [
|
|
274
|
+
(
|
|
275
|
+
re.compile(
|
|
276
|
+
rf"\bstatus\s*(?:is\s+not|isn't|not|!=|<>)\s*({_STATUS_VALUE_PATTERN})\b",
|
|
277
|
+
re.IGNORECASE,
|
|
278
|
+
),
|
|
279
|
+
"!=",
|
|
280
|
+
),
|
|
281
|
+
(
|
|
282
|
+
re.compile(rf"\bstatus\s*(?:is|=)\s*({_STATUS_VALUE_PATTERN})\b", re.IGNORECASE),
|
|
283
|
+
"=",
|
|
284
|
+
),
|
|
285
|
+
(
|
|
286
|
+
re.compile(rf"\bstatus\s+({_STATUS_VALUE_PATTERN})\b", re.IGNORECASE),
|
|
287
|
+
"=",
|
|
288
|
+
),
|
|
289
|
+
(
|
|
290
|
+
re.compile(rf"状态\s*(?:不是|不为|!=|<>)\s*({_STATUS_VALUE_PATTERN})"),
|
|
291
|
+
"!=",
|
|
292
|
+
),
|
|
293
|
+
(
|
|
294
|
+
re.compile(rf"状态\s*(?:为|是|=)\s*({_STATUS_VALUE_PATTERN})"),
|
|
295
|
+
"=",
|
|
296
|
+
),
|
|
297
|
+
]
|
|
298
|
+
for pattern, operator in explicit_patterns:
|
|
299
|
+
for match in pattern.finditer(executable):
|
|
300
|
+
raw_value = match.group(1)
|
|
301
|
+
canonical = _STATUS_VALUES[raw_value.casefold() if raw_value.isascii() else raw_value]
|
|
302
|
+
matches.append((match.start(), match.end(), f"status {operator} '{canonical}'"))
|
|
303
|
+
|
|
304
|
+
occupied = [(start, end) for start, end, _ in matches]
|
|
305
|
+
text_spans = [
|
|
306
|
+
(match.start(), match.end())
|
|
307
|
+
for pattern in (_TEXT_PATTERN, _CN_TEXT_PATTERN)
|
|
308
|
+
for match in pattern.finditer(executable)
|
|
309
|
+
]
|
|
310
|
+
for raw_value, canonical in sorted(_STATUS_VALUES.items(), key=lambda item: -len(item[0])):
|
|
311
|
+
if raw_value.isascii():
|
|
312
|
+
pattern = re.compile(rf"(?<![\w$]){re.escape(raw_value)}(?![\w$])", re.IGNORECASE)
|
|
313
|
+
else:
|
|
314
|
+
pattern = re.compile(re.escape(raw_value))
|
|
315
|
+
for match in pattern.finditer(executable):
|
|
316
|
+
if any(start <= match.start() < end or match.start() <= start < match.end() for start, end in occupied):
|
|
317
|
+
continue
|
|
318
|
+
if any(start <= match.start() < end for start, end in text_spans):
|
|
319
|
+
continue
|
|
320
|
+
prefix = executable[:match.start()]
|
|
321
|
+
negative = bool(
|
|
322
|
+
re.search(r"(?:\b(?:not|except|excluding)\s*)$", prefix, re.IGNORECASE)
|
|
323
|
+
or re.search(r"(?:不是|不为|非)\s*$", prefix)
|
|
324
|
+
)
|
|
325
|
+
operator = "!=" if negative else "="
|
|
326
|
+
matches.append((match.start(), match.end(), f"status {operator} '{canonical}'"))
|
|
327
|
+
occupied.append((match.start(), match.end()))
|
|
328
|
+
|
|
329
|
+
deleted_patterns = [
|
|
330
|
+
(
|
|
331
|
+
re.compile(r"(?:未删除|没有删除|不是删除|非删除|\bnot\s+deleted\b)", re.IGNORECASE),
|
|
332
|
+
"is_deleted = 0",
|
|
333
|
+
),
|
|
334
|
+
(re.compile(r"(?:已删除|\bdeleted\b|\bremoved\b)", re.IGNORECASE), "is_deleted = 1"),
|
|
335
|
+
]
|
|
336
|
+
for pattern, condition in deleted_patterns:
|
|
337
|
+
for match in pattern.finditer(executable):
|
|
338
|
+
if any(start <= match.start() < end or match.start() <= start < match.end() for start, end, _ in matches):
|
|
339
|
+
continue
|
|
340
|
+
matches.append((match.start(), match.end(), condition))
|
|
341
|
+
return matches
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def extract_status_conditions(text: str) -> list[str]:
|
|
345
|
+
"""Return standalone status predicates for the single-condition path."""
|
|
346
|
+
return [condition for _, _, condition in _status_matches(text)]
|
|
347
|
+
|
|
348
|
+
|
|
245
349
|
def extract_boolean_conditions(text: str) -> Optional[List[str]]:
|
|
246
350
|
"""Return one boolean SQL expression preserving connector precedence/grouping."""
|
|
247
351
|
matches = _comparison_matches(text)
|
{sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_components/core_extract.py
RENAMED
|
@@ -38,6 +38,15 @@ def detect_operation(text: str, keywords: Dict[str, str]) -> str:
|
|
|
38
38
|
Defaults to SELECT.
|
|
39
39
|
"""
|
|
40
40
|
intent_text = mask_non_executable(text)
|
|
41
|
+
# Deletion-state phrases describe a SELECT predicate. Mask only the
|
|
42
|
+
# phrase so an independent leading "删除" in e.g. "删除已删除用户"
|
|
43
|
+
# remains a genuine DELETE intent.
|
|
44
|
+
intent_text = re.sub(
|
|
45
|
+
r"(?:已删除|未删除|没有删除|不是删除|非删除|already\s+deleted|not\s+deleted)",
|
|
46
|
+
lambda match: " " * len(match.group(0)),
|
|
47
|
+
intent_text,
|
|
48
|
+
flags=re.IGNORECASE,
|
|
49
|
+
)
|
|
41
50
|
for keyword, op in keywords.items():
|
|
42
51
|
if _pattern_match(intent_text, keyword) and op in ["SELECT", "INSERT", "UPDATE", "DELETE"]:
|
|
43
52
|
return op
|
{sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_pipeline/predicates.py
RENAMED
|
@@ -11,7 +11,79 @@ the instance attribute.
|
|
|
11
11
|
import re
|
|
12
12
|
from typing import Dict, List, Optional
|
|
13
13
|
|
|
14
|
-
from ..nl2sql_components.boolean_conditions import
|
|
14
|
+
from ..nl2sql_components.boolean_conditions import (
|
|
15
|
+
extract_boolean_conditions,
|
|
16
|
+
extract_status_conditions,
|
|
17
|
+
)
|
|
18
|
+
from ..p1_sql_scanner import mask_non_executable
|
|
19
|
+
|
|
20
|
+
_QUOTED_COMPARISON_PREFIX = re.compile(
|
|
21
|
+
r"(?<![\w$])([A-Za-z_][A-Za-z0-9_$]*)\s*"
|
|
22
|
+
r"(is\s+not|isn't|not|is|<>|!=|>=|<=|=|>|<)\s*",
|
|
23
|
+
re.IGNORECASE,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _column_for_text(text: str, column_patterns: Dict[str, str]) -> Optional[str]:
|
|
28
|
+
for pattern, column in sorted(column_patterns.items(), key=lambda item: -len(item[0])):
|
|
29
|
+
expression = re.escape(pattern)
|
|
30
|
+
if not re.search(r"[\u4e00-\u9fff]", pattern):
|
|
31
|
+
expression = rf"(?<![\w$]){expression}(?![\w$])"
|
|
32
|
+
if re.search(expression, text, re.IGNORECASE):
|
|
33
|
+
return column
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _quoted_comparisons(
|
|
38
|
+
original: str, column_patterns: Dict[str, str]
|
|
39
|
+
) -> list[str]:
|
|
40
|
+
quoted = []
|
|
41
|
+
executable = mask_non_executable(original)
|
|
42
|
+
known_columns = {value.casefold(): value for value in column_patterns.values()}
|
|
43
|
+
for match in _QUOTED_COMPARISON_PREFIX.finditer(original):
|
|
44
|
+
if executable[match.start():match.end()] != original[match.start():match.end()]:
|
|
45
|
+
continue
|
|
46
|
+
position = match.end()
|
|
47
|
+
if position >= len(original) or original[position] not in "'\"":
|
|
48
|
+
continue
|
|
49
|
+
quote = original[position]
|
|
50
|
+
position += 1
|
|
51
|
+
value_chars: list[str] = []
|
|
52
|
+
closed = False
|
|
53
|
+
while position < len(original):
|
|
54
|
+
character = original[position]
|
|
55
|
+
if character == "\\" and position + 1 < len(original):
|
|
56
|
+
value_chars.extend((character, original[position + 1]))
|
|
57
|
+
position += 2
|
|
58
|
+
continue
|
|
59
|
+
if character == quote:
|
|
60
|
+
if position + 1 < len(original) and original[position + 1] == quote:
|
|
61
|
+
value_chars.append(quote)
|
|
62
|
+
position += 2
|
|
63
|
+
continue
|
|
64
|
+
closed = True
|
|
65
|
+
position += 1
|
|
66
|
+
break
|
|
67
|
+
value_chars.append(character)
|
|
68
|
+
position += 1
|
|
69
|
+
if not closed:
|
|
70
|
+
continue
|
|
71
|
+
raw_column, operator = match.groups()
|
|
72
|
+
value = "".join(value_chars)
|
|
73
|
+
column = known_columns.get(raw_column.casefold(), raw_column)
|
|
74
|
+
if quote == "'":
|
|
75
|
+
literal = quote + value.replace("'", "''") + quote
|
|
76
|
+
else:
|
|
77
|
+
literal = "'" + value.replace("'", "''") + "'"
|
|
78
|
+
normalized_operator = {
|
|
79
|
+
"is": "=",
|
|
80
|
+
"isn't": "!=",
|
|
81
|
+
"is not": "!=",
|
|
82
|
+
"not": "!=",
|
|
83
|
+
"<>": "!=",
|
|
84
|
+
}.get(operator.casefold(), operator)
|
|
85
|
+
quoted.append(f"{column} {normalized_operator} {literal}")
|
|
86
|
+
return quoted
|
|
15
87
|
|
|
16
88
|
|
|
17
89
|
def extract_conditions(
|
|
@@ -30,13 +102,11 @@ def extract_conditions(
|
|
|
30
102
|
if boolean_conditions:
|
|
31
103
|
return boolean_conditions
|
|
32
104
|
|
|
33
|
-
conditions =
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
condition_column = column
|
|
39
|
-
break
|
|
105
|
+
conditions = _quoted_comparisons(original, column_patterns)
|
|
106
|
+
executable_original = mask_non_executable(original)
|
|
107
|
+
executable_text = mask_non_executable(text)
|
|
108
|
+
numbers = re.findall(r"-?\d+(?:\.\d*)?", executable_original)
|
|
109
|
+
condition_column = _column_for_text(executable_text, column_patterns)
|
|
40
110
|
|
|
41
111
|
# Handle IN/NOT IN predicate (must come before generic comparison handling)
|
|
42
112
|
in_matched_columns: set = set()
|
|
@@ -79,9 +149,9 @@ def extract_conditions(
|
|
|
79
149
|
in_matched_columns.add(col.lower())
|
|
80
150
|
|
|
81
151
|
comparisons = [
|
|
82
|
-
(["大于等于", "不小于", "至少", "greater than or equal to", "larger than or equal to"], ">="),
|
|
83
|
-
(["小于等于", "不大于", "最多", "less than or equal to", "smaller than or equal to"], "<="),
|
|
84
|
-
(["不等于", "不是", "不为", "not equal", "isn't", "doesn't equal"], "!="),
|
|
152
|
+
([">=", "大于等于", "不小于", "至少", "greater than or equal to", "larger than or equal to"], ">="),
|
|
153
|
+
(["<=", "小于等于", "不大于", "最多", "less than or equal to", "smaller than or equal to"], "<="),
|
|
154
|
+
(["<>", "!=", "不等于", "不是", "不为", "not equal", "isn't", "doesn't equal"], "!="),
|
|
85
155
|
(["大于", "超过", "高于", "多于", "greater", "more than", "above", "over", ">"], ">"),
|
|
86
156
|
(["小于", "低于", "少于", "不足", "less", "less than", "below", "under", "<"], "<"),
|
|
87
157
|
(["等于", "是", "为", "equals", "equal", "="], "="),
|
|
@@ -109,11 +179,11 @@ def extract_conditions(
|
|
|
109
179
|
# word itself (Chinese-style adjacency, e.g. '大于100').
|
|
110
180
|
# Longest keyword first so '大于等于' never yields to '大于'.
|
|
111
181
|
for keyword in sorted(keywords, key=len, reverse=True):
|
|
112
|
-
if keyword not in
|
|
182
|
+
if keyword not in executable_original:
|
|
113
183
|
continue
|
|
114
184
|
m = re.search(
|
|
115
|
-
re.escape(keyword) + r"\s*(
|
|
116
|
-
|
|
185
|
+
re.escape(keyword) + r"\s*(-?\d+(?:\.\d*)?)",
|
|
186
|
+
executable_original,
|
|
117
187
|
)
|
|
118
188
|
if m:
|
|
119
189
|
bound_value = m.group(1)
|
|
@@ -126,12 +196,12 @@ def extract_conditions(
|
|
|
126
196
|
# "10" in "top 10 …") must never win this slot.
|
|
127
197
|
if bound_value is None:
|
|
128
198
|
for keyword in sorted(keywords, key=len, reverse=True):
|
|
129
|
-
if keyword not in
|
|
199
|
+
if keyword not in executable_original:
|
|
130
200
|
continue
|
|
131
201
|
m = re.search(
|
|
132
202
|
re.escape(keyword)
|
|
133
|
-
+ r"(?:\s+than\s+|\s+or\s+equal\s+to\s+|\s+than\s+or\s+equal\s+to\s+)?(
|
|
134
|
-
|
|
203
|
+
+ r"(?:\s+than\s+|\s+or\s+equal\s+to\s+|\s+than\s+or\s+equal\s+to\s+)?(-?\d+(?:\.\d*)?)",
|
|
204
|
+
executable_original,
|
|
135
205
|
re.IGNORECASE,
|
|
136
206
|
)
|
|
137
207
|
if m:
|
|
@@ -147,19 +217,43 @@ def extract_conditions(
|
|
|
147
217
|
# Fallback: operator word present but no number is directly or
|
|
148
218
|
# tail-adjacent to it — bind the column named before the operator
|
|
149
219
|
# word to the first available number.
|
|
150
|
-
if any(k in
|
|
220
|
+
if any(k in executable_original for k in keywords) and numbers:
|
|
151
221
|
col = condition_column or "column"
|
|
152
222
|
conditions.append(f"{col} {op} {numbers[0]}")
|
|
153
223
|
break
|
|
154
224
|
|
|
155
|
-
if any(keyword in
|
|
225
|
+
if any(keyword in executable_text for keyword in ["之间", "范围", "between", "from...to"]):
|
|
156
226
|
if len(numbers) >= 2:
|
|
157
227
|
col = condition_column or "column"
|
|
158
228
|
conditions.append(f"{col} BETWEEN {numbers[0]} AND {numbers[1]}")
|
|
159
229
|
|
|
160
|
-
if any(keyword in
|
|
230
|
+
if any(keyword in executable_text for keyword in ["包含", "含有", "contains", "like", "includes"]):
|
|
231
|
+
text_match = re.search(
|
|
232
|
+
r"(?<![\w$])([A-Za-z_][A-Za-z0-9_$]*)\s+"
|
|
233
|
+
r"(?:contains|containing|includes|including)\s+"
|
|
234
|
+
r"(?:the\s+)?([^,;,。;]+?)(?=\s+(?:and|or)\b|[,;,。;]|$)",
|
|
235
|
+
original,
|
|
236
|
+
re.IGNORECASE,
|
|
237
|
+
)
|
|
238
|
+
cn_text_match = re.search(
|
|
239
|
+
r"(名称|名字|标题|描述|分类|品牌)\s*(?:包含|含有|包括)\s*([^且并或者和,,。;]+)",
|
|
240
|
+
original,
|
|
241
|
+
)
|
|
161
242
|
match = re.search(r"[\"']([^\"']+)[\"']", original)
|
|
162
|
-
if
|
|
243
|
+
if text_match:
|
|
244
|
+
raw_column, raw_value = text_match.groups()
|
|
245
|
+
column = next(
|
|
246
|
+
(value for value in column_patterns.values() if value.casefold() == raw_column.casefold()),
|
|
247
|
+
raw_column,
|
|
248
|
+
)
|
|
249
|
+
value = raw_value.strip().strip("'\"")
|
|
250
|
+
if value:
|
|
251
|
+
conditions.append(f"{column} LIKE '%{value.replace(chr(39), chr(39) * 2)}%'")
|
|
252
|
+
elif cn_text_match:
|
|
253
|
+
raw_column, raw_value = cn_text_match.groups()
|
|
254
|
+
aliases = {"名称": "name", "名字": "name", "标题": "title", "描述": "description", "分类": "category", "品牌": "brand"}
|
|
255
|
+
conditions.append(f"{aliases[raw_column]} LIKE '%{raw_value.strip().replace(chr(39), chr(39) * 2)}%'")
|
|
256
|
+
elif match:
|
|
163
257
|
col = condition_column or "column"
|
|
164
258
|
conditions.append(f"{col} LIKE '%{match.group(1)}%'")
|
|
165
259
|
|
|
@@ -172,14 +266,14 @@ def extract_conditions(
|
|
|
172
266
|
elif any(keyword in text for keyword in ["为空", "空值", "is null", "null", "empty"]):
|
|
173
267
|
conditions.append(f"{condition_column or 'column'} IS NULL")
|
|
174
268
|
|
|
175
|
-
date_col = "created_at" if "created_at" in
|
|
176
|
-
if "今天" in
|
|
269
|
+
date_col = "created_at" if "created_at" in executable_text else "date"
|
|
270
|
+
if "今天" in executable_text or "today" in executable_text:
|
|
177
271
|
conditions.append(f"{date_col} = CURRENT_DATE")
|
|
178
|
-
elif "昨天" in
|
|
272
|
+
elif "昨天" in executable_text or "yesterday" in executable_text:
|
|
179
273
|
conditions.append(f"{date_col} = DATE_SUB(CURRENT_DATE, 1)")
|
|
180
|
-
elif "前天" in
|
|
274
|
+
elif "前天" in executable_text or "day before" in executable_text:
|
|
181
275
|
conditions.append(f"{date_col} = DATE_SUB(CURRENT_DATE, 2)")
|
|
182
|
-
elif "明天" in
|
|
276
|
+
elif "明天" in executable_text or "tomorrow" in executable_text:
|
|
183
277
|
conditions.append(f"{date_col} = DATE_ADD(CURRENT_DATE, 1)")
|
|
184
278
|
|
|
185
279
|
time_patterns = [
|
|
@@ -189,7 +283,7 @@ def extract_conditions(
|
|
|
189
283
|
(r"(最近|过去|last|past)\s*(\d+)\s*(年|year)", "YEAR"),
|
|
190
284
|
]
|
|
191
285
|
for pattern, unit in time_patterns:
|
|
192
|
-
match = re.search(pattern,
|
|
286
|
+
match = re.search(pattern, executable_text, re.IGNORECASE)
|
|
193
287
|
if match:
|
|
194
288
|
n = match.group(2)
|
|
195
289
|
if unit == "DAY":
|
|
@@ -204,39 +298,24 @@ def extract_conditions(
|
|
|
204
298
|
|
|
205
299
|
# Bare period expressions without explicit count
|
|
206
300
|
if not any(c for c in conditions if "DATE_SUB" in c or "DATE_ADD" in c or "ADD_MONTHS" in c or "CURRENT_DATE" in c):
|
|
207
|
-
if ("last week" in
|
|
301
|
+
if ("last week" in executable_text or "本周" in executable_text) and "this week" not in executable_text and "本周" not in executable_text:
|
|
208
302
|
conditions.append(f"{date_col} >= DATE_SUB(CURRENT_DATE, 7)")
|
|
209
|
-
if "last month" in
|
|
303
|
+
if "last month" in executable_text:
|
|
210
304
|
conditions.append(f"{date_col} >= ADD_MONTHS(CURRENT_DATE, -1)")
|
|
211
|
-
if "last year" in
|
|
305
|
+
if "last year" in executable_text:
|
|
212
306
|
conditions.append(f"{date_col} >= ADD_MONTHS(CURRENT_DATE, -12)")
|
|
213
307
|
|
|
214
|
-
if "本周" in
|
|
308
|
+
if "本周" in executable_text or "this week" in executable_text:
|
|
215
309
|
conditions.append(f"WEEKOFYEAR({date_col}) = WEEKOFYEAR(CURRENT_DATE)")
|
|
216
|
-
if "本月" in
|
|
310
|
+
if "本月" in executable_text or "this month" in executable_text:
|
|
217
311
|
conditions.append(
|
|
218
312
|
f"MONTH({date_col}) = MONTH(CURRENT_DATE) AND YEAR({date_col}) = YEAR(CURRENT_DATE)"
|
|
219
313
|
)
|
|
220
|
-
if "本年" in
|
|
314
|
+
if "本年" in executable_text or "this year" in executable_text:
|
|
221
315
|
conditions.append(f"YEAR({date_col}) = YEAR(CURRENT_DATE)")
|
|
222
316
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
(
|
|
226
|
-
|
|
227
|
-
(["未删除", "not deleted"], "is_deleted = 0"),
|
|
228
|
-
(["已完成", "completed", "done", "finished"], "status = 'completed'"),
|
|
229
|
-
(["未完成", "pending", "incomplete"], "status = 'pending'"),
|
|
230
|
-
(["已支付", "paid"], "status = 'paid'"),
|
|
231
|
-
(["未支付", "unpaid"], "status = 'unpaid'"),
|
|
232
|
-
]
|
|
233
|
-
for keywords, condition in status_patterns:
|
|
234
|
-
# Skip status pattern if the column was already matched via IN predicate
|
|
235
|
-
col_match = re.match(r"^(\w+)\s*=", condition)
|
|
236
|
-
col_name = col_match.group(1).lower() if col_match else None
|
|
237
|
-
if col_name and col_name in in_matched_columns:
|
|
238
|
-
continue
|
|
239
|
-
if any(keyword in text for keyword in keywords):
|
|
240
|
-
conditions.append(condition)
|
|
241
|
-
break
|
|
317
|
+
status_conditions = extract_status_conditions(text)
|
|
318
|
+
for status_condition in status_conditions:
|
|
319
|
+
if status_condition.split(" ", 1)[0] not in in_matched_columns:
|
|
320
|
+
conditions.append(status_condition)
|
|
242
321
|
return conditions
|
{sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_pipeline/templates.py
RENAMED
|
@@ -316,17 +316,6 @@ def generate_from_template(
|
|
|
316
316
|
# never re-append them here as a second WHERE.
|
|
317
317
|
if template.name == "top_n_query" and "DATE_SUB" not in c and "ADD_MONTHS" not in c and "DATE_ADD" not in c:
|
|
318
318
|
continue
|
|
319
|
-
if not any(
|
|
320
|
-
kw in text_lower
|
|
321
|
-
for kw in [
|
|
322
|
-
"today", "yesterday", "tomorrow", "last", "past",
|
|
323
|
-
"本周", "本月", "本年", "今天", "昨天", "明天"
|
|
324
|
-
]
|
|
325
|
-
) and c in [
|
|
326
|
-
"status = 'active'", "status = 'inactive'",
|
|
327
|
-
"is_deleted = 1", "is_deleted = 0"
|
|
328
|
-
]:
|
|
329
|
-
continue
|
|
330
319
|
merged_conditions.append(c)
|
|
331
320
|
if merged_conditions:
|
|
332
321
|
if "WHERE" in sql.upper():
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sql-dialect-master
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.1
|
|
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.4.
|
|
41
|
+
Current release: 1.4.1
|
|
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.4.0 → sql_dialect_master-1.4.1}/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.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_components/__init__.py
RENAMED
|
File without changes
|
{sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_components/aggregations.py
RENAMED
|
File without changes
|
{sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_components/evidence.py
RENAMED
|
File without changes
|
{sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_components/mappings.py
RENAMED
|
File without changes
|
{sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_components/relations.py
RENAMED
|
File without changes
|
{sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_components/templates.py
RENAMED
|
File without changes
|
{sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_components/tokenizer.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_pipeline/__init__.py
RENAMED
|
File without changes
|
{sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_pipeline/aggregations.py
RENAMED
|
File without changes
|
{sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_pipeline/clause_extract.py
RENAMED
|
File without changes
|
{sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_pipeline/dialect.py
RENAMED
|
File without changes
|
{sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_pipeline/intent.py
RENAMED
|
File without changes
|
|
File without changes
|
{sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_pipeline/models.py
RENAMED
|
File without changes
|
{sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_pipeline/orchestrator.py
RENAMED
|
File without changes
|
{sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_pipeline/renderer.py
RENAMED
|
File without changes
|
{sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_pipeline/resolution.py
RENAMED
|
File without changes
|
{sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/backend/core/nl2sql_pipeline/suggestions.py
RENAMED
|
File without changes
|
{sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/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.4.0 → sql_dialect_master-1.4.1}/sql_dialect_master.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/sql_dialect_master.egg-info/requires.txt
RENAMED
|
File without changes
|
{sql_dialect_master-1.4.0 → sql_dialect_master-1.4.1}/sql_dialect_master.egg-info/top_level.txt
RENAMED
|
File without changes
|