自定义 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 语句,从而导致变量名冲突。这是因为两个查询(match1match2)是分开构建的。

如果无法在执行 .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"
}

自定义函数和存储过程

Cypher Builder 提供了一些内置的函数和存储过程,但也支持自定义函数和存储过程,例如在使用插件或创建用户自定义函数时。

函数

可以使用 Cypher.Function 类构建任意函数调用。例如:

new Cypher.Function("myFunc");

请参阅自定义函数以了解有关创建自定义函数的更多信息。

过程 (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")

尝试将 "columnA""columnB" 以外的任何内容用于 .yield 将返回 TypeScript 错误。

无返回值的存储过程 (Void procedures)

某些存储过程不能与 YIELD 一起使用,因为它们不返回任何值。这些可以使用 Cypher.VoidProcedure 定义:

const myProcedure = new Cypher.VoidProcedure("my-proc");

它可以用作任何其他存储过程,只是 .yield 方法不可用。

重用自定义存储过程

可以通过将自定义存储过程包装在 JavaScript 函数中来重用它们:

function myCustomProcedure(param1) {
    return new Cypher.Procedure("my-custom-procedure", [param1])
}

此函数可以像内置存储过程一样使用:

myCustomProcedure(new Cypher.Variable()).yield("column")
CALL my-custom-procedure(var0) YIELD "column"

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,并传递了变量 moviecontext 参数,该参数随后返回字符串 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,
    },
});
© . This site is unofficial and not affiliated with Neo4j, Inc.