flowquery 1.0.0 → 1.0.2
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/.github/workflows/npm-publish.yml +2 -0
- package/.github/workflows/release.yml +24 -9
- package/dist/compute/runner.js +75 -0
- package/dist/compute/runner.js.map +1 -0
- package/dist/flowquery.min.js +1 -0
- package/dist/index.browser.js +119 -0
- package/dist/index.browser.js.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/index.node.js +123 -0
- package/dist/index.node.js.map +1 -0
- package/dist/io/command_line.js +102 -0
- package/dist/io/command_line.js.map +1 -0
- package/dist/parsing/alias.js +23 -0
- package/dist/parsing/alias.js.map +1 -0
- package/dist/parsing/alias_option.js +11 -0
- package/dist/parsing/alias_option.js.map +1 -0
- package/dist/parsing/ast_node.js +145 -0
- package/dist/parsing/ast_node.js.map +1 -0
- package/dist/parsing/base_parser.js +92 -0
- package/dist/parsing/base_parser.js.map +1 -0
- package/dist/parsing/components/csv.js +13 -0
- package/dist/parsing/components/csv.js.map +1 -0
- package/dist/parsing/components/from.js +16 -0
- package/dist/parsing/components/from.js.map +1 -0
- package/dist/parsing/components/headers.js +16 -0
- package/dist/parsing/components/headers.js.map +1 -0
- package/dist/parsing/components/json.js +13 -0
- package/dist/parsing/components/json.js.map +1 -0
- package/dist/parsing/components/null.js +13 -0
- package/dist/parsing/components/null.js.map +1 -0
- package/dist/parsing/components/post.js +13 -0
- package/dist/parsing/components/post.js.map +1 -0
- package/dist/parsing/components/text.js +13 -0
- package/dist/parsing/components/text.js.map +1 -0
- package/dist/parsing/context.js +47 -0
- package/dist/parsing/context.js.map +1 -0
- package/dist/parsing/data_structures/associative_array.js +45 -0
- package/dist/parsing/data_structures/associative_array.js.map +1 -0
- package/dist/parsing/data_structures/json_array.js +35 -0
- package/dist/parsing/data_structures/json_array.js.map +1 -0
- package/dist/parsing/data_structures/key_value_pair.js +41 -0
- package/dist/parsing/data_structures/key_value_pair.js.map +1 -0
- package/dist/parsing/data_structures/lookup.js +44 -0
- package/dist/parsing/data_structures/lookup.js.map +1 -0
- package/dist/parsing/data_structures/range_lookup.js +40 -0
- package/dist/parsing/data_structures/range_lookup.js.map +1 -0
- package/dist/parsing/expressions/expression.js +142 -0
- package/dist/parsing/expressions/expression.js.map +1 -0
- package/dist/parsing/expressions/f_string.js +29 -0
- package/dist/parsing/expressions/f_string.js.map +1 -0
- package/dist/parsing/expressions/identifier.js +26 -0
- package/dist/parsing/expressions/identifier.js.map +1 -0
- package/dist/parsing/expressions/number.js +41 -0
- package/dist/parsing/expressions/number.js.map +1 -0
- package/dist/parsing/expressions/operator.js +180 -0
- package/dist/parsing/expressions/operator.js.map +1 -0
- package/dist/parsing/expressions/reference.js +45 -0
- package/dist/parsing/expressions/reference.js.map +1 -0
- package/dist/parsing/expressions/string.js +34 -0
- package/dist/parsing/expressions/string.js.map +1 -0
- package/dist/parsing/functions/aggregate_function.js +58 -0
- package/dist/parsing/functions/aggregate_function.js.map +1 -0
- package/dist/parsing/functions/async_function.js +119 -0
- package/dist/parsing/functions/async_function.js.map +1 -0
- package/dist/parsing/functions/avg.js +43 -0
- package/dist/parsing/functions/avg.js.map +1 -0
- package/dist/parsing/functions/collect.js +52 -0
- package/dist/parsing/functions/collect.js.map +1 -0
- package/dist/parsing/functions/function.js +59 -0
- package/dist/parsing/functions/function.js.map +1 -0
- package/dist/parsing/functions/function_factory.js +259 -0
- package/dist/parsing/functions/function_factory.js.map +1 -0
- package/dist/parsing/functions/function_metadata.js +159 -0
- package/dist/parsing/functions/function_metadata.js.map +1 -0
- package/dist/parsing/functions/functions.js +47 -0
- package/dist/parsing/functions/functions.js.map +1 -0
- package/dist/parsing/functions/join.js +29 -0
- package/dist/parsing/functions/join.js.map +1 -0
- package/dist/parsing/functions/predicate_function.js +37 -0
- package/dist/parsing/functions/predicate_function.js.map +1 -0
- package/dist/parsing/functions/predicate_function_factory.js +19 -0
- package/dist/parsing/functions/predicate_function_factory.js.map +1 -0
- package/dist/parsing/functions/predicate_sum.js +33 -0
- package/dist/parsing/functions/predicate_sum.js.map +1 -0
- package/dist/parsing/functions/rand.js +17 -0
- package/dist/parsing/functions/rand.js.map +1 -0
- package/dist/parsing/functions/range.js +22 -0
- package/dist/parsing/functions/range.js.map +1 -0
- package/dist/parsing/functions/reducer_element.js +12 -0
- package/dist/parsing/functions/reducer_element.js.map +1 -0
- package/dist/parsing/functions/replace.js +23 -0
- package/dist/parsing/functions/replace.js.map +1 -0
- package/dist/parsing/functions/round.js +21 -0
- package/dist/parsing/functions/round.js.map +1 -0
- package/dist/parsing/functions/size.js +21 -0
- package/dist/parsing/functions/size.js.map +1 -0
- package/dist/parsing/functions/split.js +29 -0
- package/dist/parsing/functions/split.js.map +1 -0
- package/dist/parsing/functions/stringify.js +29 -0
- package/dist/parsing/functions/stringify.js.map +1 -0
- package/dist/parsing/functions/sum.js +38 -0
- package/dist/parsing/functions/sum.js.map +1 -0
- package/dist/parsing/functions/to_json.js +21 -0
- package/dist/parsing/functions/to_json.js.map +1 -0
- package/dist/parsing/functions/value_holder.js +16 -0
- package/dist/parsing/functions/value_holder.js.map +1 -0
- package/dist/parsing/logic/case.js +27 -0
- package/dist/parsing/logic/case.js.map +1 -0
- package/dist/parsing/logic/else.js +16 -0
- package/dist/parsing/logic/else.js.map +1 -0
- package/dist/parsing/logic/end.js +13 -0
- package/dist/parsing/logic/end.js.map +1 -0
- package/dist/parsing/logic/then.js +16 -0
- package/dist/parsing/logic/then.js.map +1 -0
- package/dist/parsing/logic/when.js +16 -0
- package/dist/parsing/logic/when.js.map +1 -0
- package/dist/parsing/operations/aggregated_return.js +35 -0
- package/dist/parsing/operations/aggregated_return.js.map +1 -0
- package/dist/parsing/operations/aggregated_with.js +41 -0
- package/dist/parsing/operations/aggregated_with.js.map +1 -0
- package/dist/parsing/operations/group_by.js +139 -0
- package/dist/parsing/operations/group_by.js.map +1 -0
- package/dist/parsing/operations/limit.js +38 -0
- package/dist/parsing/operations/limit.js.map +1 -0
- package/dist/parsing/operations/load.js +174 -0
- package/dist/parsing/operations/load.js.map +1 -0
- package/dist/parsing/operations/operation.js +81 -0
- package/dist/parsing/operations/operation.js.map +1 -0
- package/dist/parsing/operations/projection.js +21 -0
- package/dist/parsing/operations/projection.js.map +1 -0
- package/dist/parsing/operations/return.js +60 -0
- package/dist/parsing/operations/return.js.map +1 -0
- package/dist/parsing/operations/unwind.js +46 -0
- package/dist/parsing/operations/unwind.js.map +1 -0
- package/dist/parsing/operations/where.js +53 -0
- package/dist/parsing/operations/where.js.map +1 -0
- package/dist/parsing/operations/with.js +36 -0
- package/dist/parsing/operations/with.js.map +1 -0
- package/dist/parsing/parser.js +812 -0
- package/dist/parsing/parser.js.map +1 -0
- package/dist/parsing/token_to_node.js +121 -0
- package/dist/parsing/token_to_node.js.map +1 -0
- package/dist/tokenization/keyword.js +46 -0
- package/dist/tokenization/keyword.js.map +1 -0
- package/dist/tokenization/operator.js +28 -0
- package/dist/tokenization/operator.js.map +1 -0
- package/dist/tokenization/string_walker.js +165 -0
- package/dist/tokenization/string_walker.js.map +1 -0
- package/dist/tokenization/symbol.js +18 -0
- package/dist/tokenization/symbol.js.map +1 -0
- package/dist/tokenization/token.js +484 -0
- package/dist/tokenization/token.js.map +1 -0
- package/dist/tokenization/token_mapper.js +55 -0
- package/dist/tokenization/token_mapper.js.map +1 -0
- package/dist/tokenization/token_type.js +19 -0
- package/dist/tokenization/token_type.js.map +1 -0
- package/dist/tokenization/tokenizer.js +220 -0
- package/dist/tokenization/tokenizer.js.map +1 -0
- package/dist/tokenization/trie.js +111 -0
- package/dist/tokenization/trie.js.map +1 -0
- package/dist/utils/object_utils.js +19 -0
- package/dist/utils/object_utils.js.map +1 -0
- package/dist/utils/string_utils.js +110 -0
- package/dist/utils/string_utils.js.map +1 -0
- package/docs/flowquery.min.js +1 -1
- package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
- package/package.json +22 -4
- package/src/compute/runner.ts +45 -0
- package/src/index.browser.ts +118 -0
- package/src/index.node.ts +141 -0
- package/src/parsing/functions/async_function.ts +95 -0
- package/src/parsing/functions/function_factory.ts +230 -1
- package/src/parsing/functions/function_metadata.ts +238 -0
- package/src/parsing/functions/functions.ts +43 -0
- package/src/parsing/operations/load.ts +51 -2
- package/src/parsing/parser.ts +45 -4
- package/tests/parsing/function_plugins.test.ts +369 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import FunctionFactory from "../../src/parsing/functions/function_factory";
|
|
2
|
+
import Function from "../../src/parsing/functions/function";
|
|
3
|
+
import { FunctionMetadata } from "../../src/parsing/functions/function_metadata";
|
|
4
|
+
import Runner from "../../src/compute/runner";
|
|
5
|
+
|
|
6
|
+
// Custom test function
|
|
7
|
+
class UpperCase extends Function {
|
|
8
|
+
constructor() {
|
|
9
|
+
super("uppercase");
|
|
10
|
+
this._expectedParameterCount = 1;
|
|
11
|
+
}
|
|
12
|
+
public value(): any {
|
|
13
|
+
return String(this.getChildren()[0].value()).toUpperCase();
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe("FunctionFactory Plugin Registration", () => {
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
// Clean up registered functions after each test
|
|
20
|
+
FunctionFactory.unregister("uppercase");
|
|
21
|
+
FunctionFactory.unregister("testfunc");
|
|
22
|
+
FunctionFactory.unregisterAsyncProvider("testprovider");
|
|
23
|
+
FunctionFactory.unregisterAsyncProvider("asyncdata");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("register()", () => {
|
|
27
|
+
it("should register a simple function factory", () => {
|
|
28
|
+
FunctionFactory.register("uppercase", () => new UpperCase());
|
|
29
|
+
const func = FunctionFactory.create("uppercase");
|
|
30
|
+
expect(func).toBeInstanceOf(UpperCase);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should register a function with metadata", () => {
|
|
34
|
+
FunctionFactory.register("uppercase", {
|
|
35
|
+
factory: () => new UpperCase(),
|
|
36
|
+
metadata: {
|
|
37
|
+
name: "uppercase",
|
|
38
|
+
description: "Converts a string to uppercase",
|
|
39
|
+
category: "string",
|
|
40
|
+
parameters: [
|
|
41
|
+
{ name: "text", description: "String to convert", type: "string" }
|
|
42
|
+
],
|
|
43
|
+
output: { description: "Uppercase string", type: "string" },
|
|
44
|
+
examples: ["WITH 'hello' AS s RETURN uppercase(s)"]
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const func = FunctionFactory.create("uppercase");
|
|
49
|
+
expect(func).toBeInstanceOf(UpperCase);
|
|
50
|
+
|
|
51
|
+
const metadata = FunctionFactory.getMetadata("uppercase");
|
|
52
|
+
expect(metadata).toBeDefined();
|
|
53
|
+
expect(metadata?.description).toBe("Converts a string to uppercase");
|
|
54
|
+
expect(metadata?.category).toBe("string");
|
|
55
|
+
expect(metadata?.parameters).toHaveLength(1);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("should be case-insensitive", () => {
|
|
59
|
+
FunctionFactory.register("UpperCase", () => new UpperCase());
|
|
60
|
+
const func = FunctionFactory.create("UPPERCASE");
|
|
61
|
+
expect(func).toBeInstanceOf(UpperCase);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("unregister()", () => {
|
|
66
|
+
it("should unregister a function and its metadata", () => {
|
|
67
|
+
FunctionFactory.register("testfunc", {
|
|
68
|
+
factory: () => new Function("testfunc"),
|
|
69
|
+
metadata: {
|
|
70
|
+
name: "testfunc",
|
|
71
|
+
description: "Test function",
|
|
72
|
+
parameters: [],
|
|
73
|
+
output: { description: "Test output", type: "string" }
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
expect(FunctionFactory.getMetadata("testfunc")).toBeDefined();
|
|
78
|
+
|
|
79
|
+
FunctionFactory.unregister("testfunc");
|
|
80
|
+
|
|
81
|
+
expect(FunctionFactory.getMetadata("testfunc")).toBeUndefined();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("registerAsyncProvider()", () => {
|
|
86
|
+
it("should register a simple async provider", () => {
|
|
87
|
+
const provider = async function* () {
|
|
88
|
+
yield { id: 1 };
|
|
89
|
+
yield { id: 2 };
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
FunctionFactory.registerAsyncProvider("testprovider", provider);
|
|
93
|
+
|
|
94
|
+
expect(FunctionFactory.isAsyncProvider("testprovider")).toBe(true);
|
|
95
|
+
expect(FunctionFactory.getAsyncProvider("testprovider")).toBe(provider);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("should register an async provider with metadata", () => {
|
|
99
|
+
FunctionFactory.registerAsyncProvider("asyncdata", {
|
|
100
|
+
provider: async function* (url: string) {
|
|
101
|
+
yield { data: url };
|
|
102
|
+
},
|
|
103
|
+
metadata: {
|
|
104
|
+
name: "asyncdata",
|
|
105
|
+
description: "Fetches data from a URL",
|
|
106
|
+
category: "data",
|
|
107
|
+
parameters: [
|
|
108
|
+
{ name: "url", description: "The URL to fetch", type: "string" }
|
|
109
|
+
],
|
|
110
|
+
output: {
|
|
111
|
+
description: "Data object",
|
|
112
|
+
type: "object",
|
|
113
|
+
properties: {
|
|
114
|
+
data: { description: "The fetched data", type: "string" }
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
examples: ["LOAD JSON FROM asyncdata('http://example.com') AS item"]
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
expect(FunctionFactory.isAsyncProvider("asyncdata")).toBe(true);
|
|
122
|
+
|
|
123
|
+
const metadata = FunctionFactory.getMetadata("asyncdata");
|
|
124
|
+
expect(metadata).toBeDefined();
|
|
125
|
+
expect(metadata?.description).toBe("Fetches data from a URL");
|
|
126
|
+
expect(metadata?.isAsyncProvider).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe("unregisterAsyncProvider()", () => {
|
|
131
|
+
it("should unregister an async provider and its metadata", () => {
|
|
132
|
+
FunctionFactory.registerAsyncProvider("testprovider", {
|
|
133
|
+
provider: async function* () { yield 1; },
|
|
134
|
+
metadata: {
|
|
135
|
+
name: "testprovider",
|
|
136
|
+
description: "Test provider",
|
|
137
|
+
parameters: [],
|
|
138
|
+
output: { description: "Number", type: "number" }
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
expect(FunctionFactory.isAsyncProvider("testprovider")).toBe(true);
|
|
143
|
+
expect(FunctionFactory.getMetadata("testprovider")).toBeDefined();
|
|
144
|
+
|
|
145
|
+
FunctionFactory.unregisterAsyncProvider("testprovider");
|
|
146
|
+
|
|
147
|
+
expect(FunctionFactory.isAsyncProvider("testprovider")).toBe(false);
|
|
148
|
+
expect(FunctionFactory.getMetadata("testprovider")).toBeUndefined();
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe("FunctionFactory Metadata Queries", () => {
|
|
154
|
+
beforeAll(() => {
|
|
155
|
+
// Register test functions for metadata queries
|
|
156
|
+
FunctionFactory.register("customfunc", {
|
|
157
|
+
factory: () => new Function("customfunc"),
|
|
158
|
+
metadata: {
|
|
159
|
+
name: "customfunc",
|
|
160
|
+
description: "A custom test function",
|
|
161
|
+
category: "test",
|
|
162
|
+
parameters: [{ name: "arg", description: "An argument", type: "string" }],
|
|
163
|
+
output: { description: "Result", type: "string" }
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
FunctionFactory.registerAsyncProvider("customprovider", {
|
|
168
|
+
provider: async function* () { yield 1; },
|
|
169
|
+
metadata: {
|
|
170
|
+
name: "customprovider",
|
|
171
|
+
description: "A custom async provider",
|
|
172
|
+
category: "test",
|
|
173
|
+
parameters: [],
|
|
174
|
+
output: { description: "Number", type: "number" }
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
afterAll(() => {
|
|
180
|
+
FunctionFactory.unregister("customfunc");
|
|
181
|
+
FunctionFactory.unregisterAsyncProvider("customprovider");
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe("getMetadata()", () => {
|
|
185
|
+
it("should return metadata for built-in functions", () => {
|
|
186
|
+
const metadata = FunctionFactory.getMetadata("sum");
|
|
187
|
+
expect(metadata).toBeDefined();
|
|
188
|
+
expect(metadata?.name).toBe("sum");
|
|
189
|
+
expect(metadata?.category).toBe("aggregation");
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("should return metadata for plugin functions", () => {
|
|
193
|
+
const metadata = FunctionFactory.getMetadata("customfunc");
|
|
194
|
+
expect(metadata).toBeDefined();
|
|
195
|
+
expect(metadata?.description).toBe("A custom test function");
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("should return undefined for unknown functions", () => {
|
|
199
|
+
const metadata = FunctionFactory.getMetadata("nonexistent");
|
|
200
|
+
expect(metadata).toBeUndefined();
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("should be case-insensitive", () => {
|
|
204
|
+
const metadata = FunctionFactory.getMetadata("SUM");
|
|
205
|
+
expect(metadata).toBeDefined();
|
|
206
|
+
expect(metadata?.name).toBe("sum");
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe("listFunctions()", () => {
|
|
211
|
+
it("should list all functions including built-ins", () => {
|
|
212
|
+
const functions = FunctionFactory.listFunctions();
|
|
213
|
+
expect(functions.length).toBeGreaterThan(10);
|
|
214
|
+
expect(functions.some(f => f.name === "sum")).toBe(true);
|
|
215
|
+
expect(functions.some(f => f.name === "customfunc")).toBe(true);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("should filter by category", () => {
|
|
219
|
+
const aggregationFuncs = FunctionFactory.listFunctions({ category: "aggregation" });
|
|
220
|
+
expect(aggregationFuncs.every(f => f.category === "aggregation")).toBe(true);
|
|
221
|
+
expect(aggregationFuncs.some(f => f.name === "sum")).toBe(true);
|
|
222
|
+
expect(aggregationFuncs.some(f => f.name === "avg")).toBe(true);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("should filter async providers only", () => {
|
|
226
|
+
const asyncFuncs = FunctionFactory.listFunctions({ asyncOnly: true });
|
|
227
|
+
expect(asyncFuncs.every(f => f.isAsyncProvider === true)).toBe(true);
|
|
228
|
+
expect(asyncFuncs.some(f => f.name === "customprovider")).toBe(true);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("should filter sync functions only", () => {
|
|
232
|
+
const syncFuncs = FunctionFactory.listFunctions({ syncOnly: true });
|
|
233
|
+
expect(syncFuncs.every(f => !f.isAsyncProvider)).toBe(true);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("should exclude built-ins when specified", () => {
|
|
237
|
+
const pluginsOnly = FunctionFactory.listFunctions({ includeBuiltins: false });
|
|
238
|
+
expect(pluginsOnly.some(f => f.name === "sum")).toBe(false);
|
|
239
|
+
expect(pluginsOnly.some(f => f.name === "customfunc")).toBe(true);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
describe("listFunctionNames()", () => {
|
|
244
|
+
it("should return all function names", () => {
|
|
245
|
+
const names = FunctionFactory.listFunctionNames();
|
|
246
|
+
expect(names).toContain("sum");
|
|
247
|
+
expect(names).toContain("avg");
|
|
248
|
+
expect(names).toContain("customfunc");
|
|
249
|
+
expect(names).toContain("customprovider");
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("should not have duplicates", () => {
|
|
253
|
+
const names = FunctionFactory.listFunctionNames();
|
|
254
|
+
const uniqueNames = [...new Set(names)];
|
|
255
|
+
expect(names.length).toBe(uniqueNames.length);
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
describe("toJSON()", () => {
|
|
260
|
+
it("should return functions and categories", () => {
|
|
261
|
+
const result = FunctionFactory.toJSON();
|
|
262
|
+
expect(result.functions).toBeDefined();
|
|
263
|
+
expect(Array.isArray(result.functions)).toBe(true);
|
|
264
|
+
expect(result.categories).toBeDefined();
|
|
265
|
+
expect(Array.isArray(result.categories)).toBe(true);
|
|
266
|
+
expect(result.categories).toContain("aggregation");
|
|
267
|
+
expect(result.categories).toContain("string");
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
describe("functions() built-in function", () => {
|
|
273
|
+
it("should return all functions when called without arguments", async () => {
|
|
274
|
+
const runner = new Runner("WITH functions() AS funcs RETURN size(funcs)");
|
|
275
|
+
await runner.run();
|
|
276
|
+
expect(runner.results[0].expr0).toBeGreaterThan(10);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it("should return function metadata with expected properties", async () => {
|
|
280
|
+
const runner = new Runner("WITH functions() AS funcs UNWIND funcs AS f WITH f WHERE f.name = 'sum' RETURN f.name, f.description, f.category");
|
|
281
|
+
await runner.run();
|
|
282
|
+
expect(runner.results).toHaveLength(1);
|
|
283
|
+
expect(runner.results[0].expr0).toBe("sum");
|
|
284
|
+
expect(runner.results[0].expr1).toBe("Calculates the sum of numeric values across grouped rows");
|
|
285
|
+
expect(runner.results[0].expr2).toBe("aggregation");
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("should filter by category when argument provided", async () => {
|
|
289
|
+
const runner = new Runner("WITH functions('aggregation') AS funcs UNWIND funcs AS f RETURN f.name");
|
|
290
|
+
await runner.run();
|
|
291
|
+
const names = runner.results.map((r: any) => r.expr0);
|
|
292
|
+
expect(names).toContain("sum");
|
|
293
|
+
expect(names).toContain("avg");
|
|
294
|
+
expect(names).toContain("collect");
|
|
295
|
+
expect(names).not.toContain("split"); // string category
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it("should include the functions() function itself", async () => {
|
|
299
|
+
const runner = new Runner("WITH functions('introspection') AS funcs UNWIND funcs AS f RETURN f.name");
|
|
300
|
+
await runner.run();
|
|
301
|
+
expect(runner.results.some((r: any) => r.expr0 === "functions")).toBe(true);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("should include parameter information", async () => {
|
|
305
|
+
const runner = new Runner("WITH functions() AS funcs UNWIND funcs AS f WITH f WHERE f.name = 'split' RETURN f.parameters");
|
|
306
|
+
await runner.run();
|
|
307
|
+
const params = runner.results[0].expr0;
|
|
308
|
+
expect(Array.isArray(params)).toBe(true);
|
|
309
|
+
expect(params.length).toBe(2);
|
|
310
|
+
expect(params[0].name).toBe("text");
|
|
311
|
+
expect(params[1].name).toBe("delimiter");
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it("should include output schema", async () => {
|
|
315
|
+
const runner = new Runner("WITH functions() AS funcs UNWIND funcs AS f WITH f WHERE f.name = 'sum' RETURN f.output");
|
|
316
|
+
await runner.run();
|
|
317
|
+
const output = runner.results[0].expr0;
|
|
318
|
+
expect(output.type).toBe("number");
|
|
319
|
+
expect(output.description).toBeDefined();
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it("should include examples", async () => {
|
|
323
|
+
const runner = new Runner("WITH functions() AS funcs UNWIND funcs AS f WITH f WHERE f.name = 'range' RETURN f.examples");
|
|
324
|
+
await runner.run();
|
|
325
|
+
const examples = runner.results[0].expr0;
|
|
326
|
+
expect(Array.isArray(examples)).toBe(true);
|
|
327
|
+
expect(examples.length).toBeGreaterThan(0);
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
describe("Plugin function execution", () => {
|
|
332
|
+
beforeAll(() => {
|
|
333
|
+
FunctionFactory.register("double", {
|
|
334
|
+
factory: () => {
|
|
335
|
+
const func = new Function("double");
|
|
336
|
+
(func as any)._expectedParameterCount = 1;
|
|
337
|
+
(func as any).value = function() {
|
|
338
|
+
return this.getChildren()[0].value() * 2;
|
|
339
|
+
};
|
|
340
|
+
return func;
|
|
341
|
+
},
|
|
342
|
+
metadata: {
|
|
343
|
+
name: "double",
|
|
344
|
+
description: "Doubles a number",
|
|
345
|
+
category: "math",
|
|
346
|
+
parameters: [{ name: "n", description: "Number to double", type: "number" }],
|
|
347
|
+
output: { description: "Doubled value", type: "number" }
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
afterAll(() => {
|
|
353
|
+
FunctionFactory.unregister("double");
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it("should execute a registered plugin function", async () => {
|
|
357
|
+
const runner = new Runner("WITH 5 AS n RETURN double(n)");
|
|
358
|
+
await runner.run();
|
|
359
|
+
expect(runner.results[0].expr0).toBe(10);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it("should show plugin function in functions() output", async () => {
|
|
363
|
+
const runner = new Runner("WITH functions('math') AS funcs UNWIND funcs AS f RETURN f.name");
|
|
364
|
+
await runner.run();
|
|
365
|
+
const names = runner.results.map((r: any) => r.expr0);
|
|
366
|
+
expect(names).toContain("double");
|
|
367
|
+
expect(names).toContain("round");
|
|
368
|
+
});
|
|
369
|
+
});
|