flowquery 1.0.28 → 1.0.29

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.
Files changed (31) hide show
  1. package/dist/flowquery.min.js +1 -1
  2. package/dist/parsing/expressions/operator.d.ts +2 -1
  3. package/dist/parsing/expressions/operator.d.ts.map +1 -1
  4. package/dist/parsing/expressions/operator.js +35 -2
  5. package/dist/parsing/expressions/operator.js.map +1 -1
  6. package/dist/parsing/functions/count.d.ts +21 -0
  7. package/dist/parsing/functions/count.d.ts.map +1 -0
  8. package/dist/parsing/functions/count.js +70 -0
  9. package/dist/parsing/functions/count.js.map +1 -0
  10. package/dist/parsing/functions/function_factory.d.ts +1 -0
  11. package/dist/parsing/functions/function_factory.d.ts.map +1 -1
  12. package/dist/parsing/functions/function_factory.js +1 -0
  13. package/dist/parsing/functions/function_factory.js.map +1 -1
  14. package/dist/parsing/parser.d.ts.map +1 -1
  15. package/dist/parsing/parser.js +14 -2
  16. package/dist/parsing/parser.js.map +1 -1
  17. package/docs/flowquery.min.js +1 -1
  18. package/flowquery-py/pyproject.toml +1 -1
  19. package/flowquery-py/src/parsing/functions/__init__.py +2 -0
  20. package/flowquery-py/src/parsing/functions/count.py +79 -0
  21. package/flowquery-py/src/parsing/parser.py +12 -2
  22. package/flowquery-py/tests/compute/test_runner.py +138 -0
  23. package/flowquery-py/tests/parsing/test_expression.py +79 -0
  24. package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
  25. package/package.json +1 -1
  26. package/src/parsing/expressions/operator.ts +26 -2
  27. package/src/parsing/functions/count.ts +54 -0
  28. package/src/parsing/functions/function_factory.ts +1 -0
  29. package/src/parsing/parser.ts +14 -2
  30. package/tests/compute/runner.test.ts +129 -0
  31. package/tests/parsing/expression.test.ts +129 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowquery",
3
- "version": "1.0.28",
3
+ "version": "1.0.29",
4
4
  "description": "A declarative query language for data processing pipelines.",
5
5
  "main": "dist/index.node.js",
6
6
  "types": "dist/index.node.d.ts",
@@ -1,5 +1,28 @@
1
1
  import ASTNode from "../ast_node";
2
2
 
3
+ function deepEquals(a: any, b: any): boolean {
4
+ if (a === b) return true;
5
+ if (a === null || b === null || a === undefined || b === undefined) return false;
6
+ if (typeof a !== typeof b) return false;
7
+ if (typeof a !== "object") return false;
8
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
9
+ if (Array.isArray(a)) {
10
+ if (a.length !== b.length) return false;
11
+ for (let i = 0; i < a.length; i++) {
12
+ if (!deepEquals(a[i], b[i])) return false;
13
+ }
14
+ return true;
15
+ }
16
+ const keysA = Object.keys(a);
17
+ const keysB = Object.keys(b);
18
+ if (keysA.length !== keysB.length) return false;
19
+ for (const key of keysA) {
20
+ if (!Object.prototype.hasOwnProperty.call(b, key)) return false;
21
+ if (!deepEquals(a[key], b[key])) return false;
22
+ }
23
+ return true;
24
+ }
25
+
3
26
  abstract class Operator extends ASTNode {
4
27
  private _precedence: number = 0;
5
28
  private _leftAssociative: boolean = true;
@@ -88,7 +111,7 @@ class Equals extends Operator {
88
111
  super(0, true);
89
112
  }
90
113
  public value(): number {
91
- return this.lhs.value() === this.rhs.value() ? 1 : 0;
114
+ return deepEquals(this.lhs.value(), this.rhs.value()) ? 1 : 0;
92
115
  }
93
116
  }
94
117
 
@@ -97,7 +120,7 @@ class NotEquals extends Operator {
97
120
  super(0, true);
98
121
  }
99
122
  public value(): number {
100
- return this.lhs.value() !== this.rhs.value() ? 1 : 0;
123
+ return !deepEquals(this.lhs.value(), this.rhs.value()) ? 1 : 0;
101
124
  }
102
125
  }
103
126
 
@@ -296,6 +319,7 @@ class NotEndsWith extends Operator {
296
319
  }
297
320
 
298
321
  export {
322
+ deepEquals,
299
323
  Operator,
300
324
  Add,
301
325
  Subtract,
@@ -0,0 +1,54 @@
1
+ import AggregateFunction from "./aggregate_function";
2
+ import { FunctionDef } from "./function_metadata";
3
+ import ReducerElement from "./reducer_element";
4
+
5
+ class CountReducerElement extends ReducerElement {
6
+ private _value: number = 0;
7
+ public get value(): any {
8
+ return this._value;
9
+ }
10
+ public set value(value: any) {
11
+ this._value += 1;
12
+ }
13
+ }
14
+
15
+ class DistinctCountReducerElement extends ReducerElement {
16
+ private _seen: Set<string> = new Set();
17
+ public get value(): any {
18
+ return this._seen.size;
19
+ }
20
+ public set value(value: any) {
21
+ const key: string = JSON.stringify(value);
22
+ this._seen.add(key);
23
+ }
24
+ }
25
+
26
+ @FunctionDef({
27
+ description: "Counts the number of values across grouped rows",
28
+ category: "aggregate",
29
+ parameters: [{ name: "value", description: "Value to count", type: "any" }],
30
+ output: { description: "Number of values", type: "number", example: 3 },
31
+ examples: [
32
+ "WITH [1, 2, 3] AS nums UNWIND nums AS n RETURN count(n)",
33
+ "WITH [1, 2, 2, 3] AS nums UNWIND nums AS n RETURN count(distinct n)",
34
+ ],
35
+ })
36
+ class Count extends AggregateFunction {
37
+ private _distinct: boolean = false;
38
+ constructor() {
39
+ super("count");
40
+ this._expectedParameterCount = 1;
41
+ this._supports_distinct = true;
42
+ }
43
+ public reduce(element: CountReducerElement | DistinctCountReducerElement): void {
44
+ element.value = this.firstChild().value();
45
+ }
46
+ public element(): CountReducerElement | DistinctCountReducerElement {
47
+ return this._distinct ? new DistinctCountReducerElement() : new CountReducerElement();
48
+ }
49
+ public set distinct(distinct: boolean) {
50
+ this._distinct = distinct;
51
+ }
52
+ }
53
+
54
+ export default Count;
@@ -1,6 +1,7 @@
1
1
  import AsyncFunction from "./async_function";
2
2
  import "./avg";
3
3
  import "./collect";
4
+ import "./count";
4
5
  import Function from "./function";
5
6
  import {
6
7
  AsyncDataProvider,
@@ -171,11 +171,17 @@ class Parser extends BaseParser {
171
171
  }
172
172
  this.setNextToken();
173
173
  this.expectAndSkipWhitespaceAndComments();
174
+ let distinct = false;
175
+ if (this.token.isDistinct()) {
176
+ distinct = true;
177
+ this.setNextToken();
178
+ this.expectAndSkipWhitespaceAndComments();
179
+ }
174
180
  const expressions = Array.from(this.parseExpressions(AliasOption.REQUIRED));
175
181
  if (expressions.length === 0) {
176
182
  throw new Error("Expected expression");
177
183
  }
178
- if (expressions.some((expression: Expression) => expression.has_reducers())) {
184
+ if (distinct || expressions.some((expression: Expression) => expression.has_reducers())) {
179
185
  return new AggregatedWith(expressions);
180
186
  }
181
187
  return new With(expressions);
@@ -220,11 +226,17 @@ class Parser extends BaseParser {
220
226
  }
221
227
  this.setNextToken();
222
228
  this.expectAndSkipWhitespaceAndComments();
229
+ let distinct = false;
230
+ if (this.token.isDistinct()) {
231
+ distinct = true;
232
+ this.setNextToken();
233
+ this.expectAndSkipWhitespaceAndComments();
234
+ }
223
235
  const expressions = Array.from(this.parseExpressions(AliasOption.OPTIONAL));
224
236
  if (expressions.length === 0) {
225
237
  throw new Error("Expected expression");
226
238
  }
227
- if (expressions.some((expression: Expression) => expression.has_reducers())) {
239
+ if (distinct || expressions.some((expression: Expression) => expression.has_reducers())) {
228
240
  return new AggregatedReturn(expressions);
229
241
  }
230
242
  this._returns++;
@@ -140,6 +140,58 @@ test("Test aggregated return with multiple aggregates", async () => {
140
140
  expect(results[1]).toEqual({ i: 2, sum: 20, avg: 2.5 });
141
141
  });
142
142
 
143
+ test("Test count", async () => {
144
+ const runner = new Runner(
145
+ "unwind [1, 1, 2, 2] as i unwind [1, 2, 3, 4] as j return i, count(j) as cnt"
146
+ );
147
+ await runner.run();
148
+ const results = runner.results;
149
+ expect(results.length).toBe(2);
150
+ expect(results[0]).toEqual({ i: 1, cnt: 8 });
151
+ expect(results[1]).toEqual({ i: 2, cnt: 8 });
152
+ });
153
+
154
+ test("Test count distinct", async () => {
155
+ const runner = new Runner(
156
+ `
157
+ unwind [1, 1, 2, 2] as i
158
+ unwind [1, 2, 1, 2] as j
159
+ return i, count(distinct j) as cnt
160
+ `
161
+ );
162
+ await runner.run();
163
+ const results = runner.results;
164
+ expect(results.length).toBe(2);
165
+ expect(results[0]).toEqual({ i: 1, cnt: 2 });
166
+ expect(results[1]).toEqual({ i: 2, cnt: 2 });
167
+ });
168
+
169
+ test("Test count with strings", async () => {
170
+ const runner = new Runner(
171
+ `
172
+ unwind ["a", "b", "a", "c"] as s
173
+ return count(s) as cnt
174
+ `
175
+ );
176
+ await runner.run();
177
+ const results = runner.results;
178
+ expect(results.length).toBe(1);
179
+ expect(results[0]).toEqual({ cnt: 4 });
180
+ });
181
+
182
+ test("Test count distinct with strings", async () => {
183
+ const runner = new Runner(
184
+ `
185
+ unwind ["a", "b", "a", "c"] as s
186
+ return count(distinct s) as cnt
187
+ `
188
+ );
189
+ await runner.run();
190
+ const results = runner.results;
191
+ expect(results.length).toBe(1);
192
+ expect(results[0]).toEqual({ cnt: 3 });
193
+ });
194
+
143
195
  test("Test avg with null", async () => {
144
196
  const runner = new Runner("return avg(null) as avg");
145
197
  await runner.run();
@@ -330,6 +382,83 @@ test("Test collect distinct with associative array", async () => {
330
382
  expect(results[1]).toEqual({ i: 2, collected: [{ j: 1 }, { j: 2 }, { j: 3 }] });
331
383
  });
332
384
 
385
+ test("Test return distinct", async () => {
386
+ const runner = new Runner(
387
+ `
388
+ unwind [1, 1, 2, 2, 3, 3] as i
389
+ return distinct i
390
+ `
391
+ );
392
+ await runner.run();
393
+ const results = runner.results;
394
+ expect(results.length).toBe(3);
395
+ expect(results[0]).toEqual({ i: 1 });
396
+ expect(results[1]).toEqual({ i: 2 });
397
+ expect(results[2]).toEqual({ i: 3 });
398
+ });
399
+
400
+ test("Test return distinct with multiple expressions", async () => {
401
+ const runner = new Runner(
402
+ `
403
+ unwind [1, 1, 2, 2] as i
404
+ unwind [10, 10, 20, 20] as j
405
+ return distinct i, j
406
+ `
407
+ );
408
+ await runner.run();
409
+ const results = runner.results;
410
+ expect(results.length).toBe(4);
411
+ expect(results[0]).toEqual({ i: 1, j: 10 });
412
+ expect(results[1]).toEqual({ i: 1, j: 20 });
413
+ expect(results[2]).toEqual({ i: 2, j: 10 });
414
+ expect(results[3]).toEqual({ i: 2, j: 20 });
415
+ });
416
+
417
+ test("Test with distinct", async () => {
418
+ const runner = new Runner(
419
+ `
420
+ unwind [1, 1, 2, 2, 3, 3] as i
421
+ with distinct i as i
422
+ return i
423
+ `
424
+ );
425
+ await runner.run();
426
+ const results = runner.results;
427
+ expect(results.length).toBe(3);
428
+ expect(results[0]).toEqual({ i: 1 });
429
+ expect(results[1]).toEqual({ i: 2 });
430
+ expect(results[2]).toEqual({ i: 3 });
431
+ });
432
+
433
+ test("Test with distinct and aggregation", async () => {
434
+ const runner = new Runner(
435
+ `
436
+ unwind [1, 1, 2, 2] as i
437
+ with distinct i as i
438
+ return sum(i) as total
439
+ `
440
+ );
441
+ await runner.run();
442
+ const results = runner.results;
443
+ expect(results.length).toBe(1);
444
+ expect(results[0]).toEqual({ total: 3 });
445
+ });
446
+
447
+ test("Test return distinct with strings", async () => {
448
+ const runner = new Runner(
449
+ `
450
+ unwind ["a", "b", "a", "c", "b"] as x
451
+ return distinct x
452
+ `
453
+ );
454
+ await runner.run();
455
+ const results = runner.results;
456
+ expect(results.length).toBe(3);
457
+ expect(results[0]).toEqual({ x: "a" });
458
+ expect(results[1]).toEqual({ x: "b" });
459
+ expect(results[2]).toEqual({ x: "c" });
460
+ });
461
+
333
462
  test("Test join function", async () => {
334
463
  const runner = new Runner('RETURN join(["a", "b", "c"], ",") as join');
335
464
  await runner.run();
@@ -1,3 +1,4 @@
1
+ import ASTNode from "../../src/parsing/ast_node";
1
2
  import Null from "../../src/parsing/components/null";
2
3
  import Expression from "../../src/parsing/expressions/expression";
3
4
  import Number from "../../src/parsing/expressions/number";
@@ -6,19 +7,33 @@ import {
6
7
  And,
7
8
  Contains,
8
9
  EndsWith,
10
+ Equals,
9
11
  GreaterThan,
10
12
  Is,
11
13
  IsNot,
12
14
  Multiply,
13
15
  NotContains,
14
16
  NotEndsWith,
17
+ NotEquals,
15
18
  NotStartsWith,
16
19
  Power,
17
20
  StartsWith,
18
21
  Subtract,
22
+ deepEquals,
19
23
  } from "../../src/parsing/expressions/operator";
20
24
  import String from "../../src/parsing/expressions/string";
21
25
 
26
+ class ObjectValue extends ASTNode {
27
+ private _value: any;
28
+ constructor(value: any) {
29
+ super();
30
+ this._value = value;
31
+ }
32
+ public value(): any {
33
+ return this._value;
34
+ }
35
+ }
36
+
22
37
  test("Test Expression Shunting Yard algorithm", () => {
23
38
  const expression = new Expression();
24
39
  expression.addNode(new Number("2"));
@@ -172,3 +187,117 @@ test("Test NOT ENDS WITH", () => {
172
187
  expression.finish();
173
188
  expect(expression.value()).toBe(1);
174
189
  });
190
+
191
+ // deepEquals unit tests
192
+
193
+ test("deepEquals with identical primitives", () => {
194
+ expect(deepEquals(1, 1)).toBe(true);
195
+ expect(deepEquals("abc", "abc")).toBe(true);
196
+ expect(deepEquals(null, null)).toBe(true);
197
+ });
198
+
199
+ test("deepEquals with different primitives", () => {
200
+ expect(deepEquals(1, 2)).toBe(false);
201
+ expect(deepEquals("abc", "def")).toBe(false);
202
+ expect(deepEquals(null, 1)).toBe(false);
203
+ expect(deepEquals(1, "1")).toBe(false);
204
+ });
205
+
206
+ test("deepEquals with identical objects", () => {
207
+ const obj = { id: "1", name: "Alice" };
208
+ expect(deepEquals(obj, obj)).toBe(true);
209
+ });
210
+
211
+ test("deepEquals with structurally equal objects", () => {
212
+ expect(deepEquals({ id: "1", name: "Alice" }, { id: "1", name: "Alice" })).toBe(true);
213
+ });
214
+
215
+ test("deepEquals with different objects", () => {
216
+ expect(deepEquals({ id: "1", name: "Alice" }, { id: "2", name: "Bob" })).toBe(false);
217
+ });
218
+
219
+ test("deepEquals with nested objects", () => {
220
+ expect(
221
+ deepEquals({ id: "1", data: { x: 1, y: [2, 3] } }, { id: "1", data: { x: 1, y: [2, 3] } })
222
+ ).toBe(true);
223
+ expect(
224
+ deepEquals({ id: "1", data: { x: 1, y: [2, 3] } }, { id: "1", data: { x: 1, y: [2, 4] } })
225
+ ).toBe(false);
226
+ });
227
+
228
+ test("deepEquals with arrays", () => {
229
+ expect(deepEquals([1, 2, 3], [1, 2, 3])).toBe(true);
230
+ expect(deepEquals([1, 2, 3], [1, 2])).toBe(false);
231
+ expect(deepEquals([1, 2], [1, 2, 3])).toBe(false);
232
+ });
233
+
234
+ test("deepEquals with objects having different keys", () => {
235
+ expect(deepEquals({ a: 1 }, { b: 1 })).toBe(false);
236
+ expect(deepEquals({ a: 1 }, { a: 1, b: 2 })).toBe(false);
237
+ });
238
+
239
+ // Equals operator with objects
240
+
241
+ test("Test Equals with equal numbers", () => {
242
+ const expression = new Expression();
243
+ expression.addNode(new Number("42"));
244
+ expression.addNode(new Equals());
245
+ expression.addNode(new Number("42"));
246
+ expression.finish();
247
+ expect(expression.value()).toBe(1);
248
+ });
249
+
250
+ test("Test Equals with different numbers", () => {
251
+ const expression = new Expression();
252
+ expression.addNode(new Number("42"));
253
+ expression.addNode(new Equals());
254
+ expression.addNode(new Number("99"));
255
+ expression.finish();
256
+ expect(expression.value()).toBe(0);
257
+ });
258
+
259
+ test("Test Equals with structurally equal objects", () => {
260
+ const expression = new Expression();
261
+ expression.addNode(new ObjectValue({ id: "1", name: "Alice" }));
262
+ expression.addNode(new Equals());
263
+ expression.addNode(new ObjectValue({ id: "1", name: "Alice" }));
264
+ expression.finish();
265
+ expect(expression.value()).toBe(1);
266
+ });
267
+
268
+ test("Test Equals with different objects", () => {
269
+ const expression = new Expression();
270
+ expression.addNode(new ObjectValue({ id: "1", name: "Alice" }));
271
+ expression.addNode(new Equals());
272
+ expression.addNode(new ObjectValue({ id: "2", name: "Bob" }));
273
+ expression.finish();
274
+ expect(expression.value()).toBe(0);
275
+ });
276
+
277
+ test("Test NotEquals with structurally equal objects", () => {
278
+ const expression = new Expression();
279
+ expression.addNode(new ObjectValue({ id: "1", name: "Alice" }));
280
+ expression.addNode(new NotEquals());
281
+ expression.addNode(new ObjectValue({ id: "1", name: "Alice" }));
282
+ expression.finish();
283
+ expect(expression.value()).toBe(0);
284
+ });
285
+
286
+ test("Test NotEquals with different objects", () => {
287
+ const expression = new Expression();
288
+ expression.addNode(new ObjectValue({ id: "1", name: "Alice" }));
289
+ expression.addNode(new NotEquals());
290
+ expression.addNode(new ObjectValue({ id: "2", name: "Bob" }));
291
+ expression.finish();
292
+ expect(expression.value()).toBe(1);
293
+ });
294
+
295
+ test("Test Equals with same reference object", () => {
296
+ const obj = { id: "1", name: "Alice" };
297
+ const expression = new Expression();
298
+ expression.addNode(new ObjectValue(obj));
299
+ expression.addNode(new Equals());
300
+ expression.addNode(new ObjectValue(obj));
301
+ expression.finish();
302
+ expect(expression.value()).toBe(1);
303
+ });