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.
Files changed (130) hide show
  1. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/PKG-INFO +1 -1
  2. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/pyproject.toml +1 -1
  3. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/__init__.py +1 -1
  4. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/adapters/postgres_repository.py +192 -148
  5. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/adapters/regex_query_validator.py +1 -3
  6. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/client.py +1 -0
  7. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/core/entities.py +0 -2
  8. nlp2sql-0.2.0rc9/src/nlp2sql/core/sql_keywords.py +104 -0
  9. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/tests/conftest.py +1 -4
  10. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/tests/test_postgres_integration.py +14 -11
  11. nlp2sql-0.2.0rc8/src/nlp2sql/core/sql_keywords.py +0 -19
  12. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/.claude/commands/db.md +0 -0
  13. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/.claude/commands/quality.md +0 -0
  14. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/.claude/commands/test.md +0 -0
  15. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/.claude/rules/adapters.md +0 -0
  16. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/.claude/rules/architecture.md +0 -0
  17. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/.claude/rules/python-style.md +0 -0
  18. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/.claude/rules/testing.md +0 -0
  19. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/.claude/settings.json +0 -0
  20. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  21. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  22. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/.github/workflows/ci.yml +0 -0
  23. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/.github/workflows/publish-pypi.yml +0 -0
  24. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/.github/workflows/publish-testpypi.yml +0 -0
  25. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/.gitignore +0 -0
  26. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/.mcp.json.example +0 -0
  27. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/.python-version +0 -0
  28. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/CLAUDE.md +0 -0
  29. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/CONTRIBUTING.md +0 -0
  30. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/LICENSE +0 -0
  31. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/README.md +0 -0
  32. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/SECURITY.md +0 -0
  33. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/assets/nlp2sql-logo.png +0 -0
  34. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/benchmarks/enterprise_benchmark_results.json +0 -0
  35. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/docker/.env.example +0 -0
  36. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/docker/README.md +0 -0
  37. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/docker/docker-compose.yml +0 -0
  38. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/docker/get-db-urls.sh +0 -0
  39. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/docker/init-schema.sql +0 -0
  40. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/docker/large-schema.sql +0 -0
  41. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/docker/localstack-init.sh +0 -0
  42. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/docker/test-redshift.sh +0 -0
  43. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/docs/API.md +0 -0
  44. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/docs/ARCHITECTURE.md +0 -0
  45. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/docs/CONFIGURATION.md +0 -0
  46. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/docs/ENTERPRISE.md +0 -0
  47. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/docs/Redshift.md +0 -0
  48. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/README.md +0 -0
  49. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/advanced/test_multiple_providers.py +0 -0
  50. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/advanced/test_simple_api.py +0 -0
  51. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/database_specific/test_odoo_integration.py +0 -0
  52. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/default_examples.json +0 -0
  53. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/documentation/real_world_example.py +0 -0
  54. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/getting_started/basic_usage.py +0 -0
  55. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/getting_started/simple_demo.py +0 -0
  56. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/getting_started/test_api_setup.py +0 -0
  57. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/schema_management/test_auto_schema.py +0 -0
  58. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/schema_management/test_schema_filters_comprehensive.py +0 -0
  59. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/strands-fastapi-agent/.dockerignore +0 -0
  60. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/strands-fastapi-agent/.env.example +0 -0
  61. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/strands-fastapi-agent/DOCKER_README.md +0 -0
  62. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/strands-fastapi-agent/DOCKER_SETUP.md +0 -0
  63. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/strands-fastapi-agent/DOCKER_TESTING_SUMMARY.md +0 -0
  64. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/strands-fastapi-agent/Dockerfile +0 -0
  65. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/strands-fastapi-agent/INDEX.md +0 -0
  66. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/strands-fastapi-agent/README.md +0 -0
  67. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/strands-fastapi-agent/docker/init-ecommerce-schema.sql +0 -0
  68. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/strands-fastapi-agent/docker-compose.yml +0 -0
  69. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/strands-fastapi-agent/main.py +0 -0
  70. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/strands-fastapi-agent/pyproject.toml +0 -0
  71. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/examples/strands-fastapi-agent/test-docker-endpoints.sh +0 -0
  72. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/install-dev.sh +0 -0
  73. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/mcp_server/README.md +0 -0
  74. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/mcp_server/__init__.py +0 -0
  75. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/mcp_server/example_config.json +0 -0
  76. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/mcp_server/pyproject.toml +0 -0
  77. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/mcp_server/server.py +0 -0
  78. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/adapters/__init__.py +0 -0
  79. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/adapters/anthropic_adapter.py +0 -0
  80. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/adapters/gemini_adapter.py +0 -0
  81. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/adapters/local_embedding_adapter.py +0 -0
  82. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/adapters/openai_adapter.py +0 -0
  83. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/adapters/openai_embedding_adapter.py +0 -0
  84. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/adapters/redshift_adapter.py +0 -0
  85. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/cli.py +0 -0
  86. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/config/__init__.py +0 -0
  87. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/config/settings.py +0 -0
  88. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/core/__init__.py +0 -0
  89. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/core/database_prompts.py +0 -0
  90. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/core/provider_config.py +0 -0
  91. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/core/result.py +0 -0
  92. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/core/sql_safety.py +0 -0
  93. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/exceptions/__init__.py +0 -0
  94. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/factories/__init__.py +0 -0
  95. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/factories/repository_factory.py +0 -0
  96. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/ports/__init__.py +0 -0
  97. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/ports/ai_provider.py +0 -0
  98. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/ports/cache.py +0 -0
  99. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/ports/embedding_provider.py +0 -0
  100. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/ports/example_repository.py +0 -0
  101. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/ports/query_optimizer.py +0 -0
  102. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/ports/query_safety.py +0 -0
  103. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/ports/query_validator.py +0 -0
  104. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/ports/schema_repository.py +0 -0
  105. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/ports/schema_strategy.py +0 -0
  106. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/schema/__init__.py +0 -0
  107. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/schema/analyzer.py +0 -0
  108. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/schema/embedding_manager.py +0 -0
  109. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/schema/example_loader.py +0 -0
  110. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/schema/example_store.py +0 -0
  111. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/schema/manager.py +0 -0
  112. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/services/__init__.py +0 -0
  113. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/services/query_service.py +0 -0
  114. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/utils/__init__.py +0 -0
  115. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/utils/helpers.py +0 -0
  116. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/src/nlp2sql/utils/storage.py +0 -0
  117. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/test_tmp/.gitignore +0 -0
  118. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/tests/mcp/__init__.py +0 -0
  119. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/tests/mcp/test_sql_security.py +0 -0
  120. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/tests/test_basic.py +0 -0
  121. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/tests/test_batch_scoring.py +0 -0
  122. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/tests/test_column_validation.py +0 -0
  123. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/tests/test_database_prompts.py +0 -0
  124. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/tests/test_dsl_integration.py +0 -0
  125. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/tests/test_embedding_manager.py +0 -0
  126. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/tests/test_integration.py +0 -0
  127. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/tests/test_redshift_integration.py +0 -0
  128. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/tests/test_repository_cache.py +0 -0
  129. {nlp2sql-0.2.0rc8 → nlp2sql-0.2.0rc9}/tests/test_repository_factory.py +0 -0
  130. {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.0rc8
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.0rc8"
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.0rc8"
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 = "1.0" # Increment when cache format changes
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
- t.table_name,
211
- t.table_schema,
212
- obj_description(c.oid) as table_comment,
213
- pg_total_relation_size(c.oid) as size_bytes,
214
- COALESCE(s.n_tup_ins + s.n_tup_upd + s.n_tup_del, 0) as row_count
215
- FROM information_schema.tables t
216
- LEFT JOIN pg_class c ON c.relname = t.table_name
217
- LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
218
- AND n.nspname = t.table_schema
219
- LEFT JOIN pg_stat_user_tables s ON s.relname = t.table_name
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
- c.table_name,
227
- c.column_name,
228
- c.data_type,
229
- c.is_nullable,
230
- c.column_default,
231
- c.character_maximum_length,
232
- c.numeric_precision,
233
- c.numeric_scale,
234
- c.ordinal_position,
235
- col_description(pgc.oid, c.ordinal_position) as column_comment
236
- FROM information_schema.columns c
237
- LEFT JOIN pg_class pgc ON pgc.relname = c.table_name
238
- LEFT JOIN pg_namespace pgn ON pgn.oid = pgc.relnamespace
239
- AND pgn.nspname = c.table_schema
240
- WHERE c.table_schema = :schema
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
- tc.table_name,
245
- array_agg(kcu.column_name ORDER BY kcu.ordinal_position) as pk_columns
246
- FROM information_schema.table_constraints tc
247
- JOIN information_schema.key_column_usage kcu
248
- ON tc.constraint_name = kcu.constraint_name
249
- AND tc.table_schema = kcu.table_schema
250
- WHERE tc.table_schema = :schema
251
- AND tc.constraint_type = 'PRIMARY KEY'
252
- GROUP BY tc.table_name
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
- kcu.table_name,
257
- kcu.column_name,
258
- kcu2.table_name as ref_table,
259
- kcu2.column_name as ref_column,
260
- rc.constraint_name
261
- FROM information_schema.key_column_usage kcu
262
- JOIN information_schema.referential_constraints rc
263
- ON kcu.constraint_name = rc.constraint_name
264
- AND kcu.table_schema = rc.constraint_schema
265
- JOIN information_schema.key_column_usage kcu2
266
- ON rc.unique_constraint_name = kcu2.constraint_name
267
- AND rc.unique_constraint_schema = kcu2.table_schema
268
- WHERE kcu.table_schema = :schema
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
- t.table_name,
397
- t.table_schema,
398
- obj_description(c.oid) as table_comment,
399
- pg_size_pretty(pg_total_relation_size(c.oid)) as table_size,
400
- pg_total_relation_size(c.oid) as size_bytes,
401
- COALESCE(s.n_tup_ins + s.n_tup_upd + s.n_tup_del, 0) as row_count
402
- FROM information_schema.tables t
403
- LEFT JOIN pg_class c ON c.relname = t.table_name
404
- LEFT JOIN pg_namespace n ON n.oid = c.relnamespace AND n.nspname = t.table_schema
405
- LEFT JOIN pg_stat_user_tables s ON s.relname = t.table_name AND s.schemaname = t.table_schema
406
- WHERE t.table_name = :table_name AND t.table_schema = :schema
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
- t.table_name,
427
- t.table_schema,
428
- obj_description(c.oid) as table_comment,
429
- pg_size_pretty(pg_total_relation_size(c.oid)) as table_size,
430
- pg_total_relation_size(c.oid) as size_bytes,
431
- COALESCE(s.n_tup_ins + s.n_tup_upd + s.n_tup_del, 0) as row_count
432
- FROM information_schema.tables t
433
- LEFT JOIN pg_class c ON c.relname = t.table_name
434
- LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
435
- LEFT JOIN pg_stat_user_tables s ON s.relname = t.table_name
436
- WHERE t.table_schema = :schema
437
- AND t.table_type = 'BASE TABLE'
438
- AND (t.table_name ILIKE :pattern OR obj_description(c.oid) ILIKE :pattern)
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
- kcu.table_name as related_table,
465
- kcu.table_schema as related_schema
466
- FROM information_schema.key_column_usage kcu
467
- JOIN information_schema.referential_constraints rc ON kcu.constraint_name = rc.constraint_name
468
- JOIN information_schema.key_column_usage kcu2 ON rc.unique_constraint_name = kcu2.constraint_name
469
- WHERE kcu2.table_name = :table_name AND kcu2.table_schema = :schema
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
- kcu2.table_name as related_table,
476
- kcu2.table_schema as related_schema
477
- FROM information_schema.key_column_usage kcu
478
- JOIN information_schema.referential_constraints rc ON kcu.constraint_name = rc.constraint_name
479
- JOIN information_schema.key_column_usage kcu2 ON rc.unique_constraint_name = kcu2.constraint_name
480
- WHERE kcu.table_name = :table_name AND kcu.table_schema = :schema
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
- t.table_name,
484
- t.table_schema,
485
- obj_description(c.oid) as table_comment,
486
- pg_size_pretty(pg_total_relation_size(c.oid)) as table_size,
487
- pg_total_relation_size(c.oid) as size_bytes,
488
- COALESCE(s.n_tup_ins + s.n_tup_upd + s.n_tup_del, 0) as row_count
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 information_schema.tables t ON rt.related_table = t.table_name AND rt.related_schema = t.table_schema
491
- LEFT JOIN pg_class c ON c.relname = t.table_name
492
- LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
493
- LEFT JOIN pg_stat_user_tables s ON s.relname = t.table_name
494
- WHERE t.table_type = 'BASE TABLE'
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() as database_name,
519
- version() as database_version,
520
- COUNT(*) as total_tables,
521
- SUM(pg_total_relation_size(c.oid)) as total_size
522
- FROM information_schema.tables t
523
- LEFT JOIN pg_class c ON c.relname = t.table_name
524
- LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
525
- WHERE t.table_schema = :schema
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), (self.schema_name,))
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
- character_maximum_length,
617
- numeric_precision,
618
- numeric_scale,
619
- col_description(pgc.oid, ordinal_position) as column_comment
620
- FROM information_schema.columns c
621
- LEFT JOIN pg_class pgc ON pgc.relname = c.table_name
622
- LEFT JOIN pg_namespace pgn ON pgn.oid = pgc.relnamespace
623
- WHERE c.table_name = :table_name AND c.table_schema = :schema
624
- ORDER BY ordinal_position
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 kcu.column_name
652
- FROM information_schema.table_constraints tc
653
- JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name
654
- WHERE tc.table_name = :table_name AND tc.table_schema = :schema
655
- AND tc.constraint_type = 'PRIMARY KEY'
656
- ORDER BY kcu.ordinal_position
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
- kcu.column_name,
670
- kcu2.table_name as referenced_table,
671
- kcu2.column_name as referenced_column,
672
- rc.constraint_name
673
- FROM information_schema.key_column_usage kcu
674
- JOIN information_schema.referential_constraints rc ON kcu.constraint_name = rc.constraint_name
675
- JOIN information_schema.key_column_usage kcu2 ON rc.unique_constraint_name = kcu2.constraint_name
676
- WHERE kcu.table_name = :table_name AND kcu.table_schema = :schema
677
- ORDER BY kcu.ordinal_position
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)
@@ -91,6 +91,7 @@ class NLP2SQL:
91
91
  # Factory — the ``connect()`` function
92
92
  # ------------------------------------------------------------------
93
93
 
94
+
94
95
  async def connect(
95
96
  database_url: str,
96
97
  *,
@@ -90,5 +90,3 @@ class QueryExample:
90
90
  complexity: int # 1-5 scale
91
91
  tags: List[str] = field(default_factory=list)
92
92
  metadata: Dict[str, Any] = field(default_factory=dict)
93
-
94
-
@@ -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({"type": "table", "name": table.name, "columns": [{"name": c["name"]} for c in table.columns]})
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({"type": "column", "name": col["name"], "table_name": table.name, "data_type": col.get("data_type", "unknown")})
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