moonbinlog

    Pure MoonBit MySQL binlog decoding library

    mysql
    binlog
    parser
    Download zip
    Version
    0.1.0
    License
    Apache-2.0
    Last updated
    5 hours ago
    Downloads
    4

    #MoonBinlog

    纯 MoonBit 的 MySQL Binlog 解析库,正在按功能逐步开发。

    当前已实现未加密 Binlog 的文件标识、v4 公共事件头、MySQL 8.0 格式描述、CRC32 校验、表映射、常用字段值、ROWS v2 事件,以及显式边界的事务型 RBR 汇总。 输入使用 BytesView,不依赖数据库连接或第三方解析程序。

    现已提供 BinlogFileReader,可顺序遍历已读入内存的完整文件字节,并为每个事件返回原始视图、body、真实偏移和当前格式上下文。

    查看本地文件可运行 moon run --target native cmd/moonbinlog -- FILE;仅查看统计添加 --summary,机器处理可添加 --json 输出 JSON Lines。文件默认上限 64 MiB,参数、输出协议和退出码见 CLI 文档。

    --rows --schema PATH 自动以 JSON Lines 显示带列名的 INSERT / UPDATE / DELETE,所有行与摘要标记为 unconfirmed。精确值、原始字节和缺失列规则见行变化 CLI。

    --transactions --schema PATH 输出显式 BEGIN 到 XID/COMMIT/ROLLBACK 的事务计数与各表汇总,EOF 拒绝未结束事务;与 --rows 互斥。增加 --include-rows 在完整提交对象中导出 row_changes,回滚不导出行值,详见事务 CLI。

    行/事务模式可使用 --database NAME、--table NAME 精确筛选输出;事务以整笔选择,所有输入仍完整解码,规则见库表筛选。

    --schema-template FILE 从 TABLE_MAP 提取 schema 草稿:物理列描述自动填充,column_N 标签与整数 unsigned:null 需要对照 DDL 确认。同名表结构冲突时失败,正常 EOF 后整份输出,详见 生成草稿。可选 --use-table-metadata 根据可用元数据补齐名称/整数符号,严格检查缺损及冲突,见 元数据提取。

    --decode-rotate FILE 在事件 JSON 中增加轮转目标位置、hex 和可用 UTF-8 文本;--summary 仍验证全部轮转,详见 轮转 CLI。

    --chain FILE1 FILE2 ... 显式浏览多份有轮转连接的日志,事件附来源、成功后汇总;可组合 --summary/--decode-rotate,总预算由 --max-total-bytes 控制。行/事务模式仍为单文件,见 多文件 CLI。

    #使用示例

    ///|
    test "validate a file signature and decode a common header" {
    @moonbinlog.validate_file_header([0xfe, 0x62, 0x69, 0x6e])
    let header : Bytes = [
    0x78, 0x56, 0x34, 0x12, 0x1e, 0x01, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
    0x17, 0x00, 0x00, 0x00, 0x00, 0x00,
    ]
    let decoded = @moonbinlog.EventHeader::parse(header, file_offset=4)
    assert_eq(decoded.event_size, 19U)
    assert_eq(decoded.next_position, 23U)
    }

    #当前接口与边界

    • BinlogEventStream 接收文件标识及逐个完整事件,维护物理偏移和 FDE;--stream 可逐事件读取本地文件,见 逐事件读取。

    • BinlogFileReader::new 校验文件标识;next() 按事件大小遍历并校验,首个事件要求 FDE。未知类型保留原始数据,错误后永久停止;本层不进行业务事件分发。详见文件遍历说明。

    • validate_file_header 校验前四字节 FE 62 69 6E,不据此确认 Binlog 版本。
    • EventHeader::parse 读取前 19 字节,保留时间、类型、服务端 ID、事件长度、日志位置和标志。
    • 调用者传入真实 file_offset;它与日志头中的 next_position 分开保存。
    • 事件长度下限为 19 字节;默认上限为 16 MiB,可显式调整。
    • 数据截断、错误文件标识、非法事件长度及上限配置通过 DecodeError 返回。
    • EventHeader::parse 允许仅输入头部;格式描述及 payload 接口要求包含完整事件。
    • FormatDescription::parse 验证 MySQL 8.0.x、Binlog v4、19 字节公共头和事件长度表,建立不可变校验上下文。
    • FormatDescription::payload 检查后续事件的完整性,返回去除公共头和校验尾部的 body;它不解码 body 的业务语义。
    • CRC32 策略验证普通事件并移除四字节 CRC;OFF 策略保留普通事件的全部 body。
    • 格式描述自身始终校验 CRC32,校验时仅忽略它的 in-use 标志位。
    • 遇到新格式描述必须重新解析建立上下文;未知算法、版本和格式明确报错。
    • RotateEvent::parse 解析类型 4 的目标文件名原始字节和完整 UInt64 位置,验证 post-header、完整性及 CRC32/OFF,不打开下一份文件,见 轮转说明。
    • GtidEvent::parse 解析类型 33/34 的标识、逻辑时钟及可选提交时间/事务长度/版本/ticket,严格校验完整字段阶段,不绑定或确认事务,见 GTID 事件。
    • PreviousGtidsEvent::parse 解析类型 35 的历史 SID / GTID 区间,支持累计配额、CRC32/OFF 和精确整数;尚不绑定事务 GTID,见 历史 GTID 集合。
    • TableMap::parse 解析表 ID、库表名、列类型、逐列原始元数据及 nullable 标志,可选元数据按 TLV 保留。
    • SchemaConfig::parse 校验外部 JSON 配置;bind 按库表名、列数及逐列物理描述匹配 TABLE_MAP,返回列标签和整数符号向量,详见 schema 说明。
    • TableMapContext 提供有容量及原始事件字节预算的映射缓存;同 ID 覆盖,失败清空。decode_rows 会在语句结束时清理,手动消费仍须显式 clear,新文件/格式须 reset。
    • TableColumn::decode_integer 解码五种整数类型,显式指定 signed/unsigned,保留完整 Int64/UInt64 精度并返回消费字节数。
    • TableColumn::decode_string 解码 VARCHAR/STRING 的长度前缀,返回原始字节及消费长度;不推断字符集,不转换或补齐 CHAR/BINARY 填充。
    • TableColumn::decode_blob 支持 BLOB/TEXT 的 1~4 字节长度前缀,返回原始字节及消费长度;默认内容上限为 16 MiB,可显式配置。
    • TableColumn::decode_json 解码完整 binary JSON 的普通标量及小/大容器,保留整数精度、浮点位模式,拒绝 opaque/partial diff,详见 JSON 字段。CLI 导出为 json_text 字符串。
    • TableColumn::decode_decimal 支持 NEWDECIMAL 的 65 位精度与最多 30 位小数,返回保留 scale 的精确十进制字符串及消费长度。
    • TableColumn::decode_temporal 支持 DATE、YEAR、DATETIME2、TIME2、TIMESTAMP2,保留日期分量、带符号时长或原始时间戳秒数及微秒,不进行时区转换。
    • TableColumn::decode_floating 解码 FLOAT/DOUBLE,返回相应精度的有限值、原始位模式及消费长度;保留负零和次正规数,明确拒绝 NaN/无穷大。
    • TableColumn::decode_bit 支持 BIT(1)~BIT(64),返回 UInt64、声明位宽和消费长度;大端解码并屏蔽部分首字节的未使用高位。
    • TableColumn::decode_enum_set 解析 STRING 元数据中内嵌的 ENUM/SET,分别返回枚举索引或集合位掩码及消费长度,不推断成员名称或数量。
    • TableMap::decode_row_image 组合已有字段解码器,处理列存在/紧凑 NULL 位图,区分缺失列、NULL 和实际值,返回按表列号排列的结果及消费长度;整数符号属性必须显式提供。
    • TableMapContext::decode_rows 校验 v2 行事件、查找表映射并解码多行/更新配对,成功遇到 STMT_END_F 或任意解析失败均清空缓存,返回表定义快照。
    • GtidTransactionDecoder 将紧邻的 GTID/匿名事件与显式事务结果关联,外层范围/预算包括前缀,任何错误永久终止实例,详见 GTID 事务绑定。CLI 事务模式可通过 --with-gtid 显式选择该绑定器。
    • TransactionBoundary::parse 校验 QUERY/XID 边界;TransactionDecoder 缓存显式 BEGIN 后的行事件,在 XID/COMMIT 返回事务,ROLLBACK 或错误时丢弃,EOF 必须调用 finish 检查未结束事务。
    • CLI 已支持文件读取、事件列表、外部 schema 行变化及显式事务汇总/行导出。--rows 仍不确认提交;--transactions 只对调用者已知为事务型表的受支持显式区间报告提交/回滚,--include-rows 可附带提交行值。--with-gtid 可附带 GTID 绑定;其他字段类型、XA/DDL/隐式事务或非事务表语义尚未实现。
    • 合成样本由协议构造和独立 zlib 校验生成;另有九组真实 MySQL 8.0.43 日志(含三组 JSON 捕获和三组 GTID=ON 捕获),已与官方 mysqlbinlog 逐字段对照,详见集成验证。Java 客户端差分尚未完成。

    格式描述的 body 布局、OFF 特例与校验标志位规则见 格式与校验说明。 表映射范围、元数据宽度及上下文生命周期见 表映射说明。 整数类型、符号属性及 NULL 边界见 整数值说明。 字符串类型、长度元数据及字符集边界见 字符串值说明。 BLOB/TEXT 长度、安全边界及支持范围见 BLOB 值说明。 DECIMAL 的精度、符号及分组校验见 DECIMAL 值说明。 日期时间类型、微秒精度和零日期边界见 日期时间值说明。 FLOAT/DOUBLE 的精度、位模式及非有限值策略见 浮点值说明。 BIT 的元数据、字节序和填充位策略见 BIT 值说明。 ENUM/SET 的元数据、存储宽度和成员边界见 ENUM/SET 值说明。 单个行镜像的位图、字段分派和资源边界见 行镜像说明。 行事件布局、多行配对及缓存生命周期见 ROWS 事件说明。 事务型 RBR 子集、连续偏移与提交/回滚边界见 事务说明。

    #验证

    Windows 可执行 ./scripts/ci.ps1 运行统一检查入口;GitHub Actions 使用固定工具链调用同一脚本,配置及环境边界见 CI 说明。下列命令列出各项独立检查:

    moon check --warn-list +unnecessary_annotation --deny-warn moon test --target native moon build --target native moon fmt --check moon info python scripts/generate_format_fixtures.py --check python scripts/generate_table_map_fixtures.py --check python scripts/generate_integer_fixtures.py --check python scripts/generate_string_fixtures.py --check python scripts/generate_blob_fixtures.py --check python scripts/generate_decimal_fixtures.py --check python scripts/generate_temporal_fixtures.py --check python scripts/generate_floating_fixtures.py --check python scripts/generate_bit_fixtures.py --check python scripts/generate_enum_set_fixtures.py --check python scripts/generate_row_image_fixtures.py --check python scripts/generate_rows_event_fixtures.py --check python scripts/generate_transaction_fixtures.py --check python scripts/generate_rotate_fixtures.py --check python scripts/generate_json_fixtures.py --check python scripts/verify_json_fixtures.py --check python scripts/verify_mysql_fixtures.py --check python scripts/test_cli.py python scripts/test_rows_cli.py python scripts/test_transactions_cli.py python scripts/test_filters_cli.py python scripts/test_json_cli.py python scripts/test_schema_template_cli.py python scripts/test_template_metadata_cli.py python scripts/test_rotate_cli.py python scripts/test_chain_cli.py python scripts/test_previous_gtids_cli.py python scripts/test_gtid_cli.py python scripts/test_gtid_transactions_cli.py python scripts/test_assigned_gtid_cli.py python scripts/test_stream_cli.py python scripts/test_stream_rows_cli.py python scripts/test_stream_transactions_cli.py python scripts/demo_acceptance.py python scripts/verify_assigned_gtid_fixtures.py --check python scripts/generate_previous_gtids_fixtures.py --check python scripts/generate_gtid_fixtures.py --check python scripts/generate_schema_example.py --check

    公共接口由 moon info 生成在 pkg.generated.mbti。

    #协议依据

    实现根据格式独立编写,当前没有复制上游实现代码或测试数据。

    #开源许可证

    Copyright 2026 Noverberrain.

    本项目采用 Apache License 2.0,版权声明见 NOTICE。

    #开发节奏

    每次完成一个功能后暂停,报告行为变化、验证结果和新增行数。 源码、测试、生成接口及文档/配置分别统计;行数包含空行与注释,不作为有效代码量或完成度指标。

    本批已完成本地打包及解包验证;实际发布等待账号与命名空间问题解决,见 发布预检查。暂停,等待用户继续指令。

    DecodeError

    pub(all) suberror DecodeError {
    InvalidMagic(actual~ : Bytes)
    TruncatedInput(file_offset~ : UInt64, expected~ : Int, actual~ : Int)
    InvalidEventLength(file_offset~ : UInt64, event_size~ : UInt)
    EventTooLarge(file_offset~ : UInt64, event_size~ : UInt, max_event_size~ : UInt)
    InvalidEventLimit(max_event_size~ : UInt)
    TruncatedEvent(file_offset~ : UInt64, expected~ : UInt, actual~ : UInt)
    UnexpectedEventType(file_offset~ : UInt64, expected~ : Byte, actual~ : Byte)
    UnsupportedBinlogVersion(file_offset~ : UInt64, version~ : UInt16)
    UnsupportedServerVersion(file_offset~ : UInt64, server_version~ : String)
    UnsupportedHeaderLength(file_offset~ : UInt64, length~ : Byte)
    InvalidFormatDescription(file_offset~ : UInt64, reason~ : String)
    UnsupportedChecksumAlgorithm(file_offset~ : UInt64, algorithm~ : Byte)
    ChecksumMismatch(file_offset~ : UInt64, expected~ : UInt, actual~ : UInt)
    FormatContextResetRequired(file_offset~ : UInt64)
    InvalidRotateEvent(file_offset~ : UInt64, reason~ : String)
    UnsupportedGtidEvent(file_offset~ : UInt64, event_type~ : Byte)
    InvalidGtidEvent(file_offset~ : UInt64, body_offset~ : Int, reason~ : String)
    InvalidPreviousGtidsEvent(file_offset~ : UInt64, body_offset~ : Int, reason~ : String)
    InvalidGtidSetLimits(max_sids~ : Int, max_intervals~ : Int)
    GtidSetLimitExceeded(file_offset~ : UInt64, reason~ : String)
    InvalidTableMap(file_offset~ : UInt64, body_offset~ : Int, reason~ : String)
    UnsupportedColumnType(file_offset~ : UInt64, column_index~ : Int, wire_type~ : Byte)
    InvalidColumnLimit(max_columns~ : Int)
    ColumnLimitExceeded(file_offset~ : UInt64, count~ : UInt64, max_columns~ : Int)
    InvalidTableContextLimit(max_tables~ : Int, max_bytes~ : UInt64)
    TableContextLimitExceeded(file_offset~ : UInt64, reason~ : String)
    UnsupportedIntegerColumn(file_offset~ : UInt64, column_index~ : Int, wire_type~ : Byte)
    TruncatedColumnValue(file_offset~ : UInt64, column_index~ : Int, expected~ : Int, actual~ : Int)
    UnsupportedStringColumn(file_offset~ : UInt64, column_index~ : Int, wire_type~ : Byte)
    InvalidStringMetadata(file_offset~ : UInt64, column_index~ : Int, reason~ : String)
    StringLengthExceeded(file_offset~ : UInt64, column_index~ : Int, length~ : Int, max_length~ : Int)
    UnsupportedBlobColumn(file_offset~ : UInt64, column_index~ : Int, wire_type~ : Byte)
    InvalidBlobMetadata(file_offset~ : UInt64, column_index~ : Int, reason~ : String)
    InvalidBlobLimit(max_value_bytes~ : Int)
    BlobTooLarge(file_offset~ : UInt64, column_index~ : Int, length~ : UInt64, max_value_bytes~ : Int)
    UnsupportedDecimalColumn(file_offset~ : UInt64, column_index~ : Int, wire_type~ : Byte)
    InvalidDecimalMetadata(file_offset~ : UInt64, column_index~ : Int, reason~ : String)
    InvalidDecimalValue(file_offset~ : UInt64, column_index~ : Int, value_offset~ : Int)
    UnsupportedTemporalColumn(file_offset~ : UInt64, column_index~ : Int, wire_type~ : Byte)
    InvalidTemporalMetadata(file_offset~ : UInt64, column_index~ : Int, reason~ : String)
    InvalidTemporalValue(file_offset~ : UInt64, column_index~ : Int, reason~ : String)
    UnsupportedFloatingColumn(file_offset~ : UInt64, column_index~ : Int, wire_type~ : Byte)
    InvalidFloatingMetadata(file_offset~ : UInt64, column_index~ : Int, expected_width~ : Int)
    NonFiniteFloatingValue(file_offset~ : UInt64, column_index~ : Int, wire_type~ : Byte, bits~ : UInt64)
    UnsupportedBitColumn(file_offset~ : UInt64, column_index~ : Int, wire_type~ : Byte)
    InvalidBitMetadata(file_offset~ : UInt64, column_index~ : Int, reason~ : String)
    UnsupportedEnumSetColumn(file_offset~ : UInt64, column_index~ : Int, wire_type~ : Byte)
    InvalidEnumSetMetadata(file_offset~ : UInt64, column_index~ : Int, reason~ : String)
    InvalidRowImage(file_offset~ : UInt64, reason~ : String)
    InvalidRowImageLimit(max_bytes~ : Int)
    RowImageLimitExceeded(file_offset~ : UInt64, max_bytes~ : Int)
    UnexpectedRowNull(file_offset~ : UInt64, column_index~ : Int)
    MissingIntegerSignedness(file_offset~ : UInt64, column_index~ : Int)
    UnsupportedRowColumn(file_offset~ : UInt64, column_index~ : Int, wire_type~ : Byte)
    UnsupportedJsonColumn(file_offset~ : UInt64, column_index~ : Int, wire_type~ : Byte)
    InvalidJsonMetadata(file_offset~ : UInt64, column_index~ : Int)
    InvalidJsonLimits(max_value_bytes~ : Int, max_depth~ : Int, max_nodes~ : Int)
    JsonLimitExceeded(file_offset~ : UInt64, column_index~ : Int, reason~ : String)
    InvalidJsonValue(file_offset~ : UInt64, column_index~ : Int, value_offset~ : Int, reason~ : String)
    UnsupportedJsonType(file_offset~ : UInt64, column_index~ : Int, value_offset~ : Int, binary_type~ : Byte)
    UnsupportedRowsEvent(file_offset~ : UInt64, event_type~ : Byte)
    InvalidRowsEvent(file_offset~ : UInt64, body_offset~ : Int, reason~ : String)
    MissingTableMap(file_offset~ : UInt64, table_id~ : UInt64)
    InvalidRowsLimit(max_rows~ : Int)
    RowsLimitExceeded(file_offset~ : UInt64, max_rows~ : Int)
    UnsupportedTransactionEvent(file_offset~ : UInt64, event_type~ : Byte)
    InvalidTransactionEvent(file_offset~ : UInt64, reason~ : String)
    UnsupportedTransactionStatement(file_offset~ : UInt64)
    InvalidTransactionState(file_offset~ : UInt64, reason~ : String)
    InvalidTransactionLimits(max_events~ : Int, max_changes~ : Int, max_bytes~ : UInt64)
    TransactionLimitExceeded(file_offset~ : UInt64, reason~ : String)
    IncompleteTransaction(start_offset~ : UInt64)
    InvalidGtidTransactionState(file_offset~ : UInt64, reason~ : String)
    IncompleteGtidTransaction(start_offset~ : UInt64)
    GtidTransactionFailed
    MissingFormatDescription(file_offset~ : UInt64)
    FileReaderFailed(file_offset~ : UInt64)
    StreamAlreadyFinished(file_offset~ : UInt64)
    InvalidStreamOffset(file_offset~ : UInt64)
    } derive(Eq,
    Debug
    )

    Errors from file signatures, events, table maps and column values. Offsets are physical file positions, not the header's next-position field.

    DecodeError::equal

    fn DecodeError::equal(DecodeError, DecodeError) -> Bool

    DecodeError::not_equal

    fn DecodeError::not_equal(x : DecodeError, y : DecodeError) -> Bool

    SchemaError

    pub(all) suberror SchemaError {
    InvalidSchema(path~ : String, reason~ : String)
    InvalidSchemaLimits(max_tables~ : Int, max_columns~ : Int, max_input_units~ : Int)
    SchemaLimitExceeded(path~ : String, reason~ : String)
    MissingTableSchema(file_offset~ : UInt64, database~ : String, table~ : String)
    SchemaMismatch(file_offset~ : UInt64, database~ : String, table~ : String, column_index~ : Int?, reason~ : String)
    } derive(Eq,
    Debug
    )

    SchemaError::equal

    fn SchemaError::equal(SchemaError, SchemaError) -> Bool

    SchemaError::not_equal

    fn SchemaError::not_equal(x : SchemaError, y : SchemaError) -> Bool

    BinaryJsonValue

    pub(all) enum BinaryJsonValue {
    Null
    Boolean(Bool)
    Integer(IntegerValue)
    DoubleNumber(value~ : Double, bits~ : UInt64)
    Text(String)
    Array(ReadOnlyArray[BinaryJsonValue])
    Object(ReadOnlyArray[(String, BinaryJsonValue)])
    } derive(Eq,
    Debug
    )

    Complete (not partial-update) MySQL binary JSON, with exact integer values. Object members retain binary order. Floating bits preserve negative zero.

    BinaryJsonValue::equal

    BinaryJsonValue::not_equal

    fn BinaryJsonValue::not_equal(x : BinaryJsonValue, y : BinaryJsonValue) -> Bool

    BinaryJsonValue::to_json_text

    fn BinaryJsonValue::to_json_text(self : BinaryJsonValue) -> String

    Compact JSON text with exact decimal integer spelling. JSON consumers must still use an integer-preserving parser. Parsed documents are depth-bounded.

    test {
    let value = @moonbinlog.BinaryJsonValue::Integer(
    Unsigned(18446744073709551615UL),
    )
    assert_eq(value.to_json_text(), "18446744073709551615")
    }

    BinlogEventStream

    pub struct BinlogEventStream {
    // private fields
    }

    Validates a signature and then one complete event at a time. The caller provides I/O and retains each returned event's underlying Bytes as needed. Processing errors clear the FDE and permanently stop the reader. A feed after successful finish is rejected without changing its final context. No transaction or cross-file continuity is inferred.

    BinlogEventStream::feed

    fn BinlogEventStream::feed(self : BinlogEventStream, data : BytesView) -> BinlogFileEvent raise DecodeError

    Feed a complete event. Bytes after its declared size are not consumed. The returned views retain their input, including after later calls.

    BinlogEventStream::finish

    fn BinlogEventStream::finish(self : BinlogEventStream) -> Unit raise DecodeError

    Mark clean EOF after at least one valid FDE. Successful finish is idempotent; no further events can be fed. An unfinished transaction is outside scope.

    BinlogEventStream::format

    BinlogEventStream::new

    fn BinlogEventStream::new(signature : BytesView, max_event_size? : UInt) -> BinlogEventStream raise DecodeError

    Signature must start at the beginning of an unencrypted v4 binlog file.

    BinlogEventStream::position

    fn BinlogEventStream::position(self : BinlogEventStream) -> UInt64

    Physical offset of the next event, or the failing event after an error.

    BinlogFileEvent

    pub struct BinlogFileEvent {
    header : EventHeader
    raw : BytesView
    body : BytesView
    format : FormatDescription
    } derive(Eq,
    Debug
    )

    One integrity-checked event and the format active for it. Views retain the input bytes. raw includes header/checksum; body excludes both. For FDE, body still includes the checksum-algorithm descriptor. An unknown type has validated framing, not validated body semantics.

    BinlogFileEvent::equal

    BinlogFileEvent::not_equal

    fn BinlogFileEvent::not_equal(x : BinlogFileEvent, y : BinlogFileEvent) -> Bool

    BinlogFileReader

    pub struct BinlogFileReader {
    // private fields
    }

    Sequential traversal of a complete, in-memory unencrypted binlog file. The first event must be FDE. Later FDEs replace the format context. No OS I/O, transaction assembly or event-body dispatch is performed.

    BinlogFileReader::format

    Current validated format; absent before FDE or after any traversal error.

    BinlogFileReader::new

    fn BinlogFileReader::new(data : BytesView, max_event_size? : UInt) -> BinlogFileReader raise DecodeError

    Validate the signature and configuration. Events are validated lazily. The supplied view must start at the file signature, not a resume offset.

    test {
    // A valid signature initializes the reader; next() still requires FDE.
    let reader = @moonbinlog.BinlogFileReader::new([0xfe, 0x62, 0x69, 0x6e])
    assert_eq(reader.position(), 4UL)
    assert_eq(reader.format(), None)
    }

    BinlogFileReader::next

    Return exactly one event, or None at a clean event-boundary EOF after FDE. Any error permanently stops this reader and clears its format. Already returned events remain valid, but do not imply a complete file/transaction. Consume until None to validate the entire supplied input.

    BinlogFileReader::position

    fn BinlogFileReader::position(self : BinlogFileReader) -> UInt64

    Physical offset of the next event, or the failing event after an error. Offsets are relative to the supplied view, independent of next_position.

    BoundTableSchema

    pub struct BoundTableSchema {
    table_map : TableMap
    schema : TableSchema
    integer_unsigned : ReadOnlyArray[Bool?]
    } derive(Eq,
    Debug
    )

    Immutable binding for this exact TABLE_MAP snapshot, not a table-id cache.

    BoundTableSchema::equal

    BoundTableSchema::not_equal

    fn BoundTableSchema::not_equal(x : BoundTableSchema, y : BoundTableSchema) -> Bool

    ChecksumAlgorithm

    pub(all) enum ChecksumAlgorithm {
    Off
    Crc32
    } derive(Eq,
    Debug
    )

    The policy for events following FORMAT_DESCRIPTION_EVENT. The format-description event itself always carries CRC32 in MySQL 8.0.

    ChecksumAlgorithm::equal

    ChecksumAlgorithm::not_equal

    fn ChecksumAlgorithm::not_equal(x : ChecksumAlgorithm, y : ChecksumAlgorithm) -> Bool

    CommittedTransaction

    pub struct CommittedTransaction {
    start_offset : UInt64
    end_offset : UInt64
    xid : UInt64?
    events : ReadOnlyArray[RowsEvent]
    event_count : Int
    change_count : Int
    wire_bytes : UInt64
    } derive(Eq,
    Debug
    )

    A declared committed span. XID is absent for QUERY COMMIT.

    CommittedTransaction::equal

    CommittedTransaction::not_equal

    DecodedBit

    pub struct DecodedBit {
    value : UInt64
    bit_length : Int
    bytes_read : Int
    } derive(Eq,
    Debug
    )

    An unsigned BIT value and its declared width, preserving leading-zero width.

    DecodedBit::equal

    fn DecodedBit::equal(DecodedBit, DecodedBit) -> Bool

    DecodedBit::not_equal

    fn DecodedBit::not_equal(x : DecodedBit, y : DecodedBit) -> Bool

    DecodedBlob

    pub struct DecodedBlob {
    value : Bytes
    bytes_read : Int
    } derive(Eq,
    Debug
    )

    Exact bytes of a non-NULL BLOB/TEXT, without its length prefix.

    DecodedBlob::equal

    fn DecodedBlob::equal(DecodedBlob, DecodedBlob) -> Bool

    DecodedBlob::not_equal

    fn DecodedBlob::not_equal(x : DecodedBlob, y : DecodedBlob) -> Bool

    DecodedDecimal

    pub struct DecodedDecimal {
    value : String
    precision : Int
    scale : Int
    bytes_read : Int
    } derive(Eq,
    Debug
    )

    An exact decimal string retaining schema scale, without floating conversion.

    DecodedDecimal::equal

    DecodedDecimal::not_equal

    fn DecodedDecimal::not_equal(x : DecodedDecimal, y : DecodedDecimal) -> Bool

    DecodedEnumSet

    pub struct DecodedEnumSet {
    value : EnumSetValue
    bytes_read : Int
    } derive(Eq,
    Debug
    )

    DecodedEnumSet::equal

    DecodedEnumSet::not_equal

    fn DecodedEnumSet::not_equal(x : DecodedEnumSet, y : DecodedEnumSet) -> Bool

    DecodedFloating

    pub struct DecodedFloating {
    value : FloatingValue
    bits : UInt64
    bytes_read : Int
    } derive(Eq,
    Debug
    )

    A finite value, its raw IEEE bits (low 32 for FLOAT), and bytes consumed. Bits preserve negative zero independently of numeric equality or printing.

    DecodedFloating::equal

    DecodedFloating::not_equal

    fn DecodedFloating::not_equal(x : DecodedFloating, y : DecodedFloating) -> Bool

    DecodedInteger

    pub struct DecodedInteger {
    value : IntegerValue
    bytes_read : Int
    } derive(Eq,
    Debug
    )

    One non-NULL integer value and the number of bytes consumed from its view.

    DecodedInteger::equal

    DecodedInteger::not_equal

    fn DecodedInteger::not_equal(x : DecodedInteger, y : DecodedInteger) -> Bool

    DecodedJson

    pub struct DecodedJson {
    value : BinaryJsonValue
    bytes_read : Int
    } derive(Eq,
    Debug
    )

    DecodedJson::equal

    fn DecodedJson::equal(DecodedJson, DecodedJson) -> Bool

    DecodedJson::not_equal

    fn DecodedJson::not_equal(x : DecodedJson, y : DecodedJson) -> Bool

    DecodedString

    pub struct DecodedString {
    value : Bytes
    bytes_read : Int
    } derive(Eq,
    Debug
    )

    One non-NULL SQL string's exact bytes; no charset conversion or padding.

    DecodedString::equal

    DecodedString::not_equal

    fn DecodedString::not_equal(x : DecodedString, y : DecodedString) -> Bool

    DecodedTemporal

    pub struct DecodedTemporal {
    value : TemporalValue
    fractional_precision : Int
    bytes_read : Int
    } derive(Eq,
    Debug
    )

    DecodedTemporal::equal

    DecodedTemporal::not_equal

    fn DecodedTemporal::not_equal(x : DecodedTemporal, y : DecodedTemporal) -> Bool

    EnumSetValue

    pub(all) enum EnumSetValue {
    EnumIndex(UInt16)
    SetMask(UInt64)
    } derive(Eq,
    Debug
    )

    Raw ordinal or membership bits. Names and member counts are not inferred.

    EnumSetValue::equal

    EnumSetValue::not_equal

    fn EnumSetValue::not_equal(x : EnumSetValue, y : EnumSetValue) -> Bool

    EventHeader

    pub struct EventHeader {
    file_offset : UInt64
    timestamp : UInt
    event_type : Byte
    server_id : UInt
    event_size : UInt
    next_position : UInt
    flags : UInt16
    } derive(Eq,
    Debug
    )

    Read-only decoded fields of a v4 common event header. event_size includes the common header and any checksum trailer. next_position is retained verbatim and must not replace the input cursor.

    EventHeader::equal

    fn EventHeader::equal(EventHeader, EventHeader) -> Bool

    EventHeader::not_equal

    fn EventHeader::not_equal(x : EventHeader, y : EventHeader) -> Bool

    EventHeader::parse

    fn EventHeader::parse(data : BytesView, file_offset~ : UInt64, max_event_size? : UInt) -> EventHeader raise DecodeError

    Decode the first 19 bytes without consuming or validating an event payload. data starts at the event header; file_offset is its physical file offset. Unknown event type codes and zero next positions are preserved.

    The default event-size limit is 16 MiB. A limit below 19 is invalid. A caller may pass only the header, then read the declared payload separately. Full-event availability, FORMAT_DESCRIPTION and checksums are separate steps.

    FloatingValue

    pub(all) enum FloatingValue {
    Single(Float)
    DoublePrecision(Double)
    } derive(Eq,
    Debug
    )

    The original binary precision, without decimal rounding or widening FLOAT.

    FloatingValue::equal

    FloatingValue::not_equal

    fn FloatingValue::not_equal(x : FloatingValue, y : FloatingValue) -> Bool

    FormatDescription

    pub struct FormatDescription {
    header : EventHeader
    binlog_version : UInt16
    server_version : String
    created_timestamp : UInt
    common_header_length : Byte
    post_header_lengths : Bytes
    checksum_algorithm : ChecksumAlgorithm
    } derive(Eq,
    Debug
    )

    Immutable, validated MySQL 8.0 format context. Entry i in post_header_lengths describes event type i + 1.

    FormatDescription::equal

    FormatDescription::not_equal

    fn FormatDescription::not_equal(x : FormatDescription, y : FormatDescription) -> Bool

    FormatDescription::parse

    fn FormatDescription::parse(data : BytesView, file_offset~ : UInt64, max_event_size? : UInt) -> FormatDescription raise DecodeError

    Parse and checksum one complete FORMAT_DESCRIPTION_EVENT (type 15). Requires binlog v4, a MySQL 8.0.x version and a 19-byte common header. Trailing bytes from the next event are not consumed or checksummed.

    FormatDescription::payload

    fn FormatDescription::payload(self : FormatDescription, data : BytesView, file_offset~ : UInt64, max_event_size? : UInt) -> BytesView raise DecodeError

    Check one subsequent complete event and return its body without header/CRC. With Off, bytes after the header are retained in full, without CRC checking. A new FDE must be parsed separately to establish a fresh context. This checks framing/integrity, not the semantics of the event's body.

    GtidCommitTimestamps

    pub struct GtidCommitTimestamps {
    immediate : UInt64
    original : UInt64
    } derive(Eq,
    Debug
    )

    Commit timestamps in microseconds, with the immediate timestamp's flag bit removed. When only one timestamp is encoded, both fields have that value.

    GtidCommitTimestamps::equal

    GtidCommitTimestamps::not_equal

    GtidEvent

    pub struct GtidEvent {
    header : EventHeader
    flags : Byte
    sid : Bytes
    gno : UInt64
    last_committed : UInt64
    sequence_number : UInt64
    commit_timestamps : GtidCommitTimestamps?
    transaction_length : UInt64?
    server_versions : GtidServerVersions?
    commit_group_ticket : UInt64?
    } derive(Eq,
    Debug
    )

    MySQL 8.0 GTID_EVENT (33) or ANONYMOUS_GTID_EVENT (34), logical typecode 2. Optional extensions use None when absent, distinct from an encoded zero. SID bytes and unknown flag bits are retained; anonymous SID/GNO fields do not assign a transaction identity. This event is not evidence of a commit.

    GtidEvent::equal

    fn GtidEvent::equal(GtidEvent, GtidEvent) -> Bool

    GtidEvent::is_anonymous

    fn GtidEvent::is_anonymous(self : GtidEvent) -> Bool

    GtidEvent::may_have_sbr_statements

    fn GtidEvent::may_have_sbr_statements(self : GtidEvent) -> Bool

    GtidEvent::not_equal

    fn GtidEvent::not_equal(x : GtidEvent, y : GtidEvent) -> Bool

    GtidEvent::parse

    fn GtidEvent::parse(format : FormatDescription, data : BytesView, file_offset~ : UInt64, max_event_size? : UInt) -> GtidEvent raise DecodeError

    Validate a complete event with its current FDE, CRC32/OFF and byte budget. Requires a 42-byte post-header and logical timestamp typecode 2. Extensions may end after any complete stage: timestamps, transaction length, server versions, or an eight-byte ticket; partial fields/trailing bytes are errors. GNO must be 1..INT64_MAX-1 for type 33 and zero for type 34. Logical clocks, lengths and tickets are retained without cross-event ordering assumptions. No transaction is opened, committed, deduplicated or bound to this GTID.

    GtidInterval

    pub struct GtidInterval {
    start : UInt64
    end_exclusive : UInt64
    } derive(Eq,
    Debug
    )

    One GTID interval [start, end_exclusive). Values fit MySQL's positive GNO range; end_exclusive may equal INT64_MAX, which is not itself a valid GNO.

    GtidInterval::equal

    GtidInterval::not_equal

    fn GtidInterval::not_equal(x : GtidInterval, y : GtidInterval) -> Bool

    GtidServerVersions

    pub struct GtidServerVersions {
    immediate : UInt
    original : UInt
    } derive(Eq,
    Debug
    )

    Raw numeric MySQL server versions, without the immediate version's flag bit.

    GtidServerVersions::equal

    GtidServerVersions::not_equal

    GtidSidIntervals

    pub struct GtidSidIntervals {
    sid : Bytes
    intervals : ReadOnlyArray[GtidInterval]
    } derive(Eq,
    Debug
    )

    A 16-byte SID and its ordered, nonoverlapping, nonadjacent intervals. The SID is retained verbatim, without UUID version/variant restrictions.

    GtidSidIntervals::equal

    GtidSidIntervals::not_equal

    fn GtidSidIntervals::not_equal(x : GtidSidIntervals, y : GtidSidIntervals) -> Bool

    GtidTransactionDecoder

    pub struct GtidTransactionDecoder {
    // private fields
    }

    Strict GTID -> BEGIN -> transactional RBR -> COMMIT/XID/ROLLBACK wrapper. Every event within a span must be fed at contiguous physical offsets. Any feed/finish error permanently fails this instance and discards pending identity, rows and table mappings. Construct a fresh decoder to recover.

    GtidTransactionDecoder::feed

    fn GtidTransactionDecoder::feed(self : GtidTransactionDecoder, data : BytesView, file_offset~ : UInt64, integer_unsigned? : ReadOnlyArray[Bool?]) -> GtidTransactionStep raise DecodeError

    Feed one complete event (trailing input outside its declared size is ignored). Only an idle GTID/anonymous GTID can begin a span; it must immediately precede a supported explicit BEGIN. Other events delegate to TransactionDecoder. Signedness is needed only for ROWS. Framing retains the existing 16 MiB per-event limit. Logical clocks/transaction_length do not control traversal.

    GtidTransactionDecoder::finish

    Call at EOF or before a file/format boundary. A lone GTID prefix is also an incomplete span. Success leaves an idle instance usable; errors are final.

    GtidTransactionDecoder::is_active

    fn GtidTransactionDecoder::is_active(self : GtidTransactionDecoder) -> Bool

    True from a valid GTID prefix until its explicit span ends, including the interval before BEGIN. False after an error, which still prevents reuse.

    GtidTransactionDecoder::new

    fn GtidTransactionDecoder::new(format : FormatDescription, max_events? : Int, max_changes? : Int, max_bytes? : UInt64) -> GtidTransactionDecoder raise DecodeError

    Positive budgets include the GTID prefix, BEGIN, maps, rows and terminal event. max_changes uses the existing row-change counting rules.

    GtidTransactionOutcome

    pub struct GtidTransactionOutcome {
    gtid : GtidEvent
    transaction : TransactionOutcome
    start_offset : UInt64
    end_offset : UInt64
    event_count : Int
    wire_bytes : UInt64
    } derive(Eq,
    Debug
    )

    A completed explicit transactional span with its preceding GTID event. The outer range/count/bytes include the GTID prefix. The nested transaction retains its existing BEGIN-based range and rollback/commit semantics. Anonymous prefixes remain anonymous; no executed-GTID set is updated.

    GtidTransactionOutcome::equal

    GtidTransactionOutcome::not_equal

    GtidTransactionStep

    pub struct GtidTransactionStep {
    header : EventHeader
    outcome : GtidTransactionOutcome?
    } derive(Eq,
    Debug
    )

    No outcome is exposed until the inner span reaches COMMIT/XID/ROLLBACK.

    GtidTransactionStep::equal

    GtidTransactionStep::not_equal

    IntegerValue

    pub(all) enum IntegerValue {
    Signed(Int64)
    Unsigned(UInt64)
    } derive(Eq,
    Debug
    )

    Exact integers retain their signedness, including the full UInt64 range.

    IntegerValue::equal

    IntegerValue::not_equal

    fn IntegerValue::not_equal(x : IntegerValue, y : IntegerValue) -> Bool

    PreviousGtidsEvent

    pub struct PreviousGtidsEvent {
    header : EventHeader
    sids : ReadOnlyArray[GtidSidIntervals]
    } derive(Eq,
    Debug
    )

    Historical GTIDs declared by PREVIOUS_GTIDS_EVENT (35). SID entries retain wire order, including repeated SIDs and empty entries; this is a decoded representation, not a normalized GTID set.

    PreviousGtidsEvent::equal

    PreviousGtidsEvent::not_equal

    PreviousGtidsEvent::parse

    fn PreviousGtidsEvent::parse(format : FormatDescription, data : BytesView, file_offset~ : UInt64, max_event_size? : UInt, max_sids? : Int, max_intervals? : Int) -> PreviousGtidsEvent raise DecodeError

    Decode a complete MySQL 8.0 event using its current FDE, including CRC/OFF. Requires a zero-length post-header and exact consumption of the event body. Counts are checked against remaining bytes and quotas before narrowing or allocating entries. max_intervals bounds the sum across all SID entries. Zero quotas allow only the corresponding empty collection; negative quotas are invalid. Trailing input outside the declared event is ignored. This does not validate transaction history, advance replication state, or add support for GTID-tagged transactions to TransactionDecoder/the CLI.

    RotateEvent

    pub struct RotateEvent {
    header : EventHeader
    position : UInt64
    next_binlog : Bytes
    } derive(Eq,
    Debug
    )

    A MySQL 8.0 ROTATE_EVENT (4), independent of the common header's next_position. The target identifier is retained verbatim, without charset/path interpretation.

    RotateEvent::equal

    fn RotateEvent::equal(RotateEvent, RotateEvent) -> Bool

    RotateEvent::not_equal

    fn RotateEvent::not_equal(x : RotateEvent, y : RotateEvent) -> Bool

    RotateEvent::parse

    fn RotateEvent::parse(format : FormatDescription, data : BytesView, file_offset~ : UInt64, max_event_size? : UInt) -> RotateEvent raise DecodeError

    Validate one full event using its current FDE. Requires an eight-byte little-endian position and a nonempty identifier byte sequence. No NUL is appended or stripped from the identifier. Unknown filename encodings and all UInt64 positions (including zero) are preserved. Trailing input outside the declared event is not consumed. No next file is opened and no cross-file/transaction state is advanced. For a framed event from BinlogFileReader, pass event.format, event.raw and file_offset=event.header.file_offset; position is the target file's offset.

    RowCell

    pub(all) enum RowCell {
    Absent
    Null
    Integer(IntegerValue)
    Floating(DecodedFloating)
    StringBytes(Bytes)
    BlobBytes(Bytes)
    JsonDocument(DecodedJson)
    Decimal(DecodedDecimal)
    Bit(DecodedBit)
    EnumSet(EnumSetValue)
    Temporal(DecodedTemporal)
    } derive(Eq,
    Debug
    )

    One table column: absent, SQL NULL, or a typed decoded value.

    RowCell::equal

    fn RowCell::equal(RowCell, RowCell) -> Bool

    RowCell::not_equal

    fn RowCell::not_equal(x : RowCell, y : RowCell) -> Bool

    RowCell::to_repr

    RowChange

    pub(all) enum RowChange {
    Insert(RowImage)
    Update(before~ : RowImage, after~ : RowImage)
    Delete(RowImage)
    } derive(Eq,
    Debug
    )

    An uncommitted row change; transaction outcome must be handled separately.

    RowChange::equal

    fn RowChange::equal(RowChange, RowChange) -> Bool

    RowChange::not_equal

    fn RowChange::not_equal(x : RowChange, y : RowChange) -> Bool

    RowImage

    pub struct RowImage {
    columns : ReadOnlyArray[RowCell]
    bytes_read : Int
    } derive(Eq,
    Debug
    )

    RowImage::equal

    fn RowImage::equal(RowImage, RowImage) -> Bool

    RowImage::not_equal

    fn RowImage::not_equal(x : RowImage, y : RowImage) -> Bool

    RowImage::to_repr

    RowsEvent

    pub struct RowsEvent {
    header : EventHeader
    table : TableMap
    flags : UInt16
    extra_data : Bytes
    columns_before : Bytes?
    columns_after : Bytes?
    changes : ReadOnlyArray[RowChange]
    } derive(Eq,
    Debug
    )

    One verified v2 ROWS event and an immutable snapshot of its table map. Extra header bytes are preserved without interpreting NDB/partition tags.

    RowsEvent::equal

    fn RowsEvent::equal(RowsEvent, RowsEvent) -> Bool

    RowsEvent::not_equal

    fn RowsEvent::not_equal(x : RowsEvent, y : RowsEvent) -> Bool

    SchemaColumn

    pub struct SchemaColumn {
    name : String
    wire_type : Byte
    metadata : Bytes
    nullable : Bool
    unsigned : Bool?
    } derive(Eq,
    Debug
    )

    A positional column label and a physical TABLE_MAP descriptor. unsigned is required for integer wire types and absent for all others.

    SchemaColumn::equal

    SchemaColumn::not_equal

    fn SchemaColumn::not_equal(x : SchemaColumn, y : SchemaColumn) -> Bool

    SchemaConfig

    pub struct SchemaConfig {
    // private fields
    }

    Immutable schema registry, keyed by the exact (database, table) pair.

    SchemaConfig::bind

    fn SchemaConfig::bind(self : SchemaConfig, table_map : TableMap) -> BoundTableSchema raise SchemaError

    Compare count and positional type/raw metadata/nullable before returning labels/signedness. These descriptors cannot prove names, signedness or ordering between columns with otherwise identical physical descriptors. Rebind every new TABLE_MAP; no schema evolution or DDL inference is performed.

    SchemaConfig::get

    fn SchemaConfig::get(self : SchemaConfig, database : String, table : String) -> TableSchema?

    SchemaConfig::length

    fn SchemaConfig::length(self : SchemaConfig) -> Int

    SchemaConfig::parse

    fn SchemaConfig::parse(text : StringView, max_tables? : Int, max_columns? : Int, max_input_units? : Int) -> SchemaConfig raise SchemaError

    Parse schema_version=1 JSON. Unknown fields and duplicate object members, table identities and column names are rejected. Limits are checked before building the registry; max_input_units counts UTF-16 code units, not bytes.

    test {
    let config = @moonbinlog.SchemaConfig::parse(
    "{\"schema_version\":1,\"tables\":[]}",
    )
    assert_eq(config.length(), 0)
    assert_eq(config.get("shop", "orders"), None)
    }

    TableColumn

    pub struct TableColumn {
    wire_type : Byte
    metadata : Bytes
    nullable : Bool
    } derive(Eq,
    Debug
    )

    A wire type, its exact metadata bytes, and the column's nullable flag. This does not supply its SQL name, signedness, charset or decoded row value.

    TableColumn::decode_bit

    fn TableColumn::decode_bit(self : TableColumn, data : BytesView, file_offset~ : UInt64, column_index~ : Int) -> DecodedBit raise DecodeError

    Decode a present, non-NULL BIT (16) with declared width 1..64. Metadata contains remainder bits (0..7), then complete bytes (0..8). The value is big-endian; unused high bits of a partial first byte are ignored, matching upstream handling of padding. No shift by 64 is used. file_offset is the field's first byte; column_index is zero-based. bytes_read is ceil(bit_length/8); trailing input is not consumed. The caller handles row presence/NULL bitmaps. BIT(1) remains an integer.

    TableColumn::decode_blob

    fn TableColumn::decode_blob(self : TableColumn, data : BytesView, file_offset~ : UInt64, column_index~ : Int, max_value_bytes? : Int) -> DecodedBlob raise DecodeError

    Decode canonical MySQL BLOB (252), including TINY/MEDIUM/LONG BLOB/TEXT. Its one metadata byte specifies a 1..4-byte little-endian length prefix. file_offset is the field's first byte; column_index is zero-based. The limit excludes the prefix; 0 allows only empty values. Values above 2_147_483_643 are invalid limits so total consumed bytes fit Int32. Check limits and available input before allocating or converting indices. The caller handles row NULL/presence bitmaps and TEXT charset conversion.

    TableColumn::decode_decimal

    fn TableColumn::decode_decimal(self : TableColumn, data : BytesView, file_offset~ : UInt64, column_index~ : Int) -> DecodedDecimal raise DecodeError

    Decode a present, non-NULL NEWDECIMAL (246) for MySQL 8.0. Metadata must contain precision (1..65) and scale (0..30, <= precision). Return normalized integer digits and exactly scale fractional digits. Negative zero is normalized to zero; no unsigned schema rule is inferred. file_offset is this field's first byte; column_index is zero-based. Trailing fields are not consumed. The caller handles presence/NULL bitmaps.

    TableColumn::decode_enum_set

    fn TableColumn::decode_enum_set(self : TableColumn, data : BytesView, file_offset~ : UInt64, column_index~ : Int) -> DecodedEnumSet raise DecodeError

    Decode present, non-NULL ENUM/SET in canonical STRING (254) representation. Metadata is [247, 1|2] for ENUM, [248, 1|2|3|4|8] for SET. Field values are little-endian, without a string length prefix. Preserve ENUM index 0 and empty SET mask 0 as values. Names/member counts are not decoded here. file_offset is the field's first byte; column_index is zero-based. Trailing input is not consumed; callers handle row presence/NULL bitmaps.

    TableColumn::decode_floating

    fn TableColumn::decode_floating(self : TableColumn, data : BytesView, file_offset~ : UInt64, column_index~ : Int) -> DecodedFloating raise DecodeError

    Decode a present, non-NULL FLOAT (4) or DOUBLE (5), little-endian IEEE 754. Require a one-byte metadata width of 4 or 8 respectively; reject NaN/Inf with raw bits in the error. Preserve signed zero and finite subnormals. file_offset locates this field's first byte; column_index is zero-based. No UNSIGNED schema constraint, SQL rounding or NULL bitmap is inferred. Trailing input is untouched; bytes_read is 4 or 8.

    TableColumn::decode_integer

    fn TableColumn::decode_integer(self : TableColumn, data : BytesView, unsigned~ : Bool, file_offset~ : UInt64, column_index~ : Int) -> DecodedInteger raise DecodeError

    Decode a present, non-NULL TINY/SHORT/INT24/LONG/LONGLONG column value. unsigned is mandatory: a wire type alone does not determine signedness. file_offset locates this field's first byte; column_index is zero-based. Extra bytes in the view are left for the next field. nullable is a schema capability, not the row's NULL marker; row bitmaps must be handled by callers.

    TableColumn::decode_json

    fn TableColumn::decode_json(self : TableColumn, data : BytesView, file_offset~ : UInt64, column_index~ : Int, max_value_bytes? : Int, max_depth? : Int, max_nodes? : Int) -> DecodedJson raise DecodeError

    Decode MYSQL_TYPE_JSON (245) with its 1..4 byte length prefix. The byte budget excludes the prefix; depth counts the root as one (hard cap 128), nodes count values including containers. Opaque types and empty documents are rejected. String bytes must be UTF-8; no replacement or BOM stripping.

    TableColumn::decode_string

    fn TableColumn::decode_string(self : TableColumn, data : BytesView, file_offset~ : UInt64, column_index~ : Int) -> DecodedString raise DecodeError

    Decode a present, non-NULL VARCHAR (15) or genuine STRING (254) value. CHAR/BINARY use STRING; VARCHAR/VARBINARY use VARCHAR. ENUM/SET embedded in STRING metadata are rejected. The result owns only the value's bytes. file_offset is this field's first byte; column_index is zero-based. bytes_read includes the length prefix; trailing fields are not consumed. The caller handles row presence/NULL bitmaps and any charset conversion.

    TableColumn::decode_temporal

    fn TableColumn::decode_temporal(self : TableColumn, data : BytesView, file_offset~ : UInt64, column_index~ : Int) -> DecodedTemporal raise DecodeError

    Decode present, non-NULL DATE, YEAR, TIMESTAMP2, DATETIME2 or TIME2. Validate component ranges and fractional precision 0..6. Preserve zero and partial-zero dates and SQL-mode-dependent dates such as February 31. No calendar normalization, SQL-mode check or timestamp timezone conversion. file_offset is the field's first byte; column_index is zero-based. The caller handles row presence/NULL bitmaps; trailing bytes are untouched.

    TableColumn::equal

    fn TableColumn::equal(TableColumn, TableColumn) -> Bool

    TableColumn::not_equal

    fn TableColumn::not_equal(x : TableColumn, y : TableColumn) -> Bool

    TableMap

    pub struct TableMap {
    header : EventHeader
    table_id : UInt64
    flags : UInt16
    database : String
    table : String
    columns : ReadOnlyArray[TableColumn]
    optional_metadata : ReadOnlyArray[TableMetadataField]
    } derive(Eq,
    Debug
    )

    An immutable MySQL 8.0 TABLE_MAP_EVENT (type 19).

    TableMap::decode_row_image

    fn TableMap::decode_row_image(self : TableMap, data : BytesView, columns_present~ : BytesView, integer_unsigned~ : ReadOnlyArray[Bool?], file_offset~ : UInt64, max_bytes? : Int) -> RowImage raise DecodeError

    Decode one ordinary row image starting at its compact NULL bitmap. columns_present has exactly ceil(table_columns/8) bytes, LSB first. NULL bits enumerate only present columns, including NOT NULL columns. integer_unsigned has one entry per table column; Some(bool) is required only for present, non-NULL integer values. Other entries are ignored. Ignore bitmap padding; reject NULL on a NOT NULL column. No JSON diffs, event headers, transaction handling or automatic table-context clearing. file_offset locates the NULL bitmap. The byte budget includes this bitmap. Empty presence is allowed and consumes zero bytes; event loops must guard against zero progress. Trailing input is left for the next row/image.

    TableMap::equal

    fn TableMap::equal(TableMap, TableMap) -> Bool

    TableMap::not_equal

    fn TableMap::not_equal(x : TableMap, y : TableMap) -> Bool

    TableMap::parse

    fn TableMap::parse(format : FormatDescription, data : BytesView, file_offset~ : UInt64, max_columns? : Int, max_event_size? : UInt) -> TableMap raise DecodeError

    Decode one complete, checksum-validated table map using an existing format. Only the 8-byte table-map post-header is supported. Unknown wire types fail. Optional metadata is preserved as TLVs, not interpreted as a SQL schema.

    TableMap::to_repr

    TableMapContext

    pub struct TableMapContext {
    // private fields
    }

    Bounded table-id cache associated with one format and caller-managed scope. decode_rows clears at statement ends; manual consumers must call clear. Reset for a new file or format. Failed apply/decode_rows clears cached maps.

    TableMapContext::apply

    fn TableMapContext::apply(self : TableMapContext, data : BytesView, file_offset~ : UInt64, max_columns? : Int, max_event_size? : UInt) -> TableMap raise DecodeError

    Validate and install a map. Reusing an id replaces its earlier definition. Invalid input and quota failures invalidate the entire current map scope.

    TableMapContext::clear

    fn TableMapContext::clear(self : TableMapContext) -> Unit

    TableMapContext::decode_rows

    fn TableMapContext::decode_rows(self : TableMapContext, data : BytesView, integer_unsigned~ : ReadOnlyArray[Bool?], file_offset~ : UInt64, max_rows? : Int, max_row_bytes? : Int, max_event_size? : UInt) -> RowsEvent raise DecodeError

    Decode WRITE/UPDATE/DELETE_ROWS v2 (30/31/32) with a 10-byte post-header. Validate framing/checksum before decoding, resolve table_id from this cache, and require an exact column-count match. Ignore data after this event. Signedness is the same per-table array used by decode_row_image. max_rows counts changes (an UPDATE pair is one); max_row_bytes bounds each image. Defaults are 1024 changes and 16 MiB per image/event. Any failure clears all cached maps. A successful STMT_END_F also clears all maps; the returned table snapshot remains usable. This does not commit rows. Legacy, partial-JSON, compressed and dummy table-id events are unsupported.

    TableMapContext::get

    fn TableMapContext::get(self : TableMapContext, table_id : UInt64) -> TableMap?

    TableMapContext::length

    fn TableMapContext::length(self : TableMapContext) -> Int

    TableMapContext::new

    fn TableMapContext::new(format : FormatDescription, max_tables? : Int, max_bytes? : UInt64) -> TableMapContext raise DecodeError

    max_bytes bounds retained wire-event sizes, not exact heap usage.

    TableMapContext::reset

    fn TableMapContext::reset(self : TableMapContext, format : FormatDescription) -> Unit

    TableMetadataField

    pub struct TableMetadataField {
    field_type : Byte
    value : Bytes
    } derive(Eq,
    Debug
    )

    A structurally checked optional metadata TLV, preserved without interpretation.

    TableMetadataField::equal

    TableMetadataField::not_equal

    TableSchema

    pub struct TableSchema {
    database : String
    table : String
    columns : ReadOnlyArray[SchemaColumn]
    } derive(Eq,
    Debug
    )

    TableSchema::equal

    fn TableSchema::equal(TableSchema, TableSchema) -> Bool

    TableSchema::not_equal

    fn TableSchema::not_equal(x : TableSchema, y : TableSchema) -> Bool

    TemporalValue

    pub(all) enum TemporalValue {
    Year(Int)
    Date(year~ : Int, month~ : Int, day~ : Int)
    DateTime(year~ : Int, month~ : Int, day~ : Int, hour~ : Int, minute~ : Int, second~ : Int, microsecond~ : Int)
    Time(negative~ : Bool, hour~ : Int, minute~ : Int, second~ : Int, microsecond~ : Int)
    Timestamp(seconds~ : UInt, microsecond~ : Int)
    } derive(Eq,
    Debug
    )

    Calendar components retain MySQL zero dates; timestamps retain wire seconds. TIME is a signed duration, not a time of day. No timezone is inferred.

    TemporalValue::equal

    TemporalValue::not_equal

    fn TemporalValue::not_equal(x : TemporalValue, y : TemporalValue) -> Bool

    TransactionAction

    pub(all) enum TransactionAction {
    Begin
    Commit
    Rollback
    Xid(UInt64)
    } derive(Eq,
    Debug
    )

    TransactionAction::equal

    TransactionAction::not_equal

    fn TransactionAction::not_equal(x : TransactionAction, y : TransactionAction) -> Bool

    TransactionBoundary

    pub struct TransactionBoundary {
    header : EventHeader
    action : TransactionAction
    } derive(Eq,
    Debug
    )

    TransactionBoundary::equal

    TransactionBoundary::not_equal

    TransactionBoundary::parse

    fn TransactionBoundary::parse(format : FormatDescription, data : BytesView, file_offset~ : UInt64) -> TransactionBoundary raise DecodeError

    Parse checksum-verified QUERY (2) or XID (16) transaction control. QUERY requires a 13-byte post-header, zero error_code, bounded status/db fields and the terminating db NUL. Status variable contents are not decoded. Only BEGIN, COMMIT and ROLLBACK are accepted, case-insensitively, with ASCII whitespace and at most one final semicolon. No general SQL interpretation.

    TransactionDecoder

    pub struct TransactionDecoder {
    // private fields
    }

    Strict contiguous BEGIN..COMMIT/XID/ROLLBACK assembler for transactional RBR. Unknown events and SQL are errors, not silently skipped. No GTID/XA/DDL, autocommit inference, savepoints or nontransactional-table rollback model.

    TransactionDecoder::feed

    fn TransactionDecoder::feed(self : TransactionDecoder, data : BytesView, file_offset~ : UInt64, integer_unsigned? : ReadOnlyArray[Bool?]) -> TransactionStep raise DecodeError

    Feed one framed event; following bytes are not consumed. The caller must feed every event in order inside an active span, using physical offsets. Recognize QUERY, XID, TABLE_MAP and ordinary ROWS v2 only. Missing BEGIN, nested BEGIN, gaps, duplicates, corruption and unsupported input discard the entire pending transaction and table scope before raising. Signedness is needed only for ROWS and is passed to the row decoder. No pending changes are returned before commit; rollback returns only counts.

    TransactionDecoder::finish

    fn TransactionDecoder::finish(self : TransactionDecoder) -> Unit raise DecodeError

    Call at EOF. An unfinished transaction is discarded and reported, never emitted as committed. Construct a fresh decoder for another file/format.

    TransactionDecoder::is_active

    fn TransactionDecoder::is_active(self : TransactionDecoder) -> Bool

    TransactionDecoder::new

    fn TransactionDecoder::new(format : FormatDescription, max_events? : Int, max_changes? : Int, max_bytes? : UInt64) -> TransactionDecoder raise DecodeError

    Count all events and wire bytes, including BEGIN, maps and the ending event. UPDATE pairs count as one change; these are not exact heap-memory budgets.

    TransactionOutcome

    pub(all) enum TransactionOutcome {
    Committed(CommittedTransaction)
    RolledBack(start_offset~ : UInt64, end_offset~ : UInt64, discarded_changes~ : Int)
    } derive(Eq,
    Debug
    )

    TransactionOutcome::equal

    TransactionOutcome::not_equal

    TransactionStep

    pub struct TransactionStep {
    header : EventHeader
    outcome : TransactionOutcome?
    } derive(Eq,
    Debug
    )

    Advance by header.event_size, even while outcome is None.

    TransactionStep::equal

    TransactionStep::not_equal

    fn TransactionStep::not_equal(x : TransactionStep, y : TransactionStep) -> Bool

    validate_file_header

    fn validate_file_header(data : BytesView) -> Unit raise DecodeError

    Validate the first four bytes of an unencrypted binlog file. Bytes after the signature are ignored. This does not establish the binlog version: that requires decoding FORMAT_DESCRIPTION_EVENT in a later step.

    test {
    @moonbinlog.validate_file_header([0xfe, 0x62, 0x69, 0x6e])
    }

    Powered by MoonBit

    Site sourceReport issueModulesSkillsBuild queueStatistics

    © 2026 mooncakes.io