flowquery 1.0.33 → 1.0.35

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 (78) hide show
  1. package/dist/flowquery.min.js +1 -1
  2. package/dist/graph/database.d.ts +1 -0
  3. package/dist/graph/database.d.ts.map +1 -1
  4. package/dist/graph/database.js +43 -6
  5. package/dist/graph/database.js.map +1 -1
  6. package/dist/graph/relationship.d.ts +3 -1
  7. package/dist/graph/relationship.d.ts.map +1 -1
  8. package/dist/graph/relationship.js +12 -4
  9. package/dist/graph/relationship.js.map +1 -1
  10. package/dist/graph/relationship_data.js +1 -1
  11. package/dist/graph/relationship_data.js.map +1 -1
  12. package/dist/graph/relationship_match_collector.d.ts.map +1 -1
  13. package/dist/graph/relationship_match_collector.js +6 -3
  14. package/dist/graph/relationship_match_collector.js.map +1 -1
  15. package/dist/graph/relationship_reference.js +1 -1
  16. package/dist/graph/relationship_reference.js.map +1 -1
  17. package/dist/parsing/functions/function_factory.d.ts +3 -0
  18. package/dist/parsing/functions/function_factory.d.ts.map +1 -1
  19. package/dist/parsing/functions/function_factory.js +3 -0
  20. package/dist/parsing/functions/function_factory.js.map +1 -1
  21. package/dist/parsing/functions/predicate_sum.d.ts.map +1 -1
  22. package/dist/parsing/functions/predicate_sum.js +13 -10
  23. package/dist/parsing/functions/predicate_sum.js.map +1 -1
  24. package/dist/parsing/functions/schema.d.ts +5 -2
  25. package/dist/parsing/functions/schema.d.ts.map +1 -1
  26. package/dist/parsing/functions/schema.js +7 -4
  27. package/dist/parsing/functions/schema.js.map +1 -1
  28. package/dist/parsing/functions/to_lower.d.ts +7 -0
  29. package/dist/parsing/functions/to_lower.d.ts.map +1 -0
  30. package/dist/parsing/functions/to_lower.js +37 -0
  31. package/dist/parsing/functions/to_lower.js.map +1 -0
  32. package/dist/parsing/functions/to_string.d.ts +7 -0
  33. package/dist/parsing/functions/to_string.d.ts.map +1 -0
  34. package/dist/parsing/functions/to_string.js +44 -0
  35. package/dist/parsing/functions/to_string.js.map +1 -0
  36. package/dist/parsing/functions/trim.d.ts +7 -0
  37. package/dist/parsing/functions/trim.d.ts.map +1 -0
  38. package/dist/parsing/functions/trim.js +37 -0
  39. package/dist/parsing/functions/trim.js.map +1 -0
  40. package/dist/parsing/operations/group_by.d.ts.map +1 -1
  41. package/dist/parsing/operations/group_by.js +4 -2
  42. package/dist/parsing/operations/group_by.js.map +1 -1
  43. package/dist/parsing/parser.d.ts.map +1 -1
  44. package/dist/parsing/parser.js +15 -2
  45. package/dist/parsing/parser.js.map +1 -1
  46. package/docs/flowquery.min.js +1 -1
  47. package/flowquery-py/pyproject.toml +1 -1
  48. package/flowquery-py/src/graph/database.py +44 -11
  49. package/flowquery-py/src/graph/relationship.py +11 -3
  50. package/flowquery-py/src/graph/relationship_data.py +2 -1
  51. package/flowquery-py/src/graph/relationship_match_collector.py +7 -1
  52. package/flowquery-py/src/graph/relationship_reference.py +2 -2
  53. package/flowquery-py/src/parsing/functions/__init__.py +6 -0
  54. package/flowquery-py/src/parsing/functions/predicate_sum.py +3 -6
  55. package/flowquery-py/src/parsing/functions/schema.py +9 -5
  56. package/flowquery-py/src/parsing/functions/to_lower.py +35 -0
  57. package/flowquery-py/src/parsing/functions/to_string.py +41 -0
  58. package/flowquery-py/src/parsing/functions/trim.py +35 -0
  59. package/flowquery-py/src/parsing/operations/group_by.py +2 -0
  60. package/flowquery-py/src/parsing/parser.py +12 -2
  61. package/flowquery-py/tests/compute/test_runner.py +294 -4
  62. package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
  63. package/package.json +1 -1
  64. package/src/graph/database.ts +42 -4
  65. package/src/graph/relationship.ts +12 -4
  66. package/src/graph/relationship_data.ts +1 -1
  67. package/src/graph/relationship_match_collector.ts +6 -2
  68. package/src/graph/relationship_reference.ts +1 -1
  69. package/src/parsing/functions/function_factory.ts +3 -0
  70. package/src/parsing/functions/predicate_sum.ts +17 -12
  71. package/src/parsing/functions/schema.ts +7 -4
  72. package/src/parsing/functions/to_lower.ts +25 -0
  73. package/src/parsing/functions/to_string.ts +32 -0
  74. package/src/parsing/functions/trim.ts +25 -0
  75. package/src/parsing/operations/group_by.ts +4 -1
  76. package/src/parsing/parser.ts +15 -2
  77. package/tests/compute/runner.test.ts +319 -3
  78. package/tests/parsing/parser.test.ts +37 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowquery",
3
- "version": "1.0.33",
3
+ "version": "1.0.35",
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",
@@ -34,20 +34,34 @@ class Database {
34
34
  }
35
35
  const physical = new PhysicalRelationship(null, relationship.type);
36
36
  physical.statement = statement;
37
+ physical.source = relationship.source;
38
+ physical.target = relationship.target;
37
39
  Database.relationships.set(relationship.type, physical);
38
40
  }
39
41
  public getRelationship(relationship: Relationship): PhysicalRelationship | null {
40
42
  return Database.relationships.get(relationship.type!) || null;
41
43
  }
44
+ public getRelationships(relationship: Relationship): PhysicalRelationship[] {
45
+ const result: PhysicalRelationship[] = [];
46
+ for (const type of relationship.types) {
47
+ const physical = Database.relationships.get(type);
48
+ if (physical) {
49
+ result.push(physical);
50
+ }
51
+ }
52
+ return result;
53
+ }
42
54
  public async schema(): Promise<Record<string, any>[]> {
43
55
  const result: Record<string, any>[] = [];
44
56
 
45
57
  for (const [label, physical] of Database.nodes) {
46
58
  const records = await physical.data();
47
- const entry: Record<string, any> = { kind: "node", label };
59
+ const entry: Record<string, any> = { kind: "Node", label };
48
60
  if (records.length > 0) {
49
61
  const { id, ...sample } = records[0];
50
- if (Object.keys(sample).length > 0) {
62
+ const properties = Object.keys(sample);
63
+ if (properties.length > 0) {
64
+ entry.properties = properties;
51
65
  entry.sample = sample;
52
66
  }
53
67
  }
@@ -56,10 +70,17 @@ class Database {
56
70
 
57
71
  for (const [type, physical] of Database.relationships) {
58
72
  const records = await physical.data();
59
- const entry: Record<string, any> = { kind: "relationship", type };
73
+ const entry: Record<string, any> = {
74
+ kind: "Relationship",
75
+ type,
76
+ from_label: physical.source?.label || null,
77
+ to_label: physical.target?.label || null,
78
+ };
60
79
  if (records.length > 0) {
61
80
  const { left_id, right_id, ...sample } = records[0];
62
- if (Object.keys(sample).length > 0) {
81
+ const properties = Object.keys(sample);
82
+ if (properties.length > 0) {
83
+ entry.properties = properties;
63
84
  entry.sample = sample;
64
85
  }
65
86
  }
@@ -78,6 +99,23 @@ class Database {
78
99
  const data = await node.data();
79
100
  return new NodeData(data as NodeRecord[]);
80
101
  } else if (element instanceof Relationship) {
102
+ if (element.types.length > 1) {
103
+ const physicals = this.getRelationships(element);
104
+ if (physicals.length === 0) {
105
+ throw new Error(
106
+ `No physical relationships found for types ${element.types.join(", ")}`
107
+ );
108
+ }
109
+ const allRecords: RelationshipRecord[] = [];
110
+ for (let i = 0; i < physicals.length; i++) {
111
+ const records = (await physicals[i].data()) as RelationshipRecord[];
112
+ const typeName = element.types[i];
113
+ for (const record of records) {
114
+ allRecords.push({ ...record, _type: typeName });
115
+ }
116
+ }
117
+ return new RelationshipData(allRecords);
118
+ }
81
119
  const relationship = this.getRelationship(element);
82
120
  if (relationship === null) {
83
121
  throw new Error(`Physical relationship not found for type ${element.type}`);
@@ -9,7 +9,7 @@ import RelationshipMatchCollector, {
9
9
 
10
10
  class Relationship extends ASTNode {
11
11
  protected _identifier: string | null = null;
12
- protected _type: string | null = null;
12
+ protected _types: string[] = [];
13
13
  protected _properties: Map<string, Expression> = new Map();
14
14
  protected _hops: Hops = new Hops();
15
15
 
@@ -25,7 +25,9 @@ class Relationship extends ASTNode {
25
25
  constructor(identifier: string | null = null, type: string | null = null) {
26
26
  super();
27
27
  this._identifier = identifier;
28
- this._type = type;
28
+ if (type !== null) {
29
+ this._types = [type];
30
+ }
29
31
  }
30
32
  public set identifier(identifier: string) {
31
33
  this._identifier = identifier;
@@ -34,10 +36,16 @@ class Relationship extends ASTNode {
34
36
  return this._identifier;
35
37
  }
36
38
  public set type(type: string) {
37
- this._type = type;
39
+ this._types = [type];
38
40
  }
39
41
  public get type(): string | null {
40
- return this._type;
42
+ return this._types.length > 0 ? this._types[0] : null;
43
+ }
44
+ public set types(types: string[]) {
45
+ this._types = types;
46
+ }
47
+ public get types(): string[] {
48
+ return this._types;
41
49
  }
42
50
  public get properties(): Map<string, Expression> {
43
51
  return this._properties;
@@ -18,7 +18,7 @@ class RelationshipData extends Data {
18
18
  public properties(): Record<string, any> | null {
19
19
  const current = this.current();
20
20
  if (current) {
21
- const { left_id, right_id, ...props } = current;
21
+ const { left_id, right_id, _type, ...props } = current;
22
22
  return props;
23
23
  }
24
24
  return null;
@@ -12,11 +12,15 @@ class RelationshipMatchCollector {
12
12
  private _nodeIds: Array<string> = [];
13
13
 
14
14
  public push(relationship: Relationship, traversalId: string): RelationshipMatchRecord {
15
+ const data = relationship.getData();
16
+ const currentRecord = data?.current();
17
+ const actualType =
18
+ currentRecord && "_type" in currentRecord ? currentRecord["_type"] : relationship.type!;
15
19
  const match: RelationshipMatchRecord = {
16
- type: relationship.type!,
20
+ type: actualType,
17
21
  startNode: relationship.source?.value() || {},
18
22
  endNode: null,
19
- properties: relationship.getData()?.properties() as Record<string, any>,
23
+ properties: data?.properties() as Record<string, any>,
20
24
  };
21
25
  this._matches.push(match);
22
26
  this._nodeIds.push(traversalId);
@@ -6,7 +6,7 @@ class RelationshipReference extends Relationship {
6
6
  constructor(base: Relationship, reference: Relationship) {
7
7
  super();
8
8
  this._identifier = base.identifier;
9
- this._type = base.type;
9
+ this._types = base.types;
10
10
  this._hops = base.hops!;
11
11
  this._source = base.source;
12
12
  this._target = base.target;
@@ -27,6 +27,9 @@ 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";
32
+ import "./trim";
30
33
  import "./type";
31
34
 
32
35
  // Re-export AsyncDataProvider for backwards compatibility
@@ -1,17 +1,26 @@
1
- import PredicateFunction from "./predicate_function";
2
1
  import { FunctionDef } from "./function_metadata";
2
+ import PredicateFunction from "./predicate_function";
3
3
 
4
4
  @FunctionDef({
5
- description: "Calculates the sum of values in an array with optional filtering. Uses list comprehension syntax: sum(variable IN array [WHERE condition] | expression)",
5
+ description:
6
+ "Calculates the sum of values in an array with optional filtering. Uses list comprehension syntax: sum(variable IN array [WHERE condition] | expression)",
6
7
  category: "predicate",
7
8
  parameters: [
8
9
  { name: "variable", description: "Variable name to bind each element", type: "string" },
9
10
  { name: "array", description: "Array to iterate over", type: "array" },
10
11
  { name: "expression", description: "Expression to sum for each element", type: "any" },
11
- { name: "where", description: "Optional filter condition", type: "boolean", required: false }
12
+ {
13
+ name: "where",
14
+ description: "Optional filter condition",
15
+ type: "boolean",
16
+ required: false,
17
+ },
12
18
  ],
13
19
  output: { description: "Sum of the evaluated expressions", type: "number", example: 6 },
14
- examples: ["WITH [1, 2, 3] AS nums RETURN sum(n IN nums | n)", "WITH [1, 2, 3, 4] AS nums RETURN sum(n IN nums WHERE n > 1 | n * 2)"]
20
+ examples: [
21
+ "WITH [1, 2, 3] AS nums RETURN sum(n IN nums | n)",
22
+ "WITH [1, 2, 3, 4] AS nums RETURN sum(n IN nums WHERE n > 1 | n * 2)",
23
+ ],
15
24
  })
16
25
  class PredicateSum extends PredicateFunction {
17
26
  constructor() {
@@ -24,19 +33,15 @@ class PredicateSum extends PredicateFunction {
24
33
  if (array === null || !Array.isArray(array)) {
25
34
  throw new Error("Invalid array for sum function");
26
35
  }
27
- let _sum: any | null = null;
28
- for(let i = 0; i < array.length; i++) {
36
+ let _sum: number = 0;
37
+ for (let i = 0; i < array.length; i++) {
29
38
  this._valueHolder.holder = array[i];
30
39
  if (this.where === null || this.where.value()) {
31
- if (_sum === null) {
32
- _sum = this._return.value();
33
- } else {
34
- _sum += this._return.value();
35
- }
40
+ _sum += this._return.value();
36
41
  }
37
42
  }
38
43
  return _sum;
39
44
  }
40
45
  }
41
46
 
42
- export default PredicateSum;
47
+ export default PredicateSum;
@@ -5,8 +5,11 @@ import { FunctionDef } from "./function_metadata";
5
5
  /**
6
6
  * Built-in function that returns the graph schema of the database.
7
7
  *
8
- * Lists all nodes and relationships with their labels/types and a sample
9
- * of their data (excluding id from nodes, left_id and right_id from relationships).
8
+ * Lists all nodes and relationships with their labels/types, properties,
9
+ * and a sample of their data (excluding id from nodes, left_id and right_id from relationships).
10
+ *
11
+ * Nodes: {label, properties, sample}
12
+ * Relationships: {type, from_label, to_label, properties, sample}
10
13
  *
11
14
  * @example
12
15
  * ```
@@ -15,11 +18,11 @@ import { FunctionDef } from "./function_metadata";
15
18
  */
16
19
  @FunctionDef({
17
20
  description:
18
- "Returns the graph schema listing all nodes and relationships with a sample of their data.",
21
+ "Returns the graph schema listing all nodes and relationships with their properties and a sample of their data.",
19
22
  category: "async",
20
23
  parameters: [],
21
24
  output: {
22
- description: "Schema entry with kind, label/type, and optional sample data",
25
+ description: "Schema entry with label/type, properties, and optional sample data",
23
26
  type: "object",
24
27
  },
25
28
  examples: ["LOAD FROM schema() AS s RETURN s"],
@@ -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;
@@ -0,0 +1,25 @@
1
+ import Function from "./function";
2
+ import { FunctionDef } from "./function_metadata";
3
+
4
+ @FunctionDef({
5
+ description: "Removes leading and trailing whitespace from a string",
6
+ category: "scalar",
7
+ parameters: [{ name: "text", description: "String to trim", type: "string" }],
8
+ output: { description: "Trimmed string", type: "string", example: "hello" },
9
+ examples: ["WITH ' hello ' AS s RETURN trim(s)", "WITH '\\tfoo\\n' AS s RETURN trim(s)"],
10
+ })
11
+ class Trim extends Function {
12
+ constructor() {
13
+ super("trim");
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 trim function: expected a string");
20
+ }
21
+ return val.trim();
22
+ }
23
+ }
24
+
25
+ export default Trim;
@@ -106,7 +106,10 @@ class GroupBy extends Projection {
106
106
  yield* this.generate_results(mapperIndex + 1, child);
107
107
  }
108
108
  } else {
109
- node.elements?.forEach((element, reducerIndex) => {
109
+ if (node.elements === null) {
110
+ node.elements = this.reducers.map((reducer) => reducer.element());
111
+ }
112
+ node.elements.forEach((element, reducerIndex) => {
110
113
  this.reducers[reducerIndex].overridden = element.value;
111
114
  });
112
115
  const record: Record<string, any> = {};
@@ -420,6 +420,8 @@ class Parser extends BaseParser {
420
420
  }
421
421
  relationship = new Relationship();
422
422
  relationship.type = type;
423
+ relationship.source = node;
424
+ relationship.target = target;
423
425
  }
424
426
  this.expectAndSkipWhitespaceAndComments();
425
427
  if (!this.token.isAs()) {
@@ -673,8 +675,19 @@ class Parser extends BaseParser {
673
675
  if (!this.token.isIdentifierOrKeyword()) {
674
676
  throw new Error("Expected relationship type identifier");
675
677
  }
676
- const type: string = this.token.value || "";
678
+ const types: string[] = [this.token.value || ""];
677
679
  this.setNextToken();
680
+ while (this.token.isPipe()) {
681
+ this.setNextToken();
682
+ if (this.token.isColon()) {
683
+ this.setNextToken();
684
+ }
685
+ if (!this.token.isIdentifierOrKeyword()) {
686
+ throw new Error("Expected relationship type identifier after '|'");
687
+ }
688
+ types.push(this.token.value || "");
689
+ this.setNextToken();
690
+ }
678
691
  const hops: Hops | null = this.parseRelationshipHops();
679
692
  const properties: Map<string, Expression> = new Map(this.parseProperties());
680
693
  if (!this.token.isClosingBracket()) {
@@ -711,7 +724,7 @@ class Parser extends BaseParser {
711
724
  if (hops !== null) {
712
725
  relationship.hops = hops;
713
726
  }
714
- relationship.type = type;
727
+ relationship.types = types;
715
728
  return relationship;
716
729
  }
717
730