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
backend/api/main.py ADDED
@@ -0,0 +1,732 @@
1
+ #!/usr/bin/env python3
2
+ """FastAPI Backend for SQL Dialect Master.
3
+
4
+ Enterprise-grade multi-database SQL conversion API:
5
+ - 12 database dialects
6
+ - SQL functions encyclopedia
7
+ - data type mappings
8
+ - conversion rules
9
+ - Natural language to SQL generation
10
+
11
+ Exact counts are derived from the canonical data files at runtime —
12
+ see backend.core.metadata — and must not be restated here.
13
+ """
14
+ import logging
15
+ import time
16
+ from datetime import datetime
17
+ from typing import Any, Dict, List, Optional
18
+
19
+ from fastapi import FastAPI, HTTPException, Query, Request
20
+ from fastapi.middleware.cors import CORSMiddleware
21
+ from fastapi.responses import JSONResponse
22
+ from pydantic import BaseModel, ConfigDict, Field
23
+
24
+ from backend.core.config import get_dialect_api_info, settings, setup_logging
25
+ from backend.core import metadata as meta
26
+ from backend.core.functions_lookup import FunctionEncyclopedia
27
+ from backend.core.nl2sql import NL2SQLGenerator
28
+ from backend.core.parser import SUPPORTED_DIALECTS, SQLParser
29
+ from backend.core.transpiler import SQLTranspiler
30
+ from backend.core.type_mapping import TypeMapper
31
+
32
+ from backend.api.readiness import health_probe_response
33
+
34
+ # Configure logging for the application
35
+ logger = setup_logging(level=logging.INFO)
36
+ api_logger = logging.getLogger(__name__)
37
+
38
+ # API metadata
39
+ API_VERSION = settings.api_version
40
+ API_TITLE = settings.api_title
41
+ API_DESCRIPTION = f"""
42
+ # 🔄 SQL Dialect Master API
43
+
44
+ Enterprise-grade multi-database SQL conversion engine.
45
+
46
+ ## ✨ Features
47
+
48
+ | Feature | Description |
49
+ |---------|-------------|
50
+ | 🔄 **SQL Conversion** | Convert SQL between {meta.SUPPORTED_DIALECTS_COUNT} database dialects |
51
+ | 📚 **Function Encyclopedia** | {meta.FUNCTION_COUNT} SQL functions with cross-database comparison |
52
+ | 🗂️ **Type Mapping** | {meta.TYPE_COUNT} data type mappings × {meta.SUPPORTED_DIALECTS_COUNT} databases matrix |
53
+ | 💬 **NL2SQL** | Natural language to SQL (Chinese/English) |
54
+
55
+ ## 💾 Supported Databases
56
+
57
+ | Category | Databases |
58
+ |----------|-----------|
59
+ | **RDBMS** | MySQL 🐬, PostgreSQL 🐘, Oracle 🔴, SQL Server 🟦 |
60
+ | **Big Data** | Hive 🐝, Spark ⚡, Trino 🔷, Databricks 🧱 |
61
+ | **Cloud DW** | Snowflake ❄️, Redshift 🔶 |
62
+ | **OLAP** | ClickHouse 🏠, DuckDB 🦆 |
63
+
64
+ ## 🚀 Quick Start
65
+
66
+ ```python
67
+ import requests
68
+
69
+ # Convert SQL
70
+ response = requests.post("http://localhost:8000/api/convert", json={{
71
+ "sql": "SELECT DATE_FORMAT(created_at, '%Y-%m-%d') FROM orders",
72
+ "source_dialect": "mysql",
73
+ "target_dialect": "postgres"
74
+ }})
75
+ print(response.json()["target_sql"])
76
+ ```
77
+
78
+ ## 📖 API Endpoints
79
+
80
+ - `POST /api/convert` - Convert SQL between {meta.SUPPORTED_DIALECTS_COUNT} database dialects
81
+ - `GET /api/functions` - Search SQL functions
82
+ - `GET /api/types` - Get type mapping matrix
83
+ - `POST /api/nl2sql` - Generate SQL from natural language
84
+ """
85
+
86
+ # Initialize FastAPI app with enhanced metadata
87
+ app = FastAPI(
88
+ title=API_TITLE,
89
+ description=API_DESCRIPTION,
90
+ version=API_VERSION,
91
+ docs_url="/docs",
92
+ redoc_url="/redoc",
93
+ license_info={
94
+ "name": "MIT License",
95
+ "url": "https://opensource.org/licenses/MIT"
96
+ },
97
+ openapi_tags=[
98
+ {
99
+ "name": "conversion",
100
+ "description": "🔄 SQL dialect conversion operations",
101
+ "externalDocs": {"description": "Learn more", "url": "https://sqlglot.com/"}
102
+ },
103
+ {
104
+ "name": "functions",
105
+ "description": f"📚 SQL function encyclopedia with {meta.FUNCTION_COUNT} functions"
106
+ },
107
+ {
108
+ "name": "types",
109
+ "description": f"🗂️ Data type mapping matrix ({meta.TYPE_COUNT} types × {meta.SUPPORTED_DIALECTS_COUNT} databases)"
110
+ },
111
+ {
112
+ "name": "nl2sql",
113
+ "description": "💬 Natural language to SQL generation"
114
+ },
115
+ {
116
+ "name": "system",
117
+ "description": "⚙️ System and health endpoints"
118
+ }
119
+ ]
120
+ )
121
+
122
+ # Import middleware
123
+ from backend.api.middleware import (
124
+ RateLimiter,
125
+ RateLimitMiddleware,
126
+ SecurityHeadersMiddleware,
127
+ StructuredLoggingMiddleware,
128
+ )
129
+ from backend.core.exceptions import SDMException, UnsupportedDialectError, ErrorCode
130
+
131
+ # Configure CORS from the centralized Pydantic settings source.
132
+ ALLOWED_ORIGINS = [origin.strip() for origin in settings.allowed_origins.split(",") if origin.strip()]
133
+
134
+ app.add_middleware(
135
+ CORSMiddleware,
136
+ allow_origins=ALLOWED_ORIGINS,
137
+ allow_credentials=True,
138
+ allow_methods=["GET", "POST", "OPTIONS"], # Restrict to needed methods
139
+ allow_headers=["Content-Type", "Authorization", "X-Request-ID"],
140
+ )
141
+
142
+ # Add security headers
143
+ app.add_middleware(SecurityHeadersMiddleware)
144
+
145
+ # Add rate limiting (configurable via settings)
146
+ rate_limiter = RateLimiter(
147
+ requests_per_window=settings.rate_limit_requests,
148
+ window_seconds=settings.rate_limit_window,
149
+ )
150
+ app.add_middleware(
151
+ RateLimitMiddleware,
152
+ limiter=rate_limiter,
153
+ enabled=settings.rate_limit_enabled,
154
+ )
155
+
156
+ # Structured request logging must wrap rate limiting so rejected requests also get IDs.
157
+ app.add_middleware(StructuredLoggingMiddleware)
158
+
159
+ # Initialize services
160
+ transpiler = SQLTranspiler()
161
+ func_encyclopedia = FunctionEncyclopedia()
162
+ type_mapper = TypeMapper()
163
+ nl2sql_generator = NL2SQLGenerator()
164
+
165
+ # Request timing middleware with logging
166
+ @app.middleware("http")
167
+ async def add_process_time_header(request: Request, call_next):
168
+ start_time = time.time()
169
+ response = await call_next(request)
170
+ process_time = time.time() - start_time
171
+ response.headers["X-Process-Time"] = f"{process_time:.4f}s"
172
+ response.headers["X-API-Version"] = API_VERSION
173
+
174
+ # Log request details
175
+ api_logger.info(
176
+ f"{request.method} {request.url.path} - {response.status_code} - {process_time:.4f}s"
177
+ )
178
+ return response
179
+
180
+ # Dialect info for enhanced responses (from centralized config)
181
+ DIALECT_INFO = get_dialect_api_info()
182
+
183
+
184
+ def normalize_dialect(dialect: str, field_name: str = "dialect") -> str:
185
+ """Normalize and validate a dialect at the API boundary."""
186
+ normalized = dialect.strip().lower()
187
+ if normalized not in SUPPORTED_DIALECTS:
188
+ raise HTTPException(
189
+ status_code=400,
190
+ detail=f"Unsupported {field_name}: {dialect}. Supported: {SUPPORTED_DIALECTS}",
191
+ )
192
+ return normalized
193
+
194
+ # === Request/Response Models with Enhanced Documentation ===
195
+
196
+ class ConvertRequest(BaseModel):
197
+ """SQL conversion request model."""
198
+ sql: str = Field(..., description="Source SQL statement to convert")
199
+ source_dialect: str = Field(..., description="Source database dialect")
200
+ target_dialect: str = Field(..., description="Target database dialect")
201
+ pretty: bool = Field(True, description="Format output SQL with indentation")
202
+
203
+ model_config = ConfigDict(json_schema_extra={
204
+ "example": {
205
+ "sql": "SELECT DATE_FORMAT(created_at, '%Y-%m-%d') FROM orders",
206
+ "source_dialect": "mysql",
207
+ "target_dialect": "postgres",
208
+ "pretty": True
209
+ }
210
+ })
211
+
212
+ class ConvertResponse(BaseModel):
213
+ """SQL conversion response model."""
214
+ success: bool = Field(..., description="Whether conversion was successful")
215
+ source_sql: str = Field(..., description="Original SQL statement")
216
+ target_sql: Optional[str] = Field(None, description="Converted SQL statement")
217
+ source_dialect: str = Field(..., description="Source dialect")
218
+ target_dialect: str = Field(..., description="Target dialect")
219
+ error: Optional[str] = Field(None, description="Error message if conversion failed")
220
+ error_code: Optional[str] = Field(None, description="Stable machine-readable error category")
221
+ compatibility_notes: List[str] = Field(default_factory=list, description="Compatibility notes")
222
+ transformations: List[str] = Field(default_factory=list, description="Applied transformations")
223
+ warnings: List[str] = Field(default_factory=list, description="Conversion warnings")
224
+ target_validation_state: str = Field(
225
+ "target_valid",
226
+ description=(
227
+ "Target-dialect parse status of the produced output. "
228
+ '"target_valid": target parser accepted the output; '
229
+ '"generic_only": target parser rejected it but the generic parser '
230
+ 'accepted it — the conversion is retained with a warning and must '
231
+ 'not be read as target-validated; '
232
+ '"invalid": neither parser accepts the output.'
233
+ ),
234
+ )
235
+ timestamp: str = Field(default_factory=lambda: datetime.now().isoformat(), description="Response timestamp")
236
+
237
+ class NL2SQLRequest(BaseModel):
238
+ """Natural language to SQL request model."""
239
+ text: str = Field(..., description="Natural language query (Chinese or English)")
240
+ dialect: str = Field("hive", description="Target SQL dialect")
241
+ table_hint: Optional[str] = Field(None, description="Optional table name hint")
242
+ column_hints: Optional[List[str]] = Field(
243
+ None,
244
+ description="Optional column identifier hints; accepts simple or dotted identifiers",
245
+ )
246
+
247
+ model_config = ConfigDict(json_schema_extra={
248
+ "example": {
249
+ "text": "统计每个部门的员工数量",
250
+ "dialect": "mysql",
251
+ "table_hint": "employees",
252
+ "column_hints": ["employees.department", "employees.id"]
253
+ }
254
+ })
255
+
256
+ class NL2SQLResponse(BaseModel):
257
+ """Natural language to SQL response model."""
258
+ success: bool = Field(..., description="Whether generation was successful")
259
+ input_text: str = Field(..., description="Original natural language input")
260
+ sql: Optional[str] = Field(None, description="Generated SQL statement")
261
+ dialect: str = Field(..., description="Target dialect")
262
+ explanation: str = Field("", description="Explanation of generated SQL")
263
+ confidence: float = Field(0.0, description="Confidence score (0.0-1.0)")
264
+ suggestions: List[str] = Field(default_factory=list, description="Improvement suggestions")
265
+
266
+ class ParseRequest(BaseModel):
267
+ """SQL parse request model."""
268
+ sql: str = Field(..., description="SQL statement to parse")
269
+ dialect: str = Field("hive", description="SQL dialect")
270
+
271
+ class TypeMapRequest(BaseModel):
272
+ """Type mapping request model."""
273
+ type_name: str = Field(..., description="Source data type name")
274
+ source_dialect: str = Field(..., description="Source database dialect")
275
+ target_dialect: str = Field(..., description="Target database dialect")
276
+
277
+ model_config = ConfigDict(json_schema_extra={
278
+ "example": {
279
+ "type_name": "VARCHAR",
280
+ "source_dialect": "mysql",
281
+ "target_dialect": "postgres"
282
+ }
283
+ })
284
+
285
+ class APIResponse(BaseModel):
286
+ """Standard API response wrapper."""
287
+ success: bool = Field(..., description="Operation success status")
288
+ data: Optional[Dict[str, Any]] = Field(None, description="Response data")
289
+ error: Optional[str] = Field(None, description="Error message if failed")
290
+ timestamp: str = Field(default_factory=lambda: datetime.now().isoformat())
291
+
292
+ # === API Endpoints ===
293
+
294
+ @app.get("/", tags=["system"])
295
+ async def root():
296
+ """
297
+ 🏠 API Root - Welcome & Service Information
298
+
299
+ Returns comprehensive API information including:
300
+ - Service statistics
301
+ - Available endpoints
302
+ - Quick start guide
303
+ """
304
+ return {
305
+ "name": API_TITLE,
306
+ "version": API_VERSION,
307
+ "status": "✅ Online",
308
+ "description": "Enterprise-grade multi-database SQL conversion engine",
309
+ "stats": {
310
+ "functions": {"count": len(func_encyclopedia.functions), "label": "📚 SQL Functions"},
311
+ "types": {"count": len(type_mapper.mappings), "label": "🗂️ Data Types"},
312
+ "dialects": {"count": len(SUPPORTED_DIALECTS), "label": "💾 Databases"},
313
+ "rules": {"count": len(transpiler.post_processor.engine.rules), "label": "🔧 Conversion Rules"}
314
+ },
315
+ "endpoints": {
316
+ "🔄 conversion": {"url": "/api/convert", "method": "POST"},
317
+ "📝 parsing": {"url": "/api/parse", "method": "POST"},
318
+ "💾 dialects": {"url": "/api/dialects", "method": "GET"},
319
+ "📚 functions": {"url": "/api/functions", "method": "GET"},
320
+ "🗂️ types": {"url": "/api/types", "method": "GET"},
321
+ "💬 nl2sql": {"url": "/api/nl2sql", "method": "POST"},
322
+ "❤️ health": {"url": "/health", "method": "GET"},
323
+ "🟢 readiness": {"url": "/ready", "method": "GET"},
324
+ "🔍 deep health": {"url": "/health/deep", "method": "GET"},
325
+ "📖 docs": {"url": "/docs", "method": "GET"}
326
+ },
327
+ "quick_start": {
328
+ "example": {
329
+ "endpoint": "POST /api/convert",
330
+ "body": {
331
+ "sql": "SELECT DATE_FORMAT(created_at, '%Y-%m-%d') FROM orders",
332
+ "source_dialect": "mysql",
333
+ "target_dialect": "postgres"
334
+ }
335
+ }
336
+ },
337
+ "timestamp": datetime.now().isoformat()
338
+ }
339
+
340
+ @app.get("/api/dialects", tags=["system"])
341
+ async def get_dialects():
342
+ """
343
+ 💾 Get Supported SQL Dialects
344
+
345
+ Returns all 12 supported database dialects with:
346
+ - Display name and icon
347
+ - Category (RDBMS, Big Data, Cloud DW, etc.)
348
+ - Grouped by category for easy navigation
349
+ """
350
+ # Group dialects by category
351
+ by_category = {}
352
+ for dialect, info in DIALECT_INFO.items():
353
+ cat = info["category"]
354
+ if cat not in by_category:
355
+ by_category[cat] = []
356
+ by_category[cat].append({
357
+ "id": dialect,
358
+ "name": info["name"],
359
+ "icon": info["icon"]
360
+ })
361
+
362
+ return {
363
+ "success": True,
364
+ "dialects": SUPPORTED_DIALECTS,
365
+ "count": len(SUPPORTED_DIALECTS),
366
+ "details": DIALECT_INFO,
367
+ "by_category": by_category,
368
+ "categories": list(by_category.keys())
369
+ }
370
+
371
+ @app.post("/api/convert", response_model=ConvertResponse, tags=["conversion"])
372
+ async def convert_sql(request: ConvertRequest):
373
+ """
374
+ Convert SQL from one dialect to another.
375
+
376
+ Supports conversion between 12 database dialects with:
377
+ - Automatic syntax transformation
378
+ - Function mapping
379
+ - Type conversion
380
+ - Compatibility notes
381
+
382
+ **Example:**
383
+ ```json
384
+ {
385
+ "sql": "SELECT DATE_FORMAT(created_at, '%Y-%m-%d') FROM orders",
386
+ "source_dialect": "mysql",
387
+ "target_dialect": "postgres"
388
+ }
389
+ ```
390
+ """
391
+ source_dialect = request.source_dialect.strip().lower()
392
+ target_dialect = request.target_dialect.strip().lower()
393
+ if source_dialect not in SUPPORTED_DIALECTS:
394
+ raise HTTPException(
395
+ status_code=400,
396
+ detail=f"Unsupported source dialect: {request.source_dialect}. Supported: {SUPPORTED_DIALECTS}"
397
+ )
398
+ if target_dialect not in SUPPORTED_DIALECTS:
399
+ raise HTTPException(
400
+ status_code=400,
401
+ detail=f"Unsupported target dialect: {request.target_dialect}. Supported: {SUPPORTED_DIALECTS}"
402
+ )
403
+
404
+ result = transpiler.transpile(
405
+ request.sql,
406
+ source_dialect,
407
+ target_dialect,
408
+ request.pretty
409
+ )
410
+ return ConvertResponse(
411
+ success=result.success,
412
+ source_sql=result.source_sql,
413
+ target_sql=result.target_sql,
414
+ source_dialect=result.source_dialect,
415
+ target_dialect=result.target_dialect,
416
+ error=result.error,
417
+ error_code=result.error_code,
418
+ compatibility_notes=result.compatibility_notes,
419
+ transformations=result.transformations,
420
+ warnings=result.warnings,
421
+ target_validation_state=result.target_validation_state,
422
+ )
423
+
424
+ @app.post("/api/parse", tags=["conversion"])
425
+ async def parse_sql(request: ParseRequest):
426
+ """
427
+ Parse SQL and extract elements.
428
+
429
+ Extracts tables, columns, functions, and other elements from SQL.
430
+ """
431
+ dialect = normalize_dialect(request.dialect)
432
+ parser = SQLParser(dialect)
433
+ elements = parser.extract_elements(request.sql)
434
+ return {
435
+ "success": True,
436
+ "dialect": dialect,
437
+ "elements": elements,
438
+ "timestamp": datetime.now().isoformat()
439
+ }
440
+
441
+ @app.get("/api/functions", tags=["functions"])
442
+ async def list_functions(
443
+ search: Optional[str] = Query(None, description="Search term for function name"),
444
+ category: Optional[str] = Query(None, description="Filter by category (string, date, math, etc.)"),
445
+ limit: int = Query(50, ge=1, le=500, description="Maximum number of results")
446
+ ):
447
+ """
448
+ List or search SQL functions.
449
+
450
+ **Categories:** string, date, math, aggregate, window, conditional, conversion, json, array, system, geo
451
+
452
+ **Examples:**
453
+ - `/api/functions?search=date` - Search for date functions
454
+ - `/api/functions?category=string` - List all string functions
455
+ - `/api/functions?limit=100` - Get first 100 functions
456
+ """
457
+ if search:
458
+ functions = func_encyclopedia.search(search, limit)
459
+ elif category:
460
+ functions = func_encyclopedia.list_by_category(category)
461
+ else:
462
+ functions = func_encyclopedia.functions[:limit]
463
+
464
+ return {
465
+ "success": True,
466
+ "functions": functions,
467
+ "count": len(functions),
468
+ "total": len(func_encyclopedia.functions),
469
+ "stats": func_encyclopedia.get_stats()
470
+ }
471
+
472
+ @app.get("/api/functions/categories", tags=["functions"])
473
+ async def get_function_categories():
474
+ """
475
+ Get all function categories with counts.
476
+
477
+ Returns list of categories and number of functions in each.
478
+ """
479
+ categories = func_encyclopedia.get_all_categories()
480
+ return {
481
+ "success": True,
482
+ "categories": categories,
483
+ "count": len(categories)
484
+ }
485
+
486
+ @app.get("/api/functions/{name}", tags=["functions"])
487
+ async def get_function(name: str):
488
+ """
489
+ Get detailed function information.
490
+
491
+ Returns function details with syntax for all 12 database dialects.
492
+ """
493
+ func = func_encyclopedia.get_function(name)
494
+ if not func:
495
+ raise HTTPException(
496
+ status_code=404,
497
+ detail=f"Function '{name}' not found. Try searching with /api/functions?search={name}"
498
+ )
499
+
500
+ comparison = func_encyclopedia.compare_dialects(name)
501
+ return {
502
+ "success": True,
503
+ "function": func,
504
+ "comparison": comparison
505
+ }
506
+
507
+ @app.get("/api/types", tags=["types"])
508
+ async def list_types(
509
+ source: Optional[str] = Query(None, description="Filter by source dialect"),
510
+ target: Optional[str] = Query(None, description="Filter by target dialect")
511
+ ):
512
+ """
513
+ Get type mapping matrix.
514
+
515
+ Returns the complete type mapping matrix (canonical type count × 12 databases).
516
+
517
+ **Type Categories:**
518
+ - String: STRING, VARCHAR, CHAR, TEXT, etc.
519
+ - Numeric: BIGINT, INT, DECIMAL, FLOAT, etc.
520
+ - Date/Time: DATE, TIME, TIMESTAMP, INTERVAL
521
+ - Complex: ARRAY, MAP, STRUCT, JSON
522
+ - Special: UUID, INET, GEOMETRY
523
+ """
524
+ if source is not None:
525
+ source = normalize_dialect(source, "source dialect")
526
+ if target is not None:
527
+ target = normalize_dialect(target, "target dialect")
528
+ matrix = type_mapper.get_matrix(source, target)
529
+ return {
530
+ "success": True,
531
+ "mappings": matrix,
532
+ "dialects": type_mapper.DIALECTS,
533
+ "type_count": len(matrix),
534
+ "precision_warnings": type_mapper.get_precision_warnings()
535
+ }
536
+
537
+ @app.get("/api/types/{type_name}", tags=["types"])
538
+ async def get_type(type_name: str):
539
+ """
540
+ Get type mapping details for a specific type.
541
+
542
+ Returns mapping for all 12 database dialects.
543
+ """
544
+ comparison = type_mapper.compare_types(type_name)
545
+ if "error" in comparison:
546
+ raise HTTPException(
547
+ status_code=404,
548
+ detail=f"Type '{type_name}' not found. Available types: STRING, VARCHAR, INT, BIGINT, DECIMAL, DATE, TIMESTAMP, ARRAY, MAP, JSON, etc."
549
+ )
550
+ return {
551
+ "success": True,
552
+ **comparison
553
+ }
554
+
555
+ @app.post("/api/types/map", tags=["types"])
556
+ async def map_type(request: TypeMapRequest):
557
+ """
558
+ Map a type from source to target dialect.
559
+
560
+ Returns the equivalent type in the target database with notes.
561
+ """
562
+ source_dialect = normalize_dialect(request.source_dialect, "source dialect")
563
+ target_dialect = normalize_dialect(request.target_dialect, "target dialect")
564
+
565
+ result = type_mapper.suggest_type(
566
+ request.type_name,
567
+ source_dialect,
568
+ target_dialect,
569
+ )
570
+ return {
571
+ "success": True,
572
+ **result,
573
+ "timestamp": datetime.now().isoformat()
574
+ }
575
+
576
+ @app.post("/api/nl2sql", response_model=NL2SQLResponse, tags=["nl2sql"])
577
+ async def generate_sql(request: NL2SQLRequest):
578
+ """
579
+ Generate SQL from natural language.
580
+
581
+ Supports both Chinese and English input.
582
+
583
+ **Examples:**
584
+ - "查询最近7天的订单" → SELECT * FROM orders WHERE created_at >= DATE_SUB(CURRENT_DATE, 7)
585
+ - "统计每个部门的员工数量" → SELECT dept, COUNT(*) FROM employees GROUP BY dept
586
+ - "Get top 10 users by score" → SELECT * FROM users ORDER BY score DESC LIMIT 10
587
+ """
588
+ dialect = normalize_dialect(request.dialect)
589
+ result = nl2sql_generator.generate(
590
+ request.text,
591
+ dialect,
592
+ request.table_hint,
593
+ request.column_hints,
594
+ )
595
+ return NL2SQLResponse(
596
+ success=result.success,
597
+ input_text=result.input_text,
598
+ sql=result.sql,
599
+ dialect=dialect,
600
+ explanation=result.explanation,
601
+ confidence=result.confidence,
602
+ suggestions=result.suggestions
603
+ )
604
+
605
+ @app.get("/health", tags=["system"])
606
+ async def health_check():
607
+ """Return process liveness without probing business dependencies."""
608
+ return {
609
+ "status": "alive",
610
+ "version": API_VERSION,
611
+ "timestamp": datetime.now().isoformat()
612
+ }
613
+
614
+ @app.get("/ready", tags=["system"])
615
+ async def readiness_check(request: Request):
616
+ """Return readiness after authorized dependency probes."""
617
+ return await health_probe_response(request)
618
+
619
+ @app.get("/health/deep", tags=["system"])
620
+ async def deep_health_check(request: Request):
621
+ """Return deep health using the shared authorized dependency probe service."""
622
+ return await health_probe_response(request)
623
+
624
+ @app.get("/api/stats", tags=["system"])
625
+ async def get_stats():
626
+ """
627
+ 📊 Get API Statistics
628
+
629
+ Returns comprehensive statistics about:
630
+ - Function encyclopedia
631
+ - Type mappings
632
+ - Conversion rules
633
+ - Supported dialects
634
+ """
635
+ func_categories = func_encyclopedia.get_all_categories()
636
+
637
+ return {
638
+ "success": True,
639
+ "stats": {
640
+ "overview": {
641
+ "total_functions": len(func_encyclopedia.functions),
642
+ "total_types": len(type_mapper.mappings),
643
+ "total_dialects": len(SUPPORTED_DIALECTS),
644
+ "conversion_rules": len(transpiler.post_processor.engine.rules)
645
+ },
646
+ "functions": {
647
+ "total": len(func_encyclopedia.functions),
648
+ "categories": func_categories,
649
+ "category_count": len(func_categories)
650
+ },
651
+ "types": {
652
+ "total": len(type_mapper.mappings),
653
+ "dialects": len(type_mapper.DIALECTS),
654
+ "categories": ["String", "Numeric", "Date/Time", "Complex", "Binary", "Special"]
655
+ },
656
+ "dialects": {
657
+ "list": SUPPORTED_DIALECTS,
658
+ "details": DIALECT_INFO
659
+ }
660
+ },
661
+ "timestamp": datetime.now().isoformat()
662
+ }
663
+
664
+ # Custom exception handler for better error responses
665
+ @app.exception_handler(HTTPException)
666
+ async def custom_http_exception_handler(request: Request, exc: HTTPException):
667
+ return JSONResponse(
668
+ status_code=exc.status_code,
669
+ content={
670
+ "success": False,
671
+ "error": {
672
+ "code": exc.status_code,
673
+ "message": exc.detail,
674
+ "type": "HTTPException"
675
+ },
676
+ "timestamp": datetime.now().isoformat()
677
+ }
678
+ )
679
+
680
+
681
+ @app.exception_handler(SDMException)
682
+ async def sdm_exception_handler(request: Request, exc: SDMException):
683
+ """Handle custom SDM exceptions."""
684
+ status_code = 400
685
+ if isinstance(exc, UnsupportedDialectError):
686
+ status_code = 400
687
+
688
+ error = exc.to_dict()
689
+ if "code" not in error:
690
+ error["code"] = exc.error_code.value if isinstance(exc.error_code, ErrorCode) else str(exc.error_code)
691
+
692
+ return JSONResponse(
693
+ status_code=status_code,
694
+ content={
695
+ "success": False,
696
+ "error": error,
697
+ "timestamp": datetime.now().isoformat()
698
+ }
699
+ )
700
+
701
+
702
+ @app.exception_handler(Exception)
703
+ async def unhandled_exception_handler(request: Request, exc: Exception):
704
+ """Return a generic 500 response while retaining request correlation internally."""
705
+ request_id = getattr(request.state, "request_id", None)
706
+ logger.exception("Unhandled API exception", extra={"request_id": request_id})
707
+ content = {
708
+ "success": False,
709
+ "error": {
710
+ "code": ErrorCode.INTERNAL_ERROR.value,
711
+ "message": "Internal server error",
712
+ },
713
+ "timestamp": datetime.now().isoformat(),
714
+ }
715
+ if request_id:
716
+ content["request_id"] = request_id
717
+ return JSONResponse(status_code=500, content=content)
718
+
719
+ # Run with: uvicorn backend.api.main:app --reload
720
+ if __name__ == "__main__":
721
+ import uvicorn
722
+ print(f"""
723
+ ╔══════════════════════════════════════════════════════════╗
724
+ ║ 🔄 SQL Dialect Master API v{API_VERSION} ║
725
+ ║ ────────────────────────────────────────────────────── ║
726
+ ║ 📚 Functions: {len(func_encyclopedia.functions):>3} │ 🗂️ Types: {len(type_mapper.mappings):>2} │ 💾 DBs: {len(SUPPORTED_DIALECTS):>2} ║
727
+ ║ ────────────────────────────────────────────────────── ║
728
+ ║ 📖 Docs: http://localhost:8000/docs ║
729
+ ║ ❤️ Health: http://localhost:8000/health ║
730
+ ╚══════════════════════════════════════════════════════════╝
731
+ """)
732
+ uvicorn.run(app, host="0.0.0.0", port=8000)