导读
在开始分析并实现查询逻辑之前,我们需要先理解Dagoba查询的核心概念:
- Pipetype 管道类型
- 惰性计算
这里需要先去Dagoba中阅读原文章节,可以配合沉浸式翻译插件来进行双语对照。
简单来说,管道通常是和链式调用一起的,其作用就是避免像回调地狱这样难以维护的代码;而惰性计算是面对大量计算数据时,为了避免获取三个节点,需要先遍历全部节点这种性价比低下的操作,而是现代“按需要多少数据就计算多少数据的”功能。
参考阅读:
Pipetype类型及其运行逻辑
理解Pipetype类型需要理解三个方面:
- 如何实现惰性计算?
- 如何实现管道?
- 在图查询时的管道中需要传递哪些变量?
这三个按依赖排序的话,是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
字段是用来存放管道执行结果的,其类型应该为unknown
。unknown 类型
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
,如果符合条件才执行,才会消耗计算资源;否则退出,不再消耗计算资源。