sql-dialect-master 1.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. backend/__init__.py +1 -0
  2. backend/api/__init__.py +1 -0
  3. backend/api/main.py +732 -0
  4. backend/api/middleware.py +274 -0
  5. backend/api/rate_limit_store.py +105 -0
  6. backend/api/readiness.py +178 -0
  7. backend/core/__init__.py +57 -0
  8. backend/core/cache.py +261 -0
  9. backend/core/column_hint_validation.py +22 -0
  10. backend/core/config.py +546 -0
  11. backend/core/constants.py +84 -0
  12. backend/core/exceptions.py +124 -0
  13. backend/core/function_call_scanner.py +64 -0
  14. backend/core/function_semantics.py +148 -0
  15. backend/core/functions_db.json +601 -0
  16. backend/core/functions_lookup.py +332 -0
  17. backend/core/metadata.py +71 -0
  18. backend/core/nl2sql.py +85 -0
  19. backend/core/nl2sql_components/__init__.py +72 -0
  20. backend/core/nl2sql_components/aggregations.py +35 -0
  21. backend/core/nl2sql_components/boolean_conditions.py +279 -0
  22. backend/core/nl2sql_components/core_extract.py +106 -0
  23. backend/core/nl2sql_components/evidence.py +122 -0
  24. backend/core/nl2sql_components/mappings.py +185 -0
  25. backend/core/nl2sql_components/relations.py +180 -0
  26. backend/core/nl2sql_components/templates.py +130 -0
  27. backend/core/nl2sql_components/tokenizer.py +187 -0
  28. backend/core/nl2sql_legacy.py +22 -0
  29. backend/core/nl2sql_pipeline/__init__.py +13 -0
  30. backend/core/nl2sql_pipeline/aggregations.py +26 -0
  31. backend/core/nl2sql_pipeline/clause_extract.py +97 -0
  32. backend/core/nl2sql_pipeline/dialect.py +139 -0
  33. backend/core/nl2sql_pipeline/intent.py +85 -0
  34. backend/core/nl2sql_pipeline/ir.py +70 -0
  35. backend/core/nl2sql_pipeline/models.py +30 -0
  36. backend/core/nl2sql_pipeline/orchestrator.py +576 -0
  37. backend/core/nl2sql_pipeline/predicates.py +182 -0
  38. backend/core/nl2sql_pipeline/renderer.py +248 -0
  39. backend/core/nl2sql_pipeline/resolution.py +69 -0
  40. backend/core/nl2sql_pipeline/suggestions.py +30 -0
  41. backend/core/nl2sql_pipeline/templates.py +371 -0
  42. backend/core/nl2sql_pipeline/validation.py +28 -0
  43. backend/core/p1_sql_scanner.py +196 -0
  44. backend/core/p2_data_validation.py +72 -0
  45. backend/core/parser.py +280 -0
  46. backend/core/post_processor.py +129 -0
  47. backend/core/rules.py +808 -0
  48. backend/core/semantic_diff.py +385 -0
  49. backend/core/transpiler.py +791 -0
  50. backend/core/type_mapping.json +593 -0
  51. backend/core/type_mapping.py +765 -0
  52. backend/utils/__init__.py +2 -0
  53. backend/utils/validators.py +407 -0
  54. frontend/__init__.py +10 -0
  55. frontend/app_context_v2.py +201 -0
  56. frontend/core/__init__.py +33 -0
  57. frontend/core/design_tokens.py +127 -0
  58. frontend/core/escaping.py +13 -0
  59. frontend/core/navigation.py +135 -0
  60. frontend/core/state.py +612 -0
  61. frontend/core/styles.py +523 -0
  62. frontend/core/themes.py +69 -0
  63. frontend/core/viewmodels.py +499 -0
  64. frontend/pages/__init__.py +0 -0
  65. frontend/pages/convert.py +283 -0
  66. frontend/pages/diff.py +157 -0
  67. frontend/pages/functions.py +100 -0
  68. frontend/pages/history.py +144 -0
  69. frontend/pages/lineage.py +168 -0
  70. frontend/pages/nl2sql.py +305 -0
  71. frontend/pages/query_analysis.py +120 -0
  72. frontend/pages/runtime.py +66 -0
  73. frontend/pages/settings.py +60 -0
  74. frontend/pages/templates.py +67 -0
  75. frontend/pages/types.py +110 -0
  76. frontend/templates_v2.py +53 -0
  77. frontend/ui/__init__.py +0 -0
  78. frontend/ui/command_palette.py +153 -0
  79. frontend/ui/diff.py +275 -0
  80. frontend/ui/editor.py +102 -0
  81. frontend/ui/status.py +255 -0
  82. sql_dialect_master-1.1.1.dist-info/METADATA +360 -0
  83. sql_dialect_master-1.1.1.dist-info/RECORD +86 -0
  84. sql_dialect_master-1.1.1.dist-info/WHEEL +5 -0
  85. sql_dialect_master-1.1.1.dist-info/licenses/LICENSE +21 -0
  86. sql_dialect_master-1.1.1.dist-info/top_level.txt +2 -0
@@ -0,0 +1,185 @@
1
+ #!/usr/bin/env python3
2
+ """Language mappings for NL2SQL.
3
+
4
+ Contains Chinese-English keyword mappings for tables, columns, and SQL operations.
5
+ """
6
+ from typing import Dict
7
+
8
+
9
+ # Chinese-English keyword mappings
10
+ KEYWORDS: Dict[str, str] = {
11
+ # Query operations
12
+ "查询": "SELECT", "获取": "SELECT", "查找": "SELECT", "显示": "SELECT", "列出": "SELECT",
13
+ "select": "SELECT", "get": "SELECT", "find": "SELECT", "show": "SELECT", "list": "SELECT", "fetch": "SELECT",
14
+ # Insert operations
15
+ "插入": "INSERT", "添加": "INSERT", "新增": "INSERT", "创建": "INSERT",
16
+ "insert": "INSERT", "add": "INSERT", "create": "INSERT",
17
+ # Update operations
18
+ "更新": "UPDATE", "修改": "UPDATE", "改变": "UPDATE", "设置": "UPDATE",
19
+ "update": "UPDATE", "modify": "UPDATE", "change": "UPDATE", "set": "UPDATE",
20
+ # Delete operations
21
+ "删除": "DELETE", "移除": "DELETE", "清除": "DELETE",
22
+ "delete": "DELETE", "remove": "DELETE", "drop": "DELETE",
23
+ # Aggregations
24
+ "统计": "COUNT", "计数": "COUNT", "数量": "COUNT", "多少": "COUNT", "几个": "COUNT",
25
+ "count": "COUNT", "total": "COUNT", "how many": "COUNT",
26
+ "求和": "SUM", "总和": "SUM", "合计": "SUM", "总计": "SUM",
27
+ "sum": "SUM", "total of": "SUM",
28
+ "平均": "AVG", "均值": "AVG", "平均值": "AVG",
29
+ "average": "AVG", "avg": "AVG", "mean": "AVG",
30
+ "最大": "MAX", "最高": "MAX", "最多": "MAX",
31
+ "max": "MAX", "maximum": "MAX", "highest": "MAX", "largest": "MAX",
32
+ "最小": "MIN", "最低": "MIN", "最少": "MIN",
33
+ "min": "MIN", "minimum": "MIN", "lowest": "MIN", "smallest": "MIN",
34
+ # Conditions
35
+ "大于": ">", "超过": ">", "高于": ">", "多于": ">",
36
+ "greater": ">", "more than": ">", "above": ">", "over": ">",
37
+ "小于": "<", "低于": "<", "少于": "<", "不足": "<",
38
+ "less": "<", "less than": "<", "below": "<", "under": "<",
39
+ "等于": "=", "是": "=", "为": "=",
40
+ "equals": "=", "equal": "=", "is": "=",
41
+ "不等于": "!=", "不是": "!=", "不为": "!=",
42
+ "not equal": "!=", "not": "!=", "isn't": "!=",
43
+ "包含": "LIKE", "含有": "LIKE", "像": "LIKE",
44
+ "contains": "LIKE", "like": "LIKE", "includes": "LIKE",
45
+ "开头": "LIKE_START", "以...开头": "LIKE_START",
46
+ "starts with": "LIKE_START", "beginning with": "LIKE_START",
47
+ "结尾": "LIKE_END", "以...结尾": "LIKE_END",
48
+ "ends with": "LIKE_END", "ending with": "LIKE_END",
49
+ "之间": "BETWEEN", "范围": "BETWEEN",
50
+ "between": "BETWEEN", "range": "BETWEEN",
51
+ "为空": "IS NULL", "空值": "IS NULL",
52
+ "is null": "IS NULL", "null": "IS NULL", "empty": "IS NULL",
53
+ "非空": "IS NOT NULL", "不为空": "IS NOT NULL",
54
+ "is not null": "IS NOT NULL", "not null": "IS NOT NULL", "not empty": "IS NOT NULL",
55
+ # Sorting
56
+ "排序": "ORDER BY", "按": "ORDER BY", "排列": "ORDER BY",
57
+ "sort": "ORDER BY", "order": "ORDER BY", "sorted": "ORDER BY",
58
+ "升序": "ASC", "从小到大": "ASC", "递增": "ASC",
59
+ "ascending": "ASC", "asc": "ASC", "increasing": "ASC",
60
+ "降序": "DESC", "从大到小": "DESC", "递减": "DESC",
61
+ "descending": "DESC", "desc": "DESC", "decreasing": "DESC",
62
+ # Grouping
63
+ "分组": "GROUP BY", "按...分组": "GROUP BY", "汇总": "GROUP BY",
64
+ "group": "GROUP BY", "group by": "GROUP BY", "grouped": "GROUP BY",
65
+ # Limiting
66
+ "前": "LIMIT", "限制": "LIMIT", "只要": "LIMIT", "仅": "LIMIT",
67
+ "top": "LIMIT", "limit": "LIMIT", "first": "LIMIT", "only": "LIMIT",
68
+ # Joins
69
+ "关联": "JOIN", "连接": "JOIN", "合并": "JOIN", "联合": "JOIN",
70
+ "join": "JOIN", "combine": "JOIN", "merge": "JOIN",
71
+ "左连接": "LEFT JOIN", "left join": "LEFT JOIN",
72
+ "右连接": "RIGHT JOIN", "right join": "RIGHT JOIN",
73
+ "内连接": "INNER JOIN", "inner join": "INNER JOIN",
74
+ # Time
75
+ "今天": "CURRENT_DATE", "today": "CURRENT_DATE",
76
+ "昨天": "DATE_SUB(CURRENT_DATE, 1)", "yesterday": "DATE_SUB(CURRENT_DATE, 1)",
77
+ "前天": "DATE_SUB(CURRENT_DATE, 2)", "day before yesterday": "DATE_SUB(CURRENT_DATE, 2)",
78
+ "本周": "WEEK", "this week": "WEEK",
79
+ "本月": "MONTH", "this month": "MONTH",
80
+ "本年": "YEAR", "this year": "YEAR",
81
+ "最近": ">=", "recent": ">=", "last": ">=", "past": ">=",
82
+ # Distinct
83
+ "去重": "DISTINCT", "唯一": "DISTINCT", "不重复": "DISTINCT",
84
+ "distinct": "DISTINCT", "unique": "DISTINCT",
85
+ }
86
+
87
+ # Common table patterns (expanded)
88
+ TABLE_PATTERNS: Dict[str, str] = {
89
+ # Users
90
+ "用户": "users", "user": "users", "users": "users", "会员": "users", "member": "users",
91
+ "账户": "accounts", "account": "accounts", "accounts": "accounts",
92
+ # Orders
93
+ "订单": "orders", "order": "orders", "orders": "orders", "购买": "orders",
94
+ "销售": "sales", "sale": "sales", "sales": "sales",
95
+ # Products
96
+ "产品": "products", "product": "products", "products": "products",
97
+ "商品": "items", "item": "items", "items": "items", "货物": "items",
98
+ "库存": "inventory", "inventory": "inventory", "stock": "inventory",
99
+ # Logs
100
+ "日志": "logs", "log": "logs", "logs": "logs", "记录": "logs",
101
+ "操作日志": "operation_logs", "审计": "audit_logs", "audit": "audit_logs",
102
+ # HR
103
+ "员工": "employees", "employee": "employees", "employees": "employees", "职员": "employees",
104
+ "部门": "departments", "department": "departments", "departments": "departments",
105
+ "薪资": "salaries", "salary": "salaries", "工资": "salaries",
106
+ # Customers
107
+ "客户": "customers", "customer": "customers", "customers": "customers",
108
+ "供应商": "suppliers", "supplier": "suppliers", "vendors": "suppliers",
109
+ # Finance
110
+ "交易": "transactions", "transaction": "transactions", "transactions": "transactions",
111
+ "支付": "payments", "payment": "payments", "payments": "payments",
112
+ "发票": "invoices", "invoice": "invoices", "invoices": "invoices",
113
+ # Events
114
+ "事件": "events", "event": "events", "events": "events",
115
+ "活动": "activities", "activity": "activities",
116
+ # Data
117
+ "数据": "data", "data": "data", "表": "table", "table": "table",
118
+ "配置": "configs", "config": "configs", "settings": "configs",
119
+ # Categories
120
+ "分类": "categories", "categories": "categories",
121
+ "标签": "tags", "tag": "tags", "tags": "tags",
122
+ # Messages
123
+ "消息": "messages", "message": "messages", "messages": "messages",
124
+ "通知": "notifications", "notification": "notifications",
125
+ "评论": "comments", "comment": "comments", "comments": "comments",
126
+ }
127
+
128
+ # Column patterns (expanded)
129
+ COLUMN_PATTERNS: Dict[str, str] = {
130
+ # Identity
131
+ "id": "id", "编号": "id", "标识": "id", "主键": "id",
132
+ "uuid": "uuid", "guid": "guid",
133
+ # Personal info
134
+ "姓名": "name", "名字": "name", "name": "name", "名称": "name",
135
+ "用户名": "username", "username": "username", "账号": "username",
136
+ "邮箱": "email", "email": "email", "电子邮件": "email",
137
+ "电话": "phone", "phone": "phone", "手机": "mobile", "mobile": "mobile",
138
+ "地址": "address", "address": "address", "住址": "address",
139
+ "年龄": "age", "age": "age",
140
+ "性别": "gender", "gender": "gender", "sex": "gender",
141
+ "生日": "birthday", "birthday": "birthday", "出生日期": "birth_date",
142
+ # Financial
143
+ "金额": "amount", "amount": "amount", "总额": "total_amount",
144
+ "价格": "price", "price": "price", "单价": "unit_price",
145
+ "数量": "quantity", "quantity": "quantity", "qty": "quantity",
146
+ "成本": "cost", "cost": "cost",
147
+ "利润": "profit", "profit": "profit",
148
+ "折扣": "discount", "discount": "discount",
149
+ "税": "tax", "tax": "tax",
150
+ "余额": "balance", "balance": "balance",
151
+ "薪水": "salary", "salary": "salary", "工资": "salary",
152
+ # Time
153
+ "日期": "date", "date": "date",
154
+ "时间": "time", "time": "time",
155
+ "创建时间": "created_at", "created_at": "created_at", "create_time": "created_at",
156
+ "更新时间": "updated_at", "updated_at": "updated_at", "update_time": "updated_at",
157
+ "开始时间": "start_time", "start_time": "start_time", "start_date": "start_date",
158
+ "结束时间": "end_time", "end_time": "end_time", "end_date": "end_date",
159
+ "过期时间": "expire_time", "expire_at": "expire_at",
160
+ # Status
161
+ "状态": "status", "status": "status",
162
+ "类型": "type", "type": "type", "种类": "type",
163
+ "级别": "level", "level": "level", "等级": "level",
164
+ "优先级": "priority", "priority": "priority",
165
+ "是否": "is_", "是否有效": "is_valid", "是否删除": "is_deleted",
166
+ # Content
167
+ "标题": "title", "title": "title",
168
+ "内容": "content", "content": "content",
169
+ "描述": "description", "description": "description", "desc": "description",
170
+ "备注": "remark", "remark": "remark", "note": "note", "notes": "notes",
171
+ # Relations
172
+ "用户id": "user_id", "user_id": "user_id",
173
+ "订单id": "order_id", "order_id": "order_id",
174
+ "产品id": "product_id", "product_id": "product_id",
175
+ "部门id": "dept_id", "dept_id": "dept_id", "department_id": "department_id",
176
+ "父级": "parent_id", "parent_id": "parent_id",
177
+ # Categorization
178
+ "分类": "category", "category": "category",
179
+ # Metrics
180
+ "得分": "score", "score": "score", "分数": "score",
181
+ "评分": "rating", "rating": "rating",
182
+ "浏览量": "views", "views": "views", "view_count": "view_count",
183
+ "点击量": "clicks", "clicks": "clicks",
184
+ "下载量": "downloads", "downloads": "downloads",
185
+ }
@@ -0,0 +1,180 @@
1
+ #!/usr/bin/env python3
2
+ """NL2SQL relation extraction: joins, join-key inference, relational keywords.
3
+
4
+ The join-key knowledge base (``JOIN_KEY_PATTERNS``) is the single
5
+ canonical source for inter-table relationships. Unknown relationships
6
+ return ``None`` so the caller can fail closed instead of fabricating
7
+ conditions.
8
+ """
9
+ import re
10
+ from dataclasses import dataclass
11
+ from typing import Dict, List, Optional
12
+
13
+ # Canonical join-key knowledge base.
14
+ # Pairs are stored both ways so lookups are order-independent.
15
+ JOIN_KEY_PATTERNS: Dict[tuple, str] = {
16
+ ("users", "orders"): "users.id = orders.user_id",
17
+ ("orders", "users"): "orders.user_id = users.id",
18
+ ("users", "transactions"): "users.id = transactions.user_id",
19
+ ("transactions", "users"): "transactions.user_id = users.id",
20
+ ("orders", "products"): "orders.product_id = products.id",
21
+ ("products", "orders"): "products.id = orders.product_id",
22
+ ("users", "products"): "users.id = products.user_id",
23
+ ("products", "users"): "products.user_id = users.id",
24
+ ("employees", "departments"): "employees.dept_id = departments.id",
25
+ ("departments", "employees"): "departments.id = employees.dept_id",
26
+ ("orders", "payments"): "orders.id = payments.order_id",
27
+ ("payments", "orders"): "payments.order_id = orders.id",
28
+ ("customers", "orders"): "customers.id = orders.customer_id",
29
+ ("orders", "customers"): "orders.customer_id = customers.id",
30
+ }
31
+
32
+ # Negative relational existence keywords -> NOT EXISTS
33
+ _NOT_EXISTS_KEYWORDS = [
34
+ "without", "not have", "no ",
35
+ "who don't have", "who doesn't have",
36
+ "who do not have", "who does not have",
37
+ ]
38
+
39
+ # Positive relational existence keywords -> EXISTS
40
+ _EXISTS_KEYWORDS = [
41
+ "who have", "who has", "that have", "that has",
42
+ "with orders", "with customers",
43
+ "with users", "with employees",
44
+ "with invoices", "with payments", "with transactions",
45
+ "with suppliers", "with products",
46
+ "who placed", "who made", "who created", "who submitted",
47
+ ]
48
+
49
+
50
+ # Explicit physical JOIN keywords, each mapped to its structured kind.
51
+ # Matched as whole words / substrings against the full text — never as
52
+ # "the first token of the sentence" (the legacy split()[0] bug).
53
+ _JOIN_KIND_KEYWORDS: Dict[str, str] = {
54
+ "left join": "LEFT",
55
+ "右连接": "RIGHT",
56
+ "right join": "RIGHT",
57
+ "inner join": "INNER",
58
+ "内连接": "INNER",
59
+ "outer join": "OUTER",
60
+ "cross join": "CROSS",
61
+ }
62
+
63
+
64
+ @dataclass(frozen=True)
65
+ class JoinSpec:
66
+ """Structured physical-join intent parsed from natural language.
67
+
68
+ ``kind`` is one of ``"LEFT" | "RIGHT" | "INNER" | "OUTER" | "CROSS"
69
+ | "JOIN"`` (JOIN = unqualified, defaults to inner). ``matched_text``
70
+ records which keyword fired, for evidence.
71
+ """
72
+
73
+ kind: str
74
+ matched_text: str
75
+
76
+
77
+ def parse_join_spec(text: str) -> Optional[JoinSpec]:
78
+ """Parse an explicit physical-join keyword into a structured JoinSpec.
79
+
80
+ Returns ``None`` when the text carries no physical-join intent
81
+ (e.g. relational-existence phrases like "users who have orders"), in
82
+ which case the caller falls back to its EXISTS/JOIN inference.
83
+
84
+ Keywords are located anywhere in the text via whole-phrase
85
+ matching — the join type is never inferred from the first word of the
86
+ sentence.
87
+ """
88
+ lowered = text.lower()
89
+ for keyword, kind in _JOIN_KIND_KEYWORDS.items():
90
+ if keyword.isascii():
91
+ # English: whole-word, case-insensitive.
92
+ if re.search(r"\b" + re.escape(keyword) + r"\b", lowered):
93
+ return JoinSpec(kind=kind, matched_text=keyword)
94
+ else:
95
+ # Chinese (or any non-ASCII keyword): direct substring.
96
+ if keyword in text:
97
+ return JoinSpec(kind=kind, matched_text=keyword)
98
+ # Unqualified physical "join" that is not part of a known qualified
99
+ # keyword — default to inner.
100
+ if re.search(r"\bjoin\b", lowered):
101
+ return JoinSpec(kind="JOIN", matched_text="join")
102
+ return None
103
+
104
+
105
+ def guess_join_key(table1: str, table2: str) -> Optional[str]:
106
+ """Return the canonical join condition between two tables, or None.
107
+
108
+ None means the relationship is unknown; the caller must not
109
+ fabricate a condition.
110
+ """
111
+ return JOIN_KEY_PATTERNS.get((table1, table2))
112
+
113
+
114
+ def extract_joins(
115
+ text: str,
116
+ table_patterns: Dict[str, str],
117
+ ) -> List[Dict]:
118
+ """Extract structured join contexts from text.
119
+
120
+ Detects relational existence patterns (e.g. "users who have orders")
121
+ and returns one dict per additional table, keyed as:
122
+
123
+ type - "EXISTS", "NOT EXISTS" or "JOIN"
124
+ table - the related table name
125
+ subject - the main (first-appearing) table name
126
+ condition - join key string, or None for unknown relationships
127
+ relational - True when type is EXISTS/NOT EXISTS
128
+ """
129
+ joins = []
130
+ # Collect unique tables in text-appearance order (first occurrence wins).
131
+ # Exclude "order" when it is part of an "order by" sorting clause.
132
+ table_occurrences = []
133
+ for pattern, table in table_patterns.items():
134
+ if pattern == "order" and re.search(r"\border\b\s+by\b", text, re.IGNORECASE):
135
+ continue
136
+ if re.search(r"\b" + re.escape(pattern) + r"\b", text) and table not in [t for _, t in table_occurrences]:
137
+ table_occurrences.append((text.find(pattern), table))
138
+ tables_found = [t for _, t in sorted(table_occurrences)]
139
+ if len(tables_found) >= 2:
140
+ join_spec = parse_join_spec(text)
141
+ if any(k in text for k in _NOT_EXISTS_KEYWORDS):
142
+ join_type = "NOT EXISTS"
143
+ relational = True
144
+ elif any(k in text for k in _EXISTS_KEYWORDS):
145
+ join_type = "EXISTS"
146
+ relational = True
147
+ elif join_spec is not None:
148
+ # Structured physical-join intent. Unqualified "JOIN" normalizes
149
+ # to inner; qualified kinds map directly to their SQL keyword.
150
+ join_type = "INNER" if join_spec.kind == "JOIN" else join_spec.kind
151
+ relational = False
152
+ else:
153
+ join_type = "JOIN"
154
+ relational = False
155
+
156
+ main_table = tables_found[0]
157
+ for other_table in tables_found[1:]:
158
+ join_key = guess_join_key(main_table, other_table)
159
+ record = {
160
+ "type": join_type,
161
+ "table": other_table,
162
+ "subject": main_table,
163
+ "condition": join_key,
164
+ "relational": relational,
165
+ "join_spec": join_spec,
166
+ }
167
+ if join_key is None:
168
+ # Unknown relationship - do not fabricate a condition.
169
+ # Carry both sides of the pair so downstream fail-closed
170
+ # messages can name the actual unresolved relationship.
171
+ joins.append(record)
172
+ continue
173
+ joins.append(record)
174
+ return joins
175
+
176
+
177
+ def has_relational_keyword(text: str) -> bool:
178
+ """Check if text contains any relational existence keyword."""
179
+ all_keywords = _NOT_EXISTS_KEYWORDS + _EXISTS_KEYWORDS
180
+ return any(pattern in text for pattern in all_keywords)
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env python3
2
+ """Query Template module for NL2SQL.
3
+
4
+ Provides pattern-based SQL generation using prioritized templates.
5
+ """
6
+ import logging
7
+ import re
8
+ from dataclasses import dataclass, field
9
+ from typing import Optional, Dict, Any, Callable
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ # Clause markers that mean a single-purpose or catch-all template may truncate
15
+ # part of the user's intent. Composed queries must flow to enhanced parsing.
16
+ _COMPOSITION_MARKERS = (
17
+ "order by", "sort", "排序", "排列",
18
+ "group by", "grouped by", "分组", "汇总",
19
+ "limit", "top ", "top\t", "only ", "前",
20
+ " and ", " or ", "且", "并且", "或者", "或",
21
+ )
22
+
23
+ _TEMPLATE_BLOCKERS = {
24
+ "condition_query": _COMPOSITION_MARKERS,
25
+ "aggregate_query": _COMPOSITION_MARKERS,
26
+ "time_range_query": _COMPOSITION_MARKERS,
27
+ "select_with_columns": _COMPOSITION_MARKERS,
28
+ "simple_select": _COMPOSITION_MARKERS,
29
+ }
30
+
31
+ # Keep template captures bounded. These patterns run against natural-language
32
+ # query text, so unbounded adjacent wildcards can cause excessive backtracking
33
+ # on adversarial input. 512 characters is ample for each individual template
34
+ # field while making the regex work bounded by construction.
35
+ _TEMPLATE_CAPTURE_LIMIT = 512
36
+
37
+
38
+ @dataclass
39
+ class QueryTemplate:
40
+ """Template for pattern-based SQL generation with priority.
41
+
42
+ Templates are matched in priority order (highest first).
43
+ Each template has a regex pattern and a SQL template with placeholders.
44
+ """
45
+ name: str
46
+ pattern: str
47
+ sql_template: str
48
+ priority: int = 50
49
+ extractor: Optional[Callable] = None
50
+ _compiled_pattern: re.Pattern = field(init=False, repr=False, default=None)
51
+
52
+ def __post_init__(self):
53
+ """Pre-compile regex pattern for better performance."""
54
+ try:
55
+ self._compiled_pattern = re.compile(self.pattern, re.IGNORECASE)
56
+ except re.error as e:
57
+ logger.warning(f"Failed to compile pattern for template {self.name}: {e}")
58
+ self._compiled_pattern = None
59
+
60
+ def match(self, text: str) -> Optional[Dict[str, Any]]:
61
+ """Try to match this template against input text."""
62
+ normalized = text.lower()
63
+ blockers = _TEMPLATE_BLOCKERS.get(self.name, ())
64
+ if any(marker in normalized for marker in blockers):
65
+ logger.debug(
66
+ "Skipping template '%s' because composition markers require enhanced parsing",
67
+ self.name,
68
+ )
69
+ return None
70
+
71
+ if self._compiled_pattern is None:
72
+ match = re.search(self.pattern, text, re.IGNORECASE)
73
+ else:
74
+ match = self._compiled_pattern.search(text)
75
+
76
+ if match:
77
+ return match.groupdict() if match.groupdict() else {"match": match.group(0)}
78
+ return None
79
+
80
+
81
+ DEFAULT_QUERY_TEMPLATES = [
82
+ QueryTemplate(
83
+ name="top_n_query",
84
+ pattern=rf"(?:查询|获取|get|show|find)?\s*(?:前|top|bottom|最低|最少|lowest|smallest)\s*(\d+)\s*(?:个|条|名)?\s*(.{{1,{_TEMPLATE_CAPTURE_LIMIT}}}?)(?:按|by)?\s*(.{{1,{_TEMPLATE_CAPTURE_LIMIT}}})?\s*(?:排序|排列|order)?",
85
+ sql_template="SELECT * FROM {table} ORDER BY {order_col} DESC LIMIT {n}",
86
+ priority=90,
87
+ ),
88
+ QueryTemplate(
89
+ name="count_by_group",
90
+ pattern=rf"(?:统计|计算|count)\s*(?:每个|各个|each)?\s*(.{{1,{_TEMPLATE_CAPTURE_LIMIT}}}?)\s*(?:的|的数量|数量|有多少)",
91
+ sql_template="SELECT {group_col}, COUNT(*) AS count FROM {table} GROUP BY {group_col}",
92
+ priority=85,
93
+ ),
94
+ QueryTemplate(
95
+ name="time_range_query",
96
+ pattern=r"(?:查询|获取|get|find)?\s*(?:最近|过去|last|past)\s*(\d+)\s*(天|周|月|年|days?|weeks?|months?|years?)\s*(?:的|内的)?\s*(.+)",
97
+ sql_template="SELECT * FROM {table} WHERE {date_col} >= DATE_SUB(CURRENT_DATE, {interval})",
98
+ priority=85,
99
+ ),
100
+ QueryTemplate(
101
+ name="aggregate_query",
102
+ pattern=rf"(?:计算|求|get)?\s*(.{{1,{_TEMPLATE_CAPTURE_LIMIT}}}?)\s*(?:的)?\s*(平均|总和|最大|最小|average|sum|max|min)\s*(.{{1,{_TEMPLATE_CAPTURE_LIMIT}}})",
103
+ sql_template="SELECT {agg_func}({col}) FROM {table}",
104
+ priority=80,
105
+ ),
106
+ QueryTemplate(
107
+ name="condition_query",
108
+ pattern=rf"(?:查询|获取|find|get)?\s*(.{{1,{_TEMPLATE_CAPTURE_LIMIT}}}?)\s*(大于|小于|等于|超过|不等于|greater(?:\s+than)?|less(?:\s+than)?|equal(?:\s+to)?|>|<|=)\s*(\d+\.?\d*)\s*(?:的)?\s*(.{{1,{_TEMPLATE_CAPTURE_LIMIT}}})?",
109
+ sql_template="SELECT * FROM {table} WHERE {col} {op} {value}",
110
+ priority=75,
111
+ ),
112
+ QueryTemplate(
113
+ name="join_query",
114
+ pattern=rf"(?:查询|获取)?\s*(.{{1,{_TEMPLATE_CAPTURE_LIMIT}}}?)\s*(?:和|与|关联|连接|join)\s*(.{{1,{_TEMPLATE_CAPTURE_LIMIT}}}?)(?:的|数据)?",
115
+ sql_template="SELECT * FROM {table1} JOIN {table2} ON {join_condition}",
116
+ priority=70,
117
+ ),
118
+ QueryTemplate(
119
+ name="select_with_columns",
120
+ pattern=rf"(?:查询|获取|显示|select|get|show)\s*(.{{1,{_TEMPLATE_CAPTURE_LIMIT}}}?)\s*(?:的|from)?\s*(.{{1,{_TEMPLATE_CAPTURE_LIMIT}}}?)(?:表|table)?$",
121
+ sql_template="SELECT {columns} FROM {table}",
122
+ priority=60,
123
+ ),
124
+ QueryTemplate(
125
+ name="simple_select",
126
+ pattern=r"(?:查询|获取|显示|列出|select|get|show|list|find)\s*(?:所有|全部|all)?\s*(.+)",
127
+ sql_template="SELECT * FROM {table}",
128
+ priority=40,
129
+ ),
130
+ ]