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
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""Shared utility functions for temporal (date/time) operations.
|
|
2
|
+
|
|
3
|
+
These helpers are used by the datetime_, date_, time_, localdatetime,
|
|
4
|
+
localtime, and timestamp functions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from datetime import date, datetime, timezone
|
|
8
|
+
from typing import Any, Dict
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def iso_day_of_week(d: date) -> int:
|
|
12
|
+
"""Computes the ISO day of the week (1 = Monday, 7 = Sunday) matching Neo4j convention."""
|
|
13
|
+
return d.isoweekday()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def day_of_year(d: date) -> int:
|
|
17
|
+
"""Computes the day of the year (1-based)."""
|
|
18
|
+
return d.timetuple().tm_yday
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def quarter(month: int) -> int:
|
|
22
|
+
"""Computes the quarter (1-4) from a month (1-12)."""
|
|
23
|
+
return (month - 1) // 3 + 1
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def parse_temporal_arg(arg: Any, fn_name: str) -> datetime:
|
|
27
|
+
"""Parses a temporal argument (string, number, or map) into a datetime object.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
arg: The argument to parse (string, number, or dict with components)
|
|
31
|
+
fn_name: The calling function name for error messages
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
A datetime object
|
|
35
|
+
"""
|
|
36
|
+
if isinstance(arg, str):
|
|
37
|
+
try:
|
|
38
|
+
return datetime.fromisoformat(arg.replace("Z", "+00:00"))
|
|
39
|
+
except ValueError:
|
|
40
|
+
raise ValueError(f"{fn_name}(): Invalid temporal string: '{arg}'")
|
|
41
|
+
|
|
42
|
+
if isinstance(arg, (int, float)):
|
|
43
|
+
# Treat as epoch milliseconds
|
|
44
|
+
return datetime.fromtimestamp(arg / 1000, tz=timezone.utc)
|
|
45
|
+
|
|
46
|
+
if isinstance(arg, dict):
|
|
47
|
+
# Map-style construction: {year, month, day, hour, minute, second, millisecond}
|
|
48
|
+
now = datetime.now()
|
|
49
|
+
year = arg.get("year", now.year)
|
|
50
|
+
month = arg.get("month", 1)
|
|
51
|
+
day = arg.get("day", 1)
|
|
52
|
+
hour = arg.get("hour", 0)
|
|
53
|
+
minute = arg.get("minute", 0)
|
|
54
|
+
second = arg.get("second", 0)
|
|
55
|
+
millisecond = arg.get("millisecond", 0)
|
|
56
|
+
return datetime(year, month, day, hour, minute, second, millisecond * 1000)
|
|
57
|
+
|
|
58
|
+
raise ValueError(
|
|
59
|
+
f"{fn_name}(): Expected a string, number (epoch millis), or map argument, "
|
|
60
|
+
f"got {type(arg).__name__}"
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def build_datetime_object(d: datetime, utc: bool) -> Dict[str, Any]:
|
|
65
|
+
"""Builds a datetime result object with full temporal properties.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
d: The datetime object
|
|
69
|
+
utc: If True, use UTC values; if False, use local values
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
A dict with year, month, day, hour, minute, second, millisecond,
|
|
73
|
+
epochMillis, epochSeconds, dayOfWeek, dayOfYear, quarter, formatted
|
|
74
|
+
"""
|
|
75
|
+
if utc:
|
|
76
|
+
if d.tzinfo is None:
|
|
77
|
+
d = d.replace(tzinfo=timezone.utc)
|
|
78
|
+
d_utc = d.astimezone(timezone.utc)
|
|
79
|
+
year = d_utc.year
|
|
80
|
+
month = d_utc.month
|
|
81
|
+
day = d_utc.day
|
|
82
|
+
hour = d_utc.hour
|
|
83
|
+
minute = d_utc.minute
|
|
84
|
+
second = d_utc.second
|
|
85
|
+
millisecond = d_utc.microsecond // 1000
|
|
86
|
+
formatted = d_utc.strftime("%Y-%m-%dT%H:%M:%S.") + f"{millisecond:03d}Z"
|
|
87
|
+
else:
|
|
88
|
+
if d.tzinfo is not None:
|
|
89
|
+
d = d.astimezone(tz=None).replace(tzinfo=None)
|
|
90
|
+
year = d.year
|
|
91
|
+
month = d.month
|
|
92
|
+
day = d.day
|
|
93
|
+
hour = d.hour
|
|
94
|
+
minute = d.minute
|
|
95
|
+
second = d.second
|
|
96
|
+
millisecond = d.microsecond // 1000
|
|
97
|
+
formatted = d.strftime("%Y-%m-%dT%H:%M:%S.") + f"{millisecond:03d}"
|
|
98
|
+
|
|
99
|
+
date_part = date(year, month, day)
|
|
100
|
+
epoch_millis = int(d.timestamp() * 1000) if d.tzinfo else int(
|
|
101
|
+
datetime(year, month, day, hour, minute, second, millisecond * 1000).timestamp() * 1000
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
"year": year,
|
|
106
|
+
"month": month,
|
|
107
|
+
"day": day,
|
|
108
|
+
"hour": hour,
|
|
109
|
+
"minute": minute,
|
|
110
|
+
"second": second,
|
|
111
|
+
"millisecond": millisecond,
|
|
112
|
+
"epochMillis": epoch_millis,
|
|
113
|
+
"epochSeconds": epoch_millis // 1000,
|
|
114
|
+
"dayOfWeek": iso_day_of_week(date_part),
|
|
115
|
+
"dayOfYear": day_of_year(date_part),
|
|
116
|
+
"quarter": quarter(month),
|
|
117
|
+
"formatted": formatted,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def build_date_object(d: datetime) -> Dict[str, Any]:
|
|
122
|
+
"""Builds a date result object (no time component).
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
d: The datetime object
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
A dict with year, month, day, epochMillis, dayOfWeek, dayOfYear, quarter, formatted
|
|
129
|
+
"""
|
|
130
|
+
year = d.year
|
|
131
|
+
month = d.month
|
|
132
|
+
day_val = d.day
|
|
133
|
+
|
|
134
|
+
date_only = datetime(year, month, day_val)
|
|
135
|
+
epoch_millis = int(date_only.timestamp() * 1000)
|
|
136
|
+
|
|
137
|
+
date_part = date(year, month, day_val)
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
"year": year,
|
|
141
|
+
"month": month,
|
|
142
|
+
"day": day_val,
|
|
143
|
+
"epochMillis": epoch_millis,
|
|
144
|
+
"dayOfWeek": iso_day_of_week(date_part),
|
|
145
|
+
"dayOfYear": day_of_year(date_part),
|
|
146
|
+
"quarter": quarter(month),
|
|
147
|
+
"formatted": f"{year}-{month:02d}-{day_val:02d}",
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def build_time_object(d: datetime, utc: bool) -> Dict[str, Any]:
|
|
152
|
+
"""Builds a time result object (no date component).
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
d: The datetime object
|
|
156
|
+
utc: If True, use UTC values; if False, use local values
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
A dict with hour, minute, second, millisecond, formatted
|
|
160
|
+
"""
|
|
161
|
+
if utc:
|
|
162
|
+
if d.tzinfo is None:
|
|
163
|
+
d = d.replace(tzinfo=timezone.utc)
|
|
164
|
+
d_utc = d.astimezone(timezone.utc)
|
|
165
|
+
hour = d_utc.hour
|
|
166
|
+
minute = d_utc.minute
|
|
167
|
+
second = d_utc.second
|
|
168
|
+
millisecond = d_utc.microsecond // 1000
|
|
169
|
+
else:
|
|
170
|
+
if d.tzinfo is not None:
|
|
171
|
+
d = d.astimezone(tz=None).replace(tzinfo=None)
|
|
172
|
+
hour = d.hour
|
|
173
|
+
minute = d.minute
|
|
174
|
+
second = d.second
|
|
175
|
+
millisecond = d.microsecond // 1000
|
|
176
|
+
|
|
177
|
+
time_part = f"{hour:02d}:{minute:02d}:{second:02d}.{millisecond:03d}"
|
|
178
|
+
formatted = f"{time_part}Z" if utc else time_part
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
"hour": hour,
|
|
182
|
+
"minute": minute,
|
|
183
|
+
"second": second,
|
|
184
|
+
"millisecond": millisecond,
|
|
185
|
+
"formatted": formatted,
|
|
186
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Time function."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from .function import Function
|
|
7
|
+
from .function_metadata import FunctionDef
|
|
8
|
+
from .temporal_utils import build_time_object, parse_temporal_arg
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@FunctionDef({
|
|
12
|
+
"description": (
|
|
13
|
+
"Returns a time value. With no arguments returns the current UTC time. "
|
|
14
|
+
"Accepts an ISO 8601 time string or a map of components (hour, minute, second, millisecond)."
|
|
15
|
+
),
|
|
16
|
+
"category": "scalar",
|
|
17
|
+
"parameters": [
|
|
18
|
+
{
|
|
19
|
+
"name": "input",
|
|
20
|
+
"description": "Optional. An ISO 8601 time string (HH:MM:SS) or a map of components.",
|
|
21
|
+
"type": "string",
|
|
22
|
+
"required": False,
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
"output": {
|
|
26
|
+
"description": "A time object with properties: hour, minute, second, millisecond, formatted",
|
|
27
|
+
"type": "object",
|
|
28
|
+
},
|
|
29
|
+
"examples": [
|
|
30
|
+
"RETURN time() AS now",
|
|
31
|
+
"RETURN time('12:30:00') AS t",
|
|
32
|
+
"WITH time() AS t RETURN t.hour, t.minute",
|
|
33
|
+
],
|
|
34
|
+
})
|
|
35
|
+
class Time(Function):
|
|
36
|
+
"""Time function.
|
|
37
|
+
|
|
38
|
+
Returns a time value (with timezone offset awareness).
|
|
39
|
+
When called with no arguments, returns the current UTC time.
|
|
40
|
+
When called with a string argument, parses it.
|
|
41
|
+
|
|
42
|
+
Equivalent to Neo4j's time() function.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(self) -> None:
|
|
46
|
+
super().__init__("time")
|
|
47
|
+
self._expected_parameter_count = None
|
|
48
|
+
|
|
49
|
+
def value(self) -> Any:
|
|
50
|
+
children = self.get_children()
|
|
51
|
+
if len(children) > 1:
|
|
52
|
+
raise ValueError("time() accepts at most one argument")
|
|
53
|
+
|
|
54
|
+
if len(children) == 1:
|
|
55
|
+
d = parse_temporal_arg(children[0].value(), "time")
|
|
56
|
+
else:
|
|
57
|
+
d = datetime.now(timezone.utc)
|
|
58
|
+
|
|
59
|
+
return build_time_object(d, utc=True)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Timestamp function."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from .function import Function
|
|
7
|
+
from .function_metadata import FunctionDef
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@FunctionDef({
|
|
11
|
+
"description": (
|
|
12
|
+
"Returns the number of milliseconds since the Unix epoch (1970-01-01T00:00:00Z). "
|
|
13
|
+
"Equivalent to Neo4j's timestamp() function."
|
|
14
|
+
),
|
|
15
|
+
"category": "scalar",
|
|
16
|
+
"parameters": [],
|
|
17
|
+
"output": {
|
|
18
|
+
"description": "Milliseconds since Unix epoch",
|
|
19
|
+
"type": "number",
|
|
20
|
+
"example": 1718450000000,
|
|
21
|
+
},
|
|
22
|
+
"examples": [
|
|
23
|
+
"RETURN timestamp() AS ts",
|
|
24
|
+
"WITH timestamp() AS before, timestamp() AS after RETURN after - before",
|
|
25
|
+
],
|
|
26
|
+
})
|
|
27
|
+
class Timestamp(Function):
|
|
28
|
+
"""Timestamp function.
|
|
29
|
+
|
|
30
|
+
Returns the number of milliseconds since the Unix epoch (1970-01-01T00:00:00Z).
|
|
31
|
+
Equivalent to Neo4j's timestamp() function.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self) -> None:
|
|
35
|
+
super().__init__("timestamp")
|
|
36
|
+
self._expected_parameter_count = 0
|
|
37
|
+
|
|
38
|
+
def value(self) -> Any:
|
|
39
|
+
return int(time.time() * 1000)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""ToFloat function."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from .function import Function
|
|
6
|
+
from .function_metadata import FunctionDef
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@FunctionDef({
|
|
10
|
+
"description": "Converts a value to a floating point number",
|
|
11
|
+
"category": "scalar",
|
|
12
|
+
"parameters": [
|
|
13
|
+
{"name": "value", "description": "The value to convert to a float", "type": "any"}
|
|
14
|
+
],
|
|
15
|
+
"output": {"description": "The floating point representation of the value", "type": "number", "example": 3.14},
|
|
16
|
+
"examples": [
|
|
17
|
+
'RETURN toFloat("3.14")',
|
|
18
|
+
"RETURN toFloat(42)",
|
|
19
|
+
"RETURN toFloat(true)"
|
|
20
|
+
]
|
|
21
|
+
})
|
|
22
|
+
class ToFloat(Function):
|
|
23
|
+
"""ToFloat function.
|
|
24
|
+
|
|
25
|
+
Converts a value to a floating point number.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self) -> None:
|
|
29
|
+
super().__init__("tofloat")
|
|
30
|
+
self._expected_parameter_count = 1
|
|
31
|
+
|
|
32
|
+
def value(self) -> Any:
|
|
33
|
+
val = self.get_children()[0].value()
|
|
34
|
+
if val is None:
|
|
35
|
+
return None
|
|
36
|
+
if isinstance(val, bool):
|
|
37
|
+
return 1.0 if val else 0.0
|
|
38
|
+
if isinstance(val, (int, float)):
|
|
39
|
+
return float(val)
|
|
40
|
+
if isinstance(val, str):
|
|
41
|
+
trimmed = val.strip()
|
|
42
|
+
try:
|
|
43
|
+
return float(trimmed)
|
|
44
|
+
except (ValueError, OverflowError):
|
|
45
|
+
raise ValueError(f'Cannot convert string "{val}" to float')
|
|
46
|
+
raise ValueError("toFloat() expects a number, string, or boolean")
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""ToInteger function."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from .function import Function
|
|
6
|
+
from .function_metadata import FunctionDef
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@FunctionDef({
|
|
10
|
+
"description": "Converts a value to an integer",
|
|
11
|
+
"category": "scalar",
|
|
12
|
+
"parameters": [
|
|
13
|
+
{"name": "value", "description": "The value to convert to an integer", "type": "any"}
|
|
14
|
+
],
|
|
15
|
+
"output": {"description": "The integer representation of the value", "type": "number", "example": 42},
|
|
16
|
+
"examples": [
|
|
17
|
+
'RETURN toInteger("42")',
|
|
18
|
+
"RETURN toInteger(3.14)",
|
|
19
|
+
"RETURN toInteger(true)"
|
|
20
|
+
]
|
|
21
|
+
})
|
|
22
|
+
class ToInteger(Function):
|
|
23
|
+
"""ToInteger function.
|
|
24
|
+
|
|
25
|
+
Converts a value to an integer.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self) -> None:
|
|
29
|
+
super().__init__("tointeger")
|
|
30
|
+
self._expected_parameter_count = 1
|
|
31
|
+
|
|
32
|
+
def value(self) -> Any:
|
|
33
|
+
val = self.get_children()[0].value()
|
|
34
|
+
if val is None:
|
|
35
|
+
return None
|
|
36
|
+
if isinstance(val, bool):
|
|
37
|
+
return 1 if val else 0
|
|
38
|
+
if isinstance(val, (int, float)):
|
|
39
|
+
return int(val)
|
|
40
|
+
if isinstance(val, str):
|
|
41
|
+
trimmed = val.strip()
|
|
42
|
+
try:
|
|
43
|
+
return int(float(trimmed))
|
|
44
|
+
except (ValueError, OverflowError):
|
|
45
|
+
raise ValueError(f'Cannot convert string "{val}" to integer')
|
|
46
|
+
raise ValueError("toInteger() expects a number, string, or boolean")
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Trim function."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from .function import Function
|
|
6
|
+
from .function_metadata import FunctionDef
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@FunctionDef({
|
|
10
|
+
"description": "Removes leading and trailing whitespace from a string",
|
|
11
|
+
"category": "scalar",
|
|
12
|
+
"parameters": [
|
|
13
|
+
{"name": "text", "description": "String to trim", "type": "string"}
|
|
14
|
+
],
|
|
15
|
+
"output": {"description": "Trimmed string", "type": "string", "example": "hello"},
|
|
16
|
+
"examples": [
|
|
17
|
+
"WITH ' hello ' AS s RETURN trim(s)",
|
|
18
|
+
"WITH '\\tfoo\\n' AS s RETURN trim(s)"
|
|
19
|
+
]
|
|
20
|
+
})
|
|
21
|
+
class Trim(Function):
|
|
22
|
+
"""Trim function.
|
|
23
|
+
|
|
24
|
+
Removes leading and trailing whitespace from a string.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self) -> None:
|
|
28
|
+
super().__init__("trim")
|
|
29
|
+
self._expected_parameter_count = 1
|
|
30
|
+
|
|
31
|
+
def value(self) -> Any:
|
|
32
|
+
val = self.get_children()[0].value()
|
|
33
|
+
if not isinstance(val, str):
|
|
34
|
+
raise ValueError("Invalid argument for trim function: expected a string")
|
|
35
|
+
return val.strip()
|
|
@@ -122,6 +122,8 @@ class GroupBy(Projection):
|
|
|
122
122
|
self.mappers[mapper_index].overridden = child.value
|
|
123
123
|
yield from self.generate_results(mapper_index + 1, child)
|
|
124
124
|
else:
|
|
125
|
+
if node.elements is None:
|
|
126
|
+
node.elements = [reducer.element() for reducer in self.reducers]
|
|
125
127
|
if node.elements:
|
|
126
128
|
for i, element in enumerate(node.elements):
|
|
127
129
|
self.reducers[i].overridden = element.value
|
|
@@ -398,6 +398,8 @@ class Parser(BaseParser):
|
|
|
398
398
|
raise ValueError("Expected target node definition")
|
|
399
399
|
relationship = Relationship()
|
|
400
400
|
relationship.type = rel_type
|
|
401
|
+
relationship.source = node
|
|
402
|
+
relationship.target = target
|
|
401
403
|
|
|
402
404
|
self._expect_and_skip_whitespace_and_comments()
|
|
403
405
|
if not self.token.is_as():
|
|
@@ -576,8 +578,16 @@ class Parser(BaseParser):
|
|
|
576
578
|
self.set_next_token()
|
|
577
579
|
if not self.token.is_identifier_or_keyword():
|
|
578
580
|
raise ValueError("Expected relationship type identifier")
|
|
579
|
-
|
|
581
|
+
rel_types: List[str] = [self.token.value or ""]
|
|
580
582
|
self.set_next_token()
|
|
583
|
+
while self.token.is_pipe():
|
|
584
|
+
self.set_next_token()
|
|
585
|
+
if self.token.is_colon():
|
|
586
|
+
self.set_next_token()
|
|
587
|
+
if not self.token.is_identifier_or_keyword():
|
|
588
|
+
raise ValueError("Expected relationship type identifier after '|'")
|
|
589
|
+
rel_types.append(self.token.value or "")
|
|
590
|
+
self.set_next_token()
|
|
581
591
|
hops = self._parse_relationship_hops()
|
|
582
592
|
properties: Dict[str, Expression] = dict(self._parse_properties())
|
|
583
593
|
if not self.token.is_closing_bracket():
|
|
@@ -607,7 +617,7 @@ class Parser(BaseParser):
|
|
|
607
617
|
self._state.variables[variable] = relationship
|
|
608
618
|
if hops is not None:
|
|
609
619
|
relationship.hops = hops
|
|
610
|
-
relationship.
|
|
620
|
+
relationship.types = rel_types
|
|
611
621
|
return relationship
|
|
612
622
|
|
|
613
623
|
def _parse_properties(self) -> Iterator[Tuple[str, Expression]]:
|