iris-pgwire 1.5.3__tar.gz → 1.5.5__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 (120) hide show
  1. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/PKG-INFO +1 -1
  2. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/__init__.py +1 -1
  3. iris_pgwire-1.5.5/src/iris_pgwire/conversions/vector_syntax.py +156 -0
  4. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/protocol.py +9 -2
  5. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/mappings/constructs.py +16 -4
  6. iris_pgwire-1.5.3/src/iris_pgwire/conversions/vector_syntax.py +0 -94
  7. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/.gitignore +0 -0
  8. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/LICENSE +0 -0
  9. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/README.md +0 -0
  10. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/pyproject.toml +0 -0
  11. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/_column_naming.py +0 -0
  12. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/_noop_cursor.py +0 -0
  13. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/_result_processing.py +0 -0
  14. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/_type_mapping.py +0 -0
  15. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/auth/__init__.py +0 -0
  16. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/auth/auth_selector.py +0 -0
  17. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/auth/gssapi_auth.py +0 -0
  18. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/auth/oauth_bridge.py +0 -0
  19. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/auth/scram.py +0 -0
  20. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/auth/wallet_credentials.py +0 -0
  21. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/backend_selector.py +0 -0
  22. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/bulk_executor.py +0 -0
  23. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/catalog/__init__.py +0 -0
  24. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/catalog/catalog_functions.py +0 -0
  25. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/catalog/catalog_router.py +0 -0
  26. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/catalog/oid_generator.py +0 -0
  27. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/catalog/pg_attrdef.py +0 -0
  28. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/catalog/pg_attribute.py +0 -0
  29. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/catalog/pg_class.py +0 -0
  30. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/catalog/pg_constraint.py +0 -0
  31. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/catalog/pg_index.py +0 -0
  32. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/catalog/pg_namespace.py +0 -0
  33. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/catalog/pg_type.py +0 -0
  34. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/column_validator.py +0 -0
  35. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/config.py +0 -0
  36. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/config_schema.py +0 -0
  37. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/constitutional.py +0 -0
  38. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/conversions/__init__.py +0 -0
  39. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/conversions/bulk_insert.py +0 -0
  40. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/conversions/date_horolog.py +0 -0
  41. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/conversions/ddl_idempotency.py +0 -0
  42. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/conversions/ddl_splitter.py +0 -0
  43. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/conversions/json_path.py +0 -0
  44. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/copy_handler.py +0 -0
  45. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/csv_processor.py +0 -0
  46. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/dbapi_connection_pool.py +0 -0
  47. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/dbapi_executor.py +0 -0
  48. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/debug_tracer.py +0 -0
  49. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/health_checker.py +0 -0
  50. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/integratedml.py +0 -0
  51. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/iris_constructs.py +0 -0
  52. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/iris_executor.py +0 -0
  53. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/iris_log_handler.py +0 -0
  54. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/iris_user_management.py +0 -0
  55. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/migrations/__init__.py +0 -0
  56. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/migrations/__main__.py +0 -0
  57. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/migrations/executor.py +0 -0
  58. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/models/__init__.py +0 -0
  59. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/models/backend_config.py +0 -0
  60. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/models/connection_pool_state.py +0 -0
  61. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/models/dbapi_connection.py +0 -0
  62. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/models/ipm_metadata.py +0 -0
  63. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/models/vector_query_request.py +0 -0
  64. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/observability.py +0 -0
  65. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/performance_monitor.py +0 -0
  66. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/quality/__init__.py +0 -0
  67. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/quality/__main__.py +0 -0
  68. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/quality/code_quality_validator.py +0 -0
  69. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/quality/documentation_validator.py +0 -0
  70. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/quality/package_metadata_validator.py +0 -0
  71. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/quality/security_validator.py +0 -0
  72. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/quality/validator.py +0 -0
  73. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/schema_mapper.py +0 -0
  74. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/server.py +0 -0
  75. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/__init__.py +0 -0
  76. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/alias_extractor.py +0 -0
  77. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/api.py +0 -0
  78. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/boolean_translator.py +0 -0
  79. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/cache.py +0 -0
  80. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/confidence_analyzer.py +0 -0
  81. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/config.py +0 -0
  82. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/constraint_translator.py +0 -0
  83. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/copy_parser.py +0 -0
  84. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/date_translator.py +0 -0
  85. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/ddl_parser.py +0 -0
  86. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/ddl_translator.py +0 -0
  87. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/debug.py +0 -0
  88. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/default_values.py +0 -0
  89. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/enum_registry.py +0 -0
  90. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/enum_translator.py +0 -0
  91. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/error_handler.py +0 -0
  92. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/identifier_normalizer.py +0 -0
  93. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/index_validator.py +0 -0
  94. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/interceptor.py +0 -0
  95. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/logging_config.py +0 -0
  96. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/mappings/__init__.py +0 -0
  97. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/mappings/datatypes.py +0 -0
  98. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/mappings/document_filters.py +0 -0
  99. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/mappings/functions.py +0 -0
  100. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/metadata_cache.py +0 -0
  101. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/metrics.py +0 -0
  102. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/models.py +0 -0
  103. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/normalizer.py +0 -0
  104. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/parser.py +0 -0
  105. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/performance_monitor.py +0 -0
  106. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/pipeline.py +0 -0
  107. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/refiner.py +0 -0
  108. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/reserved_words.py +0 -0
  109. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/returning_plan.py +0 -0
  110. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/skipped_table_set.py +0 -0
  111. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/statement_filter.py +0 -0
  112. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/transaction_translator.py +0 -0
  113. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/translator.py +0 -0
  114. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/type_translator.py +0 -0
  115. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/sql_translator/validator.py +0 -0
  116. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/testing/__init__.py +0 -0
  117. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/testing/base_fixture_builder.py +0 -0
  118. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/type_mapping.py +0 -0
  119. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/vector_metrics.py +0 -0
  120. {iris_pgwire-1.5.3 → iris_pgwire-1.5.5}/src/iris_pgwire/vector_optimizer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iris-pgwire
3
- Version: 1.5.3
3
+ Version: 1.5.5
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.3"
9
+ __version__ = "1.5.5"
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,156 @@
1
+ """
2
+ Utilities for translating PostgreSQL vector syntax (pgvector) to IRIS vector syntax.
3
+
4
+ IRIS HNSW index syntax (confirmed against live IRIS instance):
5
+ CREATE INDEX name ON [TABLE] schema.table (col)
6
+ AS HNSW(Distance='Cosine'[, M=16, efConstruction=64])
7
+
8
+ Distance values: 'Cosine' | 'DotProduct'
9
+ M and efConstruction are optional (IRIS defaults: M=16, efConstruction=64).
10
+ Distance is required for correct query-time behaviour.
11
+ """
12
+
13
+ import re
14
+ from dataclasses import dataclass, field
15
+ from typing import Any, Literal, Optional
16
+
17
+
18
+ @dataclass
19
+ class HnswIndexSpec:
20
+ """Parsed HNSW index specification."""
21
+
22
+ index_name: str
23
+ table_name: str # may include schema, e.g. "SQLUser.MyTable"
24
+ column_name: str
25
+ distance_metric: Literal["Cosine", "DotProduct"]
26
+ m: Optional[int] = None
27
+ ef_construction: Optional[int] = None
28
+ if_not_exists: bool = False
29
+
30
+ # Ignored PostgreSQL options (logged as warnings)
31
+ ignored_options: dict[str, Any] = field(default_factory=dict)
32
+
33
+ def to_iris_sql(self) -> str:
34
+ """Convert to IRIS SQL.
35
+
36
+ IRIS syntax:
37
+ CREATE INDEX name ON table (col) AS HNSW(Distance='Cosine'[, M=N, efConstruction=N])
38
+
39
+ Distance is always emitted so IRIS uses the right index structure at
40
+ query time (VECTOR_COSINE → Cosine, VECTOR_DOT_PRODUCT → DotProduct).
41
+ """
42
+ params = [f"Distance='{self.distance_metric}'"]
43
+ if self.m is not None:
44
+ params.append(f"M={self.m}")
45
+ if self.ef_construction is not None:
46
+ params.append(f"efConstruction={self.ef_construction}")
47
+ return (
48
+ f"CREATE INDEX {self.index_name} ON {self.table_name} "
49
+ f"({self.column_name}) AS HNSW({', '.join(params)})"
50
+ )
51
+
52
+ @classmethod
53
+ def from_postgres_sql(cls, sql: str) -> Optional["HnswIndexSpec"]:
54
+ """Parse an HNSW index statement in either pgvector or hybrid form.
55
+
56
+ Form 1 — pgvector standard (USING hnsw before column list):
57
+ CREATE [UNIQUE] INDEX [IF NOT EXISTS] name ON [schema.]table
58
+ USING hnsw (col vector_cosine_ops) [WITH (m=16, ef_construction=64)]
59
+
60
+ Form 2 — hybrid / user-written (column list before USING):
61
+ CREATE INDEX name ON [schema.]table (col)
62
+ USING HNSW [WITH (M=16, efConstruction=64, Distance='COSINE')]
63
+
64
+ Returns HnswIndexSpec if matched, None if not an HNSW statement.
65
+ Raises ValueError for unsupported operators (e.g. vector_l2_ops).
66
+ """
67
+ # Form 1: USING hnsw (col ops) [WITH (...)]
68
+ form1 = re.search(
69
+ r"CREATE\s+(?:UNIQUE\s+)?INDEX\s+(?P<ine>IF\s+NOT\s+EXISTS\s+)?"
70
+ r"(?P<name>\w+)\s+ON\s+(?P<table>[\w.]+)\s+"
71
+ r"USING\s+hnsw\s*\(\s*(?P<col>\w+)\s+(?P<op>\w+)\s*\)"
72
+ r"(?:\s+WITH\s*\((?P<with>[^)]*)\))?",
73
+ sql,
74
+ re.IGNORECASE,
75
+ )
76
+ if form1:
77
+ op = form1.group("op").lower()
78
+ if op == "vector_l2_ops":
79
+ raise ValueError(
80
+ "IRIS does not support L2/Euclidean distance for HNSW indexes. "
81
+ "Use vector_cosine_ops or vector_ip_ops."
82
+ )
83
+ if op == "vector_cosine_ops":
84
+ distance: Literal["Cosine", "DotProduct"] = "Cosine"
85
+ elif op == "vector_ip_ops":
86
+ distance = "DotProduct"
87
+ else:
88
+ raise ValueError(f"Unsupported vector operator for HNSW: {op}")
89
+ m, ef = _parse_with_options(form1.group("with") or "")
90
+ return cls(
91
+ index_name=form1.group("name"),
92
+ table_name=form1.group("table"),
93
+ column_name=form1.group("col"),
94
+ distance_metric=distance,
95
+ m=m,
96
+ ef_construction=ef,
97
+ if_not_exists=bool(form1.group("ine")),
98
+ )
99
+
100
+ # Form 2: (col) USING HNSW [WITH (...)]
101
+ form2 = re.search(
102
+ r"CREATE\s+(?:UNIQUE\s+)?INDEX\s+(?P<ine>IF\s+NOT\s+EXISTS\s+)?"
103
+ r"(?P<name>\w+)\s+ON\s+(?P<table>[\w.]+)\s*"
104
+ r"\(\s*(?P<col>\w+)\s*\)\s+"
105
+ r"USING\s+HNSW"
106
+ r"(?:\s+WITH\s*\((?P<with>[^)]*)\))?",
107
+ sql,
108
+ re.IGNORECASE,
109
+ )
110
+ if form2:
111
+ with_str = form2.group("with") or ""
112
+ m, ef = _parse_with_options(with_str)
113
+ distance = _parse_distance_from_with(with_str)
114
+ return cls(
115
+ index_name=form2.group("name"),
116
+ table_name=form2.group("table"),
117
+ column_name=form2.group("col"),
118
+ distance_metric=distance,
119
+ m=m,
120
+ ef_construction=ef,
121
+ if_not_exists=bool(form2.group("ine")),
122
+ )
123
+
124
+ return None
125
+
126
+
127
+ def _parse_with_options(with_str: str) -> tuple[Optional[int], Optional[int]]:
128
+ """Extract M and efConstruction / ef_construction from a WITH clause body."""
129
+ m: Optional[int] = None
130
+ ef: Optional[int] = None
131
+ for part in with_str.split(","):
132
+ kv = re.match(r"\s*(\w+)\s*=\s*(\d+)", part.strip())
133
+ if not kv:
134
+ continue
135
+ key, val = kv.group(1).lower(), int(kv.group(2))
136
+ if key == "m":
137
+ m = val
138
+ elif key in ("efconstruction", "ef_construction"):
139
+ ef = val
140
+ return m, ef
141
+
142
+
143
+ def _parse_distance_from_with(with_str: str) -> Literal["Cosine", "DotProduct"]:
144
+ """Extract Distance= value from a WITH clause body, defaulting to Cosine."""
145
+ dist = re.search(r"Distance\s*=\s*['\"]?(\w+)['\"]?", with_str, re.IGNORECASE)
146
+ if not dist:
147
+ return "Cosine"
148
+ val = dist.group(1).lower()
149
+ if val in ("dotproduct", "dot_product", "innerproduct", "inner_product"):
150
+ return "DotProduct"
151
+ return "Cosine"
152
+
153
+
154
+ def normalize_vector(vector: list[float]) -> list[float]:
155
+ """Ensure vector is in a format IRIS can handle (list of floats)."""
156
+ return [float(x) for x in vector]
@@ -2930,10 +2930,14 @@ class PGWireProtocol:
2930
2930
  # For SELECT/SHOW statements, send RowDescription with column metadata
2931
2931
  # For DDL/DML statements, send NoData
2932
2932
  query = stmt.get("translated_query", stmt.get("query", ""))
2933
+ # Use original_query for RETURNING detection: the translation pipeline
2934
+ # strips RETURNING before storing translated_query (IRIS doesn't support it
2935
+ # natively), so has_returning would always be False if we use translated_query.
2936
+ original_query = stmt.get("original_query", query)
2933
2937
  query_upper = query.strip().upper()
2934
2938
 
2935
2939
  plan = ReturningPlan.from_sql(
2936
- query,
2940
+ original_query,
2937
2941
  metadata_cache=self.iris_executor.metadata_cache,
2938
2942
  executor=self.iris_executor,
2939
2943
  )
@@ -3081,10 +3085,13 @@ class PGWireProtocol:
3081
3085
 
3082
3086
  stmt = self.prepared_statements[statement_name]
3083
3087
  query = stmt.get("translated_query", stmt.get("query", ""))
3088
+ # Use original_query for RETURNING detection: translated_query has the
3089
+ # RETURNING clause stripped (IRIS doesn't support it natively).
3090
+ original_query = stmt.get("original_query", query)
3084
3091
  query_upper = query.strip().upper()
3085
3092
 
3086
3093
  plan = ReturningPlan.from_sql(
3087
- query,
3094
+ original_query,
3088
3095
  metadata_cache=self.iris_executor.metadata_cache,
3089
3096
  executor=self.iris_executor,
3090
3097
  )
@@ -253,15 +253,27 @@ class IRISSQLConstructRegistry:
253
253
  notes="Strip IF NOT EXISTS from CREATE INDEX for IRIS compatibility",
254
254
  )
255
255
 
256
- # HNSW Index Support (PostgreSQL USING hnsw -> IRIS AS HNSW)
256
+ # HNSW Index — Form 1: pgvector USING hnsw (col ops) [WITH (...)]
257
+ # Handles schema-qualified table names (schema.table)
257
258
  self.add_construct(
258
259
  name="HNSW_INDEX",
259
- pattern=r"CREATE\s+INDEX\s+(?:IF\s+NOT\s+EXISTS\s+)?(\w+)\s+ON\s+(\w+)\s+USING\s+hnsw\s*\([^)]+\)",
260
- replacement=None, # Handled by post_process
260
+ pattern=r"CREATE\s+(?:UNIQUE\s+)?INDEX\s+(?:IF\s+NOT\s+EXISTS\s+)?\w+\s+ON\s+[\w.]+\s+USING\s+hnsw\s*\([^)]+\)(?:\s+WITH\s*\([^)]*\))?",
261
+ replacement=None,
261
262
  post_process=self._translate_hnsw_index,
262
263
  confidence=0.95,
263
264
  construct_type=ConstructType.SYNTAX,
264
- notes="Translate PostgreSQL HNSW index to IRIS syntax",
265
+ notes="Translate pgvector USING hnsw (col ops) to IRIS AS HNSW(Distance=...)",
266
+ )
267
+ # HNSW Index — Form 2: col-first (col) USING HNSW [WITH (...)]
268
+ # e.g. CREATE INDEX i ON t (emb) USING HNSW WITH (M=16, Distance='COSINE')
269
+ self.add_construct(
270
+ name="HNSW_INDEX_COL_FIRST",
271
+ pattern=r"CREATE\s+(?:UNIQUE\s+)?INDEX\s+(?:IF\s+NOT\s+EXISTS\s+)?\w+\s+ON\s+[\w.]+\s*\([^)]+\)\s+USING\s+HNSW(?:\s+WITH\s*\([^)]*\))?",
272
+ replacement=None,
273
+ post_process=self._translate_hnsw_index,
274
+ confidence=0.95,
275
+ construct_type=ConstructType.SYNTAX,
276
+ notes="Translate col-first (col) USING HNSW WITH (...) to IRIS AS HNSW(Distance=...)",
265
277
  )
266
278
 
267
279
  def _translate_hnsw_index(self, match: re.Match, full_sql: str) -> str:
@@ -1,94 +0,0 @@
1
- """
2
- Utilities for translating PostgreSQL vector syntax (pgvector) to IRIS vector syntax.
3
- """
4
-
5
- import re
6
- from dataclasses import dataclass, field
7
- from typing import Any, Literal, Optional
8
-
9
-
10
- @dataclass
11
- class HnswIndexSpec:
12
- """Parsed HNSW index specification."""
13
-
14
- index_name: str
15
- table_name: str
16
- column_name: str
17
- distance_metric: Literal["COSINE", "DOT_PRODUCT"]
18
- if_not_exists: bool = False
19
-
20
- # Ignored PostgreSQL options (logged as warnings)
21
- ignored_options: dict[str, Any] = field(default_factory=dict)
22
-
23
- def to_iris_sql(self) -> str:
24
- """
25
- Convert to IRIS SQL index creation syntax.
26
-
27
- Returns:
28
- IRIS SQL string (e.g., "CREATE INDEX idx ON table (col) AS HNSW")
29
- """
30
- # Note: IRIS doesn't specify distance metric in the CREATE INDEX statement
31
- # It's inferred from usage, but we validate support here.
32
- # Actually, IRIS HNSW index syntax is simply:
33
- # CREATE INDEX idxname ON table (col) AS HNSW
34
- return f"CREATE INDEX {self.index_name} ON {self.table_name} ({self.column_name}) AS HNSW"
35
-
36
- @classmethod
37
- def from_postgres_sql(cls, sql: str) -> Optional["HnswIndexSpec"]:
38
- """
39
- Parse a PostgreSQL HNSW index creation statement.
40
-
41
- Example: CREATE INDEX IF NOT EXISTS idx ON documents USING hnsw (embedding vector_cosine_ops)
42
-
43
- Args:
44
- sql: PostgreSQL CREATE INDEX statement
45
-
46
- Returns:
47
- HnswIndexSpec if matched, else None
48
- """
49
- # Regex to capture parts
50
- pattern = (
51
- r"CREATE\s+INDEX\s+(?P<if_not_exists>IF\s+NOT\s+EXISTS\s+)?(?P<index_name>\w+)\s+ON\s+"
52
- r"(?P<table_name>\w+)\s+USING\s+hnsw\s*\((?P<column_name>\w+)\s+(?P<operator>\w+)\)"
53
- )
54
-
55
- match = re.search(pattern, sql, re.IGNORECASE)
56
- if not match:
57
- return None
58
-
59
- operator = match.group("operator").lower()
60
-
61
- if operator == "vector_l2_ops":
62
- raise ValueError(
63
- "IRIS does not support L2/Euclidean distance for HNSW indexes. "
64
- "Use vector_cosine_ops or vector_ip_ops."
65
- )
66
-
67
- distance_metric: Literal["COSINE", "DOT_PRODUCT"]
68
- if operator == "vector_cosine_ops":
69
- distance_metric = "COSINE"
70
- elif operator == "vector_ip_ops":
71
- distance_metric = "DOT_PRODUCT"
72
- else:
73
- raise ValueError(f"Unsupported vector operator for HNSW: {operator}")
74
-
75
- return cls(
76
- index_name=match.group("index_name"),
77
- table_name=match.group("table_name"),
78
- column_name=match.group("column_name"),
79
- distance_metric=distance_metric,
80
- if_not_exists=bool(match.group("if_not_exists")),
81
- )
82
-
83
-
84
- def normalize_vector(vector: list[float]) -> list[float]:
85
- """
86
- Ensure vector is in a format IRIS can handle (list of floats).
87
-
88
- Args:
89
- vector: List of floats
90
-
91
- Returns:
92
- Normalized vector
93
- """
94
- return [float(x) for x in vector]
File without changes
File without changes
File without changes
File without changes