// Thaw: reconstruct a mutable Builder from an immutable Snapshot, enabling// the read-modify-refinalize-swap write loop (thaw an existing snapshot,// edit it, Finalize a new one, register it in the Manager). Snapshots stay// immutable throughout; the Manager swap is the commit point.package chickpeasimport// NewBuilderFromSnapshot thaws g back into a Builder whose Finalize// reproduces g -- a no-edit thaw -> Finalize -> WriteRCPG round trip is// byte-identical for any snapshot Finalize itself can produce. Atom ids are// preserved exactly (the interner is seeded from g's atom table), so labels,// rel types, property keys, and string values stay valid across the thaw.//// Two lossy corners are inherited from the snapshot representation itself://// - Dense columns cannot distinguish "never set" from the zero value, so// the thaw stages a pair for every position of a dense column (including// atom-0 positions of a dense string column, mirroring the read layer,// which reports those as present ""). Since tasks/041 Go only finalizes// numeric/bool columns dense at full coverage, so this zero-fill// materialization arises for those dtypes only when thawing a legacy or// Rust-written file whose dense column was written at partial fill --// the missingness was destroyed at that write, and refinalize keeps the// zero-filled positions as genuine (staged) values.// - Ghost nodes -- isolated, unlabeled, propertyless -- leave no// identifiable trace. Known nodes rebuild from labels, rel endpoints,// and column positions; ghosts outside that union are lost (their count// no longer contributes to NodeCount), and dense-column positions inside// it may register ids the original builder never saw.func ( *Snapshot) *Builder { := int(.CSRIDSpace()) := len(.outNbrs) := NewBuilder(max(, 1), max(, 1)) .interner = newInternerFromAtoms(.atoms)if .version != nil { := *.version .version = & }// Labels: invert the per-label bitmaps into per-node lists, ascending // atom order (per-node order was never serialized and Finalize only // rebuilds the bitmaps, so atom order is canonical). := make([]Label, 0, len(.labelIndex))for := range .labelIndex { = append(, ) }slices.Sort()for , := range {for := range .labelIndex[].Iter() { .nodeLabels[] = append(.nodeLabels[], ) .knownNodes.Add() } }// Rels: restage in a linear extension of BOTH CSR directions' per-node // orders, so refinalizing reproduces each CSR byte-identically (naive // outgoing order can permute parallel rels within an incoming range). := thawRels(, )for , := range .columns {thawNodeColumn(, , ) }for , := range .relColumns {thawRelColumn(, , , ) }// Seed known nodes from the retained existence set when present: // label/rel/prop restaging above cannot see a label-less isolated // node, so without this a sparse graph's such nodes would vanish // across a thaw round trip.if .existence != nil {for := range .existence.Iter() { .knownNodes.Add() } }if !.knownNodes.IsEmpty() { .nextNodeID = .knownNodes.Maximum() + 1 }// Record the source last: every staging call above runs against a // source-less builder, so the restage itself dirties nothing and an // unedited thaw finalizes by aliasing g wholesale (cow.go). .src = .srcRelToOutCSR = invertPositions()return}// invertPositions inverts the outgoing-CSR position -> staged rel index map// into the staged rel index -> outgoing-CSR position map Finalize needs to// place rel properties when it aliases the CSR rather than rebuilding it.func invertPositions( []uint32) []uint32 { := make([]uint32, len())for , := range { [] = uint32() }return}// thawRels stages g's rels in an order consistent with both CSR directions,// returning the outgoing-CSR position -> staged rel index map (for rel// column restaging). Each rel goes through the builder's staging core// (addRelTyped) so degrees, known endpoints, ceilings, and any future// staging invariant stay owned by one function.func thawRels( *Builder, *Snapshot) []uint32 { := thawRelOrder() := make([]uint32, len()) := ownersFromOffsets(.outOffsets)for , := range { , := [], .outNbrs[] , := .addRelTyped(, , .outTypes[])thawMust() [] = uint32() }return}// thawMust asserts a staging call that cannot fail during thaw: every id// and index comes from a consistent snapshot, which already fits the// builder's ceilings (the builder is pre-sized to the snapshot's id space// and rel count). A failure here is an engine bug, not caller input.func thawMust( error) {if != nil {panic("chickpeas: thaw staging invariant violated: " + .Error()) }}// thawRelOrder computes a staging order (as outgoing-CSR positions) that is// a linear extension of both CSRs' per-node orders. Each direction imposes a// chain per node (its CSR range order); the original insertion order is one// valid extension, so the constraint graph -- at most two predecessors per// rel -- is acyclic and a Kahn walk recovers an equivalent order. Rels are// paired across directions by k-th occurrence of (src, dst, type), the same// pairing computeInToOutFromCSR uses.func thawRelOrder( *Snapshot) []uint32 { := len(.outNbrs)if == 0 {returnnil } := .inToOutif == nil { = computeInToOutFromCSR( .outOffsets, .outNbrs, .outTypes, .inOffsets, .inNbrs, .inTypes) } := make([]uint32, )for , := range { [] = uint32() } := ownersFromOffsets(.outOffsets) := ownersFromOffsets(.inOffsets)// indeg[p]: one predecessor per chain p is not the head of. := make([]uint8, )for := range {ifuint32() != .outOffsets[[]] { []++ }if := []; != .inOffsets[[]] { []++ } } := make([]uint32, 0, )for := range {if [] == 0 { = append(, uint32()) } } := make([]uint32, 0, ) := func( uint32) { []--if [] == 0 { = append(, ) } }for := 0; < len(); ++ { := [] = append(, )if +1 < .outOffsets[[]+1] { ( + 1) }if := []; +1 < .inOffsets[[]+1] { ([+1]) } }// A consistent snapshot always yields a complete order; on inconsistent // CSRs (a hand-built section whose directions disagree) fall back to // outgoing order for the remainder -- the outgoing CSR still round-trips.iflen() < { := make([]bool, )for , := range { [] = true }for := range {if ![] { = append(, uint32()) } } }return}// ownersFromOffsets expands a CSR offset array into the owning node per// position.func ownersFromOffsets( []uint32) []NodeID { := len() - 1if < 0 {returnnil } := make([]NodeID, [])for := range {for := []; < [+1]; ++ { [] = NodeID() } }return}// thawNodeColumn restages one node column as builder pairs (Entries yields// every position of a dense column, present positions of sparse and// rank/select layouts), installed through the builder's bulk staging entry// so each position registers as a known node under the builder's own// invariants.func thawNodeColumn( *Builder, PropertyKey, Column) {switch .Dtype() {caseDtypeI64: := make([]i64Pair, 0, .Len())for , := range .Entries() { , := .I64() = append(, i64Pair{id: , val: }) }thawMust(setNodeColumnPairs(, .nodeColI64, , ))caseDtypeF64: := make([]f64Pair, 0, .Len())for , := range .Entries() { , := .F64() = append(, f64Pair{id: , val: }) }thawMust(setNodeColumnPairs(, .nodeColF64, , ))caseDtypeBool: := make([]boolPair, 0, .Len())for , := range .Entries() { , := .Bool() = append(, boolPair{id: , val: }) }thawMust(setNodeColumnPairs(, .nodeColBool, , ))caseDtypeStr: := make([]strPair, 0, .Len())for , := range .Entries() { , := .StrID() = append(, strPair{id: , val: }) }thawMust(setNodeColumnPairs(, .nodeColStr, , )) }}// thawRelColumn restages one rel column, remapping stored outgoing-CSR// positions to staged rel indexes (Finalize maps them back), installed// through the builder's bulk staging entry so every id is checked as a// live staged rel index.func thawRelColumn( *Builder, PropertyKey, Column, []uint32) {switch .Dtype() {caseDtypeI64: := make([]i64Pair, 0, .Len())for , := range .Entries() { , := .I64() = append(, i64Pair{id: [], val: }) }thawMust(setRelColumnPairs(, .relColI64, , ))caseDtypeF64: := make([]f64Pair, 0, .Len())for , := range .Entries() { , := .F64() = append(, f64Pair{id: [], val: }) }thawMust(setRelColumnPairs(, .relColF64, , ))caseDtypeBool: := make([]boolPair, 0, .Len())for , := range .Entries() { , := .Bool() = append(, boolPair{id: [], val: }) }thawMust(setRelColumnPairs(, .relColBool, , ))caseDtypeStr: := make([]strPair, 0, .Len())for , := range .Entries() { , := .StrID() = append(, strPair{id: [], val: }) }thawMust(setRelColumnPairs(, .relColStr, , )) }}
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.