Skip to content

Commit 3312254

Browse files
committed
Fix protocol AttributeError and KeyError (v1.1.7)
1 parent 8703b69 commit 3312254

File tree

5 files changed

+41
-27
lines changed

5 files changed

+41
-27
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.1.7] - 2026-01-17
9+
10+
### Fixed
11+
- **AttributeError in Protocol**: Fixed `PGWireProtocol` to use dictionary access instead of dot notation for `performance_stats` returned by the new `SQLPipeline`.
12+
- **KeyError in Protocol**: Added missing `cache_hit` key to the `performance_stats` dictionary in `SQLTranslator` to prevent crashes during statement parsing.
13+
- **Embedded Namespace Context**: Strengthened the `SetNamespace` logic in background execution threads to ensure consistent IRIS namespace context and prevent intermittent "Class not found" errors.
14+
- **Redundant SQL Mapping**: Removed legacy schema translation code in `iris_executor.py` that was conflicting with the centralized `SQLPipeline`.
15+
816
## [1.1.6] - 2026-01-17
917

1018
### Changed

src/iris_pgwire/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
caretdev/sqlalchemy-iris.
77
"""
88

9-
__version__ = "1.1.6"
9+
__version__ = "1.1.7"
1010
__author__ = "Thomas Dyar <thomas.dyar@intersystems.com>"
1111

1212
# Don't import server/protocol in __init__ to avoid sys.modules conflicts

src/iris_pgwire/iris_executor.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,23 +1049,17 @@ def fetchone(self):
10491049
async def _execute_embedded_async(
10501050
self, sql: str, params: list | None = None, session_id: str | None = None
10511051
) -> dict[str, Any]:
1052-
"""
1053-
Execute SQL using IRIS embedded Python with proper async threading
1054-
1055-
This method runs the blocking IRIS operations in a thread pool to avoid
1056-
blocking the event loop, following constitutional async requirements.
1057-
"""
1052+
"""Execute query in IRIS embedded Python environment (async wrapper)"""
10581053

10591054
def _sync_execute():
10601055
"""Synchronous IRIS execution in thread pool"""
10611056
import iris
10621057

1063-
# CRITICAL: Ensure correct namespace context in background thread (Feature 036 Fix)
1064-
# Embedded Python process context is thread-local or needs explicit refresh
10651058
if hasattr(iris, "system") and hasattr(iris.system, "Process"):
10661059
iris.system.Process.SetNamespace(self.iris_config.get("namespace", "USER"))
10671060

10681061
# Log entry to embedded execution path
1062+
10691063
logger.info(
10701064
"🔍 EXECUTING IN EMBEDDED MODE",
10711065
sql_preview=sql[:100],

src/iris_pgwire/protocol.py

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -229,10 +229,10 @@ async def translate_sql(
229229
"skip_reason": result.skip_reason,
230230
"command_tag": result.command_tag,
231231
"translation_used": True,
232-
"performance_stats": {
233-
"translation_time_ms": tracker.start_time
234-
and (time.perf_counter() - tracker.start_time) * 1000
235-
},
232+
"performance_stats": result.performance_stats,
233+
"construct_mappings": [],
234+
"warnings": [],
235+
"validation_result": None,
236236
}
237237

238238
except Exception as e:
@@ -1361,26 +1361,30 @@ async def _handle_single_statement(self, query: str, send_ready: bool = True):
13611361
if translation_result.get("translation_used") and translation_result.get(
13621362
"construct_mappings"
13631363
):
1364-
perf_stats = translation_result["performance_stats"]
1364+
perf_stats = translation_result.get("performance_stats", {})
13651365
logger.info(
13661366
"IRIS constructs translated",
13671367
connection_id=self.connection_id,
13681368
constructs_count=len(translation_result["construct_mappings"]),
1369-
translation_time_ms=perf_stats.translation_time_ms if perf_stats else 0,
1370-
cache_hit=perf_stats.cache_hit if perf_stats else False,
1369+
translation_time_ms=perf_stats.get("normalization_time_ms", 0)
1370+
if perf_stats
1371+
else 0,
1372+
cache_hit=perf_stats.get("cache_hit", False) if perf_stats else False,
13711373
)
13721374

13731375
# Execute translated SQL against IRIS
13741376
result = await self.iris_executor.execute_query(final_sql)
13751377

13761378
# Add translation metadata to result for debugging/monitoring
13771379
if translation_result.get("translation_used"):
1378-
perf_stats = translation_result["performance_stats"]
1380+
perf_stats = translation_result.get("performance_stats", {})
13791381
result["translation_metadata"] = {
13801382
"original_sql": translation_result["original_sql"],
13811383
"constructs_translated": len(translation_result.get("construct_mappings", [])),
1382-
"translation_time_ms": perf_stats.translation_time_ms if perf_stats else 0,
1383-
"cache_hit": perf_stats.cache_hit if perf_stats else False,
1384+
"translation_time_ms": perf_stats.get("normalization_time_ms", 0)
1385+
if perf_stats
1386+
else 0,
1387+
"cache_hit": perf_stats.get("cache_hit", False) if perf_stats else False,
13841388
"warnings": translation_result.get("warnings", []),
13851389
}
13861390

@@ -2626,10 +2630,12 @@ async def handle_parse_message(self, body: bytes):
26262630
"param_types": param_types,
26272631
"translation_metadata": {
26282632
"constructs_translated": len(translation_result.get("construct_mappings", [])),
2629-
"translation_time_ms": translation_result[
2630-
"performance_stats"
2631-
].translation_time_ms,
2632-
"cache_hit": translation_result["performance_stats"].cache_hit,
2633+
"translation_time_ms": translation_result.get("performance_stats", {}).get(
2634+
"normalization_time_ms", 0
2635+
),
2636+
"cache_hit": translation_result.get("performance_stats", {}).get(
2637+
"cache_hit", False
2638+
),
26332639
"warnings": translation_result.get("warnings", []),
26342640
},
26352641
}
@@ -2652,13 +2658,15 @@ async def handle_parse_message(self, body: bytes):
26522658
if translation_result.get("translation_used") and translation_result.get(
26532659
"construct_mappings"
26542660
):
2655-
perf_stats = translation_result["performance_stats"]
2661+
perf_stats = translation_result.get("performance_stats", {})
26562662
logger.info(
26572663
"IRIS constructs translated in prepared statement",
26582664
connection_id=self.connection_id,
26592665
statement_name=statement_name,
26602666
constructs_count=len(translation_result["construct_mappings"]),
2661-
translation_time_ms=perf_stats.translation_time_ms if perf_stats else 0,
2667+
translation_time_ms=perf_stats.get("normalization_time_ms", 0)
2668+
if perf_stats
2669+
else 0,
26622670
)
26632671

26642672
# Send ParseComplete response

src/iris_pgwire/sql_translator/normalizer.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919

2020
import re
2121
import time
22-
from dataclasses import dataclass
22+
from dataclasses import dataclass, field
23+
from typing import Any
2324

2425
from ..conversions.json_path import JsonPathBuilder
2526
from ..schema_mapper import translate_input_schema
@@ -42,6 +43,7 @@ class TranslationResult:
4243
was_skipped: bool = False
4344
skip_reason: SkipReason | None = None
4445
command_tag: str = ""
46+
performance_stats: dict[str, Any] = field(default_factory=dict)
4547

4648

4749
class SQLTranslator:
@@ -192,6 +194,7 @@ def normalize_sql_with_result(
192194
was_skipped=True,
193195
skip_reason=filter_result.reason,
194196
command_tag=filter_result.command_tag,
197+
performance_stats=self._last_metrics.copy(),
195198
)
196199

197200
normalized_sql, enum_count = self.enum_translator.translate(normalized_sql)
@@ -216,9 +219,10 @@ def normalize_sql_with_result(
216219
"boolean_translation_count": bool_count,
217220
"enum_translation_count": enum_count,
218221
"sla_violated": sla_violated,
222+
"cache_hit": False,
219223
}
220224

221-
return TranslationResult(sql=normalized_sql)
225+
return TranslationResult(sql=normalized_sql, performance_stats=self._last_metrics.copy())
222226

223227
def _translate_json_operators(self, sql: str) -> tuple[str, int]:
224228
"""Translate PostgreSQL JSON operators to IRIS JSON_VALUE/JSON_QUERY"""

0 commit comments

Comments
 (0)