nlp2sql 0.2.0rc8__tar.gz → 0.2.0rc9__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.0rc8 → nlp2sql-0.2.0rc9}/PKG-INFO +1 -1
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/pyproject.toml +1 -1
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/__init__.py +1 -1
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/adapters/postgres_repository.py +192 -148
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/adapters/regex_query_validator.py +1 -3
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/client.py +1 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/core/entities.py +0 -2
- nlp2sql-0.2.0rc9/src/nlp2sql/core/sql_keywords.py +104 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/tests/conftest.py +1 -4
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/tests/test_postgres_integration.py +14 -11
- nlp2sql-0.2.0rc8/src/nlp2sql/core/sql_keywords.py +0 -19
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/.claude/commands/db.md +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/.claude/commands/quality.md +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/.claude/commands/test.md +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/.claude/rules/adapters.md +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/.claude/rules/architecture.md +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/.claude/rules/python-style.md +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/.claude/rules/testing.md +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/.claude/settings.json +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/.github/workflows/ci.yml +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/.github/workflows/publish-pypi.yml +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/.github/workflows/publish-testpypi.yml +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/.gitignore +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/.mcp.json.example +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/.python-version +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/CLAUDE.md +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/CONTRIBUTING.md +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/LICENSE +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/README.md +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/SECURITY.md +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/assets/nlp2sql-logo.png +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/benchmarks/enterprise_benchmark_results.json +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/docker/.env.example +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/docker/README.md +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/docker/docker-compose.yml +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/docker/get-db-urls.sh +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/docker/init-schema.sql +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/docker/large-schema.sql +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/docker/localstack-init.sh +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/docker/test-redshift.sh +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/docs/API.md +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/docs/ARCHITECTURE.md +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/docs/CONFIGURATION.md +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/docs/ENTERPRISE.md +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/docs/Redshift.md +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/README.md +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/advanced/test_multiple_providers.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/advanced/test_simple_api.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/database_specific/test_odoo_integration.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/default_examples.json +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/documentation/real_world_example.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/getting_started/basic_usage.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/getting_started/simple_demo.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/getting_started/test_api_setup.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/schema_management/test_auto_schema.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/schema_management/test_schema_filters_comprehensive.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/strands-fastapi-agent/.dockerignore +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/strands-fastapi-agent/.env.example +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/strands-fastapi-agent/DOCKER_README.md +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/strands-fastapi-agent/DOCKER_SETUP.md +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/strands-fastapi-agent/DOCKER_TESTING_SUMMARY.md +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/strands-fastapi-agent/Dockerfile +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/strands-fastapi-agent/INDEX.md +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/strands-fastapi-agent/README.md +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/strands-fastapi-agent/docker/init-ecommerce-schema.sql +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/strands-fastapi-agent/docker-compose.yml +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/strands-fastapi-agent/main.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/strands-fastapi-agent/pyproject.toml +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/strands-fastapi-agent/test-docker-endpoints.sh +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/install-dev.sh +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/mcp_server/README.md +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/mcp_server/__init__.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/mcp_server/example_config.json +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/mcp_server/pyproject.toml +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/mcp_server/server.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/adapters/__init__.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/adapters/anthropic_adapter.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/adapters/gemini_adapter.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/adapters/local_embedding_adapter.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/adapters/openai_adapter.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/adapters/openai_embedding_adapter.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/adapters/redshift_adapter.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/cli.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/config/__init__.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/config/settings.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/core/__init__.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/core/database_prompts.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/core/provider_config.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/core/result.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/core/sql_safety.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/exceptions/__init__.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/factories/__init__.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/factories/repository_factory.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/ports/__init__.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/ports/ai_provider.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/ports/cache.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/ports/embedding_provider.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/ports/example_repository.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/ports/query_optimizer.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/ports/query_safety.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/ports/query_validator.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/ports/schema_repository.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/ports/schema_strategy.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/schema/__init__.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/schema/analyzer.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/schema/embedding_manager.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/schema/example_loader.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/schema/example_store.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/schema/manager.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/services/__init__.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/services/query_service.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/utils/__init__.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/utils/helpers.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/utils/storage.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/test_tmp/.gitignore +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/tests/mcp/__init__.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/tests/mcp/test_sql_security.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/tests/test_basic.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/tests/test_batch_scoring.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/tests/test_column_validation.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/tests/test_database_prompts.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/tests/test_dsl_integration.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/tests/test_embedding_manager.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/tests/test_integration.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/tests/test_redshift_integration.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/tests/test_repository_cache.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/tests/test_repository_factory.py +0 -0
- {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nlp2sql
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.0rc9
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "nlp2sql"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.0rc9"
|
|
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"}
|
|
@@ -20,7 +20,7 @@ from .ports.schema_repository import SchemaRepositoryPort
|
|
|
20
20
|
from .schema.example_store import ExampleStore
|
|
21
21
|
from .services.query_service import QueryGenerationService
|
|
22
22
|
|
|
23
|
-
__version__ = "0.2.
|
|
23
|
+
__version__ = "0.2.0rc9"
|
|
24
24
|
__author__ = "Luis Carbonel"
|
|
25
25
|
__email__ = "devhighlevel@gmail.com"
|
|
26
26
|
|
|
@@ -20,7 +20,7 @@ logger = structlog.get_logger()
|
|
|
20
20
|
|
|
21
21
|
# Cache configuration
|
|
22
22
|
SCHEMA_CACHE_TTL_HOURS = int(os.getenv("NLP2SQL_SCHEMA_CACHE_TTL_HOURS", "24"))
|
|
23
|
-
SCHEMA_CACHE_VERSION = "
|
|
23
|
+
SCHEMA_CACHE_VERSION = "2.0" # v2: pg_catalog queries replace information_schema (Issue #32)
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class PostgreSQLRepository(SchemaRepositoryPort):
|
|
@@ -204,68 +204,85 @@ class PostgreSQLRepository(SchemaRepositoryPort):
|
|
|
204
204
|
This replaces the N+1 query pattern (1 + N*2 queries) with a single
|
|
205
205
|
JOIN query, significantly improving performance for large schemas.
|
|
206
206
|
"""
|
|
207
|
+
# Uses pg_catalog instead of information_schema to avoid ownership-based
|
|
208
|
+
# filtering that hides tables from non-owner users (Issue #32).
|
|
207
209
|
bulk_query = """
|
|
208
210
|
WITH table_info AS (
|
|
209
211
|
SELECT
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
obj_description(c.oid)
|
|
213
|
-
pg_total_relation_size(c.oid)
|
|
214
|
-
COALESCE(s.n_tup_ins + s.n_tup_upd + s.n_tup_del, 0)
|
|
215
|
-
FROM
|
|
216
|
-
|
|
217
|
-
LEFT JOIN
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
AND s.schemaname = t.table_schema
|
|
221
|
-
WHERE t.table_schema = :schema
|
|
222
|
-
AND t.table_type = 'BASE TABLE'
|
|
212
|
+
c.relname AS table_name,
|
|
213
|
+
n.nspname AS table_schema,
|
|
214
|
+
obj_description(c.oid) AS table_comment,
|
|
215
|
+
pg_total_relation_size(c.oid) AS size_bytes,
|
|
216
|
+
COALESCE(s.n_tup_ins + s.n_tup_upd + s.n_tup_del, 0) AS row_count
|
|
217
|
+
FROM pg_class c
|
|
218
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
219
|
+
LEFT JOIN pg_stat_user_tables s ON s.relid = c.oid
|
|
220
|
+
WHERE n.nspname = :schema
|
|
221
|
+
AND c.relkind = 'r'
|
|
223
222
|
),
|
|
224
223
|
columns_info AS (
|
|
225
224
|
SELECT
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
225
|
+
cls.relname AS table_name,
|
|
226
|
+
a.attname AS column_name,
|
|
227
|
+
pg_catalog.format_type(a.atttypid, a.atttypmod) AS data_type,
|
|
228
|
+
CASE WHEN a.attnotnull THEN 'NO' ELSE 'YES' END AS is_nullable,
|
|
229
|
+
pg_get_expr(d.adbin, d.adrelid) AS column_default,
|
|
230
|
+
CASE
|
|
231
|
+
WHEN a.atttypid IN (1043, 1042) THEN a.atttypmod - 4
|
|
232
|
+
ELSE NULL
|
|
233
|
+
END AS character_maximum_length,
|
|
234
|
+
CASE
|
|
235
|
+
WHEN a.atttypid = 21 THEN 16
|
|
236
|
+
WHEN a.atttypid = 23 THEN 32
|
|
237
|
+
WHEN a.atttypid = 20 THEN 64
|
|
238
|
+
WHEN a.atttypid = 1700 AND a.atttypmod >= 0 THEN ((a.atttypmod - 4) >> 16) & 65535
|
|
239
|
+
ELSE NULL
|
|
240
|
+
END AS numeric_precision,
|
|
241
|
+
CASE
|
|
242
|
+
WHEN a.atttypid = 1700 AND a.atttypmod >= 0 THEN (a.atttypmod - 4) & 65535
|
|
243
|
+
ELSE NULL
|
|
244
|
+
END AS numeric_scale,
|
|
245
|
+
a.attnum AS ordinal_position,
|
|
246
|
+
col_description(cls.oid, a.attnum) AS column_comment
|
|
247
|
+
FROM pg_class cls
|
|
248
|
+
JOIN pg_namespace ns ON ns.oid = cls.relnamespace
|
|
249
|
+
JOIN pg_attribute a ON a.attrelid = cls.oid
|
|
250
|
+
LEFT JOIN pg_attrdef d ON d.adrelid = cls.oid AND d.adnum = a.attnum
|
|
251
|
+
WHERE ns.nspname = :schema
|
|
252
|
+
AND cls.relkind = 'r'
|
|
253
|
+
AND a.attnum > 0
|
|
254
|
+
AND NOT a.attisdropped
|
|
241
255
|
),
|
|
242
256
|
primary_keys AS (
|
|
243
257
|
SELECT
|
|
244
|
-
|
|
245
|
-
array_agg(
|
|
246
|
-
FROM
|
|
247
|
-
JOIN
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
258
|
+
c.relname AS table_name,
|
|
259
|
+
array_agg(a.attname ORDER BY k.ordinality) AS pk_columns
|
|
260
|
+
FROM pg_constraint con
|
|
261
|
+
JOIN pg_class c ON c.oid = con.conrelid
|
|
262
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
263
|
+
CROSS JOIN LATERAL unnest(con.conkey) WITH ORDINALITY AS k(attnum, ordinality)
|
|
264
|
+
JOIN pg_attribute a ON a.attrelid = con.conrelid AND a.attnum = k.attnum
|
|
265
|
+
WHERE n.nspname = :schema
|
|
266
|
+
AND con.contype = 'p'
|
|
267
|
+
GROUP BY c.relname
|
|
253
268
|
),
|
|
254
269
|
foreign_keys AS (
|
|
255
270
|
SELECT
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
FROM
|
|
262
|
-
JOIN
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
JOIN
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
271
|
+
c.relname AS table_name,
|
|
272
|
+
a.attname AS column_name,
|
|
273
|
+
refc.relname AS ref_table,
|
|
274
|
+
refa.attname AS ref_column,
|
|
275
|
+
con.conname AS constraint_name
|
|
276
|
+
FROM pg_constraint con
|
|
277
|
+
JOIN pg_class c ON c.oid = con.conrelid
|
|
278
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
279
|
+
JOIN pg_class refc ON refc.oid = con.confrelid
|
|
280
|
+
CROSS JOIN LATERAL unnest(con.conkey, con.confkey)
|
|
281
|
+
WITH ORDINALITY AS k(attnum, refattnum, ordinality)
|
|
282
|
+
JOIN pg_attribute a ON a.attrelid = con.conrelid AND a.attnum = k.attnum
|
|
283
|
+
JOIN pg_attribute refa ON refa.attrelid = con.confrelid AND refa.attnum = k.refattnum
|
|
284
|
+
WHERE n.nspname = :schema
|
|
285
|
+
AND con.contype = 'f'
|
|
269
286
|
)
|
|
270
287
|
SELECT
|
|
271
288
|
t.table_name,
|
|
@@ -392,18 +409,18 @@ class PostgreSQLRepository(SchemaRepositoryPort):
|
|
|
392
409
|
try:
|
|
393
410
|
# Get basic table info
|
|
394
411
|
table_query = """
|
|
395
|
-
SELECT
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
obj_description(c.oid)
|
|
399
|
-
pg_size_pretty(pg_total_relation_size(c.oid))
|
|
400
|
-
pg_total_relation_size(c.oid)
|
|
401
|
-
COALESCE(s.n_tup_ins + s.n_tup_upd + s.n_tup_del, 0)
|
|
402
|
-
FROM
|
|
403
|
-
|
|
404
|
-
LEFT JOIN
|
|
405
|
-
|
|
406
|
-
|
|
412
|
+
SELECT
|
|
413
|
+
c.relname AS table_name,
|
|
414
|
+
n.nspname AS table_schema,
|
|
415
|
+
obj_description(c.oid) AS table_comment,
|
|
416
|
+
pg_size_pretty(pg_total_relation_size(c.oid)) AS table_size,
|
|
417
|
+
pg_total_relation_size(c.oid) AS size_bytes,
|
|
418
|
+
COALESCE(s.n_tup_ins + s.n_tup_upd + s.n_tup_del, 0) AS row_count
|
|
419
|
+
FROM pg_class c
|
|
420
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
421
|
+
LEFT JOIN pg_stat_user_tables s ON s.relid = c.oid
|
|
422
|
+
WHERE c.relname = :table_name AND n.nspname = :schema
|
|
423
|
+
AND c.relkind = 'r'
|
|
407
424
|
"""
|
|
408
425
|
|
|
409
426
|
async with self.async_engine.begin() as conn:
|
|
@@ -422,21 +439,20 @@ class PostgreSQLRepository(SchemaRepositoryPort):
|
|
|
422
439
|
async def search_tables(self, pattern: str) -> List[TableInfo]:
|
|
423
440
|
"""Search tables by name pattern."""
|
|
424
441
|
query = """
|
|
425
|
-
SELECT
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
obj_description(c.oid)
|
|
429
|
-
pg_size_pretty(pg_total_relation_size(c.oid))
|
|
430
|
-
pg_total_relation_size(c.oid)
|
|
431
|
-
COALESCE(s.n_tup_ins + s.n_tup_upd + s.n_tup_del, 0)
|
|
432
|
-
FROM
|
|
433
|
-
|
|
434
|
-
LEFT JOIN
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
AND
|
|
438
|
-
|
|
439
|
-
ORDER BY t.table_name
|
|
442
|
+
SELECT
|
|
443
|
+
c.relname AS table_name,
|
|
444
|
+
n.nspname AS table_schema,
|
|
445
|
+
obj_description(c.oid) AS table_comment,
|
|
446
|
+
pg_size_pretty(pg_total_relation_size(c.oid)) AS table_size,
|
|
447
|
+
pg_total_relation_size(c.oid) AS size_bytes,
|
|
448
|
+
COALESCE(s.n_tup_ins + s.n_tup_upd + s.n_tup_del, 0) AS row_count
|
|
449
|
+
FROM pg_class c
|
|
450
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
451
|
+
LEFT JOIN pg_stat_user_tables s ON s.relid = c.oid
|
|
452
|
+
WHERE n.nspname = :schema
|
|
453
|
+
AND c.relkind = 'r'
|
|
454
|
+
AND (c.relname ILIKE :pattern OR obj_description(c.oid) ILIKE :pattern)
|
|
455
|
+
ORDER BY c.relname
|
|
440
456
|
"""
|
|
441
457
|
|
|
442
458
|
try:
|
|
@@ -459,40 +475,45 @@ class PostgreSQLRepository(SchemaRepositoryPort):
|
|
|
459
475
|
"""Get tables related through foreign keys."""
|
|
460
476
|
query = """
|
|
461
477
|
WITH related_tables AS (
|
|
462
|
-
-- Tables that reference this table
|
|
478
|
+
-- Tables that reference this table (incoming FKs)
|
|
463
479
|
SELECT DISTINCT
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
FROM
|
|
467
|
-
JOIN
|
|
468
|
-
JOIN
|
|
469
|
-
|
|
470
|
-
|
|
480
|
+
src.relname AS related_table,
|
|
481
|
+
srcn.nspname AS related_schema
|
|
482
|
+
FROM pg_constraint con
|
|
483
|
+
JOIN pg_class src ON src.oid = con.conrelid
|
|
484
|
+
JOIN pg_namespace srcn ON srcn.oid = src.relnamespace
|
|
485
|
+
JOIN pg_class tgt ON tgt.oid = con.confrelid
|
|
486
|
+
JOIN pg_namespace tgtn ON tgtn.oid = tgt.relnamespace
|
|
487
|
+
WHERE con.contype = 'f'
|
|
488
|
+
AND tgt.relname = :table_name AND tgtn.nspname = :schema
|
|
489
|
+
|
|
471
490
|
UNION
|
|
472
|
-
|
|
473
|
-
-- Tables that this table references
|
|
491
|
+
|
|
492
|
+
-- Tables that this table references (outgoing FKs)
|
|
474
493
|
SELECT DISTINCT
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
FROM
|
|
478
|
-
JOIN
|
|
479
|
-
JOIN
|
|
480
|
-
|
|
494
|
+
tgt.relname AS related_table,
|
|
495
|
+
tgtn.nspname AS related_schema
|
|
496
|
+
FROM pg_constraint con
|
|
497
|
+
JOIN pg_class src ON src.oid = con.conrelid
|
|
498
|
+
JOIN pg_namespace srcn ON srcn.oid = src.relnamespace
|
|
499
|
+
JOIN pg_class tgt ON tgt.oid = con.confrelid
|
|
500
|
+
JOIN pg_namespace tgtn ON tgtn.oid = tgt.relnamespace
|
|
501
|
+
WHERE con.contype = 'f'
|
|
502
|
+
AND src.relname = :table_name AND srcn.nspname = :schema
|
|
481
503
|
)
|
|
482
|
-
SELECT
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
obj_description(
|
|
486
|
-
pg_size_pretty(pg_total_relation_size(
|
|
487
|
-
pg_total_relation_size(
|
|
488
|
-
COALESCE(s.n_tup_ins + s.n_tup_upd + s.n_tup_del, 0)
|
|
504
|
+
SELECT
|
|
505
|
+
tc.relname AS table_name,
|
|
506
|
+
tn.nspname AS table_schema,
|
|
507
|
+
obj_description(tc.oid) AS table_comment,
|
|
508
|
+
pg_size_pretty(pg_total_relation_size(tc.oid)) AS table_size,
|
|
509
|
+
pg_total_relation_size(tc.oid) AS size_bytes,
|
|
510
|
+
COALESCE(s.n_tup_ins + s.n_tup_upd + s.n_tup_del, 0) AS row_count
|
|
489
511
|
FROM related_tables rt
|
|
490
|
-
JOIN
|
|
491
|
-
|
|
492
|
-
LEFT JOIN
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
ORDER BY t.table_name
|
|
512
|
+
JOIN pg_class tc ON tc.relname = rt.related_table
|
|
513
|
+
JOIN pg_namespace tn ON tn.oid = tc.relnamespace AND tn.nspname = rt.related_schema
|
|
514
|
+
LEFT JOIN pg_stat_user_tables s ON s.relid = tc.oid
|
|
515
|
+
WHERE tc.relkind = 'r'
|
|
516
|
+
ORDER BY tc.relname
|
|
496
517
|
"""
|
|
497
518
|
|
|
498
519
|
try:
|
|
@@ -514,21 +535,20 @@ class PostgreSQLRepository(SchemaRepositoryPort):
|
|
|
514
535
|
async def get_schema_metadata(self) -> SchemaMetadata:
|
|
515
536
|
"""Get metadata about the entire schema."""
|
|
516
537
|
query = """
|
|
517
|
-
SELECT
|
|
518
|
-
current_database()
|
|
519
|
-
version()
|
|
520
|
-
COUNT(*)
|
|
521
|
-
SUM(pg_total_relation_size(c.oid))
|
|
522
|
-
FROM
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
AND t.table_type = 'BASE TABLE'
|
|
538
|
+
SELECT
|
|
539
|
+
current_database() AS database_name,
|
|
540
|
+
version() AS database_version,
|
|
541
|
+
COUNT(*) AS total_tables,
|
|
542
|
+
SUM(pg_total_relation_size(c.oid)) AS total_size
|
|
543
|
+
FROM pg_class c
|
|
544
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
545
|
+
WHERE n.nspname = :schema
|
|
546
|
+
AND c.relkind = 'r'
|
|
527
547
|
"""
|
|
528
548
|
|
|
529
549
|
try:
|
|
530
550
|
async with self.async_engine.begin() as conn:
|
|
531
|
-
result = await conn.execute(text(query),
|
|
551
|
+
result = await conn.execute(text(query), {"schema": self.schema_name})
|
|
532
552
|
row = result.fetchone()
|
|
533
553
|
|
|
534
554
|
return SchemaMetadata(
|
|
@@ -608,20 +628,35 @@ class PostgreSQLRepository(SchemaRepositoryPort):
|
|
|
608
628
|
async def _get_table_columns(self, table_name: str, schema: str) -> List[Dict[str, Any]]:
|
|
609
629
|
"""Get column information for a table."""
|
|
610
630
|
query = """
|
|
611
|
-
SELECT
|
|
612
|
-
column_name,
|
|
613
|
-
data_type,
|
|
614
|
-
is_nullable,
|
|
615
|
-
column_default,
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
631
|
+
SELECT
|
|
632
|
+
a.attname AS column_name,
|
|
633
|
+
pg_catalog.format_type(a.atttypid, a.atttypmod) AS data_type,
|
|
634
|
+
CASE WHEN a.attnotnull THEN 'NO' ELSE 'YES' END AS is_nullable,
|
|
635
|
+
pg_get_expr(d.adbin, d.adrelid) AS column_default,
|
|
636
|
+
CASE
|
|
637
|
+
WHEN a.atttypid IN (1043, 1042) THEN a.atttypmod - 4
|
|
638
|
+
ELSE NULL
|
|
639
|
+
END AS character_maximum_length,
|
|
640
|
+
CASE
|
|
641
|
+
WHEN a.atttypid = 21 THEN 16
|
|
642
|
+
WHEN a.atttypid = 23 THEN 32
|
|
643
|
+
WHEN a.atttypid = 20 THEN 64
|
|
644
|
+
WHEN a.atttypid = 1700 AND a.atttypmod >= 0 THEN ((a.atttypmod - 4) >> 16) & 65535
|
|
645
|
+
ELSE NULL
|
|
646
|
+
END AS numeric_precision,
|
|
647
|
+
CASE
|
|
648
|
+
WHEN a.atttypid = 1700 AND a.atttypmod >= 0 THEN (a.atttypmod - 4) & 65535
|
|
649
|
+
ELSE NULL
|
|
650
|
+
END AS numeric_scale,
|
|
651
|
+
col_description(c.oid, a.attnum) AS column_comment
|
|
652
|
+
FROM pg_class c
|
|
653
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
654
|
+
JOIN pg_attribute a ON a.attrelid = c.oid
|
|
655
|
+
LEFT JOIN pg_attrdef d ON d.adrelid = c.oid AND d.adnum = a.attnum
|
|
656
|
+
WHERE c.relname = :table_name AND n.nspname = :schema
|
|
657
|
+
AND a.attnum > 0
|
|
658
|
+
AND NOT a.attisdropped
|
|
659
|
+
ORDER BY a.attnum
|
|
625
660
|
"""
|
|
626
661
|
|
|
627
662
|
async with self.async_engine.begin() as conn:
|
|
@@ -648,12 +683,15 @@ class PostgreSQLRepository(SchemaRepositoryPort):
|
|
|
648
683
|
async def _get_primary_keys(self, table_name: str, schema: str) -> List[str]:
|
|
649
684
|
"""Get primary key columns for a table."""
|
|
650
685
|
query = """
|
|
651
|
-
SELECT
|
|
652
|
-
FROM
|
|
653
|
-
JOIN
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
686
|
+
SELECT a.attname AS column_name
|
|
687
|
+
FROM pg_constraint con
|
|
688
|
+
JOIN pg_class c ON c.oid = con.conrelid
|
|
689
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
690
|
+
CROSS JOIN LATERAL unnest(con.conkey) WITH ORDINALITY AS k(attnum, ordinality)
|
|
691
|
+
JOIN pg_attribute a ON a.attrelid = con.conrelid AND a.attnum = k.attnum
|
|
692
|
+
WHERE c.relname = :table_name AND n.nspname = :schema
|
|
693
|
+
AND con.contype = 'p'
|
|
694
|
+
ORDER BY k.ordinality
|
|
657
695
|
"""
|
|
658
696
|
|
|
659
697
|
async with self.async_engine.begin() as conn:
|
|
@@ -665,16 +703,22 @@ class PostgreSQLRepository(SchemaRepositoryPort):
|
|
|
665
703
|
async def _get_foreign_keys(self, table_name: str, schema: str) -> List[Dict[str, Any]]:
|
|
666
704
|
"""Get foreign key constraints for a table."""
|
|
667
705
|
query = """
|
|
668
|
-
SELECT
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
FROM
|
|
674
|
-
JOIN
|
|
675
|
-
JOIN
|
|
676
|
-
|
|
677
|
-
|
|
706
|
+
SELECT
|
|
707
|
+
a.attname AS column_name,
|
|
708
|
+
refc.relname AS referenced_table,
|
|
709
|
+
refa.attname AS referenced_column,
|
|
710
|
+
con.conname AS constraint_name
|
|
711
|
+
FROM pg_constraint con
|
|
712
|
+
JOIN pg_class c ON c.oid = con.conrelid
|
|
713
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
714
|
+
JOIN pg_class refc ON refc.oid = con.confrelid
|
|
715
|
+
CROSS JOIN LATERAL unnest(con.conkey, con.confkey)
|
|
716
|
+
WITH ORDINALITY AS k(attnum, refattnum, ordinality)
|
|
717
|
+
JOIN pg_attribute a ON a.attrelid = con.conrelid AND a.attnum = k.attnum
|
|
718
|
+
JOIN pg_attribute refa ON refa.attrelid = con.confrelid AND refa.attnum = k.refattnum
|
|
719
|
+
WHERE c.relname = :table_name AND n.nspname = :schema
|
|
720
|
+
AND con.contype = 'f'
|
|
721
|
+
ORDER BY k.ordinality
|
|
678
722
|
"""
|
|
679
723
|
|
|
680
724
|
async with self.async_engine.begin() as conn:
|
|
@@ -48,9 +48,7 @@ class RegexQueryValidator(QueryValidatorPort):
|
|
|
48
48
|
|
|
49
49
|
# Extract table aliases (FROM/JOIN table alias, table AS alias)
|
|
50
50
|
table_aliases: set[str] = set()
|
|
51
|
-
for match in re.finditer(
|
|
52
|
-
r"\b(?:from|join)\s+(?:\w+\.)?(\w+)\s+(?:AS\s+)?([a-z_]\w*)\b", sql, re.IGNORECASE
|
|
53
|
-
):
|
|
51
|
+
for match in re.finditer(r"\b(?:from|join)\s+(?:\w+\.)?(\w+)\s+(?:AS\s+)?([a-z_]\w*)\b", sql, re.IGNORECASE):
|
|
54
52
|
alias = match.group(2).lower()
|
|
55
53
|
if alias not in SQL_KEYWORDS:
|
|
56
54
|
table_aliases.add(alias)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""SQL keywords — shared set for validation and tokenization."""
|
|
2
|
+
|
|
3
|
+
SQL_KEYWORDS: frozenset[str] = frozenset(
|
|
4
|
+
{
|
|
5
|
+
"select",
|
|
6
|
+
"from",
|
|
7
|
+
"where",
|
|
8
|
+
"and",
|
|
9
|
+
"or",
|
|
10
|
+
"not",
|
|
11
|
+
"in",
|
|
12
|
+
"between",
|
|
13
|
+
"group",
|
|
14
|
+
"by",
|
|
15
|
+
"order",
|
|
16
|
+
"having",
|
|
17
|
+
"limit",
|
|
18
|
+
"offset",
|
|
19
|
+
"as",
|
|
20
|
+
"on",
|
|
21
|
+
"join",
|
|
22
|
+
"left",
|
|
23
|
+
"right",
|
|
24
|
+
"inner",
|
|
25
|
+
"outer",
|
|
26
|
+
"cross",
|
|
27
|
+
"full",
|
|
28
|
+
"case",
|
|
29
|
+
"when",
|
|
30
|
+
"then",
|
|
31
|
+
"else",
|
|
32
|
+
"end",
|
|
33
|
+
"null",
|
|
34
|
+
"true",
|
|
35
|
+
"false",
|
|
36
|
+
"asc",
|
|
37
|
+
"desc",
|
|
38
|
+
"distinct",
|
|
39
|
+
"union",
|
|
40
|
+
"all",
|
|
41
|
+
"exists",
|
|
42
|
+
"any",
|
|
43
|
+
"count",
|
|
44
|
+
"sum",
|
|
45
|
+
"avg",
|
|
46
|
+
"min",
|
|
47
|
+
"max",
|
|
48
|
+
"abs",
|
|
49
|
+
"round",
|
|
50
|
+
"floor",
|
|
51
|
+
"ceil",
|
|
52
|
+
"date_trunc",
|
|
53
|
+
"dateadd",
|
|
54
|
+
"datediff",
|
|
55
|
+
"current_date",
|
|
56
|
+
"getdate",
|
|
57
|
+
"extract",
|
|
58
|
+
"epoch",
|
|
59
|
+
"convert_timezone",
|
|
60
|
+
"to_date",
|
|
61
|
+
"to_char",
|
|
62
|
+
"coalesce",
|
|
63
|
+
"nullif",
|
|
64
|
+
"cast",
|
|
65
|
+
"like",
|
|
66
|
+
"ilike",
|
|
67
|
+
"is",
|
|
68
|
+
"with",
|
|
69
|
+
"month",
|
|
70
|
+
"year",
|
|
71
|
+
"day",
|
|
72
|
+
"quarter",
|
|
73
|
+
"week",
|
|
74
|
+
"hour",
|
|
75
|
+
"minute",
|
|
76
|
+
"second",
|
|
77
|
+
"varchar",
|
|
78
|
+
"int",
|
|
79
|
+
"integer",
|
|
80
|
+
"bigint",
|
|
81
|
+
"numeric",
|
|
82
|
+
"decimal",
|
|
83
|
+
"date",
|
|
84
|
+
"timestamp",
|
|
85
|
+
"boolean",
|
|
86
|
+
"float",
|
|
87
|
+
"double",
|
|
88
|
+
"precision",
|
|
89
|
+
"over",
|
|
90
|
+
"partition",
|
|
91
|
+
"row_number",
|
|
92
|
+
"rank",
|
|
93
|
+
"dense_rank",
|
|
94
|
+
"lag",
|
|
95
|
+
"lead",
|
|
96
|
+
"first_value",
|
|
97
|
+
"last_value",
|
|
98
|
+
"listagg",
|
|
99
|
+
"approximate",
|
|
100
|
+
"interval",
|
|
101
|
+
"explain",
|
|
102
|
+
"analyze",
|
|
103
|
+
}
|
|
104
|
+
)
|
|
@@ -21,10 +21,7 @@ POSTGRES_USER = os.getenv("NLP2SQL_TEST_POSTGRES_USER", "testuser")
|
|
|
21
21
|
POSTGRES_PASS = os.getenv("NLP2SQL_TEST_POSTGRES_PASS", "testpass")
|
|
22
22
|
POSTGRES_DB = os.getenv("NLP2SQL_TEST_POSTGRES_DB", "testdb")
|
|
23
23
|
|
|
24
|
-
POSTGRES_URL =
|
|
25
|
-
f"postgresql://{POSTGRES_USER}:{POSTGRES_PASS}"
|
|
26
|
-
f"@{POSTGRES_HOST}:{POSTGRES_PORT}/{POSTGRES_DB}"
|
|
27
|
-
)
|
|
24
|
+
POSTGRES_URL = f"postgresql://{POSTGRES_USER}:{POSTGRES_PASS}@{POSTGRES_HOST}:{POSTGRES_PORT}/{POSTGRES_DB}"
|
|
28
25
|
|
|
29
26
|
# Expected tables from docker/init-schema.sql
|
|
30
27
|
EXPECTED_TABLES = {"users", "categories", "products", "orders", "order_items", "reviews"}
|
|
@@ -23,9 +23,7 @@ class TestPostgresRepository:
|
|
|
23
23
|
tables = await repo.get_tables()
|
|
24
24
|
table_names = {t.name.lower() for t in tables}
|
|
25
25
|
|
|
26
|
-
assert EXPECTED_TABLES.issubset(table_names),
|
|
27
|
-
f"Missing tables: {EXPECTED_TABLES - table_names}"
|
|
28
|
-
)
|
|
26
|
+
assert EXPECTED_TABLES.issubset(table_names), f"Missing tables: {EXPECTED_TABLES - table_names}"
|
|
29
27
|
|
|
30
28
|
async def test_column_discovery(self, postgres_available):
|
|
31
29
|
repo = PostgreSQLRepository(postgres_available)
|
|
@@ -77,9 +75,7 @@ class TestSchemaManagerWithPostgres:
|
|
|
77
75
|
await repo.initialize()
|
|
78
76
|
await manager.initialize(DatabaseType.POSTGRES)
|
|
79
77
|
|
|
80
|
-
context = await manager.get_optimal_schema_context(
|
|
81
|
-
"How many users?", DatabaseType.POSTGRES, max_tokens=4000
|
|
82
|
-
)
|
|
78
|
+
context = await manager.get_optimal_schema_context("How many users?", DatabaseType.POSTGRES, max_tokens=4000)
|
|
83
79
|
assert "reviews" not in context.lower()
|
|
84
80
|
|
|
85
81
|
async def test_schema_filters_include_tables(self, postgres_available, mock_embedding_provider):
|
|
@@ -92,9 +88,7 @@ class TestSchemaManagerWithPostgres:
|
|
|
92
88
|
await repo.initialize()
|
|
93
89
|
await manager.initialize(DatabaseType.POSTGRES)
|
|
94
90
|
|
|
95
|
-
context = await manager.get_optimal_schema_context(
|
|
96
|
-
"How many users?", DatabaseType.POSTGRES, max_tokens=4000
|
|
97
|
-
)
|
|
91
|
+
context = await manager.get_optimal_schema_context("How many users?", DatabaseType.POSTGRES, max_tokens=4000)
|
|
98
92
|
assert "users" in context.lower()
|
|
99
93
|
|
|
100
94
|
async def test_embedding_search_against_real_schema(self, postgres_available, mock_embedding_provider, tmp_path):
|
|
@@ -110,9 +104,18 @@ class TestSchemaManagerWithPostgres:
|
|
|
110
104
|
|
|
111
105
|
elements = []
|
|
112
106
|
for table in tables:
|
|
113
|
-
elements.append(
|
|
107
|
+
elements.append(
|
|
108
|
+
{"type": "table", "name": table.name, "columns": [{"name": c["name"]} for c in table.columns]}
|
|
109
|
+
)
|
|
114
110
|
for col in table.columns:
|
|
115
|
-
elements.append(
|
|
111
|
+
elements.append(
|
|
112
|
+
{
|
|
113
|
+
"type": "column",
|
|
114
|
+
"name": col["name"],
|
|
115
|
+
"table_name": table.name,
|
|
116
|
+
"data_type": col.get("data_type", "unknown"),
|
|
117
|
+
}
|
|
118
|
+
)
|
|
116
119
|
|
|
117
120
|
await emb_manager.add_schema_elements(elements, DatabaseType.POSTGRES)
|
|
118
121
|
results = await emb_manager.search_similar("customer email", top_k=5, min_score=0.0)
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
"""SQL keywords — shared set for validation and tokenization."""
|
|
2
|
-
|
|
3
|
-
SQL_KEYWORDS: frozenset[str] = frozenset({
|
|
4
|
-
"select", "from", "where", "and", "or", "not", "in", "between",
|
|
5
|
-
"group", "by", "order", "having", "limit", "offset", "as", "on",
|
|
6
|
-
"join", "left", "right", "inner", "outer", "cross", "full",
|
|
7
|
-
"case", "when", "then", "else", "end", "null", "true", "false",
|
|
8
|
-
"asc", "desc", "distinct", "union", "all", "exists", "any",
|
|
9
|
-
"count", "sum", "avg", "min", "max", "abs", "round", "floor", "ceil",
|
|
10
|
-
"date_trunc", "dateadd", "datediff", "current_date", "getdate",
|
|
11
|
-
"extract", "epoch", "convert_timezone", "to_date", "to_char",
|
|
12
|
-
"coalesce", "nullif", "cast", "like", "ilike", "is", "with",
|
|
13
|
-
"month", "year", "day", "quarter", "week", "hour", "minute", "second",
|
|
14
|
-
"varchar", "int", "integer", "bigint", "numeric", "decimal",
|
|
15
|
-
"date", "timestamp", "boolean", "float", "double", "precision",
|
|
16
|
-
"over", "partition", "row_number", "rank", "dense_rank",
|
|
17
|
-
"lag", "lead", "first_value", "last_value", "listagg",
|
|
18
|
-
"approximate", "interval", "explain", "analyze",
|
|
19
|
-
})
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/strands-fastapi-agent/DOCKER_TESTING_SUMMARY.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/strands-fastapi-agent/test-docker-endpoints.sh
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|