package chickpeas
import (
"errors"
"fmt"
"github.com/RoaringBitmap/roaring/v2"
)
var ErrCapacity = errors .New ("capacity exceeded" )
var ErrRelNotFound = errors .New ("relationship not found" )
var ErrBadValue = errors .New ("unsupported property value type" )
type i64Pair struct {
id uint32
val int64
}
type f64Pair struct {
id uint32
val float64
}
type boolPair struct {
id uint32
val bool
}
type strPair struct {
id uint32
val uint32
}
type Builder struct {
degOut, degIn []uint32
rels [][2 ]NodeID
nodeLabels [][]Label
relTypes []RelType
version *string
nodeColI64 map [PropertyKey ][]i64Pair
nodeColF64 map [PropertyKey ][]f64Pair
nodeColBool map [PropertyKey ][]boolPair
nodeColStr map [PropertyKey ][]strPair
relColI64 map [PropertyKey ][]i64Pair
relColF64 map [PropertyKey ][]f64Pair
relColBool map [PropertyKey ][]boolPair
relColStr map [PropertyKey ][]strPair
interner *Interner
nextNodeID NodeID
knownNodes *roaring .Bitmap
relIndex map [[3 ]uint32 ]int
removedRels *roaring .Bitmap
removedNodes map [NodeID ]int
src *Snapshot
srcRelToOutCSR []uint32
dirtyNodeCols map [PropertyKey ]struct {}
dirtyRelCols map [PropertyKey ]struct {}
dirtyLabels map [Label ]struct {}
relsDirty bool
}
func NewBuilder (capNodes , capRels int ) *Builder {
const defaultCapacity = 1 << 20
if capNodes <= 0 {
capNodes = defaultCapacity
}
if capRels <= 0 {
capRels = defaultCapacity
}
return &Builder {
degOut : make ([]uint32 , capNodes ),
degIn : make ([]uint32 , capNodes ),
rels : make ([][2 ]NodeID , 0 , capRels ),
nodeLabels : make ([][]Label , capNodes ),
relTypes : make ([]RelType , 0 , capRels ),
nodeColI64 : map [PropertyKey ][]i64Pair {},
nodeColF64 : map [PropertyKey ][]f64Pair {},
nodeColBool : map [PropertyKey ][]boolPair {},
nodeColStr : map [PropertyKey ][]strPair {},
relColI64 : map [PropertyKey ][]i64Pair {},
relColF64 : map [PropertyKey ][]f64Pair {},
relColBool : map [PropertyKey ][]boolPair {},
relColStr : map [PropertyKey ][]strPair {},
interner : NewInterner (),
knownNodes : roaring .New (),
}
}
func (b *Builder ) SetVersion (version string ) {
b .version = &version
}
func (b *Builder ) ensureCapacity (id NodeID ) error {
if int (id ) < len (b .degOut ) {
return nil
}
const maxSize = int (^uint32 (0 ))
newSize := min ((int (id )+1 )*2 , maxSize )
if newSize <= int (id ) {
return fmt .Errorf ("%w: maximum node limit (2^32 - 1)" , ErrCapacity )
}
grow := func (s []uint32 ) []uint32 {
out := make ([]uint32 , newSize )
copy (out , s )
return out
}
b .degOut = grow (b .degOut )
b .degIn = grow (b .degIn )
labels := make ([][]Label , newSize )
copy (labels , b .nodeLabels )
b .nodeLabels = labels
return nil
}
func (b *Builder ) AddNode (labels ...string ) (NodeID , error ) {
id := b .nextNodeID
if id == ^uint32 (0 ) {
return 0 , fmt .Errorf ("%w: node id counter reached u32 max" , ErrCapacity )
}
b .nextNodeID = id + 1
return b .addNodeAt (id , labels )
}
func (b *Builder ) AddNodeWithID (id NodeID , labels ...string ) (NodeID , error ) {
if id >= b .nextNodeID {
b .nextNodeID = id + 1
}
return b .addNodeAt (id , labels )
}
func (b *Builder ) addNodeAt (id NodeID , labels []string ) (NodeID , error ) {
if err := b .ensureCapacity (id ); err != nil {
return 0 , err
}
for _ , l := range labels {
label := Label (b .interner .GetOrIntern (l ))
b .nodeLabels [id ] = append (b .nodeLabels [id ], label )
b .markLabelDirty (label )
}
b .knownNodes .Add (id )
return id , nil
}
func (b *Builder ) AddRel (u , v NodeID , relType string ) (int , error ) {
return b .addRelTyped (u , v , RelType (b .interner .GetOrIntern (relType )))
}
func (b *Builder ) addRelTyped (u , v NodeID , typeID RelType ) (int , error ) {
if err := b .ensureCapacity (max (u , v )); err != nil {
return 0 , err
}
if len (b .rels ) >= int (^uint32 (0 )) {
return 0 , fmt .Errorf ("%w: maximum rel limit (2^32 - 1)" , ErrCapacity )
}
b .markRelsDirty ()
b .degOut [u ]++
b .degIn [v ]++
idx := len (b .rels )
b .rels = append (b .rels , [2 ]NodeID {u , v })
b .relTypes = append (b .relTypes , typeID )
b .knownNodes .Add (u )
b .knownNodes .Add (v )
if b .relIndex != nil {
key := [3 ]uint32 {u , v , typeID .ID ()}
if _ , ok := b .relIndex [key ]; !ok {
b .relIndex [key ] = idx
}
}
return idx , nil
}
func setNodeColumnPairs[P interface { pairID () uint32 }](b *Builder , cols map [PropertyKey ][]P , key PropertyKey , pairs []P ) error {
if len (pairs ) == 0 {
return nil
}
for _ , p := range pairs {
if err := b .ensureCapacity (p .pairID ()); err != nil {
return err
}
b .knownNodes .Add (p .pairID ())
}
cols [key ] = pairs
return nil
}
func setRelColumnPairs[P interface { pairID () uint32 }](b *Builder , cols map [PropertyKey ][]P , key PropertyKey , pairs []P ) error {
if len (pairs ) == 0 {
return nil
}
for _ , p := range pairs {
idx := int (p .pairID ())
if idx >= len (b .rels ) || b .relRemoved (idx ) {
return fmt .Errorf ("%w: rel index %d" , ErrRelNotFound , idx )
}
}
cols [key ] = pairs
return nil
}
func (b *Builder ) stageValue (value any ) (Value , error ) {
switch v := value .(type ) {
case Value :
return v , nil
case string :
return StrValue (b .interner .GetOrIntern (v )), nil
case int64 :
return I64Value (v ), nil
case int :
return I64Value (int64 (v )), nil
case int32 :
return I64Value (int64 (v )), nil
case float64 :
return F64Value (v ), nil
case bool :
return BoolValue (v ), nil
}
return Value {}, fmt .Errorf ("%w: %T" , ErrBadValue , value )
}
func (b *Builder ) InternPropertyKey (name string ) PropertyKey {
return b .interner .GetOrIntern (name )
}
func (b *Builder ) SetProp (node NodeID , key string , value any ) error {
k := b .interner .GetOrIntern (key )
return b .SetPropByKey (node , k , value )
}
func (b *Builder ) SetPropByKey (node NodeID , key PropertyKey , value any ) error {
v , err := b .stageValue (value )
if err != nil {
return err
}
if err := b .ensureCapacity (node ); err != nil {
return err
}
b .markNodeColDirty (key )
switch v .Kind () {
case KindStr :
atom , _ := v .StrID ()
b .nodeColStr [key ] = append (b .nodeColStr [key ], strPair {id : node , val : atom })
case KindI64 :
x , _ := v .I64 ()
b .nodeColI64 [key ] = append (b .nodeColI64 [key ], i64Pair {id : node , val : x })
case KindF64 :
x , _ := v .F64 ()
b .nodeColF64 [key ] = append (b .nodeColF64 [key ], f64Pair {id : node , val : x })
case KindBool :
x , _ := v .Bool ()
b .nodeColBool [key ] = append (b .nodeColBool [key ], boolPair {id : node , val : x })
}
b .knownNodes .Add (node )
return nil
}
func (b *Builder ) UpdateProp (node NodeID , key string , value any ) error {
k := b .interner .GetOrIntern (key )
v , err := b .stageValue (value )
if err != nil {
return err
}
b .removeNodePropPairsByKey (node , k )
return b .SetPropByKey (node , k , v )
}
func (p i64Pair ) pairID () uint32 { return p .id }
func (p f64Pair ) pairID () uint32 { return p .id }
func (p boolPair ) pairID () uint32 { return p .id }
func (p strPair ) pairID () uint32 { return p .id }
func (p i64Pair ) withPairID (id uint32 ) i64Pair { p .id = id ; return p }
func (p f64Pair ) withPairID (id uint32 ) f64Pair { p .id = id ; return p }
func (p boolPair ) withPairID (id uint32 ) boolPair { p .id = id ; return p }
func (p strPair ) withPairID (id uint32 ) strPair { p .id = id ; return p }
func (b *Builder ) findRelIndex (u , v NodeID , relType string ) (int , bool ) {
typeID , ok := b .interner .Get (relType )
if !ok {
return 0 , false
}
if b .relIndex == nil {
b .relIndex = make (map [[3 ]uint32 ]int , len (b .rels ))
for idx , r := range b .rels {
if b .relRemoved (idx ) {
continue
}
key := [3 ]uint32 {r [0 ], r [1 ], b .relTypes [idx ].ID ()}
if _ , seen := b .relIndex [key ]; !seen {
b .relIndex [key ] = idx
}
}
}
idx , ok := b .relIndex [[3 ]uint32 {u , v , typeID }]
return idx , ok
}
func (b *Builder ) SetRelProp (u , v NodeID , relType , key string , value any ) error {
idx , ok := b .findRelIndex (u , v , relType )
if !ok {
return fmt .Errorf ("%w: (%d)-[:%s]->(%d)" , ErrRelNotFound , u , relType , v )
}
return b .SetRelPropAt (idx , key , value )
}
func (b *Builder ) SetRelPropAt (relIdx int , key string , value any ) error {
if relIdx < 0 || relIdx >= len (b .rels ) {
return fmt .Errorf ("%w: rel index %d out of range (%d rels)" , ErrRelNotFound , relIdx , len (b .rels ))
}
if b .relRemoved (relIdx ) {
return fmt .Errorf ("%w: rel index %d removed" , ErrRelNotFound , relIdx )
}
k := b .interner .GetOrIntern (key )
v , err := b .stageValue (value )
if err != nil {
return err
}
b .markRelColDirty (k )
idx := uint32 (relIdx )
switch v .Kind () {
case KindStr :
atom , _ := v .StrID ()
b .relColStr [k ] = append (b .relColStr [k ], strPair {id : idx , val : atom })
case KindI64 :
x , _ := v .I64 ()
b .relColI64 [k ] = append (b .relColI64 [k ], i64Pair {id : idx , val : x })
case KindF64 :
x , _ := v .F64 ()
b .relColF64 [k ] = append (b .relColF64 [k ], f64Pair {id : idx , val : x })
case KindBool :
x , _ := v .Bool ()
b .relColBool [k ] = append (b .relColBool [k ], boolPair {id : idx , val : x })
}
return nil
}
The pages are generated with Golds v0.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 .