golang 源碼分析:cayley-9-

接着看下 query 目錄,首先是 session.go 文件,它定義了迭代器接口

type Iterator interface {
  // Next advances the iterator to the next value, which will then be available through
  // the Result method. It returns false if no further advancement is possible, or if an
  // error was encountered during iteration.  Err should be consulted to distinguish
  // between the two cases.
  Next(ctx context.Context) bool
  // Results returns the current result. The type depends on the collation mode of the query.
  Result() interface{}
  // Err returns any error that was encountered by the Iterator.
  Err() error
  // Close the iterator and do internal cleanup.
  Close() error
}
  type Collation int

語言可以是 json 格式也可以是 REPL 格式

const (
  // Raw collates results as maps or arrays of graph.Refs or any other query-native or graph-native data type.
  Raw = Collation(iota)
  // REPL collates results as strings which will be used in CLI.
  REPL = Collation(iota)
  // JSON collates results as maps, arrays and values, that can be encoded to JSON.
  JSON
  // JSONLD collates results as maps, arrays and values compatible with JSON-LD spec.
  JSONLD
)
type Options struct {
  Limit     int
  Collation Collation
}
type Session interface {
  // Execute runs the query and returns an iterator over the results.
  // Type of results depends on Collation. See Options for details.
  Execute(ctx context.Context, query string, opt Options) (Iterator, error)
}
type HTTP interface {
  Session
  ShapeOf(string) (interface{}, error)
}
type REPLSession = Session
type Language struct {
  Name    string
  Session func(graph.QuadStore) Session
  REPL    func(graph.QuadStore) REPLSession // deprecated
  HTTP    func(graph.QuadStore) HTTP
  // Custom HTTP handlers
  HTTPQuery func(ctx context.Context, qs graph.QuadStore, w ResponseWriter, r io.Reader)
  HTTPError func(w ResponseWriter, err error)
}
var languages = make(map[string]Language)
func RegisterLanguage(lang Language) {
  languages[lang.Name] = lang
}
func NewSession(qs graph.QuadStore, lang string) Session {
  if l := languages[lang]; l.Session != nil {
    return l.Session(qs)
func GetLanguage(lang string) *Language {
  l, ok := languages[lang]
func Execute(ctx context.Context, qs graph.QuadStore, lang, query string, opt Options) (Iterator, error) {
  l := GetLanguage(lang)
  if l == nil {
    return nil, fmt.Errorf("unsupported language: %q", lang)
  }
  sess := l.Session(qs)
  return sess.Execute(ctx, query, opt)

query/gizmo/environ.go 定義了 gizmo 的翻譯執行過程

type graphObject struct {
  s *Session
}
func (g *graphObject) NewIRI(s string) quad.IRI {
  return quad.IRI(g.s.ns.FullIRI(s))
}
func (g *graphObject) AddNamespace(pref, ns string) {
  g.s.ns.Register(voc.Namespace{Prefix: pref + ":", Full: ns})
}
func (g *graphObject) AddDefaultNamespaces() {
  voc.CloneTo(&g.s.ns)
func (g *graphObject) LoadNamespaces() error {
  return g.s.sch.LoadNamespaces(g.s.ctx, g.s.qs, &g.s.ns)
func (g *graphObject) NewV(call goja.FunctionCall) goja.Value {
  return g.NewVertex(call)
func (g *graphObject) NewVertex(call goja.FunctionCall) goja.Value {
  qv, err := toQuadValues(exportArgs(call.Arguments))
  if err != nil {
    return throwErr(g.s.vm, err)
  }
  return g.s.vm.ToValue(&pathObject{
    s:      g.s,
    finals: true,
    path:   path.StartMorphism(qv...),
  })
func (g *graphObject) NewM() *pathObject {
  return g.NewMorphism()
func (g *graphObject) NewMorphism() *pathObject {
  return &pathObject{
    s:    g.s,
    path: path.StartMorphism(),
func (g *graphObject) Emit(call goja.FunctionCall) goja.Value {
  value := call.Argument(0)
  if !goja.IsNull(value) && !goja.IsUndefined(value) {
    val := exportArgs([]goja.Value{value})[0]
    if val != nil {
      g.s.send(nil, &Result{Val: val})
    }
  }
  return goja.Null()
func (g *graphObject) CapitalizedUri(s string) quad.IRI {
  return g.NewIRI(s)
func (g *graphObject) CapitalizedAddNamespace(pref, ns string) {
  g.AddNamespace(pref, ns)
func (g *graphObject) CapitalizedAddDefaultNamespaces() {
  g.AddDefaultNamespaces()

query/gizmo/errors.go 定義了相關錯誤信息

query/gizmo/finals.go

const TopResultTag = "id"
func (p *pathObject) GetLimit(limit int) error {
  it := p.buildIteratorTree()
  it = iterator.Tag(it, TopResultTag)
  p.s.limit = limit
  p.s.count = 0
  return p.s.runIterator(it)
func (p *pathObject) toArray(call goja.FunctionCall, withTags bool) goja.Value {
func (p *pathObject) ForEach(call goja.FunctionCall) goja.Value {
  it := p.buildIteratorTree()

query/gizmo/gizmo.go 先註冊了語言

const Name = "gizmo"

支持 http 和 repl 兩種模式

func init() {
  query.RegisterLanguage(query.Language{
    Name: Name,
    Session: func(qs graph.QuadStore) query.Session {
      return NewSession(qs)
    },
    HTTP: func(qs graph.QuadStore) query.HTTP {
      return NewSession(qs)
    },
    REPL: func(qs graph.QuadStore) query.REPLSession {
      return NewSession(qs)
    },
  })
func NewSession(qs graph.QuadStore) *Session {
  s := &Session{
    ctx: context.Background(),
    sch: schema.NewConfig(),
    qs:  qs, limit: -1,
  }
  if err := s.buildEnv(); err != nil {
const constructMethodPrefix = "New"
const backwardsCompatibilityPrefix = "Capitalized"
func (fieldNameMapper) MethodName(t reflect.Type, m reflect.Method) string {
  if strings.HasPrefix(m.Name, backwardsCompatibilityPrefix) {

其中 session 定義如下:

type Session struct {
  qs  graph.QuadStore
  vm  *goja.Runtime
  ns  voc.Namespaces
  sch *schema.Config
  col query.Collation
  last string
  p    *goja.Program
  out   chan *Result
  ctx   context.Context
  limit int
  count int
  err   error
  shape map[string]interface{}
}
func (s *Session) quadValueToNative(v quad.Value) interface{} {
  if v == nil {
    return nil
  }
  if s.col == query.JSONLD {
    return jsonld.FromValue(v)
func (s *Session) runIteratorWithCallback(it graph.Iterator, callback goja.Value, this goja.FunctionCall, limit int) error {
  fnc, ok := goja.AssertFunction(callback)
func (s *Session) runIterator(it graph.Iterator) error {
  if s.shape != nil {
    iterator.OutputQueryShapeForIterator(it, s.qs, s.shape)
    return nil
  }
  ctx, cancel := context.WithCancel(s.context())
  defer cancel()
  stop := false
  err := graph.Iterate(ctx, it).Paths(true).TagEach(func(tags map[string]graph.Ref) {
type Result struct {
  Meta bool
  Val  interface{}
  Tags map[string]graph.Ref
}

中間使用到了 goja 解析器,它的作用是在 golang 環境中翻譯執行 javascript,因爲我們的 gizmo 採用的是 javascript 語法。

func (s *Session) compile(qu string) error {
  var p *goja.Program
  if s.last == qu && s.last != "" {
    p = s.p
  } else {
    var err error
    p, err = goja.Compile("", qu, false)
func (s *Session) run() (goja.Value, error) {
  v, err := s.vm.RunProgram(s.p)
  if e, ok := err.(*goja.Exception); ok && e.Value() != nil {
    if er, ok := e.Value().Export().(error); ok {
func (s *Session) Execute(ctx context.Context, qu string, opt query.Options) (query.Iterator, error) {
  switch opt.Collation {
  case query.Raw, query.JSON, query.JSONLD, query.REPL:

執行結果放在

type results struct {
  s      *Session
  col    query.Collation
  ctx    context.Context
  cancel func()
  running bool
  errc    chan error
  err error
  cur *Result
}
func (it *results) Next(ctx context.Context) bool {
  if it.errc == nil {
    it.s.out = make(chan *Result)
    it.errc = make(chan error, 1)
    it.running = true
    go func() {
      defer close(it.errc)
      v, err := it.s.run()
func (s *Session) ShapeOf(qu string) (interface{}, error) {
  s.shape = make(map[string]interface{})
  err := s.compile(qu)
  if err != nil {
    return nil, err
  }
  _, err = s.run()

query/gizmo/traversals.go

type pathObject struct {
  s      *Session
  finals bool
  path   *path.Path
}
func (p *pathObject) newVal(np *path.Path) goja.Value {
  return p.s.vm.ToValue(p.new(np))

定義了路徑樹

func (p *pathObject) buildIteratorTree() graph.Iterator {
  if p.path == nil {
    return iterator.NewNull()
  }
  return p.path.BuildIteratorOn(p.s.qs)

扇入

func (p *pathObject) In(call goja.FunctionCall) goja.Value {
  return p.inout(call, true)

扇出

func (p *pathObject) Out(call goja.FunctionCall) goja.Value {
  return p.inout(call, false)
func (p *pathObject) Both(call goja.FunctionCall) goja.Value {
  preds, tags, ok := toViaData(exportArgs(call.Arguments))
  if !ok {
    return throwErr(p.s.vm, errNoVia)
  }
  np := p.clonePath().BothWithTags(tags, preds...)
  return p.newVal(np)
func (p *pathObject) Follow(path *pathObject) *pathObject {
  return p.follow(path, false)
func (p *pathObject) FollowR(path *pathObject) *pathObject {
  return p.follow(path, true)
func (p *pathObject) FollowRecursive(call goja.FunctionCall) goja.Value {
  preds, maxDepth, tags, ok := toViaDepthData(exportArgs(call.Arguments))
  if !ok || len(preds) == 0 {
    return throwErr(p.s.vm, errNoVia)
  } else if len(preds) != 1 {
    return throwErr(p.s.vm, fmt.Errorf("expected one predicate or path for recursive follow"))
  }
  np := p.clonePath()
  np = np.FollowRecursive(preds[0], maxDepth, tags)
  return p.newVal(np)
    func (p *pathObject) has(call goja.FunctionCall, rev bool) goja.Value {
func (p *pathObject) Save(call goja.FunctionCall) goja.Value {
  return p.save(call, false, false)

            gizmo 是圖數據庫專用的查詢語言,可以採用比 sql 簡單很多倍的,人類更容易理解的方式來進行圖的查詢和遍歷。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/U5917jVuEDdd6qagbMcm2A