自定义 Cypher
本页面介绍如何使用 Cypher Builder 自定义 Cypher® 查询。当您遇到以下情况时,可能会用到此场景:
-
必须将 Cypher 字符串嵌入到现有的 Cypher 查询中。
-
将 Cypher Builder 作为大型 Cypher 查询的一部分使用,而该查询的其他部分可能未使用 Cypher Builder。
-
必须使用当前 Cypher Builder 版本不支持的功能。
-
使用自定义函数或存储过程。
|
在查询中嵌入自定义 Cypher 可能会导致代码注入和其他安全问题。 |
自定义变量名
在大多数情况下,Cypher Builder 会确保变量名是唯一的且不会冲突。然而,如果您必须显式设置变量名,可以使用 Named* 变量。
new Cypher.NamedVariable("myVarName")
更多信息,请参阅 命名变量。
构建前缀
虽然不推荐,但您可能需要将分别使用 Cypher Builder 构建的多个查询合并为一个字符串。例如:
const match1=new Cypher.Match(new Cypher.Pattern(new Cypher.Node(), { labels: ["Movie"] }))
const match2=new Cypher.Match(new Cypher.Pattern(new Cypher.Node(), { labels: ["Person"] }))
const cypher=`
${match1.build()}
${match2.build()}
`
生成的 Cypher
MATCH(this0:Movie)
MATCH(this0:Person)
在此查询中,变量 this0 同时用于两个 MATCH 语句,从而导致变量名冲突。这是因为两个查询(match1 和 match2)是分开构建的。
如果无法在执行 .build() 之前合并这些查询(例如使用 Cypher.utils.concat),则可以将前缀字符串传递给 .build() 以避免名称冲突:
const cypher=`
${match1.build({prefix: "movie"})}
${match2.build({prefix: "person"})}
`
生成的 Cypher
MATCH(movie_this0:Movie)
MATCH(person_this0:Person)
.build() 中的 prefix 参数会将提供的前缀字符串添加到每个变量(命名变量除外)之前。
自定义参数
仅当参数在查询中使用时,才会生成它们。要添加自定义参数(无论其是否被使用),可以将一个对象作为第二个参数传递给 .build。
const clause = new Cypher.Return(new Cypher.Param("Hello"))
clause.build("", {
myParameter: "Hello World"
});
生成的 Cypher
RETURN $param1
以及参数
{
"param1": "Hello",
"myParameter": "Hello World"
}
自定义参数名称
与变量类似,在定义参数时,可以通过使用 NamedParam 类代替 Param 来显式命名它。例如:
const movie = new Cypher.Node();
const matchQuery = new Cypher.Match(movie, { labels: ["Movie"]})
.where(movie, { name: new Cypher.NamedParam("myParam") })
.return(movie);
生成的 Cypher
MATCH (this0:Movie)
WHERE this0.name = $myParam
RETURN this0
请注意,$myParam 不会被 .build() 作为参数返回,因为它没有定义值。要生成该参数,请像处理普通参数一样传入一个值。
const movie = new Cypher.Node();
const matchQuery = new Cypher.Match({movie,labels: ["Movie"] })
.where(movie, { name: new Cypher.NamedParam("myParam", "Keanu Reeves") })
.return(movie);
.build() 返回的结果参数集为:
{
"myParam": "Keanu Reeves"
}
自定义函数和存储过程
过程 (Procedures)
对于任意存储过程,可以使用 Cypher.Procedure 类来定义它们:
const myProcedure = new Cypher.Procedure("my-procedure");
生成的 Cypher 会自动添加 CALL 子句。
CALL my-procedure()
然后可以将参数作为参数传递给构造函数:
const myProcedure = new Cypher.Procedure("my-procedure", [new Cypher.Literal("Keanu"), new Cypher.Variable()])
CALL my-procedure("Keanu", var0)
Yield
自定义存储过程可以使用 .yield 方法后跟 YIELD 语句:
const myProcedure = new Cypher.Procedure("my-procedure").yield("value");
CALL my-procedure() YIELD value
然而,与内置存储过程不同,此方法没有针对列名的 TypeScript 类型定义,因此 .yield 接受任何字符串。可以在 Procedure 类中设置更具体的类型:
new Cypher.Procedure<"columnA" | "columnB">("my-procedure")
|
尝试将 |
Raw
Cypher.Raw 类允许在由 Cypher Builder 构建的较大查询中嵌入 Cypher 字符串。它充当可以在任何地方使用的通配符。例如:
const customReturn = new Cypher.Raw(`10 as myVal`);
const returnClause = new Cypher.Return(customReturn);
const { cypher, params } = returnClause.build();
此查询返回以下 Cypher:
RETURN 10 as myVal
在这种情况下,RETURN 子句由 Cypher Builder 生成,但实际值 10 as myVal 是通过 Raw 注入的。该字符串可以是任何内容,包括其他子句或无效的 Cypher,并且可以动态生成。
const returnVar="myVal"
const customReturn = new Cypher.Raw(`10 as ${returnVar}`);
const returnClause = new Cypher.Return(customReturn);
此外,Raw 也可以在 Cypher.utils.concat 中使用,将任意字符串附加到任何 Cypher Builder 元素。
使用回调
在更复杂的场景中,您可能需要在自定义 Cypher 字符串中访问使用 Cypher Builder 创建的变量。但是,在执行 .build 之前,这些值是不可用的。为了实现这一点,Raw 支持一个回调函数,该函数在查询构建时执行,并可以访问这些变量。
此回调接收一个 context 参数,可用于手动编译 Cypher Builder 子句并转换变量名。它返回以下值:
-
string: 用于此元素的 Cypher 字符串。 -
[string, object]: 一个元组,第一个元素是 Cypher 字符串,第二个元素是包含要注入到查询中的参数的对象。 -
undefined: 如果为 undefined,Raw将被翻译为空字符串。
在此示例中,Cypher Builder 以通常方式创建 MATCH…RETURN 语句。但是,自定义的 Raw 被注入作为 WHERE 子句的一部分:
const movie = new Cypher.Node();
const match = new Cypher.Match(movie, { labels: ["Movie"] })
.where(
new Cypher.Raw((context) => {
const movieStr = context.compile(movie);
const cypher = `${movieStr}.prop = $myParam`;
const params = {
myParam: "Hello World",
};
return [cypher, params];
})
)
.return(movie);
const { cypher, params } = match.build();
这将返回以下 Cypher:
MATCH (this0:Movie)
WHERE this0.prop = $myParam
RETURN this0
以及以下参数:
{
"myParam": "Hello World"
}
传递给 Raw 的回调会生成字符串 this0.prop = $myParam。为实现此目的,它使用了工具方法 utils.compileCypher,并传递了变量 movie 和 context 参数,该参数随后返回字符串 this0。最后,自定义参数 $myParam 在元组 [cypher, params] 中返回,确保它在执行 match.build() 时可用。
禁用自动转义
|
更改这些选项可能会导致代码注入和不安全的 Cypher。 |
Cypher Builder 会自动转义可能导致代码注入的不安全字符串。可以使用子句的 .build 方法中的 unsafeEscapeOptions 参数来配置此行为:
-
disableNodeLabelEscaping(默认值为false): 如果设置为true,即使节点标签不安全,也不会进行转义。 -
disableRelationshipTypeEscaping(默认值为false): 如果设置为true,即使关系类型不安全,也不会进行转义。
例如:
const personNode = new Cypher.Node();
const movieNode = new Cypher.Node();
const matchQuery = new Cypher.Match(
new Cypher.Pattern(personNode, {
labels: ["Person"],
properties: {
["person name"]: new Cypher.Literal(`Uneak "Seveer`),
},
})
.related({ type: "ACTED IN" })
.to(movieNode, { labels: ["A Movie"] })
).return(personNode);
const queryResult = matchQuery.build({
unsafeEscapeOptions: {
disableNodeLabelEscaping: true,
disableRelationshipTypeEscaping: true,
},
});
此查询生成以下(无效的)Cypher:
MATCH (this0:Person { `person name`: "Uneak \"Seveer" })-[:ACTED IN]->(this1:A Movie)
RETURN this0
而不是默认的(安全的)Cypher:
MATCH (this0:Person { `person name`: "Uneak \"Seveer" })-[:`ACTED IN`]->(this1:`A Movie`)
RETURN this0
手动转义标签和类型
如果禁用了自动转义,则必须手动转义用于标签和关系类型的字符串。可以使用以下工具函数完成:
-
Cypher.utils.escapeLabel(str) -
Cypher.utils.escapeType(str)
在前面的示例中,可以手动转义标签和类型以生成有效的 Cypher:
const personNode = new Cypher.Node();
const movieNode = new Cypher.Node();
const matchQuery = new Cypher.Match(
new Cypher.Pattern(personNode, {
labels: [Cypher.utils.escapeLabel("Person")],
properties: {
["person name"]: new Cypher.Literal(`Uneak "Seveer`),
},
})
.related({ type: Cypher.utils.escapeType("ACTED IN") })
.to(movieNode, { labels: [Cypher.utils.escapeLabel("A Movie")] })
).return(personNode);
const queryResult = matchQuery.build({
unsafeEscapeOptions: {
disableNodeLabelEscaping: true,
disableRelationshipTypeEscaping: true,
},
});