flowquery 1.0.38 → 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/expressions/operator.js +4 -4
- package/dist/parsing/expressions/operator.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/aggregated_return.d.ts.map +1 -1
- package/dist/parsing/operations/aggregated_return.js +6 -2
- package/dist/parsing/operations/aggregated_return.js.map +1 -1
- 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/operations/limit.d.ts +1 -0
- package/dist/parsing/operations/limit.d.ts.map +1 -1
- package/dist/parsing/operations/limit.js +3 -0
- package/dist/parsing/operations/limit.js.map +1 -1
- package/dist/parsing/operations/order_by.d.ts +35 -0
- package/dist/parsing/operations/order_by.d.ts.map +1 -0
- package/dist/parsing/operations/order_by.js +87 -0
- package/dist/parsing/operations/order_by.js.map +1 -0
- package/dist/parsing/operations/return.d.ts +3 -0
- package/dist/parsing/operations/return.d.ts.map +1 -1
- package/dist/parsing/operations/return.js +16 -3
- package/dist/parsing/operations/return.js.map +1 -1
- package/dist/parsing/parser.d.ts +2 -0
- package/dist/parsing/parser.d.ts.map +1 -1
- package/dist/parsing/parser.js +116 -2
- package/dist/parsing/parser.js.map +1 -1
- package/dist/tokenization/token.d.ts +8 -0
- package/dist/tokenization/token.d.ts.map +1 -1
- package/dist/tokenization/token.js +24 -0
- package/dist/tokenization/token.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/expressions/operator.py +4 -4
- 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 +7 -0
- package/flowquery-py/src/parsing/operations/aggregated_return.py +4 -1
- 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/operations/limit.py +4 -0
- package/flowquery-py/src/parsing/operations/order_by.py +72 -0
- package/flowquery-py/src/parsing/operations/return_op.py +20 -3
- package/flowquery-py/src/parsing/parser.py +98 -3
- package/flowquery-py/src/tokenization/token.py +28 -0
- package/flowquery-py/tests/compute/test_runner.py +329 -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/expressions/operator.ts +4 -4
- package/src/parsing/functions/function_factory.ts +1 -0
- package/src/parsing/functions/substring.ts +65 -0
- package/src/parsing/operations/aggregated_return.ts +9 -5
- package/src/parsing/operations/delete_node.ts +33 -0
- package/src/parsing/operations/delete_relationship.ts +32 -0
- package/src/parsing/operations/limit.ts +3 -0
- package/src/parsing/operations/order_by.ts +75 -0
- package/src/parsing/operations/return.ts +17 -3
- package/src/parsing/parser.ts +115 -2
- package/src/tokenization/token.ts +32 -0
- package/tests/compute/runner.test.ts +291 -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
|
}
|
|
@@ -197,7 +197,7 @@ class Not extends Operator {
|
|
|
197
197
|
|
|
198
198
|
class Is extends Operator {
|
|
199
199
|
constructor() {
|
|
200
|
-
super(
|
|
200
|
+
super(0, true);
|
|
201
201
|
}
|
|
202
202
|
public value(): number {
|
|
203
203
|
return this.lhs.value() == this.rhs.value() ? 1 : 0;
|
|
@@ -206,7 +206,7 @@ class Is extends Operator {
|
|
|
206
206
|
|
|
207
207
|
class IsNot extends Operator {
|
|
208
208
|
constructor() {
|
|
209
|
-
super(
|
|
209
|
+
super(0, true);
|
|
210
210
|
}
|
|
211
211
|
public value(): number {
|
|
212
212
|
return this.lhs.value() != this.rhs.value() ? 1 : 0;
|
|
@@ -215,7 +215,7 @@ class IsNot extends Operator {
|
|
|
215
215
|
|
|
216
216
|
class In extends Operator {
|
|
217
217
|
constructor() {
|
|
218
|
-
super(
|
|
218
|
+
super(0, true);
|
|
219
219
|
}
|
|
220
220
|
public value(): number {
|
|
221
221
|
const list = this.rhs.value();
|
|
@@ -228,7 +228,7 @@ class In extends Operator {
|
|
|
228
228
|
|
|
229
229
|
class NotIn extends Operator {
|
|
230
230
|
constructor() {
|
|
231
|
-
super(
|
|
231
|
+
super(0, true);
|
|
232
232
|
}
|
|
233
233
|
public value(): number {
|
|
234
234
|
const list = this.rhs.value();
|
|
@@ -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;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import Return from "./return";
|
|
2
|
-
import GroupBy from "./group_by";
|
|
3
1
|
import Expression from "../expressions/expression";
|
|
2
|
+
import GroupBy from "./group_by";
|
|
3
|
+
import Return from "./return";
|
|
4
4
|
|
|
5
5
|
class AggregatedReturn extends Return {
|
|
6
6
|
private _group_by: GroupBy = new GroupBy(this.children as Expression[]);
|
|
@@ -8,11 +8,15 @@ class AggregatedReturn extends Return {
|
|
|
8
8
|
await this._group_by.run();
|
|
9
9
|
}
|
|
10
10
|
public get results(): Record<string, any>[] {
|
|
11
|
-
if(this._where !== null) {
|
|
11
|
+
if (this._where !== null) {
|
|
12
12
|
this._group_by.where = this._where;
|
|
13
13
|
}
|
|
14
|
-
|
|
14
|
+
const results = Array.from(this._group_by.generate_results());
|
|
15
|
+
if (this._orderBy !== null) {
|
|
16
|
+
return this._orderBy.sort(results);
|
|
17
|
+
}
|
|
18
|
+
return results;
|
|
15
19
|
}
|
|
16
20
|
}
|
|
17
21
|
|
|
18
|
-
export default AggregatedReturn;
|
|
22
|
+
export default AggregatedReturn;
|
|
@@ -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;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import Operation from "./operation";
|
|
2
|
+
|
|
3
|
+
export interface SortField {
|
|
4
|
+
field: string;
|
|
5
|
+
direction: "asc" | "desc";
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Represents an ORDER BY operation that sorts results.
|
|
10
|
+
*
|
|
11
|
+
* Can be attached to a RETURN operation (sorting its results),
|
|
12
|
+
* or used as a standalone accumulating operation after a non-aggregate WITH.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```
|
|
16
|
+
* RETURN x ORDER BY x DESC
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
class OrderBy extends Operation {
|
|
20
|
+
private _fields: SortField[];
|
|
21
|
+
private _results: Record<string, any>[] = [];
|
|
22
|
+
|
|
23
|
+
constructor(fields: SortField[]) {
|
|
24
|
+
super();
|
|
25
|
+
this._fields = fields;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public get fields(): SortField[] {
|
|
29
|
+
return this._fields;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Sorts an array of records according to the sort fields.
|
|
34
|
+
*/
|
|
35
|
+
public sort(records: Record<string, any>[]): Record<string, any>[] {
|
|
36
|
+
return records.sort((a, b) => {
|
|
37
|
+
for (const { field, direction } of this._fields) {
|
|
38
|
+
const aVal = a[field];
|
|
39
|
+
const bVal = b[field];
|
|
40
|
+
let cmp = 0;
|
|
41
|
+
if (aVal == null && bVal == null) cmp = 0;
|
|
42
|
+
else if (aVal == null) cmp = -1;
|
|
43
|
+
else if (bVal == null) cmp = 1;
|
|
44
|
+
else if (aVal < bVal) cmp = -1;
|
|
45
|
+
else if (aVal > bVal) cmp = 1;
|
|
46
|
+
if (cmp !== 0) {
|
|
47
|
+
return direction === "desc" ? -cmp : cmp;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return 0;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* When used as a standalone operation (after non-aggregate WITH),
|
|
56
|
+
* accumulates records to sort later.
|
|
57
|
+
*/
|
|
58
|
+
public async run(): Promise<void> {
|
|
59
|
+
const record: Record<string, any> = {};
|
|
60
|
+
// Collect current variable values from the context
|
|
61
|
+
// This gets called per-row, and then finish() sorts and emits
|
|
62
|
+
await this.next?.run();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public async initialize(): Promise<void> {
|
|
66
|
+
this._results = [];
|
|
67
|
+
await this.next?.initialize();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public get results(): Record<string, any>[] {
|
|
71
|
+
return this._results;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export default OrderBy;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Limit from "./limit";
|
|
2
|
+
import OrderBy from "./order_by";
|
|
2
3
|
import Projection from "./projection";
|
|
3
4
|
import Where from "./where";
|
|
4
5
|
|
|
@@ -17,6 +18,7 @@ class Return extends Projection {
|
|
|
17
18
|
protected _where: Where | null = null;
|
|
18
19
|
protected _results: Record<string, any>[] = [];
|
|
19
20
|
private _limit: Limit | null = null;
|
|
21
|
+
protected _orderBy: OrderBy | null = null;
|
|
20
22
|
public set where(where: Where) {
|
|
21
23
|
this._where = where;
|
|
22
24
|
}
|
|
@@ -29,11 +31,16 @@ class Return extends Projection {
|
|
|
29
31
|
public set limit(limit: Limit) {
|
|
30
32
|
this._limit = limit;
|
|
31
33
|
}
|
|
34
|
+
public set orderBy(orderBy: OrderBy) {
|
|
35
|
+
this._orderBy = orderBy;
|
|
36
|
+
}
|
|
32
37
|
public async run(): Promise<void> {
|
|
33
38
|
if (!this.where) {
|
|
34
39
|
return;
|
|
35
40
|
}
|
|
36
|
-
|
|
41
|
+
// When ORDER BY is present, skip limit during accumulation;
|
|
42
|
+
// limit will be applied after sorting in get results()
|
|
43
|
+
if (this._orderBy === null && this._limit !== null && this._limit.isLimitReached) {
|
|
37
44
|
return;
|
|
38
45
|
}
|
|
39
46
|
const record: Map<string, any> = new Map();
|
|
@@ -43,7 +50,7 @@ class Return extends Projection {
|
|
|
43
50
|
record.set(alias, value);
|
|
44
51
|
}
|
|
45
52
|
this._results.push(Object.fromEntries(record));
|
|
46
|
-
if (this._limit !== null) {
|
|
53
|
+
if (this._orderBy === null && this._limit !== null) {
|
|
47
54
|
this._limit.increment();
|
|
48
55
|
}
|
|
49
56
|
}
|
|
@@ -51,7 +58,14 @@ class Return extends Projection {
|
|
|
51
58
|
this._results = [];
|
|
52
59
|
}
|
|
53
60
|
public get results(): Record<string, any>[] {
|
|
54
|
-
|
|
61
|
+
let results = this._results;
|
|
62
|
+
if (this._orderBy !== null) {
|
|
63
|
+
results = this._orderBy.sort(results);
|
|
64
|
+
}
|
|
65
|
+
if (this._orderBy !== null && this._limit !== null) {
|
|
66
|
+
results = results.slice(0, this._limit.limitValue);
|
|
67
|
+
}
|
|
68
|
+
return results;
|
|
55
69
|
}
|
|
56
70
|
}
|
|
57
71
|
|
package/src/parsing/parser.ts
CHANGED
|
@@ -53,10 +53,13 @@ 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";
|
|
59
61
|
import Operation from "./operations/operation";
|
|
62
|
+
import OrderBy, { SortField } from "./operations/order_by";
|
|
60
63
|
import Return from "./operations/return";
|
|
61
64
|
import Union from "./operations/union";
|
|
62
65
|
import UnionAll from "./operations/union_all";
|
|
@@ -143,6 +146,15 @@ class Parser extends BaseParser {
|
|
|
143
146
|
operation = where;
|
|
144
147
|
}
|
|
145
148
|
}
|
|
149
|
+
const orderBy = this.parseOrderBy();
|
|
150
|
+
if (orderBy !== null) {
|
|
151
|
+
if (operation instanceof Return) {
|
|
152
|
+
(operation as Return).orderBy = orderBy;
|
|
153
|
+
} else {
|
|
154
|
+
operation!.addSibling(orderBy);
|
|
155
|
+
operation = orderBy;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
146
158
|
const limit = this.parseLimit();
|
|
147
159
|
if (limit !== null) {
|
|
148
160
|
if (operation instanceof Return) {
|
|
@@ -176,9 +188,13 @@ class Parser extends BaseParser {
|
|
|
176
188
|
!(operation instanceof Return) &&
|
|
177
189
|
!(operation instanceof Call) &&
|
|
178
190
|
!(operation instanceof CreateNode) &&
|
|
179
|
-
!(operation instanceof CreateRelationship)
|
|
191
|
+
!(operation instanceof CreateRelationship) &&
|
|
192
|
+
!(operation instanceof DeleteNode) &&
|
|
193
|
+
!(operation instanceof DeleteRelationship)
|
|
180
194
|
) {
|
|
181
|
-
throw new Error(
|
|
195
|
+
throw new Error(
|
|
196
|
+
"Last statement must be a RETURN, WHERE, CALL, CREATE, or DELETE statement"
|
|
197
|
+
);
|
|
182
198
|
}
|
|
183
199
|
return root;
|
|
184
200
|
}
|
|
@@ -191,6 +207,7 @@ class Parser extends BaseParser {
|
|
|
191
207
|
this.parseLoad() ||
|
|
192
208
|
this.parseCall() ||
|
|
193
209
|
this.parseCreate() ||
|
|
210
|
+
this.parseDelete() ||
|
|
194
211
|
this.parseMatch()
|
|
195
212
|
);
|
|
196
213
|
}
|
|
@@ -449,6 +466,60 @@ class Parser extends BaseParser {
|
|
|
449
466
|
return create;
|
|
450
467
|
}
|
|
451
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
|
+
|
|
452
523
|
private parseMatch(): Match | null {
|
|
453
524
|
let optional = false;
|
|
454
525
|
if (this.token.isOptional()) {
|
|
@@ -790,6 +861,48 @@ class Parser extends BaseParser {
|
|
|
790
861
|
return limit;
|
|
791
862
|
}
|
|
792
863
|
|
|
864
|
+
private parseOrderBy(): OrderBy | null {
|
|
865
|
+
this.skipWhitespaceAndComments();
|
|
866
|
+
if (!this.token.isOrder()) {
|
|
867
|
+
return null;
|
|
868
|
+
}
|
|
869
|
+
this.expectPreviousTokenToBeWhitespaceOrComment();
|
|
870
|
+
this.setNextToken();
|
|
871
|
+
this.expectAndSkipWhitespaceAndComments();
|
|
872
|
+
if (!this.token.isByKeyword()) {
|
|
873
|
+
throw new Error("Expected BY after ORDER");
|
|
874
|
+
}
|
|
875
|
+
this.setNextToken();
|
|
876
|
+
this.expectAndSkipWhitespaceAndComments();
|
|
877
|
+
const fields: SortField[] = [];
|
|
878
|
+
while (true) {
|
|
879
|
+
if (!this.token.isIdentifierOrKeyword()) {
|
|
880
|
+
throw new Error("Expected field name in ORDER BY");
|
|
881
|
+
}
|
|
882
|
+
const field = this.token.value!;
|
|
883
|
+
this.setNextToken();
|
|
884
|
+
this.skipWhitespaceAndComments();
|
|
885
|
+
let direction: "asc" | "desc" = "asc";
|
|
886
|
+
if (this.token.isAsc()) {
|
|
887
|
+
direction = "asc";
|
|
888
|
+
this.setNextToken();
|
|
889
|
+
this.skipWhitespaceAndComments();
|
|
890
|
+
} else if (this.token.isDesc()) {
|
|
891
|
+
direction = "desc";
|
|
892
|
+
this.setNextToken();
|
|
893
|
+
this.skipWhitespaceAndComments();
|
|
894
|
+
}
|
|
895
|
+
fields.push({ field, direction });
|
|
896
|
+
if (this.token.isComma()) {
|
|
897
|
+
this.setNextToken();
|
|
898
|
+
this.skipWhitespaceAndComments();
|
|
899
|
+
} else {
|
|
900
|
+
break;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
return new OrderBy(fields);
|
|
904
|
+
}
|
|
905
|
+
|
|
793
906
|
private *parseExpressions(
|
|
794
907
|
alias_option: AliasOption = AliasOption.NOT_ALLOWED
|
|
795
908
|
): IterableIterator<Expression> {
|
|
@@ -675,6 +675,38 @@ class Token {
|
|
|
675
675
|
return this._type === TokenType.KEYWORD && this._value === Keyword.DISTINCT;
|
|
676
676
|
}
|
|
677
677
|
|
|
678
|
+
public static get ORDER(): Token {
|
|
679
|
+
return new Token(TokenType.KEYWORD, Keyword.ORDER);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
public isOrder(): boolean {
|
|
683
|
+
return this._type === TokenType.KEYWORD && this._value === Keyword.ORDER;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
public static get BY(): Token {
|
|
687
|
+
return new Token(TokenType.KEYWORD, Keyword.BY);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
public isByKeyword(): boolean {
|
|
691
|
+
return this._type === TokenType.KEYWORD && this._value === Keyword.BY;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
public static get ASC(): Token {
|
|
695
|
+
return new Token(TokenType.KEYWORD, Keyword.ASC);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
public isAsc(): boolean {
|
|
699
|
+
return this._type === TokenType.KEYWORD && this._value === Keyword.ASC;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
public static get DESC(): Token {
|
|
703
|
+
return new Token(TokenType.KEYWORD, Keyword.DESC);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
public isDesc(): boolean {
|
|
707
|
+
return this._type === TokenType.KEYWORD && this._value === Keyword.DESC;
|
|
708
|
+
}
|
|
709
|
+
|
|
678
710
|
public static get LIMIT(): Token {
|
|
679
711
|
return new Token(TokenType.KEYWORD, Keyword.LIMIT);
|
|
680
712
|
}
|