golang 源碼分析:cayley-4-
在分析完如何使用 cayley 後,我們總結下使用的時候的步驟:
1,初始化後端存儲,比如 memory、kv、sql 等
2,加入四元祖
3,指定起始節點
4,綁定查詢條件
5,使用迭代器獲取結果
如何初始化後端存儲呢?我們分析下它的源碼,比如 memstore 的初始化邏輯位於 imports.go,第一個參數是存儲類型,第二個參數存儲的磁盤路徑,第三個參數是可選項:
func NewMemoryGraph() (*Handle, error) {
return NewGraph("memstore", "", nil)
}
初始化後端存儲的同時會初始化四元祖 writer
func NewGraph(name, dbpath string, opts graph.Options) (*Handle, error) {
qs, err := graph.NewQuadStore(name, dbpath, opts)
qw, err := graph.NewQuadWriter("single", qs, nil)
return &Handle{qs, qw}, nil
type Handle struct {
graph.QuadStore
graph.QuadWriter
}
graph/quadwriter.go
func Unwrap(qs QuadStore) QuadStore {
if h, ok := qs.(*Handle); ok {
return h.QuadStore
}
return qs
}
四元祖 writer 定義如下:
type QuadWriter interface {
// AddQuad adds a quad to the store.
AddQuad(quad.Quad) error
// TODO(barakmich): Deprecate in favor of transaction.
// AddQuadSet adds a set of quads to the store, atomically if possible.
AddQuadSet([]quad.Quad) error
// RemoveQuad removes a quad matching the given one from the database,
// if it exists. Does nothing otherwise.
RemoveQuad(quad.Quad) error
// ApplyTransaction applies a set of quad changes.
ApplyTransaction(*Transaction) error
// RemoveNode removes all quads which have the given node as subject, predicate, object, or label.
//
// It returns ErrNodeNotExists if node is missing.
RemoveNode(quad.Value) error
// Close cleans up replication and closes the writing aspect of the database.
Close() error
}
這個文件也通過 name alias 的方式導出了其它包的對象:
StartMorphism = path.StartMorphism
StartPath = path.StartPath
NewTransaction = graph.NewTransaction
綁定起始節點的邏輯位於 graph/path/path.go
func StartPath(qs graph.QuadStore, nodes ...quad.Value) *Path {
return newPath(qs, isMorphism(nodes...))
}
它的第二個參數是 isMorphism 的返回值,其中 morphism 的定義如下,表示一個路徑的映射,內部包含了反向映射:
graph/path/morthisms_apply_functions.go
func isMorphism(nodes ...quad.Value) morphism {
return morphism{
Reversal: func(ctx *pathContext) (morphism, *pathContext) { return isMorphism(nodes...), ctx },
Apply: func(in shape.Shape, ctx *pathContext) (shape.Shape, *pathContext) {
type morphism struct {
IsTag bool
Reversal func(*pathContext) (morphism, *pathContext)
Apply applyMorphism
tags []string
}
func newPath(qs graph.QuadStore, m ...morphism) *Path {
qs = graph.Unwrap(qs)
return &Path{
stack: m,
qs: qs,
}
path 是我們進行鏈式綁定查詢條件的基礎對象,其定義如下:
type Path struct {
stack []morphism
qs graph.QuadStore // Optionally. A nil qs is equivalent to a morphism.
baseContext pathContext
}
比如我們綁定扇出查詢條件:它會複製一份路徑信息,然後把查詢條件放在後面。
func (p *Path) Out(via ...interface{}) *Path {
np := p.clone()
np.stack = append(np.stack, outMorphism(nil, via...))
if len(tags) != 0 {
via = Save{From: via, Tags: tags}
}
具體如何綁定扇出條件的代碼位於 graph/shape/path.go
func Out(from, via, labels Shape, tags ...string) Shape {
return buildOut(from, via, labels, tags, false)
}
func buildOut(from, via, labels Shape, tags []string, in bool) Shape {
start, goal := quad.Subject, quad.Object
if in {
start, goal = goal, start
}
if _, ok := from.(AllNodes); !ok {
quads = append(quads, QuadFilter{
Dir: start, Values: from,
})
if _, ok := via.(AllNodes); !ok {
quads = append(quads, QuadFilter{
Dir: quad.Predicate, Values: via,
})
if labels != nil {
if _, ok := labels.(AllNodes); !ok {
quads = append(quads, QuadFilter{
Dir: quad.Label, Values: labels,
})
return NodesFrom{Quads: quads, Dir: goal}
本質上是構造了查詢四元祖。
func Iterate(ctx context.Context, qs graph.QuadStore, s Shape) *graph.IterateChain {
it := BuildIterator(qs, s)
return graph.Iterate(ctx, it).On(qs)
四元祖過濾邏輯位於 graph/shape/shape.go
type Quads []QuadFilter
type QuadFilter struct {
Dir quad.Direction
Values Shape
}
type Shape interface {
// BuildIterator constructs an iterator tree from a given shapes and binds it to QuadStore.
BuildIterator(qs graph.QuadStore) graph.Iterator
// Optimize runs an optimization pass over a query shape.
//
// It returns a bool that indicates if shape was replaced and should always return a copy of shape in this case.
// In case no optimizations were made, it returns the same unmodified shape.
//
// If Optimizer is specified, it will be used instead of default optimizations.
Optimize(r Optimizer) (Shape, bool)
}
func BuildIterator(qs graph.QuadStore, s Shape) graph.Iterator {
qs = graph.Unwrap(qs)
return s.BuildIterator(qs)
綁定完條件,我們就通過迭代器來獲取查詢結果:
func (p *Path) Iterate(ctx context.Context) *graph.IterateChain {
return shape.Iterate(ctx, p.qs, p.Shape())
graph/iterate.go,多個迭代器會組成一個迭代器鏈
type IterateChain struct {
ctx context.Context
s IteratorShape
it Scanner
qs QuadStore
paths bool
optimize bool
limit int
n int
}
func Iterate(ctx context.Context, it Iterator) *IterateChain {
return &IterateChain{
ctx: ctx, s: AsShape(it),
limit: -1, paths: true,
optimize: true,
}
func (c *IterateChain) On(qs QuadStore) *IterateChain {
c.qs = qs
return c
設置好迭代器後通過 EachValue 來遍歷結果集:
func (c *IterateChain) EachValue(qs QuadStore, fnc func(quad.Value)) error {
return c.Each(func(v Ref) {
if nv := c.qs.NameOf(v); nv != nil {
fnc(nv)
}
})
func (c *IterateChain) Each(fnc func(Ref)) error {
c.start()
defer c.end()
for c.next() {
select {
case <-done:
return c.ctx.Err()
default:
}
fnc(c.it.Result())
for c.nextPath() {
select {
case <-done:
fnc(c.it.Result())
在 each 函數內部進行了廣度優先遍歷。
func (c *IterateChain) start() {
c.it = c.s.Iterate()
func (c *IterateChain) end() {
其中 next 函數和 nextPath 函數是邏輯的核心所在:
func (c *IterateChain) next() bool {
ok := (c.limit < 0 || c.n < c.limit) && c.it.Next(c.ctx)
func (c *IterateChain) nextPath() bool {
ok := c.paths && (c.limit < 0 || c.n < c.limit) && c.it.NextPath(c.ctx)
遍歷完了,通過 ref 獲取結果 graph/values.go
type Ref interface {
// Key returns a dynamic type that is comparable according to the Go language specification.
// The returned value must be unique for each receiver value.
Key() interface{}
}
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/uMoItpZjVrCFYNCtHlWjXQ