// Builder removal surface: property removal (node and rel), rel tombstones,// and detach-delete of nodes. Removals stay cheap at call time -- rels are// tombstoned rather than compacted so handed-out rel indexes stay stable,// and detach-delete defers the incident-rel cascade to Finalize (no per-node// rel index exists, so an eager cascade would be O(m) per call).//// Miss-reporting convention, uniform across the family: every method// reports whether it changed staged state as a bool, and an error is// reserved for a dangling rel handle -- a rel index (returned by AddRel)// or (u, v, type) address that is out of range, tombstoned, or dead via a// detach-deleted endpoint is ErrRelNotFound. Node ids are open-world// (AddNodeWithID stages arbitrary ids), so an unknown node is a plain// miss, never an error.package chickpeasimport ()// removeAllPairs filters every pair staged for id, preserving staging order,// reporting whether any was removed.func removeAllPairs[ interface{ () uint32 }]( [], uint32) ([], bool) { := [:0]for , := range {if .() != { = append(, ) } }return , len() != len()}// sweepPairs applies removeAllPairs to one key of a typed column map,// dropping the key entirely when its last pair goes (an empty column must// not finalize).func sweepPairs[ interface{ () uint32 }]( map[PropertyKey][], PropertyKey, uint32) bool { , := []if ! {returnfalse } , := removeAllPairs(, )iflen() == 0 {delete(, ) } else { [] = }return}// removeNodePropPairsByKey sweeps node's staged pairs for key across all// four typed columns -- a key staged under multiple value types loses every// staged occurrence, enforcing last-write-wins across types (Finalize's four// column loops would otherwise resolve a cross-type duplicate by loop order,// not write order).func ( *Builder) ( NodeID, PropertyKey) bool { := sweepPairs(.nodeColI64, , ) = sweepPairs(.nodeColF64, , ) || = sweepPairs(.nodeColBool, , ) || = sweepPairs(.nodeColStr, , ) || if { .markNodeColDirty() }return}// RemoveProp deletes node's staged property key, sweeping every staged// occurrence across all four typed columns (duplicate staged writes and// cross-type stagings all go -- a partial sweep would resurrect a stale// value at Finalize). Reports whether anything was removed; false covers// every kind of miss uniformly (a key never staged anywhere, or staged but// not on this node -- either way no staged pair existed for (node, key)).func ( *Builder) ( NodeID, string) bool { , := .interner.Get()if ! {returnfalse }return .removeNodePropPairsByKey(, )}// removeNodePropPairs purges every staged property pair of node across all// keys (the detach-delete property sweep). Only the keys that actually held a// pair for node are marked dirty; the rest still alias at Finalize, since a// node the column never covered leaves the rebuilt column unchanged.func ( *Builder) ( NodeID) { := func( bool, PropertyKey) {if { .markNodeColDirty() } }for := range .nodeColI64 { (sweepPairs(.nodeColI64, , ), ) }for := range .nodeColF64 { (sweepPairs(.nodeColF64, , ), ) }for := range .nodeColBool { (sweepPairs(.nodeColBool, , ), ) }for := range .nodeColStr { (sweepPairs(.nodeColStr, , ), ) }}// RemoveRelProp deletes the staged property key on the first rel matching// (u, v, relType) -- the addressing dual of SetRelProp. For parallel rels,// address the specific rel via RemoveRelPropAt. Reports whether anything// was removed; an unmatched (u, v, relType) address is ErrRelNotFound.func ( *Builder) (, NodeID, , string) (bool, error) { , := .findRelIndex(, , )if ! {returnfalse, fmt.Errorf("%w: (%d)-[:%s]->(%d)", ErrRelNotFound, , , ) }return .RemoveRelPropAt(, )}// RemoveRelPropAt deletes the staged property key on the rel at relIdx (as// returned by AddRel), sweeping every staged occurrence across all four// typed columns. Reports whether anything was removed -- a key with no// staged pair on this rel is (false, nil), distinguishable from a real// removal; a removed or out-of-range rel is ErrRelNotFound.func ( *Builder) ( int, string) (bool, error) {if < 0 || >= len(.rels) || .relRemoved() {returnfalse, fmt.Errorf("%w: rel index %d", ErrRelNotFound, ) } , := .interner.Get()if ! {returnfalse, nil } := uint32() := sweepPairs(.relColI64, , ) = sweepPairs(.relColF64, , ) || = sweepPairs(.relColBool, , ) || = sweepPairs(.relColStr, , ) || if { .markRelColDirty() }return , nil}// RemoveRel tombstones the rel at relIdx (as returned by AddRel). The// staging array is never compacted -- swap-removal would invalidate every// handed-out rel index and the staged rel-prop ids -- so the rel is marked// removed, degrees adjust immediately, and Finalize compacts in one pass.// Removing a rel that is out of range, already removed, or dead via a// detach-deleted endpoint is ErrRelNotFound (no removed bool: a live// handle always removes, so removal happened exactly when err is nil).func ( *Builder) ( int) error {if < 0 || >= len(.rels) || .relRemoved() {returnfmt.Errorf("%w: rel index %d", ErrRelNotFound, ) }if .removedRels == nil { .removedRels = roaring.New() } .markRelsDirty() .removedRels.Add(uint32()) := .rels[] .degOut[[0]]-- .degIn[[1]]-- .relIndex = nil// lazily rebuilds skipping tombstonesreturnnil}// RemoveNode detach-deletes node: its labels and staged properties go// immediately, and every currently staged incident rel (with its rel// properties) dies at Finalize. Reports whether the node was known.//// Ids retire rather than reuse -- nextNodeID never rewinds -- but removal is// not a permanent tombstone: any later staging touch (AddNodeWithID,// SetProp, or being an AddRel endpoint) resurrects the id as a fresh// unlabeled, propertyless node. Rels staged before the removal stay dead// either way; only rels added after the resurrection survive.func ( *Builder) ( NodeID) bool {if !.knownNodes.Contains() {returnfalse } .knownNodes.Remove()ifint() < len(.nodeLabels) {for , := range .nodeLabels[] { .markLabelDirty() } .nodeLabels[] = nil }// The Finalize cascade rescans every staged rel for incidence, so the rel // set is treated as dirty even when the node turns out to be isolated. .markRelsDirty() .removeNodePropPairs()if .removedNodes == nil { .removedNodes = map[NodeID]int{} }// The watermark (staged rel count at removal time) scopes the Finalize // cascade: rels below it die, rels added afterwards -- which resurrect // the node -- survive. .removedNodes[] = len(.rels) .relIndex = nil// may reference rels the cascade will killreturntrue}// relRemoved reports whether the staged rel at idx is dead -- explicitly// tombstoned, or staged before a detach-delete of either endpoint.func ( *Builder) ( int) bool {if .removedRels != nil && .removedRels.Contains(uint32()) {returntrue }iflen(.removedNodes) > 0 { := .rels[]if , := .removedNodes[[0]]; && < {returntrue }if , := .removedNodes[[1]]; && < {returntrue } }returnfalse}// hasRemovals reports whether any removal state is pending compaction.func ( *Builder) () bool {return (.removedRels != nil && !.removedRels.IsEmpty()) || len(.removedNodes) > 0}// compactRemovals folds pending removals into the staging state in one// O(m + pairs) pass: dead rels (explicit tombstones plus the detach-delete// cascade) drop out in staging order, degrees recompute, and staged rel-prop// pairs remap to the compacted indexes or drop with their rel. Called by// Finalize before any build phase; a no-op without removals.func ( *Builder) () {if !.hasRemovals() {return } := len(.rels) := make([]int32, ) := 0for := range {if .relRemoved() { [] = -1continue } [] = int32() .rels[] = .rels[] .relTypes[] = .relTypes[] ++ } .rels = .rels[:] .relTypes = .relTypes[:]clear(.degOut)clear(.degIn)for , := range .rels { .degOut[[0]]++ .degIn[[1]]++ }remapRelPairs(.relColI64, )remapRelPairs(.relColF64, )remapRelPairs(.relColBool, )remapRelPairs(.relColStr, ) .removedRels = nil .removedNodes = nil .relIndex = nil}// relPair constrains the four staged pair types for compaction remapping.type relPair[ any] interface { pairID() uint32 withPairID(id uint32) }// remapRelPairs rewrites staged rel-prop ids through the compaction map,// dropping pairs on dead rels and keys left empty.func remapRelPairs[ relPair[]]( map[PropertyKey][], []int32) {for , := range { := [:0]for , := range { := [.pairID()]if < 0 {continue } = append(, .withPairID(uint32())) }iflen() == 0 {delete(, ) } else { [] = } }}
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.