nlp2sql 0.2.0rc6__tar.gz → 0.2.0rc7__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.
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/CLAUDE.md +4 -1
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/PKG-INFO +28 -2
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/README.md +27 -1
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/docs/API.md +38 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/docs/ARCHITECTURE.md +4 -2
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/pyproject.toml +1 -1
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/src/nlp2sql/__init__.py +11 -4
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/src/nlp2sql/adapters/anthropic_adapter.py +7 -9
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/src/nlp2sql/adapters/gemini_adapter.py +7 -9
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/src/nlp2sql/adapters/openai_adapter.py +7 -9
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/src/nlp2sql/adapters/postgres_repository.py +34 -28
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/src/nlp2sql/adapters/redshift_adapter.py +32 -26
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/src/nlp2sql/cli.py +2 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/src/nlp2sql/config/settings.py +1 -1
- nlp2sql-0.2.0rc7/src/nlp2sql/core/database_prompts.py +48 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/src/nlp2sql/schema/analyzer.py +42 -17
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/src/nlp2sql/schema/manager.py +4 -4
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/src/nlp2sql/services/query_service.py +112 -1
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/tests/test_batch_scoring.py +20 -41
- nlp2sql-0.2.0rc7/tests/test_column_validation.py +180 -0
- nlp2sql-0.2.0rc7/tests/test_database_prompts.py +64 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/.claude/commands/db.md +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/.claude/commands/quality.md +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/.claude/commands/test.md +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/.claude/rules/adapters.md +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/.claude/rules/architecture.md +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/.claude/rules/python-style.md +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/.claude/rules/testing.md +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/.claude/settings.json +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/.github/workflows/publish-pypi.yml +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/.github/workflows/publish-testpypi.yml +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/.gitignore +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/.mcp.json.example +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/.python-version +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/CONTRIBUTING.md +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/LICENSE +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/SECURITY.md +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/assets/nlp2sql-logo.png +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/benchmarks/enterprise_benchmark_results.json +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/docker/.env.example +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/docker/README.md +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/docker/docker-compose.yml +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/docker/get-db-urls.sh +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/docker/init-schema.sql +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/docker/large-schema.sql +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/docker/localstack-init.sh +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/docker/test-redshift.sh +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/docs/CONFIGURATION.md +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/docs/ENTERPRISE.md +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/docs/Redshift.md +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/examples/README.md +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/examples/advanced/test_multiple_providers.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/examples/advanced/test_simple_api.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/examples/database_specific/test_odoo_integration.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/examples/default_examples.json +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/examples/documentation/real_world_example.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/examples/getting_started/basic_usage.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/examples/getting_started/simple_demo.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/examples/getting_started/test_api_setup.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/examples/schema_management/test_auto_schema.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/examples/schema_management/test_schema_filters_comprehensive.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/examples/strands-fastapi-agent/.dockerignore +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/examples/strands-fastapi-agent/.env.example +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/examples/strands-fastapi-agent/DOCKER_README.md +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/examples/strands-fastapi-agent/DOCKER_SETUP.md +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/examples/strands-fastapi-agent/DOCKER_TESTING_SUMMARY.md +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/examples/strands-fastapi-agent/Dockerfile +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/examples/strands-fastapi-agent/INDEX.md +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/examples/strands-fastapi-agent/README.md +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/examples/strands-fastapi-agent/docker/init-ecommerce-schema.sql +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/examples/strands-fastapi-agent/docker-compose.yml +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/examples/strands-fastapi-agent/main.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/examples/strands-fastapi-agent/pyproject.toml +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/examples/strands-fastapi-agent/test-docker-endpoints.sh +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/install-dev.sh +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/mcp_server/README.md +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/mcp_server/__init__.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/mcp_server/example_config.json +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/mcp_server/pyproject.toml +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/mcp_server/server.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/src/nlp2sql/adapters/__init__.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/src/nlp2sql/adapters/local_embedding_adapter.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/src/nlp2sql/adapters/openai_embedding_adapter.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/src/nlp2sql/config/__init__.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/src/nlp2sql/core/__init__.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/src/nlp2sql/core/entities.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/src/nlp2sql/exceptions/__init__.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/src/nlp2sql/factories/__init__.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/src/nlp2sql/factories/repository_factory.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/src/nlp2sql/ports/__init__.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/src/nlp2sql/ports/ai_provider.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/src/nlp2sql/ports/cache.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/src/nlp2sql/ports/embedding_provider.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/src/nlp2sql/ports/query_optimizer.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/src/nlp2sql/ports/schema_repository.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/src/nlp2sql/ports/schema_strategy.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/src/nlp2sql/schema/__init__.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/src/nlp2sql/schema/embedding_manager.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/src/nlp2sql/schema/example_loader.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/src/nlp2sql/schema/example_store.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/src/nlp2sql/services/__init__.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/src/nlp2sql/utils/__init__.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/test_tmp/.gitignore +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/tests/mcp/__init__.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/tests/mcp/test_sql_security.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/tests/test_basic.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/tests/test_embedding_manager.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/tests/test_integration.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/tests/test_redshift_integration.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/tests/test_repository_cache.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/tests/test_repository_factory.py +0 -0
- {nlp2sql-0.2.0rc6 → nlp2sql-0.2.0rc7}/uv.lock +0 -0
|
@@ -51,6 +51,8 @@ The codebase follows Clean Architecture (Hexagonal/Ports & Adapters):
|
|
|
51
51
|
```
|
|
52
52
|
src/nlp2sql/
|
|
53
53
|
├── core/ # Business entities (pure Python, no external dependencies)
|
|
54
|
+
│ ├── entities.py # Query, SQLQuery, DatabaseType
|
|
55
|
+
│ └── database_prompts.py # SQL dialect hints for AI providers
|
|
54
56
|
├── ports/ # Interfaces/abstractions (contracts)
|
|
55
57
|
│ ├── ai_provider.py # AIProviderPort - interface for AI providers
|
|
56
58
|
│ ├── embedding_provider.py # EmbeddingProviderPort - interface for embeddings
|
|
@@ -69,7 +71,8 @@ src/nlp2sql/
|
|
|
69
71
|
├── schema/ # Schema management
|
|
70
72
|
│ ├── schema_manager.py # Coordinates filtering and strategies
|
|
71
73
|
│ ├── schema_analyzer.py # Scores schema relevance
|
|
72
|
-
│
|
|
74
|
+
│ ├── schema_embedding_manager.py # FAISS vector embeddings
|
|
75
|
+
│ └── example_store.py # ExampleStore - FAISS-indexed few-shot examples
|
|
73
76
|
├── config/ # Configuration (Pydantic Settings)
|
|
74
77
|
├── exceptions/ # Custom exceptions hierarchy
|
|
75
78
|
└── cli.py # Click-based CLI
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nlp2sql
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.0rc7
|
|
4
4
|
Summary: Enterprise-ready Natural Language to SQL converter with multi-provider support. Built for production scale (1000+ tables) with Clean Architecture.
|
|
5
5
|
Project-URL: Homepage, https://github.com/luiscarbonel1991/nlp2sql
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/luiscarbonel1991/nlp2sql/issues
|
|
@@ -180,7 +180,33 @@ async def main():
|
|
|
180
180
|
result2 = await service.generate_sql("Show recent orders")
|
|
181
181
|
```
|
|
182
182
|
|
|
183
|
-
### 4.
|
|
183
|
+
### 4. Few-Shot Examples (Improved Accuracy)
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
from nlp2sql import ExampleStore, create_embedding_provider, create_and_initialize_service
|
|
187
|
+
|
|
188
|
+
# Create example store with domain-specific examples
|
|
189
|
+
embedding_provider = create_embedding_provider("openai", api_key=os.getenv("OPENAI_API_KEY"))
|
|
190
|
+
example_store = ExampleStore(embedding_provider=embedding_provider)
|
|
191
|
+
await example_store.add_examples([
|
|
192
|
+
{
|
|
193
|
+
"question": "What was the total revenue last month?",
|
|
194
|
+
"sql": "SELECT SUM(gross_revenue) FROM sales WHERE date >= DATE_TRUNC('month', DATEADD(month, -1, CURRENT_DATE))",
|
|
195
|
+
"database_type": "redshift",
|
|
196
|
+
"metadata": {"category": "aggregation"}
|
|
197
|
+
}
|
|
198
|
+
])
|
|
199
|
+
|
|
200
|
+
service = await create_and_initialize_service(
|
|
201
|
+
database_url="redshift://user:pass@host:5439/db",
|
|
202
|
+
ai_provider="openai",
|
|
203
|
+
api_key=os.getenv("OPENAI_API_KEY"),
|
|
204
|
+
example_store=example_store
|
|
205
|
+
)
|
|
206
|
+
result = await service.generate_sql("Show me total sales this quarter")
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### 5. Large Database with Schema Filtering
|
|
184
210
|
|
|
185
211
|
```python
|
|
186
212
|
from nlp2sql import create_and_initialize_service
|
|
@@ -101,7 +101,33 @@ async def main():
|
|
|
101
101
|
result2 = await service.generate_sql("Show recent orders")
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
-
### 4.
|
|
104
|
+
### 4. Few-Shot Examples (Improved Accuracy)
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from nlp2sql import ExampleStore, create_embedding_provider, create_and_initialize_service
|
|
108
|
+
|
|
109
|
+
# Create example store with domain-specific examples
|
|
110
|
+
embedding_provider = create_embedding_provider("openai", api_key=os.getenv("OPENAI_API_KEY"))
|
|
111
|
+
example_store = ExampleStore(embedding_provider=embedding_provider)
|
|
112
|
+
await example_store.add_examples([
|
|
113
|
+
{
|
|
114
|
+
"question": "What was the total revenue last month?",
|
|
115
|
+
"sql": "SELECT SUM(gross_revenue) FROM sales WHERE date >= DATE_TRUNC('month', DATEADD(month, -1, CURRENT_DATE))",
|
|
116
|
+
"database_type": "redshift",
|
|
117
|
+
"metadata": {"category": "aggregation"}
|
|
118
|
+
}
|
|
119
|
+
])
|
|
120
|
+
|
|
121
|
+
service = await create_and_initialize_service(
|
|
122
|
+
database_url="redshift://user:pass@host:5439/db",
|
|
123
|
+
ai_provider="openai",
|
|
124
|
+
api_key=os.getenv("OPENAI_API_KEY"),
|
|
125
|
+
example_store=example_store
|
|
126
|
+
)
|
|
127
|
+
result = await service.generate_sql("Show me total sales this quarter")
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 5. Large Database with Schema Filtering
|
|
105
131
|
|
|
106
132
|
```python
|
|
107
133
|
from nlp2sql import create_and_initialize_service
|
|
@@ -80,6 +80,7 @@ print(result['validation']) # Validation results
|
|
|
80
80
|
| `api_key` | `str` | Yes | Provider API key |
|
|
81
81
|
| `embedding_provider_type` | `str` | No | Embedding provider: `local` (default), `openai` |
|
|
82
82
|
| `schema_filters` | `dict` | No | Schema filtering options |
|
|
83
|
+
| `example_store` | `ExampleStore` | No | Pre-loaded few-shot examples for better SQL generation |
|
|
83
84
|
|
|
84
85
|
**Returns:** `dict` with keys `sql`, `confidence`, `explanation`, `validation`
|
|
85
86
|
|
|
@@ -111,6 +112,7 @@ result2 = await service.generate_sql("Show recent orders")
|
|
|
111
112
|
| `schema_filters` | `dict` | No | Schema filtering options |
|
|
112
113
|
| `embedding_provider_type` | `str` | No | Embedding provider type (see note below) |
|
|
113
114
|
| `database_type` | `DatabaseType` | No | Database type (auto-detected) |
|
|
115
|
+
| `example_store` | `ExampleStore` | No | Pre-loaded few-shot examples for better SQL generation |
|
|
114
116
|
|
|
115
117
|
**Note on `embedding_provider_type`:** This controls how schema relevance filtering works. With `"local"` or `"openai"`, the system builds a FAISS vector index for semantic search (recommended for databases with 50+ tables). With `None` (default), the system attempts to load local embeddings silently and falls back to text-only matching if `sentence-transformers` is not installed. Text-only matching uses exact name, normalized singular/plural, and token overlap -- effective for small schemas but less accurate for large ones.
|
|
116
118
|
|
|
@@ -141,6 +143,42 @@ result = await service.generate_sql(
|
|
|
141
143
|
)
|
|
142
144
|
```
|
|
143
145
|
|
|
146
|
+
### Few-Shot Examples
|
|
147
|
+
|
|
148
|
+
Improve SQL accuracy by providing domain-specific examples via `ExampleStore`. Examples are indexed with FAISS for semantic retrieval -- the most relevant examples are automatically included in the LLM prompt.
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from nlp2sql import ExampleStore, create_embedding_provider, create_and_initialize_service
|
|
152
|
+
|
|
153
|
+
# Create example store
|
|
154
|
+
embedding_provider = create_embedding_provider("openai", api_key="your-key")
|
|
155
|
+
example_store = ExampleStore(embedding_provider=embedding_provider)
|
|
156
|
+
|
|
157
|
+
# Add domain-specific examples
|
|
158
|
+
await example_store.add_examples([
|
|
159
|
+
{
|
|
160
|
+
"question": "What was the total revenue last month?",
|
|
161
|
+
"sql": "SELECT SUM(gross_revenue) FROM sales WHERE date >= DATE_TRUNC('month', DATEADD(month, -1, CURRENT_DATE))",
|
|
162
|
+
"database_type": "redshift",
|
|
163
|
+
"metadata": {"category": "aggregation"}
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
"question": "Show orders by country",
|
|
167
|
+
"sql": "SELECT country, COUNT(*) AS total_orders FROM orders GROUP BY country ORDER BY total_orders DESC",
|
|
168
|
+
"database_type": "redshift",
|
|
169
|
+
"metadata": {"category": "aggregation"}
|
|
170
|
+
}
|
|
171
|
+
])
|
|
172
|
+
|
|
173
|
+
# Pass to service
|
|
174
|
+
service = await create_and_initialize_service(
|
|
175
|
+
database_url="redshift://user:pass@host:5439/db",
|
|
176
|
+
ai_provider="openai",
|
|
177
|
+
api_key="your-key",
|
|
178
|
+
example_store=example_store
|
|
179
|
+
)
|
|
180
|
+
```
|
|
181
|
+
|
|
144
182
|
### Embedding Providers
|
|
145
183
|
|
|
146
184
|
```python
|
|
@@ -229,7 +229,8 @@ The system has multiple caching layers:
|
|
|
229
229
|
```
|
|
230
230
|
src/nlp2sql/
|
|
231
231
|
├── core/ # Business entities (pure Python, no external dependencies)
|
|
232
|
-
│
|
|
232
|
+
│ ├── entities.py # Query, SQLQuery, DatabaseType
|
|
233
|
+
│ └── database_prompts.py # SQL dialect hints for AI providers (centralized)
|
|
233
234
|
├── ports/ # Interfaces/abstractions
|
|
234
235
|
│ ├── ai_provider.py # AIProviderPort, QueryContext, QueryResponse
|
|
235
236
|
│ ├── schema_repository.py # SchemaRepositoryPort, TableInfo
|
|
@@ -250,7 +251,8 @@ src/nlp2sql/
|
|
|
250
251
|
├── schema/ # Schema management
|
|
251
252
|
│ ├── manager.py # SchemaManager (coordinates strategies)
|
|
252
253
|
│ ├── analyzer.py # SchemaAnalyzer (scoring, compression)
|
|
253
|
-
│
|
|
254
|
+
│ ├── embedding_manager.py # SchemaEmbeddingManager (FAISS + TF-IDF)
|
|
255
|
+
│ └── example_store.py # ExampleStore (FAISS-indexed few-shot examples)
|
|
254
256
|
├── config/ # Configuration
|
|
255
257
|
│ └── settings.py # Pydantic Settings (env vars)
|
|
256
258
|
├── exceptions/ # Custom exception hierarchy
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "nlp2sql"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.0rc7"
|
|
4
4
|
description = "Enterprise-ready Natural Language to SQL converter with multi-provider support. Built for production scale (1000+ tables) with Clean Architecture."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = {text = "MIT"}
|
|
@@ -11,9 +11,10 @@ from .exceptions import *
|
|
|
11
11
|
from .factories import RepositoryFactory
|
|
12
12
|
from .ports.embedding_provider import EmbeddingProviderPort
|
|
13
13
|
from .ports.schema_repository import SchemaRepositoryPort
|
|
14
|
+
from .schema.example_store import ExampleStore
|
|
14
15
|
from .services.query_service import QueryGenerationService
|
|
15
16
|
|
|
16
|
-
__version__ = "0.2.
|
|
17
|
+
__version__ = "0.2.0rc7"
|
|
17
18
|
__author__ = "Luis Carbonel"
|
|
18
19
|
__email__ = "devhighlevel@gmail.com"
|
|
19
20
|
|
|
@@ -38,6 +39,8 @@ __all__ = [
|
|
|
38
39
|
"SQLQuery",
|
|
39
40
|
# Embedding Provider
|
|
40
41
|
"EmbeddingProviderPort",
|
|
42
|
+
# Example Store
|
|
43
|
+
"ExampleStore",
|
|
41
44
|
# Configuration
|
|
42
45
|
"settings",
|
|
43
46
|
# Exceptions
|
|
@@ -130,9 +133,7 @@ async def create_repository(
|
|
|
130
133
|
repo = await create_repository("postgresql://user:pass@localhost/db")
|
|
131
134
|
tables = await repo.get_tables()
|
|
132
135
|
"""
|
|
133
|
-
return await RepositoryFactory.create_and_initialize(
|
|
134
|
-
database_url, schema_name, database_type
|
|
135
|
-
)
|
|
136
|
+
return await RepositoryFactory.create_and_initialize(database_url, schema_name, database_type)
|
|
136
137
|
|
|
137
138
|
|
|
138
139
|
def create_query_service(
|
|
@@ -144,6 +145,7 @@ def create_query_service(
|
|
|
144
145
|
embedding_provider: Optional[EmbeddingProviderPort] = None,
|
|
145
146
|
embedding_provider_type: Optional[str] = None,
|
|
146
147
|
schema_name: str = "public",
|
|
148
|
+
example_store: Optional[ExampleStore] = None,
|
|
147
149
|
) -> QueryGenerationService:
|
|
148
150
|
"""
|
|
149
151
|
Create a configured query service instance.
|
|
@@ -224,6 +226,7 @@ def create_query_service(
|
|
|
224
226
|
schema_filters=schema_filters,
|
|
225
227
|
embedding_provider=embedding_provider,
|
|
226
228
|
schema_name=schema_name,
|
|
229
|
+
example_store=example_store,
|
|
227
230
|
)
|
|
228
231
|
|
|
229
232
|
return service
|
|
@@ -238,6 +241,7 @@ async def create_and_initialize_service(
|
|
|
238
241
|
embedding_provider: Optional[EmbeddingProviderPort] = None,
|
|
239
242
|
embedding_provider_type: Optional[str] = None,
|
|
240
243
|
schema_name: str = "public",
|
|
244
|
+
example_store: Optional[ExampleStore] = None,
|
|
241
245
|
) -> QueryGenerationService:
|
|
242
246
|
"""
|
|
243
247
|
Create and initialize a query service with automatic schema loading.
|
|
@@ -274,6 +278,7 @@ async def create_and_initialize_service(
|
|
|
274
278
|
embedding_provider,
|
|
275
279
|
embedding_provider_type,
|
|
276
280
|
schema_name,
|
|
281
|
+
example_store,
|
|
277
282
|
)
|
|
278
283
|
await service.initialize(database_type)
|
|
279
284
|
return service
|
|
@@ -289,6 +294,7 @@ async def generate_sql_from_db(
|
|
|
289
294
|
embedding_provider: Optional[EmbeddingProviderPort] = None,
|
|
290
295
|
embedding_provider_type: Optional[str] = None,
|
|
291
296
|
schema_name: str = "public",
|
|
297
|
+
example_store: Optional[ExampleStore] = None,
|
|
292
298
|
**kwargs,
|
|
293
299
|
) -> Dict[str, Any]:
|
|
294
300
|
"""
|
|
@@ -346,5 +352,6 @@ async def generate_sql_from_db(
|
|
|
346
352
|
embedding_provider,
|
|
347
353
|
embedding_provider_type,
|
|
348
354
|
schema_name,
|
|
355
|
+
example_store,
|
|
349
356
|
)
|
|
350
357
|
return await service.generate_sql(question=question, database_type=database_type, **kwargs)
|
|
@@ -129,7 +129,11 @@ class AnthropicAdapter(AIProviderPort):
|
|
|
129
129
|
# Add instructions
|
|
130
130
|
prompt_parts.append("Instructions:")
|
|
131
131
|
prompt_parts.append(f"1. Generate a {context.database_type} SQL query that answers the question")
|
|
132
|
-
prompt_parts.append(
|
|
132
|
+
prompt_parts.append(
|
|
133
|
+
"2. Use EXACT column names as listed in the schema above. "
|
|
134
|
+
"NEVER abbreviate, shorten, or infer column names. "
|
|
135
|
+
"If a column is not explicitly listed in the schema, DO NOT use it."
|
|
136
|
+
)
|
|
133
137
|
prompt_parts.append("3. Follow SQL best practices and optimize for performance")
|
|
134
138
|
prompt_parts.append("4. Include appropriate JOINs, WHERE clauses, and ORDER BY if needed")
|
|
135
139
|
prompt_parts.append("5. Return the response in JSON format with 'sql', 'explanation', and 'confidence' fields")
|
|
@@ -151,15 +155,9 @@ Your response must be a valid JSON object with exactly these fields:
|
|
|
151
155
|
|
|
152
156
|
Ensure the JSON is properly formatted with no syntax errors. Escape any quotes inside strings properly."""
|
|
153
157
|
|
|
154
|
-
|
|
155
|
-
"postgres": "You specialize in PostgreSQL syntax and features. Use PostgreSQL-specific functions and syntax when appropriate.",
|
|
156
|
-
"mysql": "You specialize in MySQL syntax and features. Use MySQL-specific functions and syntax when appropriate.",
|
|
157
|
-
"sqlite": "You specialize in SQLite syntax and features. Use SQLite-specific functions and syntax when appropriate.",
|
|
158
|
-
"mssql": "You specialize in SQL Server syntax and features. Use T-SQL syntax when appropriate.",
|
|
159
|
-
"oracle": "You specialize in Oracle SQL syntax and features. Use Oracle-specific functions and syntax when appropriate.",
|
|
160
|
-
}
|
|
158
|
+
from ..core.database_prompts import get_database_hint
|
|
161
159
|
|
|
162
|
-
specific_prompt =
|
|
160
|
+
specific_prompt = get_database_hint(database_type)
|
|
163
161
|
|
|
164
162
|
return f"{base_prompt} {specific_prompt}"
|
|
165
163
|
|
|
@@ -142,7 +142,11 @@ class GeminiAdapter(AIProviderPort):
|
|
|
142
142
|
# Add instructions
|
|
143
143
|
prompt_parts.append("Instructions:")
|
|
144
144
|
prompt_parts.append(f"1. Generate a {context.database_type} SQL query that answers the question")
|
|
145
|
-
prompt_parts.append(
|
|
145
|
+
prompt_parts.append(
|
|
146
|
+
"2. Use EXACT column names as listed in the schema above. "
|
|
147
|
+
"NEVER abbreviate, shorten, or infer column names. "
|
|
148
|
+
"If a column is not explicitly listed in the schema, DO NOT use it."
|
|
149
|
+
)
|
|
146
150
|
prompt_parts.append("3. Follow SQL best practices and optimize for performance")
|
|
147
151
|
prompt_parts.append("4. Include appropriate JOINs, WHERE clauses, and ORDER BY if needed")
|
|
148
152
|
prompt_parts.append("5. Return the response in JSON format with 'sql', 'explanation', and 'confidence' fields")
|
|
@@ -164,15 +168,9 @@ Your response must be a valid JSON object with exactly these fields:
|
|
|
164
168
|
|
|
165
169
|
Ensure the JSON is properly formatted with no syntax errors. Escape any quotes inside strings properly."""
|
|
166
170
|
|
|
167
|
-
|
|
168
|
-
"postgres": "You specialize in PostgreSQL syntax and features. Use PostgreSQL-specific functions and syntax when appropriate.",
|
|
169
|
-
"mysql": "You specialize in MySQL syntax and features. Use MySQL-specific functions and syntax when appropriate.",
|
|
170
|
-
"sqlite": "You specialize in SQLite syntax and features. Use SQLite-specific functions and syntax when appropriate.",
|
|
171
|
-
"mssql": "You specialize in SQL Server syntax and features. Use T-SQL syntax when appropriate.",
|
|
172
|
-
"oracle": "You specialize in Oracle SQL syntax and features. Use Oracle-specific functions and syntax when appropriate.",
|
|
173
|
-
}
|
|
171
|
+
from ..core.database_prompts import get_database_hint
|
|
174
172
|
|
|
175
|
-
specific_prompt =
|
|
173
|
+
specific_prompt = get_database_hint(database_type)
|
|
176
174
|
|
|
177
175
|
return f"{base_prompt} {specific_prompt}"
|
|
178
176
|
|
|
@@ -163,7 +163,11 @@ class OpenAIAdapter(AIProviderPort):
|
|
|
163
163
|
# Add instructions
|
|
164
164
|
prompt_parts.append("Instructions:")
|
|
165
165
|
prompt_parts.append(f"1. Generate a {context.database_type} SQL query that answers the question")
|
|
166
|
-
prompt_parts.append(
|
|
166
|
+
prompt_parts.append(
|
|
167
|
+
"2. Use EXACT column names as listed in the schema above. "
|
|
168
|
+
"NEVER abbreviate, shorten, or infer column names. "
|
|
169
|
+
"If a column is not explicitly listed in the schema, DO NOT use it."
|
|
170
|
+
)
|
|
167
171
|
prompt_parts.append("3. Follow SQL best practices and optimize for performance")
|
|
168
172
|
prompt_parts.append("4. Include appropriate JOINs, WHERE clauses, and ORDER BY if needed")
|
|
169
173
|
prompt_parts.append("5. Return the response in JSON format with 'sql', 'explanation', and 'confidence' fields")
|
|
@@ -204,15 +208,9 @@ Your response must be a valid JSON object with exactly these fields:
|
|
|
204
208
|
|
|
205
209
|
Ensure the JSON is properly formatted with no syntax errors. Escape any quotes inside strings properly."""
|
|
206
210
|
|
|
207
|
-
|
|
208
|
-
"postgres": "You specialize in PostgreSQL syntax and features. Use PostgreSQL-specific functions and syntax when appropriate.",
|
|
209
|
-
"mysql": "You specialize in MySQL syntax and features. Use MySQL-specific functions and syntax when appropriate.",
|
|
210
|
-
"sqlite": "You specialize in SQLite syntax and features. Use SQLite-specific functions and syntax when appropriate.",
|
|
211
|
-
"mssql": "You specialize in SQL Server syntax and features. Use T-SQL syntax when appropriate.",
|
|
212
|
-
"oracle": "You specialize in Oracle SQL syntax and features. Use Oracle-specific functions and syntax when appropriate.",
|
|
213
|
-
}
|
|
211
|
+
from ..core.database_prompts import get_database_hint
|
|
214
212
|
|
|
215
|
-
specific_prompt =
|
|
213
|
+
specific_prompt = get_database_hint(database_type)
|
|
216
214
|
|
|
217
215
|
return f"{base_prompt} {specific_prompt}"
|
|
218
216
|
|
|
@@ -406,16 +406,18 @@ class PostgreSQLRepository(SchemaRepositoryPort):
|
|
|
406
406
|
col_name = row[5]
|
|
407
407
|
if col_name and col_name not in tables_dict[table_name]["_seen_columns"]:
|
|
408
408
|
tables_dict[table_name]["_seen_columns"].add(col_name)
|
|
409
|
-
tables_dict[table_name]["columns"].append(
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
409
|
+
tables_dict[table_name]["columns"].append(
|
|
410
|
+
{
|
|
411
|
+
"name": col_name,
|
|
412
|
+
"type": row[6],
|
|
413
|
+
"nullable": row[7] == "YES",
|
|
414
|
+
"default": row[8],
|
|
415
|
+
"max_length": row[9],
|
|
416
|
+
"precision": row[10],
|
|
417
|
+
"scale": row[11],
|
|
418
|
+
"description": row[12],
|
|
419
|
+
}
|
|
420
|
+
)
|
|
419
421
|
|
|
420
422
|
# Add foreign key (if present and not seen)
|
|
421
423
|
fk_column = row[15]
|
|
@@ -424,12 +426,14 @@ class PostgreSQLRepository(SchemaRepositoryPort):
|
|
|
424
426
|
fk_key = f"{fk_constraint}:{fk_column}"
|
|
425
427
|
if fk_key not in tables_dict[table_name]["_seen_fks"]:
|
|
426
428
|
tables_dict[table_name]["_seen_fks"].add(fk_key)
|
|
427
|
-
tables_dict[table_name]["foreign_keys"].append(
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
429
|
+
tables_dict[table_name]["foreign_keys"].append(
|
|
430
|
+
{
|
|
431
|
+
"column": fk_column,
|
|
432
|
+
"ref_table": row[16],
|
|
433
|
+
"ref_column": row[17],
|
|
434
|
+
"constraint_name": fk_constraint,
|
|
435
|
+
}
|
|
436
|
+
)
|
|
433
437
|
|
|
434
438
|
# Convert to TableInfo objects
|
|
435
439
|
tables = []
|
|
@@ -438,18 +442,20 @@ class PostgreSQLRepository(SchemaRepositoryPort):
|
|
|
438
442
|
del table_data["_seen_columns"]
|
|
439
443
|
del table_data["_seen_fks"]
|
|
440
444
|
|
|
441
|
-
tables.append(
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
445
|
+
tables.append(
|
|
446
|
+
TableInfo(
|
|
447
|
+
name=table_data["name"],
|
|
448
|
+
schema=table_data["schema"],
|
|
449
|
+
columns=table_data["columns"],
|
|
450
|
+
primary_keys=table_data["primary_keys"],
|
|
451
|
+
foreign_keys=table_data["foreign_keys"],
|
|
452
|
+
indexes=table_data["indexes"],
|
|
453
|
+
row_count=table_data["row_count"],
|
|
454
|
+
size_bytes=table_data["size_bytes"],
|
|
455
|
+
description=table_data["description"],
|
|
456
|
+
last_updated=datetime.now(),
|
|
457
|
+
)
|
|
458
|
+
)
|
|
453
459
|
|
|
454
460
|
logger.info("Tables processed from bulk query", count=len(tables))
|
|
455
461
|
return tables
|
|
@@ -12,6 +12,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
|
|
12
12
|
|
|
13
13
|
import psycopg2
|
|
14
14
|
import structlog
|
|
15
|
+
from psycopg2 import sql as psycopg2_sql
|
|
15
16
|
from psycopg2.extras import RealDictCursor
|
|
16
17
|
|
|
17
18
|
from ..exceptions import SchemaException, SecurityException
|
|
@@ -333,9 +334,7 @@ class RedshiftRepository(SchemaRepositoryPort):
|
|
|
333
334
|
|
|
334
335
|
try:
|
|
335
336
|
start_time = time.time()
|
|
336
|
-
rows = await asyncio.to_thread(
|
|
337
|
-
self._execute_query, bulk_query, (schema,)
|
|
338
|
-
)
|
|
337
|
+
rows = await asyncio.to_thread(self._execute_query, bulk_query, (schema,))
|
|
339
338
|
|
|
340
339
|
elapsed_ms = round((time.time() - start_time) * 1000, 2)
|
|
341
340
|
logger.info("Bulk query completed", rows=len(rows), elapsed_ms=elapsed_ms)
|
|
@@ -374,34 +373,38 @@ class RedshiftRepository(SchemaRepositoryPort):
|
|
|
374
373
|
col_name = row.get("column_name")
|
|
375
374
|
if col_name and col_name not in tables_dict[table_name]["_seen_columns"]:
|
|
376
375
|
tables_dict[table_name]["_seen_columns"].add(col_name)
|
|
377
|
-
tables_dict[table_name]["columns"].append(
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
376
|
+
tables_dict[table_name]["columns"].append(
|
|
377
|
+
{
|
|
378
|
+
"name": col_name,
|
|
379
|
+
"type": row.get("data_type", ""),
|
|
380
|
+
"nullable": row.get("is_nullable") == "YES",
|
|
381
|
+
"default": row.get("column_default"),
|
|
382
|
+
"max_length": row.get("character_maximum_length"),
|
|
383
|
+
"precision": row.get("numeric_precision"),
|
|
384
|
+
"scale": row.get("numeric_scale"),
|
|
385
|
+
"description": "",
|
|
386
|
+
}
|
|
387
|
+
)
|
|
387
388
|
|
|
388
389
|
# Convert to TableInfo objects
|
|
389
390
|
tables = []
|
|
390
391
|
for table_data in tables_dict.values():
|
|
391
392
|
del table_data["_seen_columns"]
|
|
392
393
|
|
|
393
|
-
tables.append(
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
394
|
+
tables.append(
|
|
395
|
+
TableInfo(
|
|
396
|
+
name=table_data["name"],
|
|
397
|
+
schema=table_data["schema"],
|
|
398
|
+
columns=table_data["columns"],
|
|
399
|
+
primary_keys=table_data["primary_keys"],
|
|
400
|
+
foreign_keys=table_data["foreign_keys"],
|
|
401
|
+
indexes=table_data["indexes"],
|
|
402
|
+
row_count=0,
|
|
403
|
+
size_bytes=0,
|
|
404
|
+
description=table_data["description"],
|
|
405
|
+
last_updated=datetime.now(),
|
|
406
|
+
)
|
|
407
|
+
)
|
|
405
408
|
|
|
406
409
|
logger.info("Tables processed from bulk query", count=len(tables))
|
|
407
410
|
return tables
|
|
@@ -646,8 +649,11 @@ class RedshiftRepository(SchemaRepositoryPort):
|
|
|
646
649
|
start_time = time.time()
|
|
647
650
|
conn = self._get_connection()
|
|
648
651
|
try:
|
|
649
|
-
# Set statement timeout (in milliseconds for Redshift)
|
|
650
652
|
cursor = conn.cursor()
|
|
653
|
+
# Set search_path so unqualified table names resolve to our schema
|
|
654
|
+
cursor.execute(
|
|
655
|
+
psycopg2_sql.SQL("SET search_path TO {}, public").format(psycopg2_sql.Identifier(self.schema_name))
|
|
656
|
+
)
|
|
651
657
|
cursor.execute(f"SET statement_timeout TO {timeout_seconds * 1000}")
|
|
652
658
|
cursor.close()
|
|
653
659
|
|
|
@@ -913,6 +913,7 @@ def cache_clear(clear_all: bool, embeddings: bool, queries: bool, tables: bool):
|
|
|
913
913
|
if Path(path).exists():
|
|
914
914
|
if Path(path).is_dir():
|
|
915
915
|
import shutil
|
|
916
|
+
|
|
916
917
|
shutil.rmtree(path)
|
|
917
918
|
else:
|
|
918
919
|
Path(path).unlink()
|
|
@@ -925,6 +926,7 @@ def cache_clear(clear_all: bool, embeddings: bool, queries: bool, tables: bool):
|
|
|
925
926
|
if Path(path).exists():
|
|
926
927
|
if Path(path).is_dir():
|
|
927
928
|
import shutil
|
|
929
|
+
|
|
928
930
|
shutil.rmtree(path)
|
|
929
931
|
else:
|
|
930
932
|
Path(path).unlink()
|
|
@@ -29,7 +29,7 @@ class Settings(BaseSettings):
|
|
|
29
29
|
|
|
30
30
|
# General settings
|
|
31
31
|
app_name: str = "nlp2sql"
|
|
32
|
-
version: str = "0.2.
|
|
32
|
+
version: str = "0.2.0rc7"
|
|
33
33
|
debug: bool = Field(default=False, validation_alias="NLP2SQL_DEBUG")
|
|
34
34
|
log_level: LogLevel = Field(default=LogLevel.INFO, validation_alias="NLP2SQL_LOG_LEVEL")
|
|
35
35
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""SQL dialect hints for AI providers.
|
|
2
|
+
|
|
3
|
+
Each entry provides database-specific guidance that helps AI providers
|
|
4
|
+
generate syntactically correct SQL for the target database. Centralized
|
|
5
|
+
here to avoid duplication across adapters.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
DATABASE_SQL_HINTS: dict[str, str] = {
|
|
9
|
+
"postgres": (
|
|
10
|
+
"You specialize in PostgreSQL syntax and features. "
|
|
11
|
+
"Use PostgreSQL-specific functions and syntax when appropriate."
|
|
12
|
+
),
|
|
13
|
+
"mysql": ("You specialize in MySQL syntax and features. Use MySQL-specific functions and syntax when appropriate."),
|
|
14
|
+
"sqlite": (
|
|
15
|
+
"You specialize in SQLite syntax and features. Use SQLite-specific functions and syntax when appropriate."
|
|
16
|
+
),
|
|
17
|
+
"mssql": ("You specialize in SQL Server syntax and features. Use T-SQL syntax when appropriate."),
|
|
18
|
+
"oracle": (
|
|
19
|
+
"You specialize in Oracle SQL syntax and features. Use Oracle-specific functions and syntax when appropriate."
|
|
20
|
+
),
|
|
21
|
+
"redshift": (
|
|
22
|
+
"You specialize in Amazon Redshift SQL syntax for data warehouse analytics. "
|
|
23
|
+
"CRITICAL RULES: "
|
|
24
|
+
"1) Use DATEADD(datepart, n, date) instead of INTERVAL arithmetic "
|
|
25
|
+
"(e.g., DATEADD(month, -1, CURRENT_DATE) not CURRENT_DATE - INTERVAL '1 month'). "
|
|
26
|
+
"2) Use DATE_TRUNC('unit', date) for truncation - NEVER use TRUNC(date, 'format'). "
|
|
27
|
+
"3) Use LISTAGG(col, delimiter) WITHIN GROUP (ORDER BY ...) instead of STRING_AGG. "
|
|
28
|
+
"4) CONCAT() only takes 2 arguments - use || operator to chain more. "
|
|
29
|
+
"5) Use GETDATE() or CURRENT_DATE instead of NOW(). "
|
|
30
|
+
"6) DISTINCT ON is not supported - use ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...) = 1 pattern. "
|
|
31
|
+
"7) generate_series() is not supported - use CTEs or date tables for sequences. "
|
|
32
|
+
"8) No ARRAY types or ARRAY_AGG - use SUPER type for semi-structured data. "
|
|
33
|
+
"9) Use APPROXIMATE COUNT(DISTINCT col) for large cardinality counts. "
|
|
34
|
+
"10) Avoid SELECT * - only select needed columns for columnar storage efficiency."
|
|
35
|
+
),
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def get_database_hint(database_type: str) -> str:
|
|
40
|
+
"""Get SQL dialect hint for a database type.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
database_type: Database type string (e.g., 'postgres', 'redshift').
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
Dialect-specific prompt hint, or empty string if unknown.
|
|
47
|
+
"""
|
|
48
|
+
return DATABASE_SQL_HINTS.get(database_type.lower(), "")
|