iris-pgwire 1.5.6__tar.gz → 1.5.8__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 (124) hide show
  1. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/PKG-INFO +1 -1
  2. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/__init__.py +1 -1
  3. iris_pgwire-1.5.8/src/iris_pgwire/auth/AGENTS.md +38 -0
  4. iris_pgwire-1.5.8/src/iris_pgwire/catalog/AGENTS.md +44 -0
  5. iris_pgwire-1.5.8/src/iris_pgwire/conversions/AGENTS.md +31 -0
  6. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/iris_executor.py +12 -1
  7. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/protocol.py +27 -6
  8. iris_pgwire-1.5.8/src/iris_pgwire/quality/AGENTS.md +32 -0
  9. iris_pgwire-1.5.8/src/iris_pgwire/sql_translator/AGENTS.md +96 -0
  10. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/vector_optimizer.py +118 -1
  11. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/.gitignore +0 -0
  12. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/LICENSE +0 -0
  13. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/README.md +0 -0
  14. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/pyproject.toml +0 -0
  15. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/_column_naming.py +0 -0
  16. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/_noop_cursor.py +0 -0
  17. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/_result_processing.py +0 -0
  18. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/_type_mapping.py +0 -0
  19. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/auth/__init__.py +0 -0
  20. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/auth/auth_selector.py +0 -0
  21. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/auth/gssapi_auth.py +0 -0
  22. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/auth/oauth_bridge.py +0 -0
  23. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/auth/scram.py +0 -0
  24. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/auth/wallet_credentials.py +0 -0
  25. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/backend_selector.py +0 -0
  26. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/bulk_executor.py +0 -0
  27. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/catalog/__init__.py +0 -0
  28. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/catalog/catalog_functions.py +0 -0
  29. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/catalog/catalog_router.py +0 -0
  30. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/catalog/oid_generator.py +0 -0
  31. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/catalog/pg_attrdef.py +0 -0
  32. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/catalog/pg_attribute.py +0 -0
  33. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/catalog/pg_class.py +0 -0
  34. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/catalog/pg_constraint.py +0 -0
  35. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/catalog/pg_index.py +0 -0
  36. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/catalog/pg_namespace.py +0 -0
  37. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/catalog/pg_type.py +0 -0
  38. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/column_validator.py +0 -0
  39. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/config.py +0 -0
  40. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/config_schema.py +0 -0
  41. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/constitutional.py +0 -0
  42. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/conversions/__init__.py +0 -0
  43. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/conversions/bulk_insert.py +0 -0
  44. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/conversions/date_horolog.py +0 -0
  45. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/conversions/ddl_idempotency.py +0 -0
  46. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/conversions/ddl_splitter.py +0 -0
  47. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/conversions/json_path.py +0 -0
  48. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/conversions/vector_syntax.py +0 -0
  49. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/copy_handler.py +0 -0
  50. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/csv_processor.py +0 -0
  51. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/dbapi_connection_pool.py +0 -0
  52. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/dbapi_executor.py +0 -0
  53. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/debug_tracer.py +0 -0
  54. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/health_checker.py +0 -0
  55. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/integratedml.py +0 -0
  56. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/iris_constructs.py +0 -0
  57. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/iris_log_handler.py +0 -0
  58. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/iris_user_management.py +0 -0
  59. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/migrations/__init__.py +0 -0
  60. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/migrations/__main__.py +0 -0
  61. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/migrations/executor.py +0 -0
  62. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/models/__init__.py +0 -0
  63. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/models/backend_config.py +0 -0
  64. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/models/connection_pool_state.py +0 -0
  65. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/models/dbapi_connection.py +0 -0
  66. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/models/ipm_metadata.py +0 -0
  67. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/models/vector_query_request.py +0 -0
  68. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/observability.py +0 -0
  69. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/performance_monitor.py +0 -0
  70. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/quality/__init__.py +0 -0
  71. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/quality/__main__.py +0 -0
  72. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/quality/code_quality_validator.py +0 -0
  73. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/quality/documentation_validator.py +0 -0
  74. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/quality/package_metadata_validator.py +0 -0
  75. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/quality/security_validator.py +0 -0
  76. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/quality/validator.py +0 -0
  77. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/schema_mapper.py +0 -0
  78. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/server.py +0 -0
  79. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/__init__.py +0 -0
  80. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/alias_extractor.py +0 -0
  81. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/api.py +0 -0
  82. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/boolean_translator.py +0 -0
  83. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/cache.py +0 -0
  84. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/confidence_analyzer.py +0 -0
  85. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/config.py +0 -0
  86. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/constraint_translator.py +0 -0
  87. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/copy_parser.py +0 -0
  88. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/date_translator.py +0 -0
  89. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/ddl_parser.py +0 -0
  90. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/ddl_translator.py +0 -0
  91. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/debug.py +0 -0
  92. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/default_values.py +0 -0
  93. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/enum_registry.py +0 -0
  94. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/enum_translator.py +0 -0
  95. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/error_handler.py +0 -0
  96. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/identifier_normalizer.py +0 -0
  97. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/index_validator.py +0 -0
  98. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/interceptor.py +0 -0
  99. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/logging_config.py +0 -0
  100. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/mappings/__init__.py +0 -0
  101. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/mappings/constructs.py +0 -0
  102. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/mappings/datatypes.py +0 -0
  103. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/mappings/document_filters.py +0 -0
  104. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/mappings/functions.py +0 -0
  105. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/metadata_cache.py +0 -0
  106. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/metrics.py +0 -0
  107. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/models.py +0 -0
  108. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/normalizer.py +0 -0
  109. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/parser.py +0 -0
  110. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/performance_monitor.py +0 -0
  111. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/pipeline.py +0 -0
  112. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/refiner.py +0 -0
  113. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/reserved_words.py +0 -0
  114. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/returning_plan.py +0 -0
  115. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/skipped_table_set.py +0 -0
  116. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/statement_filter.py +0 -0
  117. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/transaction_translator.py +0 -0
  118. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/translator.py +0 -0
  119. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/type_translator.py +0 -0
  120. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/sql_translator/validator.py +0 -0
  121. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/testing/__init__.py +0 -0
  122. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/testing/base_fixture_builder.py +0 -0
  123. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/type_mapping.py +0 -0
  124. {iris_pgwire-1.5.6 → iris_pgwire-1.5.8}/src/iris_pgwire/vector_metrics.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iris-pgwire
3
- Version: 1.5.6
3
+ Version: 1.5.8
4
4
  Summary: PostgreSQL Wire Protocol Server for InterSystems IRIS - Connect BI tools, Python frameworks, and PostgreSQL clients to IRIS databases
5
5
  Project-URL: Homepage, https://github.com/intersystems-community/iris-pgwire
6
6
  Project-URL: Documentation, https://github.com/intersystems-community/iris-pgwire#readme
@@ -6,7 +6,7 @@ Based on the specification from docs/iris_pgwire_plan.md and proven patterns fro
6
6
  caretdev/sqlalchemy-iris.
7
7
  """
8
8
 
9
- __version__ = "1.5.6"
9
+ __version__ = "1.5.8"
10
10
  __author__ = "Thomas Dyar <thomas.dyar@intersystems.com>"
11
11
 
12
12
  # Don't import server/protocol in __init__ to avoid sys.modules conflicts
@@ -0,0 +1,38 @@
1
+ # auth — Agent Knowledge Base
2
+
3
+ **6 files.** Pluggable authentication: SCRAM-SHA-256, OAuth 2.0, GSSAPI/Kerberos,
4
+ and IRIS Wallet credentials.
5
+
6
+ ## STRUCTURE
7
+
8
+ ```
9
+ auth/
10
+ ├── auth_selector.py # AuthenticationSelector — routes to correct backend
11
+ ├── scram.py # SCRAM-SHA-256 implementation
12
+ ├── oauth_bridge.py # OAuth 2.0 token validation bridge
13
+ ├── gssapi_auth.py # GSSAPI/Kerberos (optional dep: gssapi>=1.8.0)
14
+ └── wallet_credentials.py # IRIS Wallet credential store integration
15
+ ```
16
+
17
+ ## WHERE TO LOOK
18
+
19
+ | Task | File |
20
+ |------|------|
21
+ | Auth method selection logic | `auth_selector.py` |
22
+ | SCRAM handshake failures | `scram.py` |
23
+ | OAuth token/JWT validation | `oauth_bridge.py` |
24
+ | Kerberos realm issues | `gssapi_auth.py` |
25
+ | Wallet credential retrieval | `wallet_credentials.py` |
26
+
27
+ ## CRITICAL INVARIANTS
28
+
29
+ - GSSAPI is an optional dep — `gssapi_auth.py` has conditional imports (F401 ignored by ruff)
30
+ - SCRAM-SHA-256 is the default and primary auth method
31
+ - OAuth bridge validates tokens against external IdP — never trust without validation
32
+ - Auth selector checks `oauth_enabled` and `kerberos_enabled` flags from config
33
+
34
+ ## ANTI-PATTERNS
35
+
36
+ - Never hardcode credentials — always source from env vars or IRIS Wallet
37
+ - SSL/TLS is handled via reverse proxy (nginx/HAProxy) — not in-process here
38
+ - `gssapi_auth.py` imports are conditional — don't assume gssapi is installed
@@ -0,0 +1,44 @@
1
+ # catalog — Agent Knowledge Base
2
+
3
+ **11 files.** Emulates PostgreSQL's `pg_catalog` and `information_schema` so ORMs
4
+ (SQLAlchemy, Prisma, Drizzle) can introspect IRIS schema as if it were PostgreSQL.
5
+
6
+ ## STRUCTURE
7
+
8
+ ```
9
+ catalog/
10
+ ├── catalog_router.py # Entry point — routes pg_catalog queries to emulators
11
+ ├── oid_generator.py # Deterministic OID generation for IRIS objects
12
+ ├── pg_attribute.py # pg_attribute (column info)
13
+ ├── pg_attrdef.py # pg_attrdef (column defaults)
14
+ ├── pg_class.py # pg_class (table/index info)
15
+ ├── pg_constraint.py # pg_constraint (PK/FK/CHECK)
16
+ ├── pg_index.py # pg_index (index metadata)
17
+ ├── pg_namespace.py # pg_namespace (schemas as namespaces)
18
+ ├── pg_type.py # pg_type (type OID registry)
19
+ └── catalog_functions.py # pg_get_indexdef, pg_get_constraintdef, etc.
20
+ ```
21
+
22
+ ## WHERE TO LOOK
23
+
24
+ | Task | File |
25
+ |------|------|
26
+ | ORM can't see tables | `catalog_router.py` → `pg_class.py` |
27
+ | ORM can't see columns | `pg_attribute.py` |
28
+ | ORM can't see constraints | `pg_constraint.py` |
29
+ | Wrong OIDs causing type errors | `oid_generator.py` + `pg_type.py` |
30
+ | `pg_get_indexdef` / `pg_get_constraintdef` failures | `catalog_functions.py` |
31
+ | Schema (namespace) not found | `pg_namespace.py` |
32
+
33
+ ## CRITICAL INVARIANTS
34
+
35
+ - OIDs must be deterministic (same IRIS object → same OID across connections)
36
+ - `SQLUser` namespace always maps to OID representing the `public` schema
37
+ - Array parameters from PG clients arrive as `{val1,val2}` strings — parse before use
38
+ - `regclass` references must resolve via OID lookup before catalog queries execute
39
+
40
+ ## ANTI-PATTERNS
41
+
42
+ - Never use random OIDs — breaks ORM caching
43
+ - Never return PG-specific column types that IRIS can't represent
44
+ - Don't assume `pg_catalog.` prefix is present — queries may omit schema
@@ -0,0 +1,31 @@
1
+ # conversions — Agent Knowledge Base
2
+
3
+ **7 files.** Data format conversions between PostgreSQL wire format and IRIS internals.
4
+
5
+ ## STRUCTURE
6
+
7
+ ```
8
+ conversions/
9
+ ├── date_horolog.py # PG DATE/TIMESTAMP ↔ IRIS $HOROLOG format
10
+ ├── ddl_idempotency.py # Make DDL idempotent (IF NOT EXISTS, etc.)
11
+ ├── ddl_splitter.py # Split multi-statement DDL into individual statements
12
+ ├── bulk_insert.py # Bulk INSERT optimization helpers
13
+ ├── json_path.py # JSON path expression builder
14
+ └── vector_syntax.py # pgvector syntax ↔ IRIS VECTOR syntax
15
+ ```
16
+
17
+ ## WHERE TO LOOK
18
+
19
+ | Task | File |
20
+ |------|------|
21
+ | Date/timestamp wrong values | `date_horolog.py` |
22
+ | DDL fails on re-run | `ddl_idempotency.py` |
23
+ | Multi-statement DDL errors | `ddl_splitter.py` |
24
+ | JSON/JSONB path issues | `json_path.py` |
25
+ | Vector operator translation | `vector_syntax.py` |
26
+
27
+ ## CRITICAL INVARIANTS
28
+
29
+ - IRIS uses `$HOROLOG` (days since 12/31/1840) for dates — conversion must be exact
30
+ - `<=>` (cosine) and `<#>` (dot product) supported; `<->` (L2) → NOT IMPLEMENTED error
31
+ - DDL splitter must handle semicolons inside string literals correctly
@@ -696,7 +696,18 @@ class IRISExecutor:
696
696
  columns=plan.columns,
697
697
  session_id=session_id,
698
698
  )
699
- optimized_sql = plan.stripped_sql
699
+ # Strip RETURNING / ON CONFLICT from the *translated* optimized_sql.
700
+ # When original_sql was provided, plan.stripped_sql is the original (untranslated)
701
+ # SQL with only RETURNING removed — we must NOT use it to overwrite optimized_sql or
702
+ # we'd undo the entire normalization pipeline (DEFAULT, $1 params, schema names, etc.
703
+ # still present → IRIS SQLCODE -12).
704
+ # Instead, apply the same stripping regex directly to optimized_sql.
705
+ if original_sql:
706
+ optimized_sql = ReturningPlan._strip_clauses(
707
+ optimized_sql, plan.returning_clause, plan.on_conflict_clause
708
+ )
709
+ else:
710
+ optimized_sql = plan.stripped_sql
700
711
 
701
712
  # 6. Semicolon Stripping
702
713
  optimized_sql = optimized_sql.strip().rstrip(";")
@@ -32,6 +32,7 @@ from .sql_translator import PerformanceStats, get_translator
32
32
  from .sql_translator.copy_parser import CopyCommandParser, CopyDirection
33
33
  from .sql_translator.performance_monitor import MetricType, PerformanceTracker, get_monitor
34
34
  from .sql_translator.returning_plan import ReturningPlan
35
+ from .vector_optimizer import VectorQueryOptimizer
35
36
 
36
37
  logger = structlog.get_logger()
37
38
 
@@ -1378,12 +1379,32 @@ class PGWireProtocol:
1378
1379
  if result["success"]:
1379
1380
  await self.send_query_result(result, send_ready=send_ready)
1380
1381
  else:
1381
- await self.send_error_response(
1382
- "ERROR", "42000", "syntax_error", result.get("error", "Query execution failed")
1383
- )
1384
- # CRITICAL: Send ReadyForQuery after error (only if last statement)
1385
- if send_ready:
1386
- await self.send_ready_for_query()
1382
+ error_msg = result.get("error", "Query execution failed")
1383
+ if (
1384
+ VectorQueryOptimizer.sql_has_if_not_exists(final_sql)
1385
+ and VectorQueryOptimizer.is_duplicate_object_error(error_msg)
1386
+ ):
1387
+ logger.info(
1388
+ "IF NOT EXISTS: suppressing duplicate-object error",
1389
+ connection_id=self.connection_id,
1390
+ sql_preview=final_sql[:100],
1391
+ error=error_msg,
1392
+ )
1393
+ fake_result = {
1394
+ "success": True,
1395
+ "rows": [],
1396
+ "columns": [],
1397
+ "row_count": 0,
1398
+ "command_tag": "CREATE TABLE",
1399
+ }
1400
+ await self.send_query_result(fake_result, send_ready=send_ready)
1401
+ else:
1402
+ await self.send_error_response(
1403
+ "ERROR", "42000", "syntax_error", error_msg
1404
+ )
1405
+ # CRITICAL: Send ReadyForQuery after error (only if last statement)
1406
+ if send_ready:
1407
+ await self.send_ready_for_query()
1387
1408
 
1388
1409
  except Exception as e:
1389
1410
  logger.error(
@@ -0,0 +1,32 @@
1
+ # quality — Agent Knowledge Base
2
+
3
+ **7 files.** Package and code quality validation — mirrors CI checks. Run locally
4
+ with `./scripts/validate_package.sh` or `python -m iris_pgwire.quality`.
5
+
6
+ ## STRUCTURE
7
+
8
+ ```
9
+ quality/
10
+ ├── __main__.py # CLI entry: python -m iris_pgwire.quality
11
+ ├── validator.py # Orchestrator — runs all validators
12
+ ├── package_metadata_validator.py # pyroma score, MANIFEST.in, classifiers
13
+ ├── code_quality_validator.py # black/ruff compliance checks
14
+ ├── documentation_validator.py # interrogate docstring coverage (fail-under=80)
15
+ └── security_validator.py # bandit security scan + pip-audit
16
+ ```
17
+
18
+ ## WHERE TO LOOK
19
+
20
+ | Task | File |
21
+ |------|------|
22
+ | CI quality gate failing | `validator.py` — run locally first |
23
+ | Docstring coverage below 80% | `documentation_validator.py` |
24
+ | Security audit failures | `security_validator.py` |
25
+ | Package metadata issues | `package_metadata_validator.py` |
26
+
27
+ ## CRITICAL INVARIANTS
28
+
29
+ - interrogate fail-under=80 — excludes `tests/` but includes all `src/`
30
+ - bandit ignores `tests/` (`.bandit` config at repo root)
31
+ - CI runs on Python 3.11 and 3.12 (matrix in `package-quality.yml`)
32
+ - `check-manifest` is disabled — MANIFEST.in deliberately excludes non-essential files
@@ -0,0 +1,96 @@
1
+ # sql_translator — Agent Knowledge Base
2
+
3
+ **37 files.** Core translation pipeline converting PostgreSQL SQL to IRIS-compatible SQL.
4
+
5
+ ## OVERVIEW
6
+
7
+ Multi-stage pipeline: schema mapping → statement filtering → parameter translation →
8
+ normalization → refinement → optimization. Entry: `pipeline.py` → `normalizer.py`.
9
+
10
+ ## STRUCTURE
11
+
12
+ ```
13
+ sql_translator/
14
+ ├── pipeline.py # SQLPipeline — orchestrates all stages
15
+ ├── normalizer.py # SQLTranslator — main orchestrator (stage coordinator)
16
+ ├── translator.py # Low-level translation primitives
17
+ ├── identifier_normalizer.py # Quoted/unquoted identifier casing
18
+ ├── ddl_translator.py # Strip PG-specific DDL (fillfactor, GENERATED, USING btree)
19
+ ├── date_translator.py # DATE/TIMESTAMP → IRIS horolog
20
+ ├── boolean_translator.py # true/false → 1/0
21
+ ├── enum_translator.py # ENUM type handling
22
+ ├── enum_registry.py # Tracks CREATE TYPE enums
23
+ ├── statement_filter.py # Skip unsupported statements (RLS, etc.)
24
+ ├── transaction_translator.py # BEGIN/COMMIT/SAVEPOINT handling
25
+ ├── type_translator.py # PG type names → IRIS equivalents
26
+ ├── constraint_translator.py # CHECK/FOREIGN KEY translation
27
+ ├── returning_plan.py # RETURNING clause emulation plan
28
+ ├── refiner.py # ORDER BY alias fixes, post-normalize cleanup
29
+ ├── cache.py # Translation result cache
30
+ ├── metadata_cache.py # IRIS schema metadata cache
31
+ ├── confidence_analyzer.py # Translation confidence scoring
32
+ ├── error_handler.py # Translation error handling/recovery
33
+ ├── interceptor.py # Pre/post translation hooks
34
+ ├── copy_parser.py # COPY statement parsing
35
+ ├── api.py # Public translation API
36
+ ├── parser.py # SQL parsing utilities
37
+ ├── debug.py # Debug output helpers
38
+ ├── models.py # TranslationResult, PerformanceStats
39
+ ├── reserved_words.py # IRIS reserved word list
40
+ ├── skipped_table_set.py # Tables whose DDL should be skipped
41
+ ├── index_validator.py # Index statement validation
42
+ ├── default_values.py # DEFAULT clause metadata resolution
43
+ ├── alias_extractor.py # SELECT alias extraction
44
+ ├── mappings/ # Static translation tables
45
+ │ ├── datatypes.py # PG → IRIS type mapping
46
+ │ ├── functions.py # PG → IRIS function mapping
47
+ │ ├── constructs.py # SQL construct mapping
48
+ │ └── document_filters.py # JSON path filter mapping
49
+ ```
50
+
51
+ ## WHERE TO LOOK
52
+
53
+ | Task | File |
54
+ |------|------|
55
+ | Fix a translation bug (end-to-end) | `pipeline.py` → `normalizer.py` |
56
+ | Identifier casing (`"MyTable"` vs `MYTABLE`) | `identifier_normalizer.py` |
57
+ | DDL stripping (fillfactor, GENERATED, etc.) | `ddl_translator.py` |
58
+ | Date/timestamp handling | `date_translator.py` |
59
+ | ENUM support | `enum_translator.py` + `enum_registry.py` |
60
+ | Add new PG function mapping | `mappings/functions.py` |
61
+ | Add new PG type mapping | `mappings/datatypes.py` |
62
+ | Skip/filter unsupported SQL | `statement_filter.py` |
63
+ | RETURNING clause issues | `returning_plan.py` |
64
+ | Performance / overhead | `cache.py`, `metrics.py` |
65
+ | Transaction handling | `transaction_translator.py` |
66
+
67
+ ## CRITICAL INVARIANTS
68
+
69
+ - `SQLUser` casing MUST NEVER be changed — IRIS is case-sensitive on schema names
70
+ - Translation overhead MUST be <5ms per query
71
+ - Quoted identifiers preserve case; unquoted → UPPERCASE
72
+ - `$1`, `$2` PG params → `?` IRIS params
73
+ - `public.` schema prefix → `SQLUser.` (configurable via `IRIS_SCHEMA` env var)
74
+
75
+ ## ANTI-PATTERNS
76
+
77
+ - Never hardcode `SQLUser` — always use `IRIS_SCHEMA` from `schema_mapper`
78
+ - Never return partial results on translation error — use `error_handler.py`
79
+ - Never skip the cache for hot paths — check `cache.py` first
80
+ - Don't add L2 (`<->`) translation — must raise NOT IMPLEMENTED
81
+
82
+ ## PIPELINE FLOW
83
+
84
+ ```
85
+ input SQL
86
+ → StatementFilter (skip RLS, unsupported DDL)
87
+ → SQLTranslator.normalize_sql_with_result()
88
+ → schema mapping (public→SQLUser)
89
+ → identifier normalization
90
+ → date translation
91
+ → boolean translation
92
+ → enum translation
93
+ → construct/function mapping
94
+ → SQLRefiner (ORDER BY alias, post-normalize fixes)
95
+ → output SQL + TranslationResult
96
+ ```
@@ -102,6 +102,9 @@ class VectorQueryOptimizer:
102
102
  # Rewrite pgvector operators to IRIS vector functions
103
103
  sql = self._rewrite_pgvector_operators(sql)
104
104
 
105
+ if "->" in sql:
106
+ sql = self._translate_nested_json_operators(sql)
107
+
105
108
  sql_upper = sql.upper()
106
109
 
107
110
  # Handle INSERT/UPDATE with vector patterns
@@ -266,6 +269,9 @@ class VectorQueryOptimizer:
266
269
  def _rewrite_pgvector_operators(self, sql: str) -> str:
267
270
  """Rewrite pgvector operators (<=> / <#>) to IRIS vector functions.
268
271
 
272
+ For DDL containing HNSW index syntax, translates to IRIS-native form instead
273
+ of rewriting operators.
274
+
269
275
  Raises:
270
276
  NotImplementedError: If L2 distance operator (<->) is found.
271
277
  """
@@ -274,7 +280,11 @@ class VectorQueryOptimizer:
274
280
 
275
281
  sql_upper = sql.upper()
276
282
  if any(kw in sql_upper for kw in _DDL_KEYWORDS):
277
- return sql
283
+ # Translate pgvector HNSW DDL (CREATE INDEX … USING hnsw) to IRIS syntax
284
+ translated = self._translate_hnsw_index_ddl(sql)
285
+ if translated != sql:
286
+ logger.info("HNSW DDL translated", translated=translated[:200])
287
+ return translated
278
288
 
279
289
  return self._rewrite_operators_in_text(sql)
280
290
 
@@ -604,6 +614,113 @@ class VectorQueryOptimizer:
604
614
  }
605
615
 
606
616
 
617
+ # ------------------------------------------------------------------
618
+ # HNSW index DDL translation (pgvector → IRIS)
619
+ # ------------------------------------------------------------------
620
+
621
+ _HNSW_DDL_RE = re.compile(
622
+ r"""
623
+ CREATE\s+INDEX\s+ # CREATE INDEX
624
+ (?:IF\s+NOT\s+EXISTS\s+)? # optional IF NOT EXISTS
625
+ (\w+)\s+ # index name (group 1)
626
+ ON\s+(\w+(?:\.\w+)?)\s+ # ON table (group 2; schema.table ok)
627
+ USING\s+hnsw\s* # USING hnsw
628
+ \(\s*(\w+)(?:\s+(\w+))?\s*\) # (column [inline_op_class]) groups 3,4
629
+ (?:\s+WITH\s*\(([^)]*)\))? # optional WITH (options) group 5
630
+ """,
631
+ re.IGNORECASE | re.VERBOSE,
632
+ )
633
+
634
+ _DISTANCE_MAP = {
635
+ "vector_cosine_ops": "Cosine",
636
+ "vector_ip_ops": "DotProduct",
637
+ "vector_l2_ops": None,
638
+ }
639
+
640
+ def _translate_hnsw_index_ddl(self, sql: str) -> str:
641
+ """Translate pgvector HNSW index DDL to IRIS-native HNSW syntax."""
642
+
643
+ def _replace(m: re.Match) -> str:
644
+ idx_name = m.group(1)
645
+ table = m.group(2)
646
+ col = m.group(3)
647
+ inline_ops = m.group(4) or ""
648
+ with_clause = m.group(5) or ""
649
+ distance = "Cosine"
650
+ ops_source = inline_ops or ""
651
+ ops_match = re.search(r"op_class\s*=\s*(\w+)", with_clause, re.IGNORECASE)
652
+ if ops_match:
653
+ ops_source = ops_match.group(1)
654
+ if ops_source:
655
+ mapped = self._DISTANCE_MAP.get(ops_source.lower())
656
+ if mapped is None:
657
+ raise NotImplementedError(
658
+ "IRIS does not support L2 distance (vector_l2_ops) in HNSW indexes. "
659
+ "Use Cosine or DotProduct."
660
+ )
661
+ distance = mapped
662
+ iris_ddl = f"CREATE INDEX {idx_name} ON {table} ({col}) AS HNSW(Distance={distance}"
663
+ m_opts = re.search(r"\bm\s*=\s*(\d+)", with_clause, re.IGNORECASE)
664
+ ef_opts = re.search(r"ef_construction\s*=\s*(\d+)", with_clause, re.IGNORECASE)
665
+ if m_opts:
666
+ iris_ddl += f", M={m_opts.group(1)}"
667
+ if ef_opts:
668
+ iris_ddl += f", efConstruction={ef_opts.group(1)}"
669
+ iris_ddl += ")"
670
+ return iris_ddl
671
+
672
+ return self._HNSW_DDL_RE.sub(_replace, sql)
673
+
674
+ # ------------------------------------------------------------------
675
+ # PostgreSQL JSON operators → IRIS JSON_VALUE / JSON_QUERY
676
+ # ------------------------------------------------------------------
677
+
678
+ _JSON_CHAIN_RE = re.compile(
679
+ r"(\w+(?:\.\w+)?)"
680
+ r"((?:\s*->>\s*'[^']+'"
681
+ r"|\s*->\s*'[^']+')+)",
682
+ re.IGNORECASE,
683
+ )
684
+
685
+ def _translate_nested_json_operators(self, sql: str) -> str:
686
+ """Translate PostgreSQL -> / ->> chains to IRIS JSON_VALUE / JSON_QUERY."""
687
+
688
+ def _replace(m: re.Match) -> str:
689
+ col = m.group(1)
690
+ chain = m.group(2)
691
+ text_extract = "->>" in chain
692
+ keys = re.findall(r"->>?\s*'([^']+)'", chain)
693
+ path = "." + ".".join(keys)
694
+ if text_extract:
695
+ return f"JSON_VALUE({col}, '${path}')"
696
+ return f"JSON_QUERY({col}, '${path}')"
697
+
698
+ return self._JSON_CHAIN_RE.sub(_replace, sql)
699
+
700
+ # ------------------------------------------------------------------
701
+ # IF NOT EXISTS error classification (Feature 026)
702
+ # ------------------------------------------------------------------
703
+
704
+ _IRIS_DUPLICATE_CODES = frozenset({
705
+ "5016",
706
+ "5019",
707
+ "5002",
708
+ "SQLCODE: <-5016>",
709
+ "SQLCODE: <-5019>",
710
+ "SQLCODE: <-5002>",
711
+ })
712
+
713
+ @staticmethod
714
+ def is_duplicate_object_error(error_msg: str) -> bool:
715
+ """Return True if error_msg indicates an object-already-exists SQLCODE."""
716
+ return any(code in error_msg for code in VectorQueryOptimizer._IRIS_DUPLICATE_CODES)
717
+
718
+ @staticmethod
719
+ def sql_has_if_not_exists(sql: str) -> bool:
720
+ """Return True if sql contains an IF NOT EXISTS clause."""
721
+ return bool(re.search(r"\bIF\s+NOT\s+EXISTS\b", sql, re.IGNORECASE))
722
+
723
+
607
724
  # ---------------------------------------------------------------------------
608
725
  # Module-level API
609
726
  # ---------------------------------------------------------------------------
File without changes
File without changes
File without changes
File without changes