Per-type binlog event body decoder — the second decode layer, over
Capstan.Binlog.Event.
Capstan.Binlog.Event.parse/1 has already parsed the 19-byte header and verified
and stripped the CRC32 trailer. This module dispatches on the event type byte
and decodes event.body into a per-type term. It does not re-parse the header
or re-verify the CRC.
Return contract
decode/1 returns one of three shapes:
{:ok, decoded}— a recognised event, decoded to its per-type term.{:halt, :unsupported_transaction_shape}— anXA_PREPARE_LOG_EVENT(type 38). This is a fail-closed control signal, distinct from both success and a decode error, so a consumer that omits a{:halt, _}clause crashes loudly rather than silently treating XA rows as a normal event (ADR-0003).{:error, reason}— an event C1 refuses: a compressed transaction payload, or an unknown type byte. An unknown type fails closed and is never silently skipped — a dropped event of an unrecognised shape could hide a condition capstan must halt on.
Two silent-failure safety branches (the reason this module is high-Risk)
ROWS_QUERY_LOG_EVENT(29) carries the complete original SQL of the row change with every literal value — a total Rule-1 leak if surfaced. The header is recognised and the body is discarded:decode/1returns{:rows_query, :discarded}and the SQL text never enters the returned term.XA_PREPARE_LOG_EVENT(38) halts fail-closed. Its row images ride the prepare event and are committed later by a separateXID— or discarded byXA ROLLBACK— so treating them as committed delivers rolled-back rows effect-once. The halt is keyed purely on the type byte (ADR-0003).
Rule-1 note on QUERY text
For QUERY (2) the SQL text is returned — Capstan.Assembler inspects it to
detect BEGIN/COMMIT and self-committing DDL terminators. That raw text is
Rule-1-sensitive (DDL routinely embeds literals like DEFAULT 'secret') and must
never be logged. DDL redaction happens later, at the %Capstan.SchemaChange{}
boundary (Rule 1), not in this module.
Summary
Types
A successfully decoded event body.
A reason C1 refuses an event outright.
A single GTID: {server_uuid, transaction_sequence_number}.
The structural decode of a row event body: identity, present bitmap(s), raw image bytes.
Functions
Decodes one %Capstan.Binlog.Event{} body to its per-type term.
Types
@type decoded() :: {:rotate, String.t(), non_neg_integer()} | {:format_description, non_neg_integer(), String.t()} | :previous_gtids | {:gtid, gtid()} | {:query, %{schema: String.t(), sql: String.t()}} | Capstan.Binlog.TableMap.t() | rows() | {:xid, non_neg_integer()} | :stop | :heartbeat | {:rows_query, :discarded}
A successfully decoded event body.
@type error_reason() :: :compressed_payload_unsupported | {:unknown_event_type, byte()}
A reason C1 refuses an event outright.
@type gtid() :: {String.t(), pos_integer()}
A single GTID: {server_uuid, transaction_sequence_number}.
@type rows() :: {:write_rows, non_neg_integer(), binary(), binary()} | {:delete_rows, non_neg_integer(), binary(), binary()} | {:update_rows, non_neg_integer(), binary(), binary(), binary()}
The structural decode of a row event body: identity, present bitmap(s), raw image bytes.
Functions
@spec decode(Capstan.Binlog.Event.t()) :: {:ok, decoded()} | {:halt, :unsupported_transaction_shape} | {:error, error_reason()}
Decodes one %Capstan.Binlog.Event{} body to its per-type term.
See the module doc for the {:ok, _} / {:halt, _} / {:error, _} contract and
the two safety branches (ROWS_QUERY discard, XA_PREPARE halt).