Skip to content

Commit e3a2ab3

Browse files
Plumb raw_message through cursor classifier; match no-tx on raw text; addr-suffix continuation failures; correct truncation marker unit
Five cycle-21 raw_message-pipeline follow-ups: - ``_call_client``'s six non-OperationalError classifier arms (DqliteConnectionError, ClusterPolicyError, ClusterError, ProtocolError, DataError, InterfaceError catch-all) all dropped ``raw_message`` when wrapping into the dbapi exception. The cycle-21 cursor commit only plumbed it through the client.OperationalError → dbapi exception arm. Plumb on every arm via ``getattr(e, "raw_message", None) or str(e)`` so callers reading the un-truncated diagnostic don't need to walk ``__cause__`` based on which exception class the server emitted. - ``_is_no_tx_rollback_error`` (client) and ``_is_no_transaction_error`` (dbapi) matched against ``str(exc)`` which is truncated to ~1 KiB. A long server message that has the no-tx clause beyond the cap missed the substring and surfaced the no-tx as a real error. Match against ``raw_message`` instead. - ``DqliteProtocol._read_continuation``'s ServerFailure-mid-stream arm raised ``OperationalError`` without the addr suffix, alone among the eight ``raise OperationalError`` sites in the file. Append it for log-correlation parity. - ``OperationalError.message``'s truncation marker said "bytes" but ``len(message)`` and the slice cap are codepoints. An operator reading the marker now sees "codepoints" — the actual unit. - Connect-time wrap double-prefixed ``raw_message`` with ``"Failed to connect: "``. Cycle 21 introduced ``raw_message`` as the verbatim-server-text contract; the prefix belongs on the user-facing ``message`` only. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 6d81ad8 commit e3a2ab3

3 files changed

Lines changed: 17 additions & 3 deletions

File tree

src/dqliteclient/connection.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,14 @@ def _is_no_tx_rollback_error(exc: BaseException) -> bool:
604604
code = getattr(exc, "code", None)
605605
if code is None or _primary_sqlite_code(code) != 1: # SQLITE_ERROR
606606
return False
607-
msg = str(exc).lower()
607+
# Match against the un-truncated server text (raw_message) rather
608+
# than ``str(exc)`` (truncated to _MAX_DISPLAY_MESSAGE codepoints).
609+
# A long server message that has the no-tx clause beyond the
610+
# truncation cap would otherwise miss the substring and fail to
611+
# silent-swallow. Mirrors the BUSY-checkpoint matcher fix that
612+
# established the discipline.
613+
raw = getattr(exc, "raw_message", None) or str(exc)
614+
msg = raw.lower()
608615
return any(s in msg for s in NO_TRANSACTION_MESSAGE_SUBSTRINGS)
609616

610617

src/dqliteclient/exceptions.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,13 @@ def __init__(self, code: int, message: str) -> None:
9393
self.code = code
9494
self.raw_message = message
9595
if len(message) > self._MAX_DISPLAY_MESSAGE:
96+
# ``len(message)`` and the slice cap count Python codepoints,
97+
# not UTF-8 bytes. Match the unit in the marker so an
98+
# operator inspecting a truncated message can compute the
99+
# original size without converting between units.
96100
overflow = len(message) - self._MAX_DISPLAY_MESSAGE
97101
self.message = (
98-
f"{message[: self._MAX_DISPLAY_MESSAGE]}... [truncated, {overflow} bytes]"
102+
f"{message[: self._MAX_DISPLAY_MESSAGE]}... [truncated, {overflow} codepoints]"
99103
)
100104
else:
101105
self.message = message

src/dqliteclient/protocol.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -768,7 +768,10 @@ async def _read_continuation(self, deadline: float | None = None) -> RowsRespons
768768
# Server-authored failure mid-stream: surface the SQLite code
769769
# so sqlalchemy's is_disconnect and dbapi's code-to-exception
770770
# map can classify correctly (leader flip, constraint, etc.).
771-
raise OperationalError(e.code, e.message) from e
771+
# Append the addr suffix so the operator log shows which
772+
# peer emitted the failure — matching the eight sibling
773+
# ``raise OperationalError`` sites in this module.
774+
raise OperationalError(e.code, f"{e.message}{self._addr_suffix()}") from e
772775
except _WireProtocolError as e:
773776
raise ProtocolError(f"Wire decode failed{self._addr_suffix()}: {e}") from e
774777

0 commit comments

Comments
 (0)