Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

附录 B:查询语言文法

这是 openCypher 子集的完整文法,也就是解析器实际接受的形式。仪表盘把查询页签标注为 GraphQL,但这门语言并不是 GraphQL,而是一个 openCypher 子集,本附录正是它的参考。 第 4 章讲的是各构造用来做什么,本附录讲的则是什么能被 解析。

记号约定:| 表示择一,[…] 表示可选,{…} 表示零次或多次,'…' 表示一个字面 记号。关键字不区分大小写(match 等同于 MATCH);标识符则区分。

语句

statement      ::= read-query | write-statement

读语句编译为一个逻辑计划并返回行;写语句改动平面并返回变更计数。

读

read-query     ::= source { beam } [ where ] return
                   [ order-by ] [ skip ] [ limit ] [ as-of ]

每一种数据源都恰好绑定一个节点模式,之后可以继续接一段关系尾巴:无论种子来自检索、 算法,还是普通的 MATCH 节点,带类型跳转的写法都完全一样。

source         ::= 'MATCH'  pattern
                 | 'SEARCH' node-pat vector-seed  tail
                 | 'SEARCH' node-pat keyword-seed tail
                 | 'HYBRID' node-pat { hybrid-part } tail
                 | 'CALL'   ident '(' [ call-args ] ')' 'ON' node-pat tail

tail           ::= { rel-pat node-pat }

vector-seed    ::= [ 'ON' ident ] 'NEAR' vec-arg [ 'METRIC' metric ] [ 'TOPK' uint ]
keyword-seed   ::= 'ON' ident 'MATCHING' string [ 'TOPK' uint ]

hybrid-part    ::= 'VECTOR'  [ 'ON' ident ] 'NEAR' vec-arg
                     [ 'METRIC' metric ] [ 'WEIGHT' number ]
                 | 'KEYWORD' 'ON' ident 'MATCHING' string [ 'WEIGHT' number ]
                 | 'GRAPH'   'HOPS' uint [ 'DECAY' number ]
                     [ 'SEEDS' uint ] [ 'WEIGHT' number ]
                 | 'CANDIDATES' uint
                 | 'TOPK' uint

call-args      ::= call-arg { ',' call-arg }
call-arg       ::= ident ':' value

HYBRID 至少需要 VECTOR 或 KEYWORD 之一,GRAPH 只是在二者找到的结果上做 提升。各部分的书写顺序任意,但每种至多出现一次。

遍历

beam           ::= 'BEAM' node-pat direction [ ':' ident ] [ 'ON' ident ]
                   'NEAR' vec-arg [ 'METRIC' metric ] 'WIDTH' uint 'DEPTH' uint

direction      ::= 'OUT' | 'IN' | 'BOTH'

pattern        ::= node-pat { rel-pat node-pat }
node-pat       ::= '(' [ ident ] [ ':' ident ] ')'
rel-pat        ::= [ '<' ] '-' [ '[' rel-body ']' ] '-' [ '>' ]
rel-body       ::= [ ident ] [ ':' ident ] [ var-len ]
var-len        ::= '*' [ uint ] [ '..' [ uint ] ]

关系的方向由其箭头决定:-[…]-> 为出边,<-[…]- 为入边,-[…]- 为双向;<-…-> 会被拒绝。关系变量可以书写,但不会被绑定。var-len 必须有上界(*1..3、*2、 *..4);无界的 * 或 *2.. 会明确报错。

尾部子句

where          ::= 'WHERE' expr
return         ::= 'RETURN' [ 'DISTINCT' ] return-item { ',' return-item }
return-item    ::= ident | '*'                (* 行本身 *)
                 | ( expr | aggregate ) [ 'AS' ident ]
aggregate      ::= 'count' '(' '*' ')'
                 | agg-func '(' [ 'DISTINCT' ] expr ')'
agg-func       ::= 'count' | 'sum' | 'avg' | 'min' | 'max' | 'collect'
order-by       ::= 'ORDER' 'BY' order-key { ',' order-key }
order-key      ::= ( expr | ident ) [ 'ASC' | 'DESC' ]
skip           ::= 'SKIP' uint
limit          ::= 'LIMIT' uint
as-of          ::= 'AS' 'OF' ( uint | string | 'TIME' int )

RETURN n 或 RETURN * 交回完整记录,且只能指名模式中的最后一个变量。其余任 何一项都是投影:它成为一列,列名取 AS 别名,未写别名时取该项的原文;它可以读 取模式所绑定的任一变量。节点不能与列同处一个 RETURN——节点不是值。

aggregate 在每个组上折叠;分组键是所有不是聚合的列。折叠会跳过 null,也跳过它无 法采用的值;当一组什么也没提供时,count 与 sum 为 0,avg/min/max 为 null,collect 为空列表。

在投影查询中,DISTINCT 比较整个投影行,ORDER BY 指名所返回的某一列(用别名,或 用它所返回的那个表达式),SKIP/LIMIT 计的是投影行。而在 RETURN n 之下,这四者 仍取各自的节点语义。AS OF 位于最后:裸整数为提交序号,带引号的字符串为一个 RFC-3339 时刻,TIME 后接 unix 纪元毫秒数。

表达式

由最松到最紧:

expr           ::= or-expr
or-expr        ::= and-expr { 'OR' and-expr }
and-expr       ::= not-expr { 'AND' not-expr }
not-expr       ::= 'NOT' not-expr | comparison
comparison     ::= additive [ 'IS' [ 'NOT' ] 'NULL'
                            | 'IN' '[' [ expr { ',' expr } ] ']'
                            | cmp-op additive ]
cmp-op         ::= '=' | '<>' | '!=' | '<' | '<=' | '>' | '>='
additive       ::= multiplicative { ( '+' | '-' ) multiplicative }
multiplicative ::= unary { ( '*' | '/' ) unary }
unary          ::= '-' unary | primary
primary        ::= '(' expr ')' | param | literal | term
term           ::= ident '.' ident            (* 属性 *)
                 | ident ':' ident            (* 标签判定 *)
                 | function
function       ::= 'score' '(' ')'
                 | 'hops' '(' ')'
                 | 'key' '(' ident ')'
                 | ( 'similarity' | 'distance' )
                   '(' ident '.' ident ',' vec-arg [ ',' metric ] ')'

score() 是该行的 score 通道,取值是种子的相关度,或算法给出的逐节点结果。 hops() 是路径长度。key(n) 是节点的外部键;用在源变量上时,key(n) = "…" 和 key(n) IN […] 会编译成一次键查找,而不是先扫描再过滤。x IN [a, b] 只是等值 判断的语法糖。

一个 WHERE 条件只能引用一个模式变量,编译器会把它安放在这个变量在流水线中的 位置上。

写

write-statement ::= 'CREATE' create-path { ',' create-path }
                  | 'MERGE'  create-path { merge-on }
                  | 'MATCH'  pattern [ 'WHERE' expr ] mutate-op { mutate-op }

merge-on       ::= 'ON' ( 'CREATE' | 'MATCH' ) 'SET' set-item { ',' set-item }

mutate-op      ::= 'SET' set-item { ',' set-item }
                 | 'REMOVE' remove-item { ',' remove-item }
                 | [ 'DETACH' ] 'DELETE' ident { ',' ident }
                 | 'CREATE' create-path { ',' create-path }
                 | 'MERGE'  create-path { merge-on }

set-item       ::= ident '.' ident '=' value
                 | ident ':' ident
                 | ident '+=' prop-map
remove-item    ::= ident '.' ident | ident ':' ident

create-path    ::= create-node { create-rel create-node }
create-node    ::= '(' [ ident ] [ ':' ident ] [ prop-map ] ')'
create-rel     ::= [ '<' ] '-' '[' ':' ident [ prop-map ] ']' '-' [ '>' ]
prop-map       ::= '{' [ prop-entry { ',' prop-entry } ] '}'
prop-entry     ::= ident ':' value

在 create-node 中,取字符串值的 key: 条目设定节点的外部键,而不会成为一个属性。 新建的边必须有向,且必须指明类型。MATCH … mutate-op 作用于模式的末端变量。

终结符

ident          ::= ( letter | '_' ) { letter | digit | '_' }     (* Unicode 字母 *)
                 | '`' { 除 '`' 外的任意字符 } '`'
uint           ::= digit { digit }
int            ::= [ '-' ] uint
number         ::= [ '-' ] digit { digit } [ '.' digit { digit } ] [ exponent ]
exponent       ::= ( 'e' | 'E' ) [ '+' | '-' ] digit { digit }
string         ::= '"' { string-char | escape } '"'
                 | "'" { string-char | escape } "'"
string-char    ::= 除闭合引号与 '\\' 外的任意字符
escape         ::= '\\' ( '\'' | '"' | '\\' | 'n' | 't' | 'r' | 'b' | 'f' | 'u' hex hex hex hex )
vector         ::= '[' [ number { ',' number } ] ']'
vec-arg        ::= string | vector
metric         ::= 'cosine' | 'dot' | 'l2'
param          ::= '$' ident
literal        ::= number | string | 'true' | 'false' | 'null' | vector
value          ::= param | literal

字符串内部的反斜杠开始一个转义——\'、\"、\\、\n、\t、\r、\b、\f、\uXXXX—— 因此同类引号以转义写出,字面反斜杠写作 \\(Windows 路径写作 "C:\\data"), 未知转义或未闭合的字符串是语法错误。标识符是 Unicode 单词,或当普通写法拼不出该 名字时放在反引号之间的任何内容。记号之间的空白没有意义,也不支持注释。$name 参数写在值的位置上,解析时会从调用方的参数映射里取值, 这样传值就不必担心注入问题。

默认值

省略项取值缘由
NEAR 之前的 ON <属性>embedding摄取流水线所写入的属性
METRICcosine
TOPK10
HYBRID … CANDIDATES100融合前每通道的候选池
GRAPH … DECAY0.5与 RPC、MCP 及命令行各接口一致
GRAPH … SEEDS10每通道取作种子的最高命中数
WEIGHT向量 1.0、关键词 1.0、图 0.5
CALL pagerank 参数damping: 0.85, iterations: 20, tolerance: 1e-6
CALL louvain 参数max_levels: 10, min_gain: 1e-9
CALL shortest_path 的 diroutfrom 与 to 为必需

MATCHING 没有默认属性:关键词索引按 (标签, 属性) 声明,这些属性没有约定俗成的 命名,所以 ON 是必需的;出于同样的理由,标签也是必需的。

算法

CALL 'pagerank'      ( [ damping: number ] [ iterations: uint ] [ tolerance: number ] )
CALL 'components'    ( )                      (* 别名:connected_components *)
CALL 'louvain'       ( [ max_levels: uint ] [ min_gain: number ] )
CALL 'shortest_path' ( from: key-or-id , to: key-or-id [ , dir: string ]
                       [ , weight: string ] )

key-or-id 可以是一个字符串(外部键),也可以是一个整数(节点 id)。每个算法都 通过 score() 报告逐节点结果:pagerank 给出 rank,components 和 louvain 给出 从 0 开始的紧凑分组序号,shortest_path 给出该节点在路径上的位次。未知的算法名或 参数名一律报错,绝不会被悄悄忽略。

尚不支持

以下每一项都会明确报错,而绝不会被静默地错误编译:

  • WITH 流水化——投影是尾部,其后不再接任何子句。
  • 返回模式中最后一个变量之外的任何变量的行(一次跳跃之后的 RETURN p);它的 值仍可投影(RETURN p.name)。
  • 非投影查询按模式中最后一个变量之外的变量排序。
  • 跨越两个变量的谓词——WHERE p.year < q.year。
  • 复用模式变量,那将表达一个图约束。
  • 分支模式——每条语句只允许一条线性路径。
  • 无界的可变长度关系——*、*2..。
  • 无向或未指明类型的新建边。
  • 注释,以及 WITH。