Module: ActiveRecord::Materialized::DebeziumEnvelope
- Defined in:
- lib/activerecord/materialized/debezium_envelope.rb
Overview
Maps a Debezium change envelope to an ingest_change descriptor. Pure and dependency-free: no Kafka / Debezium / Kafka-Connect runtime — just a mapping over the decoded envelope hash a consumer already holds. It removes the boilerplate (and the easy-to-miss cases: snapshot reads, tombstones, the nested/unwrapped shapes) every consumer otherwise hand-writes.
The envelope shape (a Debezium MySQL/Postgres connector event, whether or not the
ExtractNewRecordState "unwrap" SMT has been applied — a nested payload is unwrapped here):
{ "op" => "u",
"before" => { "id" => 1, "category" => "books", ... }, # full row image (see below)
"after" => { "id" => 1, "category" => "games", ... },
"ts_ms" => 1710000000000, # connector processing time (unused)
"source" => { "table" => "line_items", "ts_ms" => 1710000000000, ... } }
The source.ts_ms (the DB commit time, monotonic within a source) is forwarded as the source_ts
watermark, so a consumer relaying envelopes gets out-of-order suppression + freshness
(SourceWatermark) for free. The top-level ts_ms is a different clock (the connector's
processing time), so it is not used as a fallback — mixing clocks would break monotonicity.
Correct partition-moving updates require a full before image (+binlog-row-image=FULL+ on
MySQL, REPLICA IDENTITY FULL on Postgres); with only the primary key in before, the old
partition is under-maintained until reconciliation heals it. See docs/out-of-band-writes.md
and the CDC section of the README. This adapter is Debezium-specific; a Maxwell/other envelope
(no op) raises rather than being silently dropped.
Class Method Summary collapse
-
.to_change_descriptor(envelope, table = nil) ⇒ Hash?
{ table:, operation:, before:, after: }(plussource_ts:when the envelope carries a usablets_ms) for ingest_change, ornilfor a tombstone (nothing to relay).
Class Method Details
.to_change_descriptor(envelope, table = nil) ⇒ Hash?
Returns { table:, operation:, before:, after: } (plus source_ts: when the
envelope carries a usable ts_ms) for ActiveRecord::Materialized.ingest_change, or nil for a
tombstone (nothing to relay).
41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/activerecord/materialized/debezium_envelope.rb', line 41 def self.to_change_descriptor(envelope, table = nil) return nil if envelope.nil? # Kafka tombstone change = unwrap(envelope) op = fetch(change, "op") raise ArgumentError, "not a Debezium change envelope (no op)" if op.nil? descriptor = { table: resolve_table(table, change), operation: operation_for(op), before: fetch(change, "before"), after: fetch(change, "after") } ts = source_ts(change) ts ? descriptor.merge(source_ts: ts) : descriptor end |