// Plain-data model of a serialized graph snapshot and the per-section// codecs for atoms, meta, nodes (label index), and relationships (CSR +// type index). Port of the Rust codec's graph.rs plus the section// encoders/decoders from rcpg.rs.package rcpgimport ()// BitmapEntry is one entry of a bitmap index: an atom mapped to a roaring// bitmap (label atom -> node IDs, or type atom -> outgoing-CSR positions).typeBitmapEntrystruct { Atom uint32 Bitmap *roaring.Bitmap}// Column is one property column: a key atom and its storage.typeColumnstruct { Key uint32 Data ColumnData}// GraphSection is the complete decoded content of an RCPG file.//// All string-ish values (labels, relationship types, property keys, string// property values) are atom IDs into Atoms; atom 0 is always the empty// string. A nil Version means the meta section carried no version string// (present-but-empty is distinct on disk).typeGraphSectionstruct {// NNodes is the actual number of distinct nodes, NOT the CSR ID-space // size; see OutOffsets. NNodes uint32// NRels is the number of relationships. NRels uint64// OutOffsets has csr_id_space + 1 entries. IDs never added have empty // ranges, so the ID space can exceed NNodes under sparse IDs. OutOffsets []uint32// OutNbrs holds destination node IDs, len = NRels. OutNbrs []uint32// OutTypes holds relationship type atoms, parallel to OutNbrs. OutTypes []uint32// InOffsets is sized like OutOffsets. InOffsets []uint32// InNbrs holds source node IDs, len = NRels. InNbrs []uint32// InTypes holds relationship type atoms, parallel to InNbrs. InTypes []uint32// LabelIndex maps label atom -> node IDs, sorted by label atom. LabelIndex []BitmapEntry// TypeIndex maps type atom -> outgoing-CSR relationship positions, // sorted by type atom. TypeIndex []BitmapEntry// NodeColumns are node property columns, sorted by key atom. NodeColumns []Column// RelColumns are relationship property columns, indexed by the // relationship's outgoing-CSR position. RelColumns []Column// Version is the optional snapshot version string. Version *string// Atoms is the string table; index 0 is always "". Atoms []string// Existence is the optional section-8 node existence bitmap: the set // of ids that are actual nodes, for id spaces with gaps (builder- // chosen sparse ids). Nil means absent, in which case every // in-id-space id is a node -- exactly true for dense writers, whose // files stay byte-identical by never emitting the section. Existence *roaring.Bitmap// AtomIndex is the optional section-7 block index over the atoms // section, present only when the file carried one: lazy readers route // an atom id straight to its block instead of scanning the section's // length prefixes. Nil on a hand-built graph; the writer derives it // from the atoms at encode time, so it carries no independent // information. AtomIndex *AtomBlockIndex}// AtomBlockIndex is a parsed section-7 body: Offsets[k] is the byte offset,// relative to the atoms section body start, of atom k*BlockLen's length// prefix (entry 0 is always 4, past the section's count word).typeAtomBlockIndexstruct { BlockLen uint32 Offsets []uint64}// CSRIDSpace is the size of the CSR ID space (max node ID + 1 for non-empty// graphs).func ( *GraphSection) () uint32 {iflen(.OutOffsets) == 0 {return0 }returnuint32(len(.OutOffsets) - 1)}// OutNeighbors returns the outgoing neighbors of node, empty for IDs outside// the ID space.func ( *GraphSection) ( uint32) []uint32 {returnneighbors(.OutOffsets, .OutNbrs, )}// InNeighbors returns the incoming neighbors (source node IDs) of node,// empty for IDs outside the ID space.func ( *GraphSection) ( uint32) []uint32 {returnneighbors(.InOffsets, .InNbrs, )}func neighbors(, []uint32, uint32) []uint32 { := int()if +1 >= len() {returnnil } , := int([]), int([+1])if > || > len() {returnnil }return [:]}// preallocCap bounds an initial slice reservation by a file-declared element// count: each element is validated as the decode loop reads it, but the// up-front reservation happens first, so a crafted count could otherwise// reserve gigabytes before the loop rejects the truncation.func preallocCap( int) int {const = 4096returnmin(, )}// --- atoms -------------------------------------------------------------------func encodeAtoms( []string) ([]byte, error) {varbytes.Bufferif := wLenPrefix(&, len()); != nil {returnnil, }for , := range {if := wString(&, ); != nil {returnnil, } }return .Bytes(), nil}func decodeAtoms( []byte) ([]string, error) { := newCursor() , := .lenPrefix(1)if != nil {returnnil, }// One backing string serves every atom: substrings share it, so the // table costs two allocations instead of one per atom (the dominant // residual of the load path at millions of atoms). The backing spans // the section tail, so the interleaved length prefixes ride along -- // a few bytes per atom inside one block that stays alive with the // table anyway. := .pos := string(.b[:]) := make([]string, 0, preallocCap())forrange { , := .lenPrefix(1)if != nil {returnnil, } , := .take()if != nil {returnnil, }if !utf8.Valid() {returnnil, corruptf("invalid utf8 at offset %d", .pos) } = append(, [.pos--:.pos-]) }return , nil}// --- atom index (section 7) ----------------------------------------------// encodeAtomIndex encodes the optional block index over the atoms section:// block_len u32 + reserved u32 + count u32 + count x u64 offsets, where// offsets[k] is the atoms-section-body-relative offset of atom// k*blockLen's length prefix (entry 0 is always 4, past the count word).func encodeAtomIndex( []string, uint32) ([]byte, error) {varbytes.BufferwU32(&, )wU32(&, 0) // reserved := (len() + int() - 1) / int()wU32(&, uint32()) := uint64(4) // past the atoms section's count wordfor , := range {if %int() == 0 {wU64(&, ) } += 4 + uint64(len()) }return .Bytes(), nil}func decodeAtomIndex( []byte) (*AtomBlockIndex, error) { := newCursor() , := .u32()if != nil {returnnil, }if , := .u32(); != nil { // reservedreturnnil, } , := .lenPrefix(8)if != nil {returnnil, } := make([]uint64, 0, preallocCap()) := uint64(0) := falseforrange { , := .u64()if != nil {returnnil, }// Strictly ascending by construction; enforce here so a consumer // can trust it.if && <= {returnnil, corruptf("atom index: offsets not strictly ascending") } , = , true = append(, ) }return &AtomBlockIndex{BlockLen: , Offsets: }, nil}// validateAtomIndex checks a PRESENT atom index against the decoded atoms:// a malformed index is corrupt, never a silent fall-back to the scan path// (a lazy remote reader falling back would re-transfer the whole atoms// section). Files without the section skip this entirely.func validateAtomIndex( *AtomBlockIndex, []string) error {if .BlockLen == 0 {returncorruptf("atom index: block_len is zero") } := (len() + int(.BlockLen) - 1) / int(.BlockLen)iflen(.Offsets) != {returncorruptf("atom index: offset count does not match the atom count") }// Recompute each block-start offset from the atoms themselves: 4 // (count word) + 4 per preceding length prefix + preceding UTF-8 // bytes. := uint64(4) := 0for , := range {if %int(.BlockLen) == 0 {if .Offsets[] != {returncorruptf("atom index: offset does not point at its block's length prefix") } ++ } += 4 + uint64(len()) }returnnil}// --- meta --------------------------------------------------------------------func encodeMeta( *GraphSection) ([]byte, error) {varbytes.Bufferif .Version != nil {wU8(&, 1)if := wString(&, *.Version); != nil {returnnil, } } else {wU8(&, 0) }return .Bytes(), nil}func decodeMeta( []byte, *GraphSection) error { := newCursor() , := .u8()if != nil {return }if == 1 { , := .str()if != nil {return } .Version = & }returnnil}// --- nodes (label index) -------------------------------------------------------func encodeNodes( *GraphSection) ([]byte, error) {varbytes.BufferwU32(&, .NNodes)if := encodeBitmapIndex(&, .LabelIndex); != nil {returnnil, }return .Bytes(), nil}func decodeNodes( []byte, *GraphSection) error { := newCursor() , := .u32()if != nil {return } .NNodes = .LabelIndex, = decodeBitmapIndex()return}// --- relationships (CSR + type index) --------------------------------------------func encodeRels( *GraphSection) ([]byte, error) {varbytes.BufferwU64(&, .NRels)for , := range [][]uint32{ .OutOffsets, .OutNbrs, .OutTypes, .InOffsets, .InNbrs, .InTypes, } {if := wU32Vec(&, ); != nil {returnnil, } }if := encodeBitmapIndex(&, .TypeIndex); != nil {returnnil, }return .Bytes(), nil}func decodeRels( []byte, *GraphSection) error { := newCursor()varerrorif .NRels, = .u64(); != nil {return }for , := range []*[]uint32{ &.OutOffsets, &.OutNbrs, &.OutTypes, &.InOffsets, &.InNbrs, &.InTypes, } {if *, = .u32Vec(); != nil {return } } .TypeIndex, = decodeBitmapIndex()return}// --- existence (section 8: one portable roaring bitmap) -----------------------------// encodeExistence writes the node existence bitmap as one portable// roaring serialization (the section body is exactly the bitmap; the// directory carries the length).func encodeExistence( *roaring.Bitmap) ([]byte, error) {varbytes.Bufferif , := .WriteTo(&); != nil {returnnil, }return .Bytes(), nil}// decodeExistence parses a section-8 body back into the bitmap.func decodeExistence( []byte) (*roaring.Bitmap, error) { := roaring.New()if , := .ReadFrom(bytes.NewReader()); != nil {returnnil, corruptf("invalid existence bitmap: %v", ) }return , nil}// --- bitmap indexes (label/type -> roaring) ----------------------------------------// Bitmaps use the portable RoaringFormatSpec serialization, interoperable// with the Rust and C implementations. Go roaring's WriteTo/ReadFrom speak// exactly that format; the Go-specific "frozen" form is never used.func encodeBitmapIndex( *bytes.Buffer, []BitmapEntry) error {if := wLenPrefix(, len()); != nil {return }for , := range {wU32(, .Atom) := .Bitmap.GetSerializedSizeInBytes()if := wLenPrefix(, int()); != nil {return }if , := .Bitmap.WriteTo(); != nil {return } }returnnil}func decodeBitmapIndex( *cursor) ([]BitmapEntry, error) { , := .lenPrefix(8)if != nil {returnnil, } := make([]BitmapEntry, 0, preallocCap())forrange { , := .u32()if != nil {returnnil, } , := .lenPrefix(1)if != nil {returnnil, } , := .take()if != nil {returnnil, } := roaring.New()if , := .ReadFrom(bytes.NewReader()); != nil {returnnil, corruptf("invalid roaring bitmap: %v", ) } = append(, BitmapEntry{Atom: , Bitmap: }) }return , nil}
The pages are generated with Goldsv0.8.7. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.