flowquery 1.0.34 → 1.0.36
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 +1 -0
- package/dist/graph/database.d.ts.map +1 -1
- package/dist/graph/database.js +43 -6
- package/dist/graph/database.js.map +1 -1
- package/dist/graph/relationship.d.ts +3 -1
- package/dist/graph/relationship.d.ts.map +1 -1
- package/dist/graph/relationship.js +12 -4
- package/dist/graph/relationship.js.map +1 -1
- package/dist/graph/relationship_data.js +1 -1
- package/dist/graph/relationship_data.js.map +1 -1
- package/dist/graph/relationship_match_collector.d.ts.map +1 -1
- package/dist/graph/relationship_match_collector.js +6 -3
- package/dist/graph/relationship_match_collector.js.map +1 -1
- package/dist/graph/relationship_reference.js +1 -1
- package/dist/graph/relationship_reference.js.map +1 -1
- package/dist/parsing/data_structures/lookup.d.ts.map +1 -1
- package/dist/parsing/data_structures/lookup.js +5 -1
- package/dist/parsing/data_structures/lookup.js.map +1 -1
- package/dist/parsing/functions/coalesce.d.ts +17 -0
- package/dist/parsing/functions/coalesce.d.ts.map +1 -0
- package/dist/parsing/functions/coalesce.js +61 -0
- package/dist/parsing/functions/coalesce.js.map +1 -0
- package/dist/parsing/functions/date.d.ts +22 -0
- package/dist/parsing/functions/date.d.ts.map +1 -0
- package/dist/parsing/functions/date.js +71 -0
- package/dist/parsing/functions/date.js.map +1 -0
- package/dist/parsing/functions/datetime.d.ts +22 -0
- package/dist/parsing/functions/datetime.d.ts.map +1 -0
- package/dist/parsing/functions/datetime.js +71 -0
- package/dist/parsing/functions/datetime.js.map +1 -0
- package/dist/parsing/functions/duration.d.ts +7 -0
- package/dist/parsing/functions/duration.d.ts.map +1 -0
- package/dist/parsing/functions/duration.js +145 -0
- package/dist/parsing/functions/duration.js.map +1 -0
- package/dist/parsing/functions/element_id.d.ts +7 -0
- package/dist/parsing/functions/element_id.d.ts.map +1 -0
- package/dist/parsing/functions/element_id.js +58 -0
- package/dist/parsing/functions/element_id.js.map +1 -0
- package/dist/parsing/functions/function_factory.d.ts +21 -0
- package/dist/parsing/functions/function_factory.d.ts.map +1 -1
- package/dist/parsing/functions/function_factory.js +21 -0
- package/dist/parsing/functions/function_factory.js.map +1 -1
- package/dist/parsing/functions/head.d.ts +7 -0
- package/dist/parsing/functions/head.d.ts.map +1 -0
- package/dist/parsing/functions/head.js +53 -0
- package/dist/parsing/functions/head.js.map +1 -0
- package/dist/parsing/functions/id.d.ts +7 -0
- package/dist/parsing/functions/id.d.ts.map +1 -0
- package/dist/parsing/functions/id.js +58 -0
- package/dist/parsing/functions/id.js.map +1 -0
- package/dist/parsing/functions/last.d.ts +7 -0
- package/dist/parsing/functions/last.d.ts.map +1 -0
- package/dist/parsing/functions/last.js +53 -0
- package/dist/parsing/functions/last.js.map +1 -0
- package/dist/parsing/functions/localdatetime.d.ts +21 -0
- package/dist/parsing/functions/localdatetime.d.ts.map +1 -0
- package/dist/parsing/functions/localdatetime.js +71 -0
- package/dist/parsing/functions/localdatetime.js.map +1 -0
- package/dist/parsing/functions/localtime.d.ts +20 -0
- package/dist/parsing/functions/localtime.d.ts.map +1 -0
- package/dist/parsing/functions/localtime.js +67 -0
- package/dist/parsing/functions/localtime.js.map +1 -0
- package/dist/parsing/functions/max.d.ts +14 -0
- package/dist/parsing/functions/max.d.ts.map +1 -0
- package/dist/parsing/functions/max.js +51 -0
- package/dist/parsing/functions/max.js.map +1 -0
- package/dist/parsing/functions/min.d.ts +14 -0
- package/dist/parsing/functions/min.d.ts.map +1 -0
- package/dist/parsing/functions/min.js +51 -0
- package/dist/parsing/functions/min.js.map +1 -0
- package/dist/parsing/functions/nodes.d.ts +7 -0
- package/dist/parsing/functions/nodes.d.ts.map +1 -0
- package/dist/parsing/functions/nodes.js +63 -0
- package/dist/parsing/functions/nodes.js.map +1 -0
- package/dist/parsing/functions/predicate_sum.d.ts.map +1 -1
- package/dist/parsing/functions/predicate_sum.js +13 -10
- package/dist/parsing/functions/predicate_sum.js.map +1 -1
- package/dist/parsing/functions/properties.d.ts +7 -0
- package/dist/parsing/functions/properties.d.ts.map +1 -0
- package/dist/parsing/functions/properties.js +74 -0
- package/dist/parsing/functions/properties.js.map +1 -0
- package/dist/parsing/functions/relationships.d.ts +7 -0
- package/dist/parsing/functions/relationships.d.ts.map +1 -0
- package/dist/parsing/functions/relationships.js +61 -0
- package/dist/parsing/functions/relationships.js.map +1 -0
- package/dist/parsing/functions/schema.d.ts +5 -2
- package/dist/parsing/functions/schema.d.ts.map +1 -1
- package/dist/parsing/functions/schema.js +7 -4
- package/dist/parsing/functions/schema.js.map +1 -1
- package/dist/parsing/functions/tail.d.ts +7 -0
- package/dist/parsing/functions/tail.d.ts.map +1 -0
- package/dist/parsing/functions/tail.js +50 -0
- package/dist/parsing/functions/tail.js.map +1 -0
- package/dist/parsing/functions/temporal_utils.d.ts +39 -0
- package/dist/parsing/functions/temporal_utils.d.ts.map +1 -0
- package/dist/parsing/functions/temporal_utils.js +168 -0
- package/dist/parsing/functions/temporal_utils.js.map +1 -0
- package/dist/parsing/functions/time.d.ts +20 -0
- package/dist/parsing/functions/time.d.ts.map +1 -0
- package/dist/parsing/functions/time.js +67 -0
- package/dist/parsing/functions/time.js.map +1 -0
- package/dist/parsing/functions/timestamp.d.ts +17 -0
- package/dist/parsing/functions/timestamp.d.ts.map +1 -0
- package/dist/parsing/functions/timestamp.js +51 -0
- package/dist/parsing/functions/timestamp.js.map +1 -0
- package/dist/parsing/functions/to_float.d.ts +7 -0
- package/dist/parsing/functions/to_float.d.ts.map +1 -0
- package/dist/parsing/functions/to_float.js +61 -0
- package/dist/parsing/functions/to_float.js.map +1 -0
- package/dist/parsing/functions/to_integer.d.ts +7 -0
- package/dist/parsing/functions/to_integer.d.ts.map +1 -0
- package/dist/parsing/functions/to_integer.js +61 -0
- package/dist/parsing/functions/to_integer.js.map +1 -0
- package/dist/parsing/functions/trim.d.ts +7 -0
- package/dist/parsing/functions/trim.d.ts.map +1 -0
- package/dist/parsing/functions/trim.js +37 -0
- package/dist/parsing/functions/trim.js.map +1 -0
- package/dist/parsing/operations/group_by.d.ts.map +1 -1
- package/dist/parsing/operations/group_by.js +4 -2
- package/dist/parsing/operations/group_by.js.map +1 -1
- package/dist/parsing/parser.d.ts.map +1 -1
- package/dist/parsing/parser.js +15 -2
- package/dist/parsing/parser.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 +44 -11
- package/flowquery-py/src/graph/relationship.py +11 -3
- package/flowquery-py/src/graph/relationship_data.py +2 -1
- package/flowquery-py/src/graph/relationship_match_collector.py +7 -1
- package/flowquery-py/src/graph/relationship_reference.py +2 -2
- package/flowquery-py/src/parsing/data_structures/lookup.py +2 -0
- package/flowquery-py/src/parsing/functions/__init__.py +42 -2
- package/flowquery-py/src/parsing/functions/coalesce.py +44 -0
- package/flowquery-py/src/parsing/functions/date_.py +63 -0
- package/flowquery-py/src/parsing/functions/datetime_.py +64 -0
- package/flowquery-py/src/parsing/functions/duration.py +159 -0
- package/flowquery-py/src/parsing/functions/element_id.py +50 -0
- package/flowquery-py/src/parsing/functions/head.py +39 -0
- package/flowquery-py/src/parsing/functions/id_.py +49 -0
- package/flowquery-py/src/parsing/functions/last.py +39 -0
- package/flowquery-py/src/parsing/functions/localdatetime.py +62 -0
- package/flowquery-py/src/parsing/functions/localtime.py +59 -0
- package/flowquery-py/src/parsing/functions/max_.py +49 -0
- package/flowquery-py/src/parsing/functions/min_.py +49 -0
- package/flowquery-py/src/parsing/functions/nodes.py +48 -0
- package/flowquery-py/src/parsing/functions/predicate_sum.py +3 -6
- package/flowquery-py/src/parsing/functions/properties.py +50 -0
- package/flowquery-py/src/parsing/functions/relationships.py +46 -0
- package/flowquery-py/src/parsing/functions/schema.py +9 -5
- package/flowquery-py/src/parsing/functions/tail.py +37 -0
- package/flowquery-py/src/parsing/functions/temporal_utils.py +186 -0
- package/flowquery-py/src/parsing/functions/time_.py +59 -0
- package/flowquery-py/src/parsing/functions/timestamp.py +39 -0
- package/flowquery-py/src/parsing/functions/to_float.py +46 -0
- package/flowquery-py/src/parsing/functions/to_integer.py +46 -0
- package/flowquery-py/src/parsing/functions/trim.py +35 -0
- package/flowquery-py/src/parsing/operations/group_by.py +2 -0
- package/flowquery-py/src/parsing/parser.py +12 -2
- package/flowquery-py/tests/compute/test_runner.py +1082 -4
- package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
- package/package.json +1 -1
- package/src/graph/database.ts +42 -4
- package/src/graph/relationship.ts +12 -4
- package/src/graph/relationship_data.ts +1 -1
- package/src/graph/relationship_match_collector.ts +6 -2
- package/src/graph/relationship_reference.ts +1 -1
- package/src/parsing/data_structures/lookup.ts +8 -4
- package/src/parsing/functions/coalesce.ts +50 -0
- package/src/parsing/functions/date.ts +65 -0
- package/src/parsing/functions/datetime.ts +65 -0
- package/src/parsing/functions/duration.ts +143 -0
- package/src/parsing/functions/element_id.ts +51 -0
- package/src/parsing/functions/function_factory.ts +21 -0
- package/src/parsing/functions/head.ts +42 -0
- package/src/parsing/functions/id.ts +51 -0
- package/src/parsing/functions/last.ts +42 -0
- package/src/parsing/functions/localdatetime.ts +65 -0
- package/src/parsing/functions/localtime.ts +60 -0
- package/src/parsing/functions/max.ts +37 -0
- package/src/parsing/functions/min.ts +37 -0
- package/src/parsing/functions/nodes.ts +54 -0
- package/src/parsing/functions/predicate_sum.ts +17 -12
- package/src/parsing/functions/properties.ts +56 -0
- package/src/parsing/functions/relationships.ts +52 -0
- package/src/parsing/functions/schema.ts +7 -4
- package/src/parsing/functions/tail.ts +39 -0
- package/src/parsing/functions/temporal_utils.ts +180 -0
- package/src/parsing/functions/time.ts +60 -0
- package/src/parsing/functions/timestamp.ts +41 -0
- package/src/parsing/functions/to_float.ts +50 -0
- package/src/parsing/functions/to_integer.ts +50 -0
- package/src/parsing/functions/trim.ts +25 -0
- package/src/parsing/operations/group_by.ts +4 -1
- package/src/parsing/parser.ts +15 -2
- package/tests/compute/runner.test.ts +1005 -3
- package/tests/parsing/parser.test.ts +37 -0
package/package.json
CHANGED
package/src/graph/database.ts
CHANGED
|
@@ -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: "
|
|
59
|
+
const entry: Record<string, any> = { kind: "Node", label };
|
|
48
60
|
if (records.length > 0) {
|
|
49
61
|
const { id, ...sample } = records[0];
|
|
50
|
-
|
|
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> = {
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
39
|
+
this._types = [type];
|
|
38
40
|
}
|
|
39
41
|
public get type(): string | null {
|
|
40
|
-
return this.
|
|
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:
|
|
20
|
+
type: actualType,
|
|
17
21
|
startNode: relationship.source?.value() || {},
|
|
18
22
|
endNode: null,
|
|
19
|
-
properties:
|
|
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.
|
|
9
|
+
this._types = base.types;
|
|
10
10
|
this._hops = base.hops!;
|
|
11
11
|
this._source = base.source;
|
|
12
12
|
this._target = base.target;
|
|
@@ -2,9 +2,9 @@ import ASTNode from "../ast_node";
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Represents a lookup operation (array/object indexing) in the AST.
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
6
|
* Lookups access elements from arrays or properties from objects using an index or key.
|
|
7
|
-
*
|
|
7
|
+
*
|
|
8
8
|
* @example
|
|
9
9
|
* ```typescript
|
|
10
10
|
* // For array[0] or obj.property or obj["key"]
|
|
@@ -33,8 +33,12 @@ class Lookup extends ASTNode {
|
|
|
33
33
|
return true;
|
|
34
34
|
}
|
|
35
35
|
public value(): any {
|
|
36
|
-
|
|
36
|
+
const obj = this.variable.value();
|
|
37
|
+
if (obj === null || obj === undefined) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return obj[this.index.value()];
|
|
37
41
|
}
|
|
38
42
|
}
|
|
39
43
|
|
|
40
|
-
export default Lookup;
|
|
44
|
+
export default Lookup;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import Function from "./function";
|
|
2
|
+
import { FunctionDef } from "./function_metadata";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns the first non-null value from a list of expressions.
|
|
6
|
+
* Equivalent to Neo4j's coalesce() function.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```
|
|
10
|
+
* RETURN coalesce(null, null, 'hello', 'world') // returns 'hello'
|
|
11
|
+
* MATCH (n) RETURN coalesce(n.nickname, n.name) AS displayName
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
@FunctionDef({
|
|
15
|
+
description: "Returns the first non-null value from a list of expressions",
|
|
16
|
+
category: "scalar",
|
|
17
|
+
parameters: [
|
|
18
|
+
{ name: "expressions", description: "Two or more expressions to evaluate", type: "any" },
|
|
19
|
+
],
|
|
20
|
+
output: {
|
|
21
|
+
description: "The first non-null value, or null if all values are null",
|
|
22
|
+
type: "any",
|
|
23
|
+
},
|
|
24
|
+
examples: [
|
|
25
|
+
"RETURN coalesce(null, 'hello', 'world')",
|
|
26
|
+
"MATCH (n) RETURN coalesce(n.nickname, n.name) AS displayName",
|
|
27
|
+
],
|
|
28
|
+
})
|
|
29
|
+
class Coalesce extends Function {
|
|
30
|
+
constructor() {
|
|
31
|
+
super("coalesce");
|
|
32
|
+
this._expectedParameterCount = null; // variable number of parameters
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public value(): any {
|
|
36
|
+
const children = this.getChildren();
|
|
37
|
+
if (children.length === 0) {
|
|
38
|
+
throw new Error("coalesce() requires at least one argument");
|
|
39
|
+
}
|
|
40
|
+
for (const child of children) {
|
|
41
|
+
const val = child.value();
|
|
42
|
+
if (val !== null && val !== undefined) {
|
|
43
|
+
return val;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default Coalesce;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import Function from "./function";
|
|
2
|
+
import { FunctionDef } from "./function_metadata";
|
|
3
|
+
import { buildDateObject, parseTemporalArg } from "./temporal_utils";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Returns a date value (no time component).
|
|
7
|
+
* When called with no arguments, returns the current date.
|
|
8
|
+
* When called with a string argument, parses it as an ISO 8601 date.
|
|
9
|
+
* When called with a map argument, constructs a date from components.
|
|
10
|
+
*
|
|
11
|
+
* Equivalent to Neo4j's date() function.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```
|
|
15
|
+
* RETURN date() AS today
|
|
16
|
+
* RETURN date('2025-06-15') AS d
|
|
17
|
+
* RETURN date({year: 2025, month: 6, day: 15}) AS d
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
@FunctionDef({
|
|
21
|
+
description:
|
|
22
|
+
"Returns a date value. With no arguments returns the current date. " +
|
|
23
|
+
"Accepts an ISO 8601 date string or a map of components (year, month, day).",
|
|
24
|
+
category: "scalar",
|
|
25
|
+
parameters: [
|
|
26
|
+
{
|
|
27
|
+
name: "input",
|
|
28
|
+
description: "Optional. An ISO 8601 date string (YYYY-MM-DD) or a map of components.",
|
|
29
|
+
type: "string",
|
|
30
|
+
required: false,
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
output: {
|
|
34
|
+
description:
|
|
35
|
+
"A date object with properties: year, month, day, " +
|
|
36
|
+
"epochMillis, dayOfWeek, dayOfYear, quarter, formatted",
|
|
37
|
+
type: "object",
|
|
38
|
+
},
|
|
39
|
+
examples: [
|
|
40
|
+
"RETURN date() AS today",
|
|
41
|
+
"RETURN date('2025-06-15') AS d",
|
|
42
|
+
"RETURN date({year: 2025, month: 6, day: 15}) AS d",
|
|
43
|
+
"WITH date() AS d RETURN d.year, d.month, d.dayOfWeek",
|
|
44
|
+
],
|
|
45
|
+
})
|
|
46
|
+
class DateFunction extends Function {
|
|
47
|
+
constructor() {
|
|
48
|
+
super("date");
|
|
49
|
+
this._expectedParameterCount = null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public value(): any {
|
|
53
|
+
const children = this.getChildren();
|
|
54
|
+
if (children.length > 1) {
|
|
55
|
+
throw new Error("date() accepts at most one argument");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const d: Date =
|
|
59
|
+
children.length === 1 ? parseTemporalArg(children[0].value(), "date") : new Date();
|
|
60
|
+
|
|
61
|
+
return buildDateObject(d);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export default DateFunction;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import Function from "./function";
|
|
2
|
+
import { FunctionDef } from "./function_metadata";
|
|
3
|
+
import { buildDatetimeObject, parseTemporalArg } from "./temporal_utils";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Returns a datetime value (date + time + timezone offset).
|
|
7
|
+
* When called with no arguments, returns the current UTC datetime.
|
|
8
|
+
* When called with a string argument, parses it as an ISO 8601 datetime.
|
|
9
|
+
* When called with a map argument, constructs a datetime from components.
|
|
10
|
+
*
|
|
11
|
+
* Equivalent to Neo4j's datetime() function.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```
|
|
15
|
+
* RETURN datetime() AS now
|
|
16
|
+
* RETURN datetime('2025-06-15T12:30:00Z') AS dt
|
|
17
|
+
* RETURN datetime({year: 2025, month: 6, day: 15}) AS dt
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
@FunctionDef({
|
|
21
|
+
description:
|
|
22
|
+
"Returns a datetime value. With no arguments returns the current UTC datetime. " +
|
|
23
|
+
"Accepts an ISO 8601 string or a map of components (year, month, day, hour, minute, second, millisecond).",
|
|
24
|
+
category: "scalar",
|
|
25
|
+
parameters: [
|
|
26
|
+
{
|
|
27
|
+
name: "input",
|
|
28
|
+
description: "Optional. An ISO 8601 datetime string or a map of components.",
|
|
29
|
+
type: "string",
|
|
30
|
+
required: false,
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
output: {
|
|
34
|
+
description:
|
|
35
|
+
"A datetime object with properties: year, month, day, hour, minute, second, millisecond, " +
|
|
36
|
+
"epochMillis, epochSeconds, dayOfWeek, dayOfYear, quarter, formatted",
|
|
37
|
+
type: "object",
|
|
38
|
+
},
|
|
39
|
+
examples: [
|
|
40
|
+
"RETURN datetime() AS now",
|
|
41
|
+
"RETURN datetime('2025-06-15T12:30:00Z') AS dt",
|
|
42
|
+
"RETURN datetime({year: 2025, month: 6, day: 15, hour: 12}) AS dt",
|
|
43
|
+
"WITH datetime() AS dt RETURN dt.year, dt.month, dt.day",
|
|
44
|
+
],
|
|
45
|
+
})
|
|
46
|
+
class Datetime extends Function {
|
|
47
|
+
constructor() {
|
|
48
|
+
super("datetime");
|
|
49
|
+
this._expectedParameterCount = null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public value(): any {
|
|
53
|
+
const children = this.getChildren();
|
|
54
|
+
if (children.length > 1) {
|
|
55
|
+
throw new Error("datetime() accepts at most one argument");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const d: Date =
|
|
59
|
+
children.length === 1 ? parseTemporalArg(children[0].value(), "datetime") : new Date();
|
|
60
|
+
|
|
61
|
+
return buildDatetimeObject(d, true);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export default Datetime;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import Function from "./function";
|
|
2
|
+
import { FunctionDef } from "./function_metadata";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Regex for ISO 8601 duration strings: P[nY][nM][nW][nD][T[nH][nM][nS]]
|
|
6
|
+
*/
|
|
7
|
+
const ISO_DURATION_REGEX =
|
|
8
|
+
/^P(?:(\d+(?:\.\d+)?)Y)?(?:(\d+(?:\.\d+)?)M)?(?:(\d+(?:\.\d+)?)W)?(?:(\d+(?:\.\d+)?)D)?(?:T(?:(\d+(?:\.\d+)?)H)?(?:(\d+(?:\.\d+)?)M)?(?:(\d+(?:\.\d+)?)S)?)?$/;
|
|
9
|
+
|
|
10
|
+
function parseDurationString(s: string): Record<string, number> {
|
|
11
|
+
const match = s.match(ISO_DURATION_REGEX);
|
|
12
|
+
if (!match) {
|
|
13
|
+
throw new Error(`duration(): Invalid ISO 8601 duration string: '${s}'`);
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
years: match[1] ? parseFloat(match[1]) : 0,
|
|
17
|
+
months: match[2] ? parseFloat(match[2]) : 0,
|
|
18
|
+
weeks: match[3] ? parseFloat(match[3]) : 0,
|
|
19
|
+
days: match[4] ? parseFloat(match[4]) : 0,
|
|
20
|
+
hours: match[5] ? parseFloat(match[5]) : 0,
|
|
21
|
+
minutes: match[6] ? parseFloat(match[6]) : 0,
|
|
22
|
+
seconds: match[7] ? parseFloat(match[7]) : 0,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function buildDurationObject(components: Record<string, number>): Record<string, any> {
|
|
27
|
+
const years = components.years || 0;
|
|
28
|
+
const months = components.months || 0;
|
|
29
|
+
const weeks = components.weeks || 0;
|
|
30
|
+
const days = components.days || 0;
|
|
31
|
+
const hours = components.hours || 0;
|
|
32
|
+
const minutes = components.minutes || 0;
|
|
33
|
+
const seconds = Math.floor(components.seconds || 0);
|
|
34
|
+
const fractionalSeconds = (components.seconds || 0) - seconds;
|
|
35
|
+
|
|
36
|
+
const milliseconds = components.milliseconds
|
|
37
|
+
? Math.floor(components.milliseconds)
|
|
38
|
+
: Math.round(fractionalSeconds * 1000);
|
|
39
|
+
|
|
40
|
+
const nanoseconds = components.nanoseconds
|
|
41
|
+
? Math.floor(components.nanoseconds)
|
|
42
|
+
: Math.round(fractionalSeconds * 1_000_000_000) % 1_000_000;
|
|
43
|
+
|
|
44
|
+
// Total days including weeks
|
|
45
|
+
const totalDays = days + weeks * 7;
|
|
46
|
+
|
|
47
|
+
// Total seconds for the time portion
|
|
48
|
+
const totalSeconds = hours * 3600 + minutes * 60 + seconds;
|
|
49
|
+
|
|
50
|
+
// Approximate total in various units (months approximated at 30 days)
|
|
51
|
+
const totalMonths = years * 12 + months;
|
|
52
|
+
|
|
53
|
+
// Build ISO 8601 formatted string
|
|
54
|
+
let formatted = "P";
|
|
55
|
+
if (years) formatted += `${years}Y`;
|
|
56
|
+
if (months) formatted += `${months}M`;
|
|
57
|
+
if (weeks) formatted += `${weeks}W`;
|
|
58
|
+
if (totalDays - weeks * 7) formatted += `${totalDays - weeks * 7}D`;
|
|
59
|
+
const hasTime = hours || minutes || seconds || milliseconds;
|
|
60
|
+
if (hasTime) {
|
|
61
|
+
formatted += "T";
|
|
62
|
+
if (hours) formatted += `${hours}H`;
|
|
63
|
+
if (minutes) formatted += `${minutes}M`;
|
|
64
|
+
if (seconds || milliseconds) {
|
|
65
|
+
if (milliseconds) {
|
|
66
|
+
formatted += `${seconds}.${String(milliseconds).padStart(3, "0")}S`;
|
|
67
|
+
} else {
|
|
68
|
+
formatted += `${seconds}S`;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (formatted === "P") formatted = "PT0S";
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
years,
|
|
76
|
+
months,
|
|
77
|
+
weeks,
|
|
78
|
+
days: totalDays,
|
|
79
|
+
hours,
|
|
80
|
+
minutes,
|
|
81
|
+
seconds,
|
|
82
|
+
milliseconds,
|
|
83
|
+
nanoseconds,
|
|
84
|
+
totalMonths,
|
|
85
|
+
totalDays,
|
|
86
|
+
totalSeconds,
|
|
87
|
+
formatted,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@FunctionDef({
|
|
92
|
+
description:
|
|
93
|
+
"Creates a duration value representing a span of time. " +
|
|
94
|
+
"Accepts an ISO 8601 duration string (e.g., 'P1Y2M3DT4H5M6S') or a map of components " +
|
|
95
|
+
"(years, months, weeks, days, hours, minutes, seconds, milliseconds, nanoseconds).",
|
|
96
|
+
category: "scalar",
|
|
97
|
+
parameters: [
|
|
98
|
+
{
|
|
99
|
+
name: "input",
|
|
100
|
+
description:
|
|
101
|
+
"An ISO 8601 duration string or a map of components (years, months, weeks, days, hours, minutes, seconds, milliseconds, nanoseconds)",
|
|
102
|
+
type: "any",
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
output: {
|
|
106
|
+
description:
|
|
107
|
+
"A duration object with properties: years, months, weeks, days, hours, minutes, seconds, " +
|
|
108
|
+
"milliseconds, nanoseconds, totalMonths, totalDays, totalSeconds, formatted",
|
|
109
|
+
type: "object",
|
|
110
|
+
},
|
|
111
|
+
examples: [
|
|
112
|
+
"RETURN duration('P1Y2M3D') AS d",
|
|
113
|
+
"RETURN duration('PT2H30M') AS d",
|
|
114
|
+
"RETURN duration({days: 14, hours: 16}) AS d",
|
|
115
|
+
"RETURN duration({months: 5, days: 1, hours: 12}) AS d",
|
|
116
|
+
],
|
|
117
|
+
})
|
|
118
|
+
class Duration extends Function {
|
|
119
|
+
constructor() {
|
|
120
|
+
super("duration");
|
|
121
|
+
this._expectedParameterCount = 1;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
public value(): any {
|
|
125
|
+
const arg = this.getChildren()[0].value();
|
|
126
|
+
if (arg === null || arg === undefined) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (typeof arg === "string") {
|
|
131
|
+
const components = parseDurationString(arg);
|
|
132
|
+
return buildDurationObject(components);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (typeof arg === "object" && !Array.isArray(arg)) {
|
|
136
|
+
return buildDurationObject(arg);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
throw new Error("duration() expects a string or map argument");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export default Duration;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import Function from "./function";
|
|
2
|
+
import { FunctionDef } from "./function_metadata";
|
|
3
|
+
|
|
4
|
+
@FunctionDef({
|
|
5
|
+
description:
|
|
6
|
+
"Returns the element id of a node or relationship as a string. For nodes, returns the string representation of the id property. For relationships, returns the type.",
|
|
7
|
+
category: "scalar",
|
|
8
|
+
parameters: [
|
|
9
|
+
{
|
|
10
|
+
name: "entity",
|
|
11
|
+
description: "A node or relationship to get the element id from",
|
|
12
|
+
type: "object",
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
output: {
|
|
16
|
+
description: "The element id of the entity as a string",
|
|
17
|
+
type: "string",
|
|
18
|
+
example: '"1"',
|
|
19
|
+
},
|
|
20
|
+
examples: ["MATCH (n:Person) RETURN elementId(n)", "MATCH (a)-[r]->(b) RETURN elementId(r)"],
|
|
21
|
+
})
|
|
22
|
+
class ElementId extends Function {
|
|
23
|
+
constructor() {
|
|
24
|
+
super("elementid");
|
|
25
|
+
this._expectedParameterCount = 1;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public value(): any {
|
|
29
|
+
const obj = this.getChildren()[0].value();
|
|
30
|
+
if (obj === null || obj === undefined) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
if (typeof obj !== "object" || Array.isArray(obj)) {
|
|
34
|
+
throw new Error("elementId() expects a node or relationship");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// If it's a RelationshipMatchRecord (has type, startNode, endNode, properties)
|
|
38
|
+
if ("type" in obj && "startNode" in obj && "endNode" in obj && "properties" in obj) {
|
|
39
|
+
return String(obj.type);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// If it's a node record (has id field)
|
|
43
|
+
if ("id" in obj) {
|
|
44
|
+
return String(obj.id);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
throw new Error("elementId() expects a node or relationship");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default ElementId;
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import AsyncFunction from "./async_function";
|
|
2
2
|
import "./avg";
|
|
3
|
+
import "./coalesce";
|
|
3
4
|
import "./collect";
|
|
4
5
|
import "./count";
|
|
6
|
+
import "./date";
|
|
7
|
+
import "./datetime";
|
|
8
|
+
import "./duration";
|
|
9
|
+
import "./element_id";
|
|
5
10
|
import Function from "./function";
|
|
6
11
|
import {
|
|
7
12
|
AsyncDataProvider,
|
|
@@ -11,12 +16,22 @@ import {
|
|
|
11
16
|
getRegisteredFunctionMetadata,
|
|
12
17
|
} from "./function_metadata";
|
|
13
18
|
import "./functions";
|
|
19
|
+
import "./head";
|
|
20
|
+
import "./id";
|
|
14
21
|
import "./join";
|
|
15
22
|
import "./keys";
|
|
23
|
+
import "./last";
|
|
24
|
+
import "./localdatetime";
|
|
25
|
+
import "./localtime";
|
|
26
|
+
import "./max";
|
|
27
|
+
import "./min";
|
|
28
|
+
import "./nodes";
|
|
16
29
|
import PredicateFunction from "./predicate_function";
|
|
17
30
|
import "./predicate_sum";
|
|
31
|
+
import "./properties";
|
|
18
32
|
import "./rand";
|
|
19
33
|
import "./range";
|
|
34
|
+
import "./relationships";
|
|
20
35
|
import "./replace";
|
|
21
36
|
import "./round";
|
|
22
37
|
import "./schema";
|
|
@@ -26,9 +41,15 @@ import "./string_distance";
|
|
|
26
41
|
import "./stringify";
|
|
27
42
|
// Import built-in functions to ensure their @FunctionDef decorators run
|
|
28
43
|
import "./sum";
|
|
44
|
+
import "./tail";
|
|
45
|
+
import "./time";
|
|
46
|
+
import "./timestamp";
|
|
47
|
+
import "./to_float";
|
|
48
|
+
import "./to_integer";
|
|
29
49
|
import "./to_json";
|
|
30
50
|
import "./to_lower";
|
|
31
51
|
import "./to_string";
|
|
52
|
+
import "./trim";
|
|
32
53
|
import "./type";
|
|
33
54
|
|
|
34
55
|
// Re-export AsyncDataProvider for backwards compatibility
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import Function from "./function";
|
|
2
|
+
import { FunctionDef } from "./function_metadata";
|
|
3
|
+
|
|
4
|
+
@FunctionDef({
|
|
5
|
+
description: "Returns the first element of a list",
|
|
6
|
+
category: "scalar",
|
|
7
|
+
parameters: [
|
|
8
|
+
{
|
|
9
|
+
name: "list",
|
|
10
|
+
description: "The list to get the first element from",
|
|
11
|
+
type: "array",
|
|
12
|
+
},
|
|
13
|
+
],
|
|
14
|
+
output: {
|
|
15
|
+
description: "The first element of the list",
|
|
16
|
+
type: "any",
|
|
17
|
+
example: "1",
|
|
18
|
+
},
|
|
19
|
+
examples: ["RETURN head([1, 2, 3])", "WITH ['a', 'b', 'c'] AS items RETURN head(items)"],
|
|
20
|
+
})
|
|
21
|
+
class Head extends Function {
|
|
22
|
+
constructor() {
|
|
23
|
+
super("head");
|
|
24
|
+
this._expectedParameterCount = 1;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public value(): any {
|
|
28
|
+
const val = this.getChildren()[0].value();
|
|
29
|
+
if (val === null || val === undefined) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
if (!Array.isArray(val)) {
|
|
33
|
+
throw new Error("head() expects a list");
|
|
34
|
+
}
|
|
35
|
+
if (val.length === 0) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return val[0];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export default Head;
|