A native MoonBit Redis RDB parser and offline snapshot analysis CLI (under development).
moon check --target native
moon build --target native
moon test --target native
moon run cmd/main --target native -- --help
moon run cmd/main --target native -- inspect fixtures/redis-7.2.5-sorted-sets/sorted-sets.rdb
moon run examples/parse --target native
python -X utf8 scripts/verify_fixtures.pymoon run cmd/main --target native -- inspect fixtures/redis-7.2.5/empty.rdb --max-input-bytes 67108864moon run cmd/main --target native -- stats fixtures/redis-7.2.5-sorted-sets/sorted-sets.rdb --at 2100-01-01T00:00:00.123Z --prefix long-moon run cmd/main --target native -- top fixtures/redis-7.2.5-sorted-sets/sorted-sets.rdb --by serialized-bytes --limit 20moon run cmd/main --target native -- export fixtures/redis-7.2.5-sorted-sets/sorted-sets.rdb --db 2 --prefix long- --format jsonl --output snapshot.jsonllet version = @moonrdb.read_header(b"REDIS0011") // 返回 11;调用方需要处理 ParseError
let result = @moonrdb.parse_empty(bytes) // bytes 是调用方读取的快照;可能抛出 ParseError
let strings = @moonrdb.parse_strings(bytes) // StringSnapshot,含 records 与校验状态
let snapshot = @moonrdb.parse(bytes) // Snapshot,records 中的 String / Hash / List / Set / SortedSet 可模式匹配pub(all) suberror ParseError {
InvalidInputLimit(offset~ : Int, limit~ : Int)
InputTooLarge(offset~ : Int, size~ : Int, limit~ : Int)
UnexpectedEnd(offset~ : Int, needed~ : UInt64, remaining~ : Int)
InvalidMagic(offset~ : Int)
InvalidVersionDigit(offset~ : Int)
UnsupportedVersion(offset~ : Int, version~ : Int)
InvalidLengthTag(offset~ : Int, tag~ : Byte)
InvalidStringEncoding(offset~ : Int, encoding~ : Int)
UnexpectedEncodedLength(offset~ : Int)
InvalidResourceLimit(offset~ : Int, resource~ : String, limit~ : Int)
ResourceLimitExceeded(offset~ : Int, resource~ : String, limit~ : UInt64, actual~ : UInt64)
UnsupportedStringEncoding(offset~ : Int, encoding~ : Int)
UnsupportedObjectType(offset~ : Int, object_type~ : Byte)
UnsupportedOpcode(offset~ : Int, opcode~ : Byte)
DuplicateKeyMetadata(offset~ : Int, opcode~ : Byte)
DanglingKeyMetadata(offset~ : Int)
ChecksumMismatch(offset~ : Int, stored~ : UInt64, computed~ : UInt64)
TrailingData(offset~ : Int, remaining~ : Int)
InvalidLzf(offset~ : Int, reason~ : String)
InvalidHash(offset~ : Int, reason~ : String)
InvalidListpack(offset~ : Int, inner_offset~ : Int, reason~ : String)
InvalidList(offset~ : Int, reason~ : String)
InvalidSortedSet(offset~ : Int, reason~ : String)
InvalidSet(offset~ : Int, reason~ : String)
InvalidIntset(offset~ : Int, inner_offset~ : Int, reason~ : String)
} derive(Debug)pub(all) struct EmptySnapshot {
format_version : Int
aux_fields : Array[AuxField]
database_selections : Array[UInt64]
resize_hints : Array[DatabaseSizeHint]
checksum : ChecksumStatus
file_size : Int
} derive(Eq, Debug)pub(all) struct HashRecord {
database : UInt64
key : Bytes
key_storage : StringStorage
entries : Array[HashEntry]
storage : HashStorage
packed_storage : StringStorage?
expires_at_ms : Int64?
idle_seconds : UInt64?
frequency : Byte?
record_start : Int
record_end : Int
serialized_bytes : UInt64
} derive(Eq, Debug)pub(all) struct ListNode {
container : ListNodeContainer
storage : StringStorage
item_start : Int
item_end : Int
node_start : Int
node_end : Int
} derive(Eq, Debug)pub(all) enum RdbRecord {
String(StringRecord)
Hash(HashRecord)
List(ListRecord)
Set(SetRecord)
SortedSet(SortedSetRecord)
} derive(Eq, Debug)pub(all) struct SetRecord {
database : UInt64
key : Bytes
key_storage : StringStorage
members : Array[Bytes]
storage : SetStorage
packed_storage : StringStorage?
expires_at_ms : Int64?
idle_seconds : UInt64?
frequency : Byte?
record_start : Int
record_end : Int
serialized_bytes : UInt64
} derive(Eq, Debug)pub(all) struct Snapshot {
format_version : Int
aux_fields : Array[AuxField]
database_selections : Array[UInt64]
resize_hints : Array[DatabaseSizeHint]
records : Array[RdbRecord]
checksum : ChecksumStatus
file_size : Int
global_overhead_bytes : UInt64
} derive(Eq, Debug)pub(all) struct SortedSetRecord {
database : UInt64
key : Bytes
key_storage : StringStorage
entries : Array[SortedSetEntry]
storage : SortedSetStorage
packed_storage : StringStorage?
expires_at_ms : Int64?
idle_seconds : UInt64?
frequency : Byte?
record_start : Int
record_end : Int
serialized_bytes : UInt64
} derive(Eq, Debug)pub(all) struct StringRecord {
database : UInt64
key : Bytes
value : Bytes
key_storage : StringStorage
value_storage : StringStorage
expires_at_ms : Int64?
idle_seconds : UInt64?
frequency : Byte?
record_start : Int
record_end : Int
serialized_bytes : UInt64
} derive(Eq, Debug)pub(all) struct StringSnapshot {
format_version : Int
aux_fields : Array[AuxField]
database_selections : Array[UInt64]
resize_hints : Array[DatabaseSizeHint]
records : Array[StringRecord]
checksum : ChecksumStatus
file_size : Int
global_overhead_bytes : UInt64
} derive(Eq, Debug)fn parse(input : Bytes, max_input_bytes? : Int, max_string_bytes? : Int, max_metadata_entries? : Int, max_records? : Int, max_decoded_bytes? : Int, max_hash_entries? : Int, max_packed_bytes? : Int, max_list_nodes? : Int, max_list_items? : Int, max_set_members? : Int, max_sorted_set_entries? : Int) -> Snapshot raise ParseErrortest {
let result = @moonrdb.parse(
b"REDIS0011\x04\x01k\x01\x01f\x01v\xff\x00\x00\x00\x00\x00\x00\x00\x00",
)
assert_eq(result.records.length(), 1)
}
test {
let result = @moonrdb.parse(
b"REDIS0011\x12\x00\x01\x01\x01v\xff\x00\x00\x00\x00\x00\x00\x00\x00",
)
guard result.records[0] is List(record) else { fail("expected List") }
assert_eq(record.items, [b"v"])
}
test {
let result = @moonrdb.parse(
b"REDIS0011\x02\x00\x01\x01v\xff\x00\x00\x00\x00\x00\x00\x00\x00",
)
guard result.records[0] is Set(record) else { fail("expected Set") }
assert_eq(record.members, [b"v"])
}
test {
let result = @moonrdb.parse(
b"REDIS0011\x05\x00\x01\x01v\x00\x00\x00\x00\x00\x00\xf0\x3f\xff\x00\x00\x00\x00\x00\x00\x00\x00",
)
guard result.records[0] is SortedSet(record) else {
fail("expected Sorted Set")
}
assert_eq(record.entries[0].score, 1.0)
}fn parse_empty(input : Bytes, max_input_bytes? : Int, max_string_bytes? : Int, max_metadata_entries? : Int) -> EmptySnapshot raise ParseErrortest {
let result = @moonrdb.parse_empty(
b"REDIS0011\xff\x00\x00\x00\x00\x00\x00\x00\x00",
)
assert_true(result.checksum is Disabled)
}fn parse_strings(input : Bytes, max_input_bytes? : Int, max_string_bytes? : Int, max_metadata_entries? : Int, max_records? : Int, max_decoded_bytes? : Int) -> StringSnapshot raise ParseErrortest {
let result = @moonrdb.parse_strings(
b"REDIS0011\x00\x01k\x01v\xff\x00\x00\x00\x00\x00\x00\x00\x00",
)
assert_eq(result.records[0].value, b"v")
}test {
assert_eq(@moonrdb.read_header(b"REDIS0011"), 11)
}Install
Download zipA native MoonBit Redis RDB parser and offline snapshot analysis CLI (under development).