// Package rcpg reads and writes RCPG, the RustyChickpeas graph file, and its// companion RRSR record store (subpackage rrsr). The byte layout is frozen by// FORMAT.md in the rustychickpeas repository; this implementation is// conformance-tested byte-for-byte against the Rust codec.//// Quick sketch (all integers little-endian)://// header (16 B): magic "RCPG" | version u16 | flags u16 | section_count u32 | reserved u32// directory: section_count x { id u32, reserved u32, offset u64, length u64 }// sections: at the directory offsets (relative to file start)//// Section IDs: 1 atoms, 2 meta, 3 nodes, 4 relationships, 5 node columns,// 6 relationship columns, 7 optional atom block index, 8 optional node// existence bitmap (sparse id spaces only -- absent means every// in-id-space id is a node, which is exactly true for dense writers).// Unknown section IDs are ignored on read so the format can grow; files// with a version above 1 are rejected.
package rcpgimport ()// Magic is the RCPG file magic.varMagic = [4]byte{'R', 'C', 'P', 'G'}// Version is the RCPG format version this package reads and writes.constVersionuint16 = 1const ( sectionAtoms uint32 = 1 sectionMeta uint32 = 2 sectionNodes uint32 = 3 sectionRels uint32 = 4 sectionNodeCols uint32 = 5 sectionRelCols uint32 = 6 sectionAtomIndex uint32 = 7 sectionExistence uint32 = 8)// atomIndexBlockLen is the writer default for the section-7 atom index's// atoms-per-block: ~4 KB of u64 offsets per million atoms, small enough to// hold resident on a lazy reader.const atomIndexBlockLen = 1024const ( headerLen = 16 dirEntryLen = 24)// WriteOptions controls which optional sections a writer emits. Topology// (atoms, meta, nodes, relationships) is always written; property columns// are optional so large graphs can ship a lean traversal-only file and leave// per-node data to a range-fetched record store.typeWriteOptionsstruct { NodeColumns bool RelColumns bool// AtomIndex emits the optional section-7 atom block index. Default // off, so existing byte-identity guarantees (and the vendored // conformance corpus) are unchanged unless a writer opts in. AtomIndex bool// Existence emits the optional section-8 node existence bitmap when // the graph carries one (sparse id spaces). Default off for the same // byte-identity reason as AtomIndex: the conformance corpus is the // cross-engine contract, and the section joins it in a coordinated // corpus bump once the Rust writer emits it too. Until a sparse // graph is written WITH this, its round trip keeps the legacy // every-in-space-id-exists presumption. Existence bool}// DefaultWriteOptions emits every section.func () WriteOptions {returnWriteOptions{NodeColumns: true, RelColumns: true}}// TopologyOnlyWriteOptions emits no property column sections.func () WriteOptions {returnWriteOptions{}}// ParseOptions controls which optional sections a reader materializes.// Skipped sections cost nothing to parse and nothing in resident memory,// regardless of whether the file contains them.typeParseOptionsstruct { NodeColumns bool RelColumns bool// SkipAtoms leaves the atom table out of the parse: GraphSection.Atoms // stays nil and ParseLazy never fetches the atoms section. Labels, rel // types, and property keys remain raw atom ids; resolve them on demand // through an AtomReader over the same SectionFetch. SkipAtoms bool}// DefaultParseOptions materializes every section.func () ParseOptions {returnParseOptions{NodeColumns: true, RelColumns: true}}// TopologyOnlyParseOptions ignores property column sections even when// present.func () ParseOptions {returnParseOptions{}}// SkeletonParseOptions materializes the CSR skeleton only: no property// columns and no atom table. The lightest resident load for traversal-first// consumers that resolve atom ids lazily via AtomReader.func () ParseOptions {returnParseOptions{SkipAtoms: true}}// Write serializes a graph to RCPG bytes, including all sections.func ( *GraphSection, io.Writer) error {returnWriteWith(, , DefaultWriteOptions())}// WriteWith serializes a graph to RCPG bytes, emitting optional sections per// opts. The output is deterministic: section order, and the order of bitmap// index entries and columns within their sections, follow the GraphSection// slices exactly (which the spec requires sorted by atom).func ( *GraphSection, io.Writer, WriteOptions) error {typestruct {uint32 []byte } := func( uint32, func() ([]byte, error)) (, error) { , := ()return {: , : }, } := make([], 0, 6)for , := range []struct {uint32func() ([]byte, error)bool }{ {sectionAtoms, func() ([]byte, error) { returnencodeAtoms(.Atoms) }, true}, {sectionMeta, func() ([]byte, error) { returnencodeMeta() }, true}, {sectionNodes, func() ([]byte, error) { returnencodeNodes() }, true}, {sectionRels, func() ([]byte, error) { returnencodeRels() }, true}, {sectionNodeCols, func() ([]byte, error) { returnencodeColumns(.NodeColumns) }, .NodeColumns}, {sectionRelCols, func() ([]byte, error) { returnencodeColumns(.RelColumns) }, .RelColumns}, {sectionAtomIndex, func() ([]byte, error) { returnencodeAtomIndex(.Atoms, atomIndexBlockLen) }, .AtomIndex}, {sectionExistence, func() ([]byte, error) { returnencodeExistence(.Existence) }, .Existence && .Existence != nil}, } {if !. {continue } , := (., .)if != nil {return } = append(, ) }varbytes.Buffer .Write(Magic[:])wU16(&, Version)wU16(&, 0) // flagswU32(&, uint32(len()))wU32(&, 0) // reserved := uint64(headerLen + len()*dirEntryLen)for , := range {wU32(&, .)wU32(&, 0) // reservedwU64(&, )wU64(&, uint64(len(.))) += uint64(len(.)) }if , := .Write(.Bytes()); != nil {return }for , := range {if , := .Write(.); != nil {return } }returnnil}// Parse decodes RCPG bytes into a GraphSection, materializing all sections.// It never panics on malformed input; all structural problems surface as// errors wrapping ErrCorrupt (or ErrUnsupportedVersion for a future format// version).func ( []byte) (*GraphSection, error) {returnParseWith(, DefaultParseOptions())}// ParseWith decodes RCPG bytes, materializing optional sections per opts.// Use TopologyOnlyParseOptions to keep property columns out of memory on// large graphs.func ( []byte, ParseOptions) (*GraphSection, error) { , := ParseDirectory()if != nil {returnnil, } := &GraphSection{}for , := range .Entries() { := .Offset + .Lengthif < .Offset {returnnil, corruptf("section %d offset+length overflows", .ID) }if > uint64(len()) {returnnil, corruptf("section %d extends past end of file (%d > %d)", .ID, , len()) }if := decodeSection(.ID, [.Offset:], , ); != nil {returnnil, } }// A PRESENT atom index must be correct -- a malformed one is corrupt, // never a silent fall-back to the scan path. Deep validation needs the // materialized atoms; a SkipAtoms parse keeps the structurally-decoded // index and leaves the per-block check to the consumer (AtomReader).if .AtomIndex != nil && !.SkipAtoms {if := validateAtomIndex(.AtomIndex, .Atoms); != nil {returnnil, } }return , nil}// decodeSection decodes one section body into g, honouring opts for the// optional column sections. Unknown section IDs are ignored (forward// compatibility). Shared by the eager ParseWith and the lazy ParseLazy paths// so both decode identically.func decodeSection( uint32, []byte, ParseOptions, *GraphSection) error {varerrorswitch {case == sectionAtoms && !.SkipAtoms: .Atoms, = decodeAtoms()case == sectionMeta: = decodeMeta(, )case == sectionNodes: = decodeNodes(, )case == sectionRels: = decodeRels(, )case == sectionNodeCols && .NodeColumns: .NodeColumns, = decodeColumns()case == sectionRelCols && .RelColumns: .RelColumns, = decodeColumns()case == sectionAtomIndex: .AtomIndex, = decodeAtomIndex()case == sectionExistence: .Existence, = decodeExistence()default:// Present-but-skipped columns, or forward compatibility: ignore // unknown sections. }return}// sectionWanted reports whether a section is needed under opts: topology// (meta/nodes/relationships) always, atoms unless skipped, the property// columns only when their option is set. Unknown sections are never fetched.func sectionWanted( uint32, ParseOptions) bool {switch {casesectionAtoms:return !.SkipAtomscasesectionMeta, sectionNodes, sectionRels, sectionAtomIndex:returntruecasesectionNodeCols:return .NodeColumnscasesectionRelCols:return .RelColumns }returnfalse}
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.