【用TypeScript实现内存型图数据库】0x04:Pipetype类型及其运行逻辑

导读

在开始分析并实现查询逻辑之前,我们需要先理解Dagoba查询的核心概念:

  • Pipetype 管道类型
  • 惰性计算

这里需要先去Dagoba中阅读原文章节,可以配合沉浸式翻译插件来进行双语对照。
简单来说,管道通常是和链式调用一起的,其作用就是避免像回调地狱这样难以维护的代码;而惰性计算是面对大量计算数据时,为了避免获取三个节点,需要先遍历全部节点这种性价比低下的操作,而是现代“按需要多少数据就计算多少数据的”功能。

参考阅读:

Pipetype类型及其运行逻辑

理解Pipetype类型需要理解三个方面:

  1. 如何实现惰性计算?
  2. 如何实现管道?
  3. 在图查询时的管道中需要传递哪些变量?
    这三个按依赖排序的话,是1->2->3,所以得从3开始理解。

在图搜索的管道中需要传递哪些变量

传递的变量从pipetype的返回值看起,每个pipetype必定返回一个gremlin
Gremlin的结构如下:

  /**
   * 潜在的查询结果
   */
  class Gremlin {
      /**
       * 结果名称
       */
      result: string | undefined;
      /**
       * 顶点
       */
      vertex: Vertex | undefined;
      /**
       * 状态
       */
      state: State | undefined;
  }
  
  /**
   * 查询过程中的状态
   */
  class State {
      /**
       * 顶点列表
       */
      vertices: Vertex[] = [];
      /**
       * 边列表
       */
      edges: Edge[] = [];
      /**
       * 潜在的结果
       */
      gremlin: Gremlin | undefined;
      /**
       * 结果技术
       */
      taken: number = 0;
      /**
       * 标签
       */
      as: State | undefined;
  }

每个属性分别是什么作用

gremlin.result

property管道中,gremlin.result承载的是gremlin.vertex指定要找的属性

run()中,在收集result的值的时候,会有这样的判断:gremlin.result != null ? gremlin.result : gremlin.vertex

故可知result字段是用来存放管道执行结果的,其类型应该为unknownunknown 类型

gremlin.vertex

当前遍历到的顶点。

该属性怎么更新?得看该属性相关的写方法

  • pipetype 'vertex'中有makeGremlin方法,传的是起始Vertex。

  • pipetype 'merge'中也有makeGremlin方法,来源是state.vertices.pop(),从state.vertices中弹出一个。

    • state.vertices的数据从哪里来?

      • pipetype 'vertex'中,会设置state.vertices = graph.findVertices(args)

      • pipetype 'merge'中, 会设置state.vertices = args.map(function(id) {return obj[id]}).filter(Boolean)

  • gotoVertex函数中,也会调用makeGremlin

    • gotoVertex被哪里调用?

      • simpleTraversal函数中,会将state.edges.pop()[edge_list]边的出/入方向的顶点放入gotoVertex

      • pipetype back中会调用,回到gremlin.state.as[args[0]]的顶点。

结论:也就是gremlin.vertex会在遍历、合并、倒退时更新。

gremlin.state

state.vertices

state.vertices的数据来源见上一小节。

state.edges

simpleTraversal方法中,state.edges = graph[find_method](gremlin.vertex).filter(Dagoba.filterEdges(args[0]))设置的。这个是唯一来源

state.gremlin

simpleTraversal方法中,state.gremlin = gremlin。这个是唯一来源

state.taken

该属性的作用与pipetype take关联,作为pipetype take的计数。在pipetype take中有初始化和修改

  • state.taken = state.taken || 0

  • if (state.taken == args[0]) { state.taken = 0; ... }

  • state.taken++

state.as
  • 初始化:pipetype as中,gremlin.state.as = gremlin.state.as || {}

  • 修改属性(打标记):pipetype as中,gremlin.state.as[args[0]] = gremlin.vertex

  • 调用:

    • pipetype except中,if (gremlin.vertex == gremlin.state.as[args[0]]) { return 'pull'}

    • pipetype back中,return Dagoba.gotoVertex(gremlin, gremlin.state.as[args[0]])

管道及惰性计算的实现

原作中惰性计算是把每个管道看做循环的一个步骤,在`run()`方法中用`while`循环来调用每个管道。这样`while`执行多少个循环,这些管道就被调用多少次;如果`while`循环终止的了,那管道不会再被执行。这样便实现了惰性计算。

分析run()逻辑

直接在run()的源码上注释解析

// a machine for query processing
Dagoba.Q.run = function () {
    // activate the transformers 调用解析器进行程序优化
    this.program = Dagoba.transform(this.program);
    // index of the last step in the program 最后一个程序步骤的索引
    var max = this.program.length - 1
    // a gremlin, a signal string, or false 这个值可能是个Gremlin类型,也可能是个信号字符串,或者false布尔值
    var maybe_gremlin = false;
    // results for this particular run 运行的结果集
    var results = [];
    // behindwhich things have finished 用于控制流程是否终止的计数位
    var done = -1;
    // our program counter 程序步骤的计数器
    var pc = max;
    var step, state, pipetype;
  	// 当控制位done小于程序步骤长度max时,继续循环
    while (done < max) {
      	// query的状态数组
        var ts = this.state;
        // step is a pair of pipetype and args 根据程序步骤计数器在步骤数组中取出要执行的步骤
        step = this.program[pc];
        // this step's state must be an object 根据程序步骤计数器在状态数组中取出要执行步骤,如果这个步骤的状态为空,则初始化一个空对象作为状态
        state = (ts[pc] = ts[pc] || {})
        // a pipetype is just a function 获取管道类型,管道类型是一个函数
        pipetype = Dagoba.getPipetype(step[0]);
        // 调用管道类型获取执行结果
        maybe_gremlin = pipetype(this.graph, step[1], maybe_gremlin, state);
        // 'pull' means the pipe wants more input 'pull'意味着这个管道需要更多的输入(执行多遍)
        if (maybe_gremlin == 'pull') {
            maybe_gremlin = false;
            if (pc - 1 > done) {
                // try the previous pipe 回退到前一个管道来执行
                pc--;
                continue;
            } else {
                // previous pipe is done, so we are too 如果前一个管道已经结束了,那么自然pull不出结果,当前管道也结束
                done = pc;
            }
        }
        // 'done' tells us the pipe is finished 'done'标识管道已经结束了
        if (maybe_gremlin == 'done') {
            maybe_gremlin = false;
            done = pc;
        }
        // move on to the next pipe 移动计数器到下一个步骤
        pc++;
        // 如果计数器大于程序最后一个步骤的下标
        if (pc > max) {
          	// 字符串已在上边被拦截处理了,这里只剩下false和Gremlin两种类型了,如果不是false的话,就是结果了
            if (maybe_gremlin) {
                // a gremlin popped out of the pipeline 将结果放入结果集
                results.push(maybe_gremlin)
            }
            maybe_gremlin = false;
            // take a step back; 回到上一步
            pc--;
        }
    }
    // return projected results, or vertices 返回投射结果,或者顶点
    results = results.map(function (gremlin) {
        return gremlin.result != null ? gremlin.result : gremlin.vertex
    });
    return results;
}

什么情况下maybe_gremlin会为'pull'

原文:

A pipe can return a result of ‘pull’, which signals the head that it needs input and moves it to the right.
管道可以返回一个“pull”的结果,它向head发出需要输入的信号并将其向右移动。

  • pipetype fauxPipetype中,return maybe_gremlin || 'pull',当上一个maybe_gremlin无值时,返回'pull'

  • simpleTraversal()

    • if(!gremlin && (!state.edges || !state.edges.length)) return 'pull' Gremlin或state的边不存在(初始化)时

    • if(!state.edges.length) return 'pull'没有更多的边时

  • pipetype property

    • if(!gremlin) return 'pull'germlin没值的时候
  • pipetype unique

    • if(!gremlin) return 'pull'gremlin没值的时候

    • if(state[gremlin.veretx._id]) return 'pull'当这个节点已被遍历的时候

  • pipetype filter

    • if(!germlin) return 'pull'gremlin没值的时候

    • if(typeof args[0] == 'object') return Dagoba.objectFilter(gremlin.vertex, args[0]) ? gremlin : 'pull'当目前对象过滤器过滤得不到结果时

    • if(!args[0](gremlin.vertex, gremlin)) return 'pull'当过滤函数过滤后得不到结果时

  • pipetype take

    • if(!gremlin) return 'pull'gremlin没值(初始化)的时候
  • pipetype as

    • if(!gremlin) return 'pull'初始化的时候
  • pipetype merge

    • if(!state.vertices && !gremlin) return 'pull'初始化的时候

    • if(!state.vertices.length) return 'pull'没有待遍历的顶点的时候

  • pipetype except

    • if(!gremlin) return 'pull'初始化的时候

    • if(gremlin.vertex == gremlin.state.as[args[0]]) return 'pull'当目前顶点符合入参的时候

  • pipetype back

    • if(!gremlin) return 'pull'初始化的时候

什么情况下maybe_gremlin'done'

原文:

A result of ‘done’ tells the head that nothing prior needs to be activated again, and moves the head left.
“完成”的结果告诉头部不需要再次激活任何先前的内容,并将头部向左移动。

  • pipetype vertex
    • if(!state.vertices.length) return 'done'所有顶点都被弹出(已被遍历)时
  • pipetype take
    • if(state.taken == args[0]) {state.taken = 0; return 'done';}当获取完指定数量的顶点后
      可以看出,'done’是应用在一些多次执行会返回不同结果的管道类型中的。

结论

run()方法通过while循环同时实现了管道和惰性计算两个特性:

  • 管道的实现:将上一个步骤的执行结果maybe_gremlin在循环中传递给下一个步骤作为参数
  • 惰性计算的实现:每次循环前判断done < max,如果符合条件才执行,才会消耗计算资源;否则退出,不再消耗计算资源。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值