flowquery 1.0.32 → 1.0.34
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/compute/flowquery.d.ts +43 -0
- package/dist/compute/flowquery.d.ts.map +1 -0
- package/dist/compute/flowquery.js +30 -0
- package/dist/compute/flowquery.js.map +1 -0
- package/dist/compute/runner.d.ts +0 -21
- package/dist/compute/runner.d.ts.map +1 -1
- package/dist/compute/runner.js.map +1 -1
- package/dist/flowquery.min.js +1 -1
- package/dist/index.browser.d.ts +1 -1
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.browser.js +10 -10
- package/dist/index.browser.js.map +1 -1
- package/dist/index.node.d.ts +4 -4
- package/dist/index.node.d.ts.map +1 -1
- package/dist/index.node.js +13 -13
- package/dist/index.node.js.map +1 -1
- package/dist/parsing/context.d.ts +1 -0
- package/dist/parsing/context.d.ts.map +1 -1
- package/dist/parsing/context.js +5 -0
- package/dist/parsing/context.js.map +1 -1
- package/dist/parsing/expressions/operator.d.ts +2 -2
- package/dist/parsing/expressions/operator.d.ts.map +1 -1
- package/dist/parsing/expressions/operator.js +6 -1
- package/dist/parsing/expressions/operator.js.map +1 -1
- package/dist/parsing/functions/function_factory.d.ts +2 -0
- package/dist/parsing/functions/function_factory.d.ts.map +1 -1
- package/dist/parsing/functions/function_factory.js +2 -0
- package/dist/parsing/functions/function_factory.js.map +1 -1
- package/dist/parsing/functions/to_lower.d.ts +7 -0
- package/dist/parsing/functions/to_lower.d.ts.map +1 -0
- package/dist/parsing/functions/to_lower.js +37 -0
- package/dist/parsing/functions/to_lower.js.map +1 -0
- package/dist/parsing/functions/to_string.d.ts +7 -0
- package/dist/parsing/functions/to_string.d.ts.map +1 -0
- package/dist/parsing/functions/to_string.js +44 -0
- package/dist/parsing/functions/to_string.js.map +1 -0
- package/dist/parsing/operations/match.d.ts +5 -1
- package/dist/parsing/operations/match.d.ts.map +1 -1
- package/dist/parsing/operations/match.js +25 -1
- package/dist/parsing/operations/match.js.map +1 -1
- package/dist/parsing/operations/union.d.ts +36 -0
- package/dist/parsing/operations/union.d.ts.map +1 -0
- package/dist/parsing/operations/union.js +121 -0
- package/dist/parsing/operations/union.js.map +1 -0
- package/dist/parsing/operations/union_all.d.ts +10 -0
- package/dist/parsing/operations/union_all.d.ts.map +1 -0
- package/dist/parsing/operations/union_all.js +17 -0
- package/dist/parsing/operations/union_all.js.map +1 -0
- package/dist/parsing/parser.d.ts +2 -3
- package/dist/parsing/parser.d.ts.map +1 -1
- package/dist/parsing/parser.js +72 -24
- package/dist/parsing/parser.js.map +1 -1
- package/dist/parsing/parser_state.d.ts +13 -0
- package/dist/parsing/parser_state.d.ts.map +1 -0
- package/dist/parsing/parser_state.js +27 -0
- package/dist/parsing/parser_state.js.map +1 -0
- package/dist/tokenization/keyword.d.ts +4 -1
- package/dist/tokenization/keyword.d.ts.map +1 -1
- package/dist/tokenization/keyword.js +3 -0
- package/dist/tokenization/keyword.js.map +1 -1
- package/dist/tokenization/token.d.ts +6 -0
- package/dist/tokenization/token.d.ts.map +1 -1
- package/dist/tokenization/token.js +18 -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/__init__.py +2 -0
- package/flowquery-py/src/compute/__init__.py +2 -1
- package/flowquery-py/src/compute/flowquery.py +68 -0
- package/flowquery-py/src/graph/node.py +1 -1
- package/flowquery-py/src/parsing/functions/__init__.py +4 -0
- package/flowquery-py/src/parsing/functions/to_lower.py +35 -0
- package/flowquery-py/src/parsing/functions/to_string.py +41 -0
- package/flowquery-py/src/parsing/operations/__init__.py +4 -0
- package/flowquery-py/src/parsing/operations/match.py +24 -2
- package/flowquery-py/src/parsing/operations/union.py +115 -0
- package/flowquery-py/src/parsing/operations/union_all.py +17 -0
- package/flowquery-py/src/parsing/parser.py +68 -24
- package/flowquery-py/src/parsing/parser_state.py +26 -0
- package/flowquery-py/src/tokenization/keyword.py +3 -0
- package/flowquery-py/src/tokenization/token.py +21 -0
- package/flowquery-py/tests/compute/test_runner.py +587 -1
- package/flowquery-py/tests/parsing/test_parser.py +82 -0
- package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
- package/package.json +1 -1
- package/src/compute/flowquery.ts +46 -0
- package/src/compute/runner.ts +0 -24
- package/src/index.browser.ts +17 -14
- package/src/index.node.ts +21 -18
- package/src/parsing/context.ts +6 -0
- package/src/parsing/expressions/operator.ts +8 -3
- package/src/parsing/functions/function_factory.ts +2 -0
- package/src/parsing/functions/to_lower.ts +25 -0
- package/src/parsing/functions/to_string.ts +32 -0
- package/src/parsing/operations/match.ts +24 -1
- package/src/parsing/operations/union.ts +114 -0
- package/src/parsing/operations/union_all.ts +16 -0
- package/src/parsing/parser.ts +74 -23
- package/src/parsing/parser_state.ts +25 -0
- package/src/tokenization/keyword.ts +3 -0
- package/src/tokenization/token.ts +24 -0
- package/tests/compute/runner.test.ts +507 -0
- package/tests/parsing/parser.test.ts +76 -0
package/package.json
CHANGED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import Function from "../parsing/functions/function";
|
|
2
|
+
import { FunctionMetadata } from "../parsing/functions/function_metadata";
|
|
3
|
+
import Runner from "./runner";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* FlowQuery is the public API surface for the FlowQuery library.
|
|
7
|
+
*
|
|
8
|
+
* It extends {@link Runner} with extensibility features such as function
|
|
9
|
+
* listing and plugin registration, keeping the Runner focused on execution.
|
|
10
|
+
*
|
|
11
|
+
* The static members are assigned dynamically in
|
|
12
|
+
* {@link ../index.browser.ts | index.browser.ts} and
|
|
13
|
+
* {@link ../index.node.ts | index.node.ts}.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const fq = new FlowQuery("WITH 1 as x RETURN x");
|
|
18
|
+
* await fq.run();
|
|
19
|
+
* console.log(fq.results); // [{ x: 1 }]
|
|
20
|
+
*
|
|
21
|
+
* // List all registered functions
|
|
22
|
+
* const functions = FlowQuery.listFunctions();
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
class FlowQuery extends Runner {
|
|
26
|
+
/**
|
|
27
|
+
* List all registered functions with their metadata.
|
|
28
|
+
*/
|
|
29
|
+
static listFunctions: (options?: {
|
|
30
|
+
category?: string;
|
|
31
|
+
asyncOnly?: boolean;
|
|
32
|
+
syncOnly?: boolean;
|
|
33
|
+
}) => FunctionMetadata[];
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get metadata for a specific function.
|
|
37
|
+
*/
|
|
38
|
+
static getFunctionMetadata: (name: string) => FunctionMetadata | undefined;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Base Function class for creating custom plugin functions.
|
|
42
|
+
*/
|
|
43
|
+
static Function: typeof Function;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default FlowQuery;
|
package/src/compute/runner.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import ASTNode from "../parsing/ast_node";
|
|
2
|
-
import Function from "../parsing/functions/function";
|
|
3
|
-
import { FunctionMetadata } from "../parsing/functions/function_metadata";
|
|
4
2
|
import Operation from "../parsing/operations/operation";
|
|
5
3
|
import Parser from "../parsing/parser";
|
|
6
4
|
|
|
@@ -21,28 +19,6 @@ class Runner {
|
|
|
21
19
|
private first: Operation;
|
|
22
20
|
private last: Operation;
|
|
23
21
|
|
|
24
|
-
/**
|
|
25
|
-
* List all registered functions with their metadata.
|
|
26
|
-
* Added dynamically in index.browser.ts / index.node.ts
|
|
27
|
-
*/
|
|
28
|
-
static listFunctions: (options?: {
|
|
29
|
-
category?: string;
|
|
30
|
-
asyncOnly?: boolean;
|
|
31
|
-
syncOnly?: boolean;
|
|
32
|
-
}) => FunctionMetadata[];
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Get metadata for a specific function.
|
|
36
|
-
* Added dynamically in index.browser.ts / index.node.ts
|
|
37
|
-
*/
|
|
38
|
-
static getFunctionMetadata: (name: string) => FunctionMetadata | undefined;
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Base Function class for creating custom plugin functions.
|
|
42
|
-
* Added dynamically in index.browser.ts / index.node.ts
|
|
43
|
-
*/
|
|
44
|
-
static Function: typeof Function;
|
|
45
|
-
|
|
46
22
|
/**
|
|
47
23
|
* Creates a new Runner instance and parses the FlowQuery statement.
|
|
48
24
|
*
|
package/src/index.browser.ts
CHANGED
|
@@ -1,37 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* FlowQuery - A declarative query language for data processing pipelines.
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* This is the main entry point for the FlowQuery in-browser usage.
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
6
|
* @packageDocumentation
|
|
7
7
|
*/
|
|
8
|
-
|
|
9
|
-
import
|
|
8
|
+
import { default as FlowQuery } from "./compute/flowquery";
|
|
9
|
+
import Function from "./parsing/functions/function";
|
|
10
10
|
import FunctionFactory from "./parsing/functions/function_factory";
|
|
11
|
-
import {
|
|
12
|
-
FunctionMetadata,
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
import {
|
|
12
|
+
FunctionMetadata,
|
|
13
|
+
OutputSchema,
|
|
14
|
+
ParameterSchema,
|
|
15
15
|
} from "./parsing/functions/function_metadata";
|
|
16
|
-
import Function from "./parsing/functions/function";
|
|
17
16
|
|
|
18
17
|
/**
|
|
19
18
|
* List all registered functions with their metadata.
|
|
20
|
-
*
|
|
19
|
+
*
|
|
21
20
|
* @param options - Optional filter options
|
|
22
21
|
* @returns Array of function metadata
|
|
23
22
|
*/
|
|
24
|
-
FlowQuery.listFunctions = function(options?: {
|
|
23
|
+
FlowQuery.listFunctions = function (options?: {
|
|
24
|
+
category?: string;
|
|
25
|
+
asyncOnly?: boolean;
|
|
26
|
+
syncOnly?: boolean;
|
|
27
|
+
}): FunctionMetadata[] {
|
|
25
28
|
return FunctionFactory.listFunctions(options);
|
|
26
29
|
};
|
|
27
30
|
|
|
28
31
|
/**
|
|
29
32
|
* Get metadata for a specific function.
|
|
30
|
-
*
|
|
33
|
+
*
|
|
31
34
|
* @param name - The function name
|
|
32
35
|
* @returns Function metadata or undefined
|
|
33
36
|
*/
|
|
34
|
-
FlowQuery.getFunctionMetadata = function(name: string): FunctionMetadata | undefined {
|
|
37
|
+
FlowQuery.getFunctionMetadata = function (name: string): FunctionMetadata | undefined {
|
|
35
38
|
return FunctionFactory.getMetadata(name);
|
|
36
39
|
};
|
|
37
40
|
|
|
@@ -40,4 +43,4 @@ FlowQuery.getFunctionMetadata = function(name: string): FunctionMetadata | undef
|
|
|
40
43
|
*/
|
|
41
44
|
FlowQuery.Function = Function;
|
|
42
45
|
|
|
43
|
-
export default FlowQuery;
|
|
46
|
+
export default FlowQuery;
|
package/src/index.node.ts
CHANGED
|
@@ -1,37 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* FlowQuery - A declarative query language for data processing pipelines.
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* This is the main entry point for the FlowQuery Node.js library usage.
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
6
|
* @packageDocumentation
|
|
7
7
|
*/
|
|
8
|
-
|
|
9
|
-
import
|
|
8
|
+
import { default as FlowQuery } from "./compute/flowquery";
|
|
9
|
+
import Function from "./parsing/functions/function";
|
|
10
10
|
import FunctionFactory, { AsyncDataProvider } from "./parsing/functions/function_factory";
|
|
11
|
-
import {
|
|
12
|
-
FunctionMetadata,
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
import {
|
|
12
|
+
FunctionMetadata,
|
|
13
|
+
OutputSchema,
|
|
14
|
+
ParameterSchema,
|
|
15
15
|
} from "./parsing/functions/function_metadata";
|
|
16
|
-
import Function from "./parsing/functions/function";
|
|
17
16
|
|
|
18
17
|
/**
|
|
19
18
|
* List all registered functions with their metadata.
|
|
20
|
-
*
|
|
19
|
+
*
|
|
21
20
|
* @param options - Optional filter options
|
|
22
21
|
* @returns Array of function metadata
|
|
23
22
|
*/
|
|
24
|
-
FlowQuery.listFunctions = function(options?: {
|
|
23
|
+
FlowQuery.listFunctions = function (options?: {
|
|
24
|
+
category?: string;
|
|
25
|
+
asyncOnly?: boolean;
|
|
26
|
+
syncOnly?: boolean;
|
|
27
|
+
}): FunctionMetadata[] {
|
|
25
28
|
return FunctionFactory.listFunctions(options);
|
|
26
29
|
};
|
|
27
30
|
|
|
28
31
|
/**
|
|
29
32
|
* Get metadata for a specific function.
|
|
30
|
-
*
|
|
33
|
+
*
|
|
31
34
|
* @param name - The function name
|
|
32
35
|
* @returns Function metadata or undefined
|
|
33
36
|
*/
|
|
34
|
-
FlowQuery.getFunctionMetadata = function(name: string): FunctionMetadata | undefined {
|
|
37
|
+
FlowQuery.getFunctionMetadata = function (name: string): FunctionMetadata | undefined {
|
|
35
38
|
return FunctionFactory.getMetadata(name);
|
|
36
39
|
};
|
|
37
40
|
|
|
@@ -41,12 +44,12 @@ FlowQuery.getFunctionMetadata = function(name: string): FunctionMetadata | undef
|
|
|
41
44
|
FlowQuery.Function = Function;
|
|
42
45
|
|
|
43
46
|
export default FlowQuery;
|
|
44
|
-
export {
|
|
45
|
-
FlowQuery,
|
|
46
|
-
Function,
|
|
47
|
-
FunctionFactory,
|
|
47
|
+
export {
|
|
48
|
+
FlowQuery,
|
|
49
|
+
Function,
|
|
50
|
+
FunctionFactory,
|
|
48
51
|
AsyncDataProvider,
|
|
49
52
|
FunctionMetadata,
|
|
50
53
|
ParameterSchema,
|
|
51
|
-
OutputSchema
|
|
54
|
+
OutputSchema,
|
|
52
55
|
};
|
package/src/parsing/context.ts
CHANGED
|
@@ -43,6 +43,12 @@ class Context {
|
|
|
43
43
|
public containsType(type: new (...args: any[]) => ASTNode): boolean {
|
|
44
44
|
return this.nodes.some((v) => v instanceof type);
|
|
45
45
|
}
|
|
46
|
+
|
|
47
|
+
public clone(): Context {
|
|
48
|
+
const newContext = new Context();
|
|
49
|
+
newContext.nodes = [...this.nodes];
|
|
50
|
+
return newContext;
|
|
51
|
+
}
|
|
46
52
|
}
|
|
47
53
|
|
|
48
54
|
export default Context;
|
|
@@ -43,7 +43,7 @@ abstract class Operator extends ASTNode {
|
|
|
43
43
|
public get leftAssociative(): boolean {
|
|
44
44
|
return this._leftAssociative;
|
|
45
45
|
}
|
|
46
|
-
public abstract value():
|
|
46
|
+
public abstract value(): any;
|
|
47
47
|
public get lhs(): ASTNode {
|
|
48
48
|
return this.getChildren()[0];
|
|
49
49
|
}
|
|
@@ -56,8 +56,13 @@ class Add extends Operator {
|
|
|
56
56
|
constructor() {
|
|
57
57
|
super(1, true);
|
|
58
58
|
}
|
|
59
|
-
public value():
|
|
60
|
-
|
|
59
|
+
public value(): any {
|
|
60
|
+
const l = this.lhs.value();
|
|
61
|
+
const r = this.rhs.value();
|
|
62
|
+
if (Array.isArray(l) && Array.isArray(r)) {
|
|
63
|
+
return [...l, ...r];
|
|
64
|
+
}
|
|
65
|
+
return l + r;
|
|
61
66
|
}
|
|
62
67
|
}
|
|
63
68
|
|
|
@@ -27,6 +27,8 @@ import "./stringify";
|
|
|
27
27
|
// Import built-in functions to ensure their @FunctionDef decorators run
|
|
28
28
|
import "./sum";
|
|
29
29
|
import "./to_json";
|
|
30
|
+
import "./to_lower";
|
|
31
|
+
import "./to_string";
|
|
30
32
|
import "./type";
|
|
31
33
|
|
|
32
34
|
// Re-export AsyncDataProvider for backwards compatibility
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import Function from "./function";
|
|
2
|
+
import { FunctionDef } from "./function_metadata";
|
|
3
|
+
|
|
4
|
+
@FunctionDef({
|
|
5
|
+
description: "Converts a string to lowercase",
|
|
6
|
+
category: "scalar",
|
|
7
|
+
parameters: [{ name: "text", description: "String to convert to lowercase", type: "string" }],
|
|
8
|
+
output: { description: "Lowercase string", type: "string", example: "hello world" },
|
|
9
|
+
examples: ["WITH 'Hello World' AS s RETURN toLower(s)", "WITH 'FOO' AS s RETURN toLower(s)"],
|
|
10
|
+
})
|
|
11
|
+
class ToLower extends Function {
|
|
12
|
+
constructor() {
|
|
13
|
+
super("tolower");
|
|
14
|
+
this._expectedParameterCount = 1;
|
|
15
|
+
}
|
|
16
|
+
public value(): any {
|
|
17
|
+
const val = this.getChildren()[0].value();
|
|
18
|
+
if (typeof val !== "string") {
|
|
19
|
+
throw new Error("Invalid argument for toLower function: expected a string");
|
|
20
|
+
}
|
|
21
|
+
return val.toLowerCase();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default ToLower;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import Function from "./function";
|
|
2
|
+
import { FunctionDef } from "./function_metadata";
|
|
3
|
+
|
|
4
|
+
@FunctionDef({
|
|
5
|
+
description: "Converts a value to its string representation",
|
|
6
|
+
category: "scalar",
|
|
7
|
+
parameters: [{ name: "value", description: "Value to convert to a string", type: "any" }],
|
|
8
|
+
output: { description: "String representation of the value", type: "string", example: "42" },
|
|
9
|
+
examples: [
|
|
10
|
+
"WITH 42 AS n RETURN toString(n)",
|
|
11
|
+
"WITH true AS b RETURN toString(b)",
|
|
12
|
+
"WITH [1, 2, 3] AS arr RETURN toString(arr)",
|
|
13
|
+
],
|
|
14
|
+
})
|
|
15
|
+
class ToString extends Function {
|
|
16
|
+
constructor() {
|
|
17
|
+
super("tostring");
|
|
18
|
+
this._expectedParameterCount = 1;
|
|
19
|
+
}
|
|
20
|
+
public value(): any {
|
|
21
|
+
const val = this.getChildren()[0].value();
|
|
22
|
+
if (val === null || val === undefined) {
|
|
23
|
+
return String(val);
|
|
24
|
+
}
|
|
25
|
+
if (typeof val === "object") {
|
|
26
|
+
return JSON.stringify(val);
|
|
27
|
+
}
|
|
28
|
+
return String(val);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default ToString;
|
|
@@ -1,30 +1,53 @@
|
|
|
1
|
+
import Node from "../../graph/node";
|
|
1
2
|
import Pattern from "../../graph/pattern";
|
|
2
3
|
import Patterns from "../../graph/patterns";
|
|
3
4
|
import Operation from "./operation";
|
|
4
5
|
|
|
5
6
|
class Match extends Operation {
|
|
6
7
|
private _patterns: Patterns | null = null;
|
|
8
|
+
private _optional: boolean = false;
|
|
7
9
|
|
|
8
|
-
constructor(patterns: Pattern[] = []) {
|
|
10
|
+
constructor(patterns: Pattern[] = [], optional: boolean = false) {
|
|
9
11
|
super();
|
|
10
12
|
this._patterns = new Patterns(patterns);
|
|
13
|
+
this._optional = optional;
|
|
11
14
|
}
|
|
12
15
|
public get patterns(): Pattern[] {
|
|
13
16
|
return this._patterns ? this._patterns.patterns : [];
|
|
14
17
|
}
|
|
18
|
+
public get optional(): boolean {
|
|
19
|
+
return this._optional;
|
|
20
|
+
}
|
|
21
|
+
protected toString(): string {
|
|
22
|
+
return this._optional ? "OptionalMatch" : "Match";
|
|
23
|
+
}
|
|
15
24
|
/**
|
|
16
25
|
* Executes the match operation by chaining the patterns together.
|
|
17
26
|
* After each pattern match, it continues to the next operation in the chain.
|
|
27
|
+
* If optional and no match is found, continues with null values.
|
|
18
28
|
* @return Promise<void>
|
|
19
29
|
*/
|
|
20
30
|
public async run(): Promise<void> {
|
|
21
31
|
await this._patterns!.initialize();
|
|
32
|
+
let matched = false;
|
|
22
33
|
this._patterns!.toDoNext = async () => {
|
|
34
|
+
matched = true;
|
|
23
35
|
// Continue to the next operation after all patterns are matched
|
|
24
36
|
await this.next?.run();
|
|
25
37
|
};
|
|
26
38
|
// Kick off the graph pattern traversal
|
|
27
39
|
await this._patterns!.traverse();
|
|
40
|
+
// For OPTIONAL MATCH: if nothing matched, continue with null values
|
|
41
|
+
if (!matched && this._optional) {
|
|
42
|
+
for (const pattern of this._patterns!.patterns) {
|
|
43
|
+
for (const element of pattern.chain) {
|
|
44
|
+
if (element instanceof Node) {
|
|
45
|
+
element.setValue(null!);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
await this.next?.run();
|
|
50
|
+
}
|
|
28
51
|
}
|
|
29
52
|
}
|
|
30
53
|
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import Operation from "./operation";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Represents a UNION operation that combines results from two sub-queries.
|
|
5
|
+
*
|
|
6
|
+
* UNION merges the results of a left and right query pipeline, removing
|
|
7
|
+
* duplicate rows. Both sides must return the same column names.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```
|
|
11
|
+
* WITH 1 AS x RETURN x
|
|
12
|
+
* UNION
|
|
13
|
+
* WITH 2 AS x RETURN x
|
|
14
|
+
* // Results: [{x: 1}, {x: 2}]
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
class Union extends Operation {
|
|
18
|
+
protected _left: Operation | null = null;
|
|
19
|
+
protected _right: Operation | null = null;
|
|
20
|
+
protected _results: Record<string, any>[] = [];
|
|
21
|
+
|
|
22
|
+
public set left(operation: Operation) {
|
|
23
|
+
this._left = operation;
|
|
24
|
+
}
|
|
25
|
+
public get left(): Operation {
|
|
26
|
+
if (!this._left) {
|
|
27
|
+
throw new Error("Left operation is not set");
|
|
28
|
+
}
|
|
29
|
+
return this._left;
|
|
30
|
+
}
|
|
31
|
+
public set right(operation: Operation) {
|
|
32
|
+
this._right = operation;
|
|
33
|
+
}
|
|
34
|
+
public get right(): Operation {
|
|
35
|
+
if (!this._right) {
|
|
36
|
+
throw new Error("Right operation is not set");
|
|
37
|
+
}
|
|
38
|
+
return this._right;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private lastInChain(operation: Operation): Operation {
|
|
42
|
+
let current = operation;
|
|
43
|
+
while (current.next) {
|
|
44
|
+
current = current.next;
|
|
45
|
+
}
|
|
46
|
+
return current;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public async initialize(): Promise<void> {
|
|
50
|
+
this._results = [];
|
|
51
|
+
await this.next?.initialize();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public async run(): Promise<void> {
|
|
55
|
+
// Execute left pipeline
|
|
56
|
+
await this._left!.initialize();
|
|
57
|
+
await this._left!.run();
|
|
58
|
+
await this._left!.finish();
|
|
59
|
+
const leftLast = this.lastInChain(this._left!);
|
|
60
|
+
const leftResults: Record<string, any>[] = leftLast.results;
|
|
61
|
+
|
|
62
|
+
// Execute right pipeline
|
|
63
|
+
await this._right!.initialize();
|
|
64
|
+
await this._right!.run();
|
|
65
|
+
await this._right!.finish();
|
|
66
|
+
const rightLast = this.lastInChain(this._right!);
|
|
67
|
+
const rightResults: Record<string, any>[] = rightLast.results;
|
|
68
|
+
|
|
69
|
+
// Validate column names match
|
|
70
|
+
if (leftResults.length > 0 && rightResults.length > 0) {
|
|
71
|
+
const leftKeys = Object.keys(leftResults[0]).sort().join(",");
|
|
72
|
+
const rightKeys = Object.keys(rightResults[0]).sort().join(",");
|
|
73
|
+
if (leftKeys !== rightKeys) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
"All sub queries in a UNION must have the same return column names"
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Combine results
|
|
81
|
+
this._results = this.combine(leftResults, rightResults);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Combines results from left and right pipelines.
|
|
86
|
+
* UNION removes duplicates; subclass UnionAll overrides to keep all rows.
|
|
87
|
+
*/
|
|
88
|
+
protected combine(
|
|
89
|
+
left: Record<string, any>[],
|
|
90
|
+
right: Record<string, any>[]
|
|
91
|
+
): Record<string, any>[] {
|
|
92
|
+
const combined = [...left];
|
|
93
|
+
for (const row of right) {
|
|
94
|
+
const serialized = JSON.stringify(row);
|
|
95
|
+
const isDuplicate = combined.some(
|
|
96
|
+
(existing) => JSON.stringify(existing) === serialized
|
|
97
|
+
);
|
|
98
|
+
if (!isDuplicate) {
|
|
99
|
+
combined.push(row);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return combined;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
public async finish(): Promise<void> {
|
|
106
|
+
await this.next?.finish();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
public get results(): Record<string, any>[] {
|
|
110
|
+
return this._results;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export default Union;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import Union from "./union";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Represents a UNION ALL operation that concatenates results from two sub-queries
|
|
5
|
+
* without removing duplicates.
|
|
6
|
+
*/
|
|
7
|
+
class UnionAll extends Union {
|
|
8
|
+
protected combine(
|
|
9
|
+
left: Record<string, any>[],
|
|
10
|
+
right: Record<string, any>[]
|
|
11
|
+
): Record<string, any>[] {
|
|
12
|
+
return [...left, ...right];
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default UnionAll;
|