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,332 @@
1
+ #!/usr/bin/env python3
2
+ """Function Encyclopedia - Search and compare SQL functions across dialects.
3
+
4
+ Provides comprehensive SQL function lookup with:
5
+ - 50+ SQL functions across 11 categories
6
+ - Cross-database syntax comparison for 12 dialects
7
+ - Fuzzy search with relevance scoring
8
+ - Category-based filtering
9
+ - Function examples and parameter documentation
10
+ """
11
+ import json
12
+ import logging
13
+ from pathlib import Path
14
+ from typing import Optional, List, Dict, Any
15
+ from difflib import SequenceMatcher
16
+
17
+ from .config import (
18
+ SUPPORTED_DIALECTS,
19
+ FUNCTION_CATEGORIES,
20
+ CATEGORY_DESCRIPTIONS,
21
+ FunctionInfo,
22
+ settings
23
+ )
24
+ from .exceptions import ConfigurationError
25
+ from .function_semantics import FunctionSemanticRegistry
26
+ from .p2_data_validation import _validate_function_entry
27
+
28
+ # Configure module logger
29
+ logger = logging.getLogger(__name__)
30
+
31
+
32
+ class FunctionEncyclopedia:
33
+ """SQL Function Encyclopedia with multi-dialect comparison.
34
+
35
+ Features:
36
+ - 50+ SQL functions
37
+ - 11 categories: string, date, math, aggregate, window, conditional, conversion, json, array, system, geo
38
+ - 12 database dialects support
39
+ - Fuzzy search with relevance scoring
40
+ - Function examples and parameter documentation
41
+ - Strict semantic equivalence classification for reviewed function pairs
42
+ """
43
+
44
+ CATEGORIES = FUNCTION_CATEGORIES
45
+ CATEGORY_DESCRIPTIONS = CATEGORY_DESCRIPTIONS
46
+
47
+ def __init__(self, data_path: Optional[Path] = None, semantic_registry: Optional[FunctionSemanticRegistry] = None):
48
+ """Initialize encyclopedia with function data.
49
+
50
+ Args:
51
+ data_path: Path to functions_db.json. Uses default if None.
52
+ semantic_registry: Optional reviewed semantic registry. Defaults to the shared registry.
53
+ """
54
+ if data_path is None:
55
+ data_path = Path(__file__).parent / "functions_db.json"
56
+ self.data_path = data_path
57
+ self.semantic_registry = semantic_registry or FunctionSemanticRegistry()
58
+ self.functions: List[FunctionInfo] = self._load_data()
59
+ self._build_index()
60
+
61
+ def _load_data(self) -> List[FunctionInfo]:
62
+ """Load function data from JSON file.
63
+
64
+ Missing or malformed packaged data is a configuration error and must
65
+ not be silently converted into an empty encyclopedia.
66
+ """
67
+ try:
68
+ with open(self.data_path, 'r', encoding='utf-8') as f:
69
+ data = json.load(f)
70
+ except (OSError, json.JSONDecodeError) as exc:
71
+ raise ConfigurationError(
72
+ f"Unable to load function data from {self.data_path}: {exc}",
73
+ details={"data_path": str(self.data_path)},
74
+ ) from exc
75
+
76
+ if not isinstance(data, dict):
77
+ raise ConfigurationError(
78
+ f"Invalid function data in {self.data_path}: top-level JSON value must be an object",
79
+ details={"data_path": str(self.data_path)},
80
+ )
81
+
82
+ functions = data.get("functions", [])
83
+ if not isinstance(functions, list):
84
+ raise ConfigurationError(
85
+ f"Invalid function data in {self.data_path}: 'functions' must be a list",
86
+ details={"data_path": str(self.data_path)},
87
+ )
88
+ for index, entry in enumerate(functions):
89
+ _validate_function_entry(self.data_path, index, entry)
90
+ return functions
91
+
92
+ def _build_index(self) -> None:
93
+ """Build search index for fast lookup."""
94
+ self.name_index: Dict[str, FunctionInfo] = {}
95
+ self.category_index: Dict[str, List[FunctionInfo]] = {cat: [] for cat in self.CATEGORIES}
96
+
97
+ for func in self.functions:
98
+ name = func.get("name", "").upper()
99
+ self.name_index[name] = func
100
+
101
+ category = func.get("category", "").lower()
102
+ if category in self.category_index:
103
+ self.category_index[category].append(func)
104
+
105
+ def search(self, query: str, limit: int = None) -> List[FunctionInfo]:
106
+ """Search functions with fuzzy matching.
107
+
108
+ Args:
109
+ query: Search query
110
+ limit: Maximum results to return
111
+
112
+ Returns:
113
+ List of matching functions sorted by relevance
114
+ """
115
+ limit = limit or settings.function_search_limit
116
+ query = query.upper().strip()
117
+
118
+ if not query:
119
+ return self.functions[:limit]
120
+
121
+ results: List[tuple] = []
122
+
123
+ for func in self.functions:
124
+ name = func.get("name", "").upper()
125
+ description = func.get("description", "").upper()
126
+
127
+ if query == name:
128
+ results.append((func, 1.0))
129
+ elif name.startswith(query):
130
+ results.append((func, 0.9))
131
+ elif query in name:
132
+ results.append((func, 0.7))
133
+ elif query in description:
134
+ results.append((func, 0.5))
135
+ else:
136
+ ratio = SequenceMatcher(None, query, name).ratio()
137
+ if ratio > settings.function_fuzzy_threshold:
138
+ results.append((func, ratio))
139
+
140
+ results.sort(key=lambda x: x[1], reverse=True)
141
+ return [r[0] for r in results[:limit]]
142
+
143
+ def get_function(self, name: str) -> Optional[FunctionInfo]:
144
+ """Get detailed function information by exact name."""
145
+ return self.name_index.get(name.upper())
146
+
147
+ def list_by_category(self, category: str) -> List[FunctionInfo]:
148
+ """List all functions in a category."""
149
+ category = category.lower()
150
+ return self.category_index.get(category, [])
151
+
152
+ def get_dialect_syntax(self, func_name: str, dialect: str) -> Optional[str]:
153
+ """Get function syntax for specific dialect."""
154
+ func = self.get_function(func_name)
155
+ if func:
156
+ return func.get("dialects", {}).get(dialect.lower())
157
+ return None
158
+
159
+ def resolve_function_semantics(self, func_name: str, source_dialect: str, target_dialect: str) -> Dict[str, Any]:
160
+ """Resolve reviewed semantic equivalence without inferring from syntax presence."""
161
+ return self.semantic_registry.resolve(func_name, source_dialect, target_dialect)
162
+
163
+ def compare_dialects(self, func_name: str, dialects: List[str] = None) -> Dict[str, Any]:
164
+ """Compare function syntax across multiple dialects."""
165
+ func = self.get_function(func_name)
166
+ if not func:
167
+ return {"error": f"Function {func_name} not found"}
168
+
169
+ if dialects is None:
170
+ dialects = SUPPORTED_DIALECTS
171
+
172
+ comparison = {
173
+ "name": func.get("name"),
174
+ "description": func.get("description"),
175
+ "category": func.get("category"),
176
+ "parameters": func.get("parameters", []),
177
+ "examples": func.get("examples", {}),
178
+ "notes": func.get("notes", ""),
179
+ "dialects": {}
180
+ }
181
+
182
+ for dialect in dialects:
183
+ syntax = func.get("dialects", {}).get(dialect)
184
+ comparison["dialects"][dialect] = syntax if syntax else "N/A"
185
+
186
+ available = [d for d in dialects if comparison["dialects"].get(d, "N/A") != "N/A"]
187
+ comparison["availability"] = {
188
+ "supported": available,
189
+ "not_supported": [d for d in dialects if d not in available],
190
+ "coverage": f"{len(available)}/{len(dialects)}"
191
+ }
192
+
193
+ return comparison
194
+
195
+ def get_all_categories(self) -> List[Dict[str, Any]]:
196
+ """Get list of all categories with function counts and descriptions."""
197
+ return [
198
+ {
199
+ "name": cat,
200
+ "count": len(funcs),
201
+ "description": self.CATEGORY_DESCRIPTIONS.get(cat, ""),
202
+ "icon": self._get_category_icon(cat)
203
+ }
204
+ for cat, funcs in self.category_index.items()
205
+ if funcs
206
+ ]
207
+
208
+ def _get_category_icon(self, category: str) -> str:
209
+ """Get icon for category."""
210
+ icons = {
211
+ "string": "📝", "date": "📅", "math": "🔢", "aggregate": "📊",
212
+ "window": "🪟", "conditional": "❓", "conversion": "🔄", "json": "📋",
213
+ "array": "📦", "system": "⚙️", "geo": "🌍"
214
+ }
215
+ return icons.get(category, "📄")
216
+
217
+ def get_stats(self) -> Dict[str, Any]:
218
+ """Get comprehensive encyclopedia statistics."""
219
+ dialect_coverage = {d: 0 for d in SUPPORTED_DIALECTS}
220
+ for func in self.functions:
221
+ for dialect, syntax in func.get("dialects", {}).items():
222
+ if syntax and syntax != "N/A":
223
+ dialect_coverage[dialect] = dialect_coverage.get(dialect, 0) + 1
224
+
225
+ return {
226
+ "total_functions": len(self.functions),
227
+ "categories": len([c for c in self.category_index.values() if c]),
228
+ "by_category": {cat: len(funcs) for cat, funcs in self.category_index.items()},
229
+ "dialects_covered": len(SUPPORTED_DIALECTS),
230
+ "dialect_coverage": dialect_coverage,
231
+ "top_categories": sorted(
232
+ [(cat, len(funcs)) for cat, funcs in self.category_index.items()],
233
+ key=lambda x: x[1], reverse=True
234
+ )[:5]
235
+ }
236
+
237
+ def find_equivalent(self, func_name: str, source_dialect: str, target_dialect: str) -> Optional[Dict[str, Any]]:
238
+ """Find target syntax while exposing strict semantic classification."""
239
+ func = self.get_function(func_name)
240
+ if not func:
241
+ return None
242
+
243
+ dialects = func.get("dialects", {})
244
+ source_dialect = source_dialect.lower()
245
+ target_dialect = target_dialect.lower()
246
+ source_syntax = dialects.get(source_dialect)
247
+ target_syntax = dialects.get(target_dialect)
248
+
249
+ if not source_syntax or source_syntax == "N/A":
250
+ return None
251
+
252
+ semantic = self.resolve_function_semantics(func_name, source_dialect, target_dialect)
253
+
254
+ return {
255
+ "function": func_name,
256
+ "source_dialect": source_dialect,
257
+ "target_dialect": target_dialect,
258
+ "source_syntax": source_syntax,
259
+ "target_syntax": target_syntax if target_syntax and target_syntax != "N/A" else None,
260
+ "supported_in_target": target_syntax is not None and target_syntax != "N/A",
261
+ "semantic_status": semantic["status"],
262
+ "semantic_equivalent": semantic["equivalent"],
263
+ "semantic_argument_semantics": semantic.get("argument_semantics", ""),
264
+ "semantic_return_shape": semantic.get("return_shape", ""),
265
+ "semantic_null_behavior": semantic.get("null_behavior", ""),
266
+ "semantic_notes": semantic.get("notes", ""),
267
+ "notes": func.get("notes", "")
268
+ }
269
+
270
+ def get_functions_by_dialect(self, dialect: str) -> List[Dict[str, Any]]:
271
+ """Get all functions available in a specific dialect."""
272
+ dialect = dialect.lower()
273
+ available = []
274
+
275
+ for func in self.functions:
276
+ syntax = func.get("dialects", {}).get(dialect)
277
+ if syntax and syntax != "N/A":
278
+ available.append({
279
+ "name": func.get("name"),
280
+ "category": func.get("category"),
281
+ "syntax": syntax,
282
+ "description": func.get("description", ""),
283
+ "parameters": func.get("parameters", [])
284
+ })
285
+
286
+ return available
287
+
288
+ def search_by_description(self, query: str, limit: int = 10) -> List[FunctionInfo]:
289
+ """Search functions by description text."""
290
+ query = query.lower()
291
+ results = []
292
+
293
+ for func in self.functions:
294
+ desc = func.get("description", "").lower()
295
+ name = func.get("name", "").lower()
296
+
297
+ if query in desc or query in name:
298
+ score = 1.0 if query in name else 0.7
299
+ results.append((func, score))
300
+
301
+ results.sort(key=lambda x: x[1], reverse=True)
302
+ return [r[0] for r in results[:limit]]
303
+
304
+ def get_function_alternatives(self, func_name: str, dialect: str) -> List[Dict[str, Any]]:
305
+ """Get alternative functions for a dialect that doesn't support the original."""
306
+ func = self.get_function(func_name)
307
+ if not func:
308
+ return []
309
+
310
+ category = func.get("category", "")
311
+ dialect = dialect.lower()
312
+
313
+ alternatives = []
314
+ for other_func in self.category_index.get(category, []):
315
+ if other_func.get("name") == func_name:
316
+ continue
317
+
318
+ syntax = other_func.get("dialects", {}).get(dialect)
319
+ if syntax and syntax != "N/A":
320
+ alternatives.append({
321
+ "name": other_func.get("name"),
322
+ "syntax": syntax,
323
+ "description": other_func.get("description", ""),
324
+ "similarity": SequenceMatcher(
325
+ None,
326
+ func.get("description", ""),
327
+ other_func.get("description", "")
328
+ ).ratio()
329
+ })
330
+
331
+ alternatives.sort(key=lambda x: x["similarity"], reverse=True)
332
+ return alternatives[:5]
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env python3
2
+ """Canonical metadata for SQL Dialect Master.
3
+
4
+ Single source of truth for product counts, version facts, and
5
+ dialect information. Every public surface (API, README verifier,
6
+ frontend) must derive its displayed numbers from this module rather
7
+ than from hardcoded literals.
8
+
9
+ Design rules:
10
+ - Counts (functions, types, rules, dialects) are computed at import
11
+ time from the canonical data files / engine objects.
12
+ - Version constants live here and are the sole release source
13
+ (``PROJECT_VERSION`` / ``API_VERSION``).
14
+ - The frontend architecture version is deliberately separate from
15
+ the product version: "Frontend v2" is not "release v2.0".
16
+ """
17
+ from __future__ import annotations
18
+
19
+ # ── Release version facts (sole source of truth) ────────────────────────
20
+ PROJECT_VERSION = "1.1.1"
21
+ API_VERSION = "1.1.1"
22
+ # Frontend v2 architecture — independent of the product release version.
23
+ FRONTEND_ARCHITECTURE_VERSION = 2
24
+
25
+
26
+ def _canonical_counts() -> dict:
27
+ """Compute dialect / function / type / rule counts from data files."""
28
+ import json
29
+ from pathlib import Path
30
+
31
+ base = Path(__file__).parent
32
+ dialects, functions, types = 0, 0, 0
33
+ type_file = base / "type_mapping.json"
34
+ if type_file.exists():
35
+ with type_file.open(encoding="utf-8") as f:
36
+ data = json.load(f)
37
+ dialects = len(data.get("dialects", []))
38
+ types = len(data.get("mappings", {}))
39
+ func_file = base / "functions_db.json"
40
+ if func_file.exists():
41
+ with func_file.open(encoding="utf-8") as f:
42
+ fdata = json.load(f)
43
+ functions = len(fdata.get("functions", []))
44
+ return {"dialects": dialects, "functions": functions, "types": types}
45
+
46
+
47
+ _counts = _canonical_counts()
48
+
49
+ SUPPORTED_DIALECTS_COUNT = _counts["dialects"]
50
+ FUNCTION_COUNT = _counts["functions"]
51
+ TYPE_COUNT = _counts["types"]
52
+
53
+ # Rule count depends on the RuleEngine; compute lazily to keep the
54
+ # import graph clean. Uses the same engine instance the transpiler
55
+ # uses, so RULE_COUNT() matches the API's reported rule count.
56
+ def RULE_COUNT() -> int:
57
+ """Number of active conversion rules in the RuleEngine."""
58
+ from backend.core.post_processor import PostProcessor
59
+
60
+ return len(PostProcessor().engine.rules)
61
+
62
+
63
+ __all__ = [
64
+ "PROJECT_VERSION",
65
+ "API_VERSION",
66
+ "FRONTEND_ARCHITECTURE_VERSION",
67
+ "SUPPORTED_DIALECTS_COUNT",
68
+ "FUNCTION_COUNT",
69
+ "TYPE_COUNT",
70
+ "RULE_COUNT",
71
+ ]
backend/core/nl2sql.py ADDED
@@ -0,0 +1,85 @@
1
+ """Stable NL2SQL facade with focused semantic extraction fixes."""
2
+ import re
3
+
4
+ from .nl2sql_pipeline.orchestrator import PipelineNL2SQLGenerator as _NL2SQLGenerator
5
+ from .nl2sql_pipeline.models import NL2SQLResult
6
+
7
+
8
+ class NL2SQLGenerator(_NL2SQLGenerator):
9
+ """Keep the public generator API while fixing count/group semantics."""
10
+
11
+ def _match_templates(self, text: str):
12
+ """Route count-by-group requests through enhanced semantic extraction."""
13
+ template, match_groups = super()._match_templates(text)
14
+ if template and template.name == "count_by_group":
15
+ return None, None
16
+ return template, match_groups
17
+
18
+ def _extract_aggregations(self, text: str) -> list:
19
+ """Preserve the established ``count`` alias for COUNT aggregates."""
20
+ aggregations = super()._extract_aggregations(text)
21
+ return [
22
+ "COUNT(*) AS count" if aggregation == "COUNT(*)" else aggregation
23
+ for aggregation in aggregations
24
+ ]
25
+
26
+ def _extract_group_by(self, text: str) -> list:
27
+ """Extract explicit per-group intent such as '每个部门' when needed."""
28
+ group_cols = super()._extract_group_by(text)
29
+ if group_cols:
30
+ return group_cols
31
+
32
+ match = re.search(r"(?:每个|各个)\s*([^的\s]+?)\s*的", text, re.IGNORECASE)
33
+ if not match:
34
+ match = re.search(r"(?:per|each)\s+(\w+)\b", text, re.IGNORECASE)
35
+ if not match:
36
+ return []
37
+
38
+ group_text = match.group(1).strip()
39
+ for keyword, column in self.COLUMN_PATTERNS.items():
40
+ if keyword in group_text:
41
+ return [column]
42
+ if "部门" in group_text or "department" in group_text:
43
+ return ["department"]
44
+ if "用户" in group_text or "user" in group_text:
45
+ return ["user_id"]
46
+ if "日期" in group_text or "date" in group_text:
47
+ return ["date"]
48
+ return []
49
+
50
+ def _extract_ordering(self, text: str):
51
+ """Order aggregate-count requests by the requested count deterministically."""
52
+ ordering = super()._extract_ordering(text)
53
+ count_requested = any(
54
+ keyword in text
55
+ for keyword in ["数量", "计数", "多少", "count", "how many"]
56
+ )
57
+ sorting_requested = any(
58
+ keyword in text
59
+ for keyword in ["排序", "排列", "sort", "order", "sorted"]
60
+ )
61
+ if count_requested and sorting_requested:
62
+ descending = any(
63
+ keyword in text
64
+ for keyword in [
65
+ "降序",
66
+ "从大到小",
67
+ "递减",
68
+ "desc",
69
+ "descending",
70
+ "decreasing",
71
+ ]
72
+ )
73
+ ascending = any(
74
+ keyword in text
75
+ for keyword in [
76
+ "升序",
77
+ "从小到大",
78
+ "递增",
79
+ "asc",
80
+ "ascending",
81
+ "increasing",
82
+ ]
83
+ )
84
+ return "count", "ASC" if ascending and not descending else "DESC"
85
+ return ordering
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env python3
2
+ """NL2SQL Components Package - Modular components for NL2SQL generation.
3
+
4
+ This package provides modular components for natural language to SQL
5
+ generation:
6
+ - Tokenizer: Smart tokenization with Chinese and English support
7
+ - QueryTemplate: Pattern-based SQL generation using prioritized templates
8
+ - Mappings: Language mappings for tables, columns, and SQL operations
9
+ - CoreExtract: Operation, table, column, limit, distinct extraction
10
+ - Relations: Join extraction, join-key inference, relational keywords
11
+ - Aggregations: SQL aggregate function extraction
12
+
13
+ Example:
14
+ from backend.core.nl2sql_components import Tokenizer, KEYWORDS
15
+
16
+ tokens = Tokenizer.tokenize("查询所有用户")
17
+ print(tokens) # ['查询', '所有', '用户']
18
+ """
19
+
20
+ # Import main components for convenient access
21
+ from .aggregations import extract_aggregations
22
+ from .boolean_conditions import extract_boolean_conditions
23
+ from .core_extract import (
24
+ check_distinct,
25
+ detect_operation,
26
+ extract_columns,
27
+ extract_limit,
28
+ extract_table,
29
+ extract_table_subject,
30
+ named_tables,
31
+ )
32
+ from .mappings import COLUMN_PATTERNS, KEYWORDS, TABLE_PATTERNS
33
+ from .relations import (
34
+ JoinSpec,
35
+ extract_joins,
36
+ guess_join_key,
37
+ has_relational_keyword,
38
+ parse_join_spec,
39
+ )
40
+ from .templates import DEFAULT_QUERY_TEMPLATES, QueryTemplate
41
+ from .tokenizer import JIEBA_AVAILABLE, Tokenizer
42
+
43
+ __all__ = [
44
+ # tokenizer
45
+ "Tokenizer",
46
+ "JIEBA_AVAILABLE",
47
+ # templates
48
+ "QueryTemplate",
49
+ "DEFAULT_QUERY_TEMPLATES",
50
+ # mappings
51
+ "KEYWORDS",
52
+ "TABLE_PATTERNS",
53
+ "COLUMN_PATTERNS",
54
+ # core_extract
55
+ "detect_operation",
56
+ "extract_table",
57
+ "extract_table_subject",
58
+ "named_tables",
59
+ "extract_columns",
60
+ "extract_limit",
61
+ "check_distinct",
62
+ # relations
63
+ "extract_joins",
64
+ "guess_join_key",
65
+ "has_relational_keyword",
66
+ "JoinSpec",
67
+ "parse_join_spec",
68
+ # aggregations
69
+ "extract_aggregations",
70
+ # boolean conditions
71
+ "extract_boolean_conditions",
72
+ ]
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env python3
2
+ """NL2SQL aggregation extraction.
3
+
4
+ Extracts aggregate function calls (COUNT, SUM, AVG, MAX, MIN) from
5
+ natural-language text using keyword matching. Uses the shared
6
+ COLUMN_PATTERNS to resolve the target column for value aggregates.
7
+ """
8
+ from typing import Dict, List
9
+
10
+
11
+ def extract_aggregations(text: str, column_patterns: Dict[str, str]) -> List[str]:
12
+ """Extract aggregation expressions from text.
13
+
14
+ Returns a list of SQL aggregate expressions, e.g.
15
+ ['COUNT(*)', 'SUM(amount)']. An empty list means no aggregation
16
+ was requested.
17
+ """
18
+ aggs: List[str] = []
19
+ agg_column = None
20
+ for pattern, column in column_patterns.items():
21
+ if pattern in text:
22
+ agg_column = column
23
+ break
24
+ col = agg_column or "amount"
25
+ if any(k in text for k in ["统计", "计数", "数量", "多少", "几个", "count", "total", "how many"]):
26
+ aggs.append("COUNT(*)")
27
+ if any(k in text for k in ["求和", "总和", "合计", "总计", "sum", "total of"]):
28
+ aggs.append(f"SUM({col})")
29
+ if any(k in text for k in ["平均", "均值", "平均值", "average", "avg", "mean"]):
30
+ aggs.append(f"AVG({col})")
31
+ if any(k in text for k in ["最大", "最高", "最多", "max", "maximum", "highest", "largest"]):
32
+ aggs.append(f"MAX({col})")
33
+ if any(k in text for k in ["最小", "最低", "最少", "min", "minimum", "lowest", "smallest"]):
34
+ aggs.append(f"MIN({col})")
35
+ return aggs