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