flowquery 1.0.39 → 1.0.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/flowquery.min.js +1 -1
- package/dist/graph/database.d.ts +2 -0
- package/dist/graph/database.d.ts.map +1 -1
- package/dist/graph/database.js +12 -0
- package/dist/graph/database.js.map +1 -1
- package/dist/parsing/functions/function_factory.d.ts +1 -0
- package/dist/parsing/functions/function_factory.d.ts.map +1 -1
- package/dist/parsing/functions/function_factory.js +1 -0
- package/dist/parsing/functions/function_factory.js.map +1 -1
- package/dist/parsing/functions/substring.d.ts +9 -0
- package/dist/parsing/functions/substring.d.ts.map +1 -0
- package/dist/parsing/functions/substring.js +62 -0
- package/dist/parsing/functions/substring.js.map +1 -0
- package/dist/parsing/operations/delete_node.d.ts +11 -0
- package/dist/parsing/operations/delete_node.d.ts.map +1 -0
- package/dist/parsing/operations/delete_node.js +46 -0
- package/dist/parsing/operations/delete_node.js.map +1 -0
- package/dist/parsing/operations/delete_relationship.d.ts +11 -0
- package/dist/parsing/operations/delete_relationship.d.ts.map +1 -0
- package/dist/parsing/operations/delete_relationship.js +46 -0
- package/dist/parsing/operations/delete_relationship.js.map +1 -0
- package/dist/parsing/parser.d.ts +1 -0
- package/dist/parsing/parser.d.ts.map +1 -1
- package/dist/parsing/parser.js +62 -2
- package/dist/parsing/parser.js.map +1 -1
- package/docs/flowquery.min.js +1 -1
- package/flowquery-py/pyproject.toml +1 -1
- package/flowquery-py/src/graph/database.py +12 -0
- package/flowquery-py/src/parsing/functions/__init__.py +2 -0
- package/flowquery-py/src/parsing/functions/substring.py +74 -0
- package/flowquery-py/src/parsing/operations/__init__.py +4 -0
- package/flowquery-py/src/parsing/operations/delete_node.py +29 -0
- package/flowquery-py/src/parsing/operations/delete_relationship.py +29 -0
- package/flowquery-py/src/parsing/parser.py +54 -3
- package/flowquery-py/tests/compute/test_runner.py +171 -1
- package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
- package/package.json +1 -1
- package/src/graph/database.ts +12 -0
- package/src/parsing/functions/function_factory.ts +1 -0
- package/src/parsing/functions/substring.ts +65 -0
- package/src/parsing/operations/delete_node.ts +33 -0
- package/src/parsing/operations/delete_relationship.ts +32 -0
- package/src/parsing/parser.ts +63 -2
- package/tests/compute/runner.test.ts +147 -0
- package/tests/parsing/parser.test.ts +1 -1
package/package.json
CHANGED
package/src/graph/database.ts
CHANGED
|
@@ -25,6 +25,12 @@ class Database {
|
|
|
25
25
|
physical.statement = statement;
|
|
26
26
|
Database.nodes.set(node.label, physical);
|
|
27
27
|
}
|
|
28
|
+
public removeNode(node: Node): void {
|
|
29
|
+
if (node.label === null) {
|
|
30
|
+
throw new Error("Node label is null");
|
|
31
|
+
}
|
|
32
|
+
Database.nodes.delete(node.label);
|
|
33
|
+
}
|
|
28
34
|
public getNode(node: Node): PhysicalNode | null {
|
|
29
35
|
return Database.nodes.get(node.label!) || null;
|
|
30
36
|
}
|
|
@@ -38,6 +44,12 @@ class Database {
|
|
|
38
44
|
physical.target = relationship.target;
|
|
39
45
|
Database.relationships.set(relationship.type, physical);
|
|
40
46
|
}
|
|
47
|
+
public removeRelationship(relationship: Relationship): void {
|
|
48
|
+
if (relationship.type === null) {
|
|
49
|
+
throw new Error("Relationship type is null");
|
|
50
|
+
}
|
|
51
|
+
Database.relationships.delete(relationship.type);
|
|
52
|
+
}
|
|
41
53
|
public getRelationship(relationship: Relationship): PhysicalRelationship | null {
|
|
42
54
|
return Database.relationships.get(relationship.type!) || null;
|
|
43
55
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import ASTNode from "../ast_node";
|
|
2
|
+
import Function from "./function";
|
|
3
|
+
import { FunctionDef } from "./function_metadata";
|
|
4
|
+
|
|
5
|
+
@FunctionDef({
|
|
6
|
+
description:
|
|
7
|
+
"Returns a substring of a string, starting at a 0-based index with an optional length",
|
|
8
|
+
category: "scalar",
|
|
9
|
+
parameters: [
|
|
10
|
+
{ name: "original", description: "The original string", type: "string" },
|
|
11
|
+
{ name: "start", description: "The 0-based start index", type: "integer" },
|
|
12
|
+
{
|
|
13
|
+
name: "length",
|
|
14
|
+
description: "The length of the substring (optional, defaults to remainder of string)",
|
|
15
|
+
type: "integer",
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
output: { description: "The substring", type: "string", example: "llo" },
|
|
19
|
+
examples: ["RETURN substring('hello', 1, 3)", "RETURN substring('hello', 2)"],
|
|
20
|
+
})
|
|
21
|
+
class Substring extends Function {
|
|
22
|
+
constructor() {
|
|
23
|
+
super("substring");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public set parameters(nodes: ASTNode[]) {
|
|
27
|
+
if (nodes.length < 2 || nodes.length > 3) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
`Function substring expected 2 or 3 parameters, but got ${nodes.length}`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
this.children = nodes;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public value(): any {
|
|
36
|
+
const children = this.getChildren();
|
|
37
|
+
const original = children[0].value();
|
|
38
|
+
const start = children[1].value();
|
|
39
|
+
|
|
40
|
+
if (typeof original !== "string") {
|
|
41
|
+
throw new Error(
|
|
42
|
+
"Invalid argument for substring function: expected a string as the first argument"
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
if (typeof start !== "number" || !Number.isInteger(start)) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
"Invalid argument for substring function: expected an integer as the second argument"
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (children.length === 3) {
|
|
52
|
+
const length = children[2].value();
|
|
53
|
+
if (typeof length !== "number" || !Number.isInteger(length)) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
"Invalid argument for substring function: expected an integer as the third argument"
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return original.substring(start, start + length);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return original.substring(start);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export default Substring;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import Database from "../../graph/database";
|
|
2
|
+
import Node from "../../graph/node";
|
|
3
|
+
import Operation from "./operation";
|
|
4
|
+
|
|
5
|
+
class DeleteNode extends Operation {
|
|
6
|
+
private _node: Node | null = null;
|
|
7
|
+
constructor(node: Node) {
|
|
8
|
+
super();
|
|
9
|
+
this._node = node;
|
|
10
|
+
}
|
|
11
|
+
public get node(): Node | null {
|
|
12
|
+
return this._node;
|
|
13
|
+
}
|
|
14
|
+
public run(): Promise<void> {
|
|
15
|
+
return new Promise(async (resolve, reject) => {
|
|
16
|
+
try {
|
|
17
|
+
if (this._node === null) {
|
|
18
|
+
throw new Error("Node is null");
|
|
19
|
+
}
|
|
20
|
+
const db: Database = Database.getInstance();
|
|
21
|
+
db.removeNode(this._node);
|
|
22
|
+
resolve();
|
|
23
|
+
} catch (error) {
|
|
24
|
+
reject(error);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
public get results(): Record<string, any>[] {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default DeleteNode;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import Database from "../../graph/database";
|
|
2
|
+
import Relationship from "../../graph/relationship";
|
|
3
|
+
import Operation from "./operation";
|
|
4
|
+
|
|
5
|
+
class DeleteRelationship extends Operation {
|
|
6
|
+
private _relationship: Relationship | null = null;
|
|
7
|
+
constructor(relationship: Relationship) {
|
|
8
|
+
super();
|
|
9
|
+
this._relationship = relationship;
|
|
10
|
+
}
|
|
11
|
+
public get relationship(): Relationship | null {
|
|
12
|
+
return this._relationship;
|
|
13
|
+
}
|
|
14
|
+
public run(): Promise<void> {
|
|
15
|
+
return new Promise(async (resolve, reject) => {
|
|
16
|
+
try {
|
|
17
|
+
if (this._relationship === null) {
|
|
18
|
+
throw new Error("Relationship is null");
|
|
19
|
+
}
|
|
20
|
+
const db = Database.getInstance();
|
|
21
|
+
db.removeRelationship(this._relationship);
|
|
22
|
+
resolve();
|
|
23
|
+
} catch (error) {
|
|
24
|
+
reject(error);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
public get results(): Record<string, any>[] {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export default DeleteRelationship;
|
package/src/parsing/parser.ts
CHANGED
|
@@ -53,6 +53,8 @@ import AggregatedWith from "./operations/aggregated_with";
|
|
|
53
53
|
import Call from "./operations/call";
|
|
54
54
|
import CreateNode from "./operations/create_node";
|
|
55
55
|
import CreateRelationship from "./operations/create_relationship";
|
|
56
|
+
import DeleteNode from "./operations/delete_node";
|
|
57
|
+
import DeleteRelationship from "./operations/delete_relationship";
|
|
56
58
|
import Limit from "./operations/limit";
|
|
57
59
|
import Load from "./operations/load";
|
|
58
60
|
import Match from "./operations/match";
|
|
@@ -186,9 +188,13 @@ class Parser extends BaseParser {
|
|
|
186
188
|
!(operation instanceof Return) &&
|
|
187
189
|
!(operation instanceof Call) &&
|
|
188
190
|
!(operation instanceof CreateNode) &&
|
|
189
|
-
!(operation instanceof CreateRelationship)
|
|
191
|
+
!(operation instanceof CreateRelationship) &&
|
|
192
|
+
!(operation instanceof DeleteNode) &&
|
|
193
|
+
!(operation instanceof DeleteRelationship)
|
|
190
194
|
) {
|
|
191
|
-
throw new Error(
|
|
195
|
+
throw new Error(
|
|
196
|
+
"Last statement must be a RETURN, WHERE, CALL, CREATE, or DELETE statement"
|
|
197
|
+
);
|
|
192
198
|
}
|
|
193
199
|
return root;
|
|
194
200
|
}
|
|
@@ -201,6 +207,7 @@ class Parser extends BaseParser {
|
|
|
201
207
|
this.parseLoad() ||
|
|
202
208
|
this.parseCall() ||
|
|
203
209
|
this.parseCreate() ||
|
|
210
|
+
this.parseDelete() ||
|
|
204
211
|
this.parseMatch()
|
|
205
212
|
);
|
|
206
213
|
}
|
|
@@ -459,6 +466,60 @@ class Parser extends BaseParser {
|
|
|
459
466
|
return create;
|
|
460
467
|
}
|
|
461
468
|
|
|
469
|
+
private parseDelete(): DeleteNode | DeleteRelationship | null {
|
|
470
|
+
if (!this.token.isDelete()) {
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
this.setNextToken();
|
|
474
|
+
this.expectAndSkipWhitespaceAndComments();
|
|
475
|
+
if (!this.token.isVirtual()) {
|
|
476
|
+
throw new Error("Expected VIRTUAL");
|
|
477
|
+
}
|
|
478
|
+
this.setNextToken();
|
|
479
|
+
this.expectAndSkipWhitespaceAndComments();
|
|
480
|
+
const node: Node | null = this.parseNode();
|
|
481
|
+
if (node === null) {
|
|
482
|
+
throw new Error("Expected node definition");
|
|
483
|
+
}
|
|
484
|
+
let relationship: Relationship | null = null;
|
|
485
|
+
if (this.token.isSubtract() && this.peek()?.isOpeningBracket()) {
|
|
486
|
+
this.setNextToken();
|
|
487
|
+
this.setNextToken();
|
|
488
|
+
if (!this.token.isColon()) {
|
|
489
|
+
throw new Error("Expected ':' for relationship type");
|
|
490
|
+
}
|
|
491
|
+
this.setNextToken();
|
|
492
|
+
if (!this.token.isIdentifierOrKeyword()) {
|
|
493
|
+
throw new Error("Expected relationship type identifier");
|
|
494
|
+
}
|
|
495
|
+
const type: string = this.token.value || "";
|
|
496
|
+
this.setNextToken();
|
|
497
|
+
if (!this.token.isClosingBracket()) {
|
|
498
|
+
throw new Error("Expected closing bracket for relationship definition");
|
|
499
|
+
}
|
|
500
|
+
this.setNextToken();
|
|
501
|
+
if (!this.token.isSubtract()) {
|
|
502
|
+
throw new Error("Expected '-' for relationship definition");
|
|
503
|
+
}
|
|
504
|
+
this.setNextToken();
|
|
505
|
+
const target: Node | null = this.parseNode();
|
|
506
|
+
if (target === null) {
|
|
507
|
+
throw new Error("Expected target node definition");
|
|
508
|
+
}
|
|
509
|
+
relationship = new Relationship();
|
|
510
|
+
relationship.type = type;
|
|
511
|
+
relationship.source = node;
|
|
512
|
+
relationship.target = target;
|
|
513
|
+
}
|
|
514
|
+
let result: DeleteNode | DeleteRelationship;
|
|
515
|
+
if (relationship !== null) {
|
|
516
|
+
result = new DeleteRelationship(relationship);
|
|
517
|
+
} else {
|
|
518
|
+
result = new DeleteNode(node);
|
|
519
|
+
}
|
|
520
|
+
return result;
|
|
521
|
+
}
|
|
522
|
+
|
|
462
523
|
private parseMatch(): Match | null {
|
|
463
524
|
let optional = false;
|
|
464
525
|
if (this.token.isOptional()) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Runner from "../../src/compute/runner";
|
|
2
2
|
import Database from "../../src/graph/database";
|
|
3
3
|
import Node from "../../src/graph/node";
|
|
4
|
+
import Relationship from "../../src/graph/relationship";
|
|
4
5
|
import AsyncFunction from "../../src/parsing/functions/async_function";
|
|
5
6
|
import { FunctionDef } from "../../src/parsing/functions/function_metadata";
|
|
6
7
|
|
|
@@ -725,6 +726,38 @@ test("Test trim function with empty string", async () => {
|
|
|
725
726
|
expect(results[0]).toEqual({ result: "" });
|
|
726
727
|
});
|
|
727
728
|
|
|
729
|
+
test("Test substring function with start and length", async () => {
|
|
730
|
+
const runner = new Runner('RETURN substring("hello", 1, 3) as result');
|
|
731
|
+
await runner.run();
|
|
732
|
+
const results = runner.results;
|
|
733
|
+
expect(results.length).toBe(1);
|
|
734
|
+
expect(results[0]).toEqual({ result: "ell" });
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
test("Test substring function with start only", async () => {
|
|
738
|
+
const runner = new Runner('RETURN substring("hello", 2) as result');
|
|
739
|
+
await runner.run();
|
|
740
|
+
const results = runner.results;
|
|
741
|
+
expect(results.length).toBe(1);
|
|
742
|
+
expect(results[0]).toEqual({ result: "llo" });
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
test("Test substring function with zero start", async () => {
|
|
746
|
+
const runner = new Runner('RETURN substring("hello", 0, 5) as result');
|
|
747
|
+
await runner.run();
|
|
748
|
+
const results = runner.results;
|
|
749
|
+
expect(results.length).toBe(1);
|
|
750
|
+
expect(results[0]).toEqual({ result: "hello" });
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
test("Test substring function with zero length", async () => {
|
|
754
|
+
const runner = new Runner('RETURN substring("hello", 1, 0) as result');
|
|
755
|
+
await runner.run();
|
|
756
|
+
const results = runner.results;
|
|
757
|
+
expect(results.length).toBe(1);
|
|
758
|
+
expect(results[0]).toEqual({ result: "" });
|
|
759
|
+
});
|
|
760
|
+
|
|
728
761
|
test("Test associative array with key which is keyword", async () => {
|
|
729
762
|
const runner = new Runner("RETURN {return: 1} as aa");
|
|
730
763
|
await runner.run();
|
|
@@ -3903,3 +3936,117 @@ test("Test order by with where", async () => {
|
|
|
3903
3936
|
expect(results[3]).toEqual({ x: 4 });
|
|
3904
3937
|
expect(results[4]).toEqual({ x: 3 });
|
|
3905
3938
|
});
|
|
3939
|
+
|
|
3940
|
+
test("Test delete virtual node operation", async () => {
|
|
3941
|
+
const db = Database.getInstance();
|
|
3942
|
+
// Create a virtual node first
|
|
3943
|
+
const create = new Runner(`
|
|
3944
|
+
CREATE VIRTUAL (:DeleteTestPerson) AS {
|
|
3945
|
+
unwind [
|
|
3946
|
+
{id: 1, name: 'Person 1'},
|
|
3947
|
+
{id: 2, name: 'Person 2'}
|
|
3948
|
+
] as record
|
|
3949
|
+
RETURN record.id as id, record.name as name
|
|
3950
|
+
}
|
|
3951
|
+
`);
|
|
3952
|
+
await create.run();
|
|
3953
|
+
expect(db.getNode(new Node(null, "DeleteTestPerson"))).not.toBeNull();
|
|
3954
|
+
|
|
3955
|
+
// Delete the virtual node
|
|
3956
|
+
const del = new Runner("DELETE VIRTUAL (:DeleteTestPerson)");
|
|
3957
|
+
await del.run();
|
|
3958
|
+
expect(del.results.length).toBe(0);
|
|
3959
|
+
expect(db.getNode(new Node(null, "DeleteTestPerson"))).toBeNull();
|
|
3960
|
+
});
|
|
3961
|
+
|
|
3962
|
+
test("Test delete virtual node then match throws", async () => {
|
|
3963
|
+
// Create a virtual node
|
|
3964
|
+
const create = new Runner(`
|
|
3965
|
+
CREATE VIRTUAL (:DeleteMatchPerson) AS {
|
|
3966
|
+
unwind [{id: 1, name: 'Alice'}] as record
|
|
3967
|
+
RETURN record.id as id, record.name as name
|
|
3968
|
+
}
|
|
3969
|
+
`);
|
|
3970
|
+
await create.run();
|
|
3971
|
+
|
|
3972
|
+
// Verify it can be matched
|
|
3973
|
+
const match1 = new Runner("MATCH (n:DeleteMatchPerson) RETURN n");
|
|
3974
|
+
await match1.run();
|
|
3975
|
+
expect(match1.results.length).toBe(1);
|
|
3976
|
+
|
|
3977
|
+
// Delete the virtual node
|
|
3978
|
+
const del = new Runner("DELETE VIRTUAL (:DeleteMatchPerson)");
|
|
3979
|
+
await del.run();
|
|
3980
|
+
|
|
3981
|
+
// Matching should now throw since the node is gone
|
|
3982
|
+
const match2 = new Runner("MATCH (n:DeleteMatchPerson) RETURN n");
|
|
3983
|
+
await expect(match2.run()).rejects.toThrow();
|
|
3984
|
+
});
|
|
3985
|
+
|
|
3986
|
+
test("Test delete virtual relationship operation", async () => {
|
|
3987
|
+
const db = Database.getInstance();
|
|
3988
|
+
// Create virtual nodes and relationship
|
|
3989
|
+
await new Runner(`
|
|
3990
|
+
CREATE VIRTUAL (:DelRelUser) AS {
|
|
3991
|
+
unwind [
|
|
3992
|
+
{id: 1, name: 'Alice'},
|
|
3993
|
+
{id: 2, name: 'Bob'}
|
|
3994
|
+
] as record
|
|
3995
|
+
RETURN record.id as id, record.name as name
|
|
3996
|
+
}
|
|
3997
|
+
`).run();
|
|
3998
|
+
|
|
3999
|
+
await new Runner(`
|
|
4000
|
+
CREATE VIRTUAL (:DelRelUser)-[:DEL_KNOWS]-(:DelRelUser) AS {
|
|
4001
|
+
unwind [
|
|
4002
|
+
{left_id: 1, right_id: 2}
|
|
4003
|
+
] as record
|
|
4004
|
+
RETURN record.left_id as left_id, record.right_id as right_id
|
|
4005
|
+
}
|
|
4006
|
+
`).run();
|
|
4007
|
+
|
|
4008
|
+
// Verify relationship exists
|
|
4009
|
+
const rel = new Relationship();
|
|
4010
|
+
rel.type = "DEL_KNOWS";
|
|
4011
|
+
expect(db.getRelationship(rel)).not.toBeNull();
|
|
4012
|
+
|
|
4013
|
+
// Delete the virtual relationship
|
|
4014
|
+
const del = new Runner("DELETE VIRTUAL (:DelRelUser)-[:DEL_KNOWS]-(:DelRelUser)");
|
|
4015
|
+
await del.run();
|
|
4016
|
+
expect(del.results.length).toBe(0);
|
|
4017
|
+
expect(db.getRelationship(rel)).toBeNull();
|
|
4018
|
+
});
|
|
4019
|
+
|
|
4020
|
+
test("Test delete virtual node leaves other nodes intact", async () => {
|
|
4021
|
+
const db = Database.getInstance();
|
|
4022
|
+
// Create two virtual node types
|
|
4023
|
+
await new Runner(`
|
|
4024
|
+
CREATE VIRTUAL (:KeepNode) AS {
|
|
4025
|
+
unwind [{id: 1, name: 'Keep'}] as record
|
|
4026
|
+
RETURN record.id as id, record.name as name
|
|
4027
|
+
}
|
|
4028
|
+
`).run();
|
|
4029
|
+
|
|
4030
|
+
await new Runner(`
|
|
4031
|
+
CREATE VIRTUAL (:RemoveNode) AS {
|
|
4032
|
+
unwind [{id: 2, name: 'Remove'}] as record
|
|
4033
|
+
RETURN record.id as id, record.name as name
|
|
4034
|
+
}
|
|
4035
|
+
`).run();
|
|
4036
|
+
|
|
4037
|
+
expect(db.getNode(new Node(null, "KeepNode"))).not.toBeNull();
|
|
4038
|
+
expect(db.getNode(new Node(null, "RemoveNode"))).not.toBeNull();
|
|
4039
|
+
|
|
4040
|
+
// Delete only one
|
|
4041
|
+
await new Runner("DELETE VIRTUAL (:RemoveNode)").run();
|
|
4042
|
+
|
|
4043
|
+
// The other should still exist
|
|
4044
|
+
expect(db.getNode(new Node(null, "KeepNode"))).not.toBeNull();
|
|
4045
|
+
expect(db.getNode(new Node(null, "RemoveNode"))).toBeNull();
|
|
4046
|
+
|
|
4047
|
+
// The remaining node can still be matched
|
|
4048
|
+
const match = new Runner("MATCH (n:KeepNode) RETURN n");
|
|
4049
|
+
await match.run();
|
|
4050
|
+
expect(match.results.length).toBe(1);
|
|
4051
|
+
expect(match.results[0].n.name).toBe("Keep");
|
|
4052
|
+
});
|
|
@@ -521,7 +521,7 @@ test("Test non-well formed statements", () => {
|
|
|
521
521
|
"Only one RETURN statement is allowed"
|
|
522
522
|
);
|
|
523
523
|
expect(() => new Parser().parse("return 1 with 1 as n")).toThrow(
|
|
524
|
-
"Last statement must be a RETURN, WHERE, CALL, or
|
|
524
|
+
"Last statement must be a RETURN, WHERE, CALL, CREATE, or DELETE statement"
|
|
525
525
|
);
|
|
526
526
|
});
|
|
527
527
|
|