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.
Files changed (197) 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/data_structures/lookup.d.ts.map +1 -1
  18. package/dist/parsing/data_structures/lookup.js +5 -1
  19. package/dist/parsing/data_structures/lookup.js.map +1 -1
  20. package/dist/parsing/functions/coalesce.d.ts +17 -0
  21. package/dist/parsing/functions/coalesce.d.ts.map +1 -0
  22. package/dist/parsing/functions/coalesce.js +61 -0
  23. package/dist/parsing/functions/coalesce.js.map +1 -0
  24. package/dist/parsing/functions/date.d.ts +22 -0
  25. package/dist/parsing/functions/date.d.ts.map +1 -0
  26. package/dist/parsing/functions/date.js +71 -0
  27. package/dist/parsing/functions/date.js.map +1 -0
  28. package/dist/parsing/functions/datetime.d.ts +22 -0
  29. package/dist/parsing/functions/datetime.d.ts.map +1 -0
  30. package/dist/parsing/functions/datetime.js +71 -0
  31. package/dist/parsing/functions/datetime.js.map +1 -0
  32. package/dist/parsing/functions/duration.d.ts +7 -0
  33. package/dist/parsing/functions/duration.d.ts.map +1 -0
  34. package/dist/parsing/functions/duration.js +145 -0
  35. package/dist/parsing/functions/duration.js.map +1 -0
  36. package/dist/parsing/functions/element_id.d.ts +7 -0
  37. package/dist/parsing/functions/element_id.d.ts.map +1 -0
  38. package/dist/parsing/functions/element_id.js +58 -0
  39. package/dist/parsing/functions/element_id.js.map +1 -0
  40. package/dist/parsing/functions/function_factory.d.ts +21 -0
  41. package/dist/parsing/functions/function_factory.d.ts.map +1 -1
  42. package/dist/parsing/functions/function_factory.js +21 -0
  43. package/dist/parsing/functions/function_factory.js.map +1 -1
  44. package/dist/parsing/functions/head.d.ts +7 -0
  45. package/dist/parsing/functions/head.d.ts.map +1 -0
  46. package/dist/parsing/functions/head.js +53 -0
  47. package/dist/parsing/functions/head.js.map +1 -0
  48. package/dist/parsing/functions/id.d.ts +7 -0
  49. package/dist/parsing/functions/id.d.ts.map +1 -0
  50. package/dist/parsing/functions/id.js +58 -0
  51. package/dist/parsing/functions/id.js.map +1 -0
  52. package/dist/parsing/functions/last.d.ts +7 -0
  53. package/dist/parsing/functions/last.d.ts.map +1 -0
  54. package/dist/parsing/functions/last.js +53 -0
  55. package/dist/parsing/functions/last.js.map +1 -0
  56. package/dist/parsing/functions/localdatetime.d.ts +21 -0
  57. package/dist/parsing/functions/localdatetime.d.ts.map +1 -0
  58. package/dist/parsing/functions/localdatetime.js +71 -0
  59. package/dist/parsing/functions/localdatetime.js.map +1 -0
  60. package/dist/parsing/functions/localtime.d.ts +20 -0
  61. package/dist/parsing/functions/localtime.d.ts.map +1 -0
  62. package/dist/parsing/functions/localtime.js +67 -0
  63. package/dist/parsing/functions/localtime.js.map +1 -0
  64. package/dist/parsing/functions/max.d.ts +14 -0
  65. package/dist/parsing/functions/max.d.ts.map +1 -0
  66. package/dist/parsing/functions/max.js +51 -0
  67. package/dist/parsing/functions/max.js.map +1 -0
  68. package/dist/parsing/functions/min.d.ts +14 -0
  69. package/dist/parsing/functions/min.d.ts.map +1 -0
  70. package/dist/parsing/functions/min.js +51 -0
  71. package/dist/parsing/functions/min.js.map +1 -0
  72. package/dist/parsing/functions/nodes.d.ts +7 -0
  73. package/dist/parsing/functions/nodes.d.ts.map +1 -0
  74. package/dist/parsing/functions/nodes.js +63 -0
  75. package/dist/parsing/functions/nodes.js.map +1 -0
  76. package/dist/parsing/functions/predicate_sum.d.ts.map +1 -1
  77. package/dist/parsing/functions/predicate_sum.js +13 -10
  78. package/dist/parsing/functions/predicate_sum.js.map +1 -1
  79. package/dist/parsing/functions/properties.d.ts +7 -0
  80. package/dist/parsing/functions/properties.d.ts.map +1 -0
  81. package/dist/parsing/functions/properties.js +74 -0
  82. package/dist/parsing/functions/properties.js.map +1 -0
  83. package/dist/parsing/functions/relationships.d.ts +7 -0
  84. package/dist/parsing/functions/relationships.d.ts.map +1 -0
  85. package/dist/parsing/functions/relationships.js +61 -0
  86. package/dist/parsing/functions/relationships.js.map +1 -0
  87. package/dist/parsing/functions/schema.d.ts +5 -2
  88. package/dist/parsing/functions/schema.d.ts.map +1 -1
  89. package/dist/parsing/functions/schema.js +7 -4
  90. package/dist/parsing/functions/schema.js.map +1 -1
  91. package/dist/parsing/functions/tail.d.ts +7 -0
  92. package/dist/parsing/functions/tail.d.ts.map +1 -0
  93. package/dist/parsing/functions/tail.js +50 -0
  94. package/dist/parsing/functions/tail.js.map +1 -0
  95. package/dist/parsing/functions/temporal_utils.d.ts +39 -0
  96. package/dist/parsing/functions/temporal_utils.d.ts.map +1 -0
  97. package/dist/parsing/functions/temporal_utils.js +168 -0
  98. package/dist/parsing/functions/temporal_utils.js.map +1 -0
  99. package/dist/parsing/functions/time.d.ts +20 -0
  100. package/dist/parsing/functions/time.d.ts.map +1 -0
  101. package/dist/parsing/functions/time.js +67 -0
  102. package/dist/parsing/functions/time.js.map +1 -0
  103. package/dist/parsing/functions/timestamp.d.ts +17 -0
  104. package/dist/parsing/functions/timestamp.d.ts.map +1 -0
  105. package/dist/parsing/functions/timestamp.js +51 -0
  106. package/dist/parsing/functions/timestamp.js.map +1 -0
  107. package/dist/parsing/functions/to_float.d.ts +7 -0
  108. package/dist/parsing/functions/to_float.d.ts.map +1 -0
  109. package/dist/parsing/functions/to_float.js +61 -0
  110. package/dist/parsing/functions/to_float.js.map +1 -0
  111. package/dist/parsing/functions/to_integer.d.ts +7 -0
  112. package/dist/parsing/functions/to_integer.d.ts.map +1 -0
  113. package/dist/parsing/functions/to_integer.js +61 -0
  114. package/dist/parsing/functions/to_integer.js.map +1 -0
  115. package/dist/parsing/functions/trim.d.ts +7 -0
  116. package/dist/parsing/functions/trim.d.ts.map +1 -0
  117. package/dist/parsing/functions/trim.js +37 -0
  118. package/dist/parsing/functions/trim.js.map +1 -0
  119. package/dist/parsing/operations/group_by.d.ts.map +1 -1
  120. package/dist/parsing/operations/group_by.js +4 -2
  121. package/dist/parsing/operations/group_by.js.map +1 -1
  122. package/dist/parsing/parser.d.ts.map +1 -1
  123. package/dist/parsing/parser.js +15 -2
  124. package/dist/parsing/parser.js.map +1 -1
  125. package/docs/flowquery.min.js +1 -1
  126. package/flowquery-py/pyproject.toml +1 -1
  127. package/flowquery-py/src/graph/database.py +44 -11
  128. package/flowquery-py/src/graph/relationship.py +11 -3
  129. package/flowquery-py/src/graph/relationship_data.py +2 -1
  130. package/flowquery-py/src/graph/relationship_match_collector.py +7 -1
  131. package/flowquery-py/src/graph/relationship_reference.py +2 -2
  132. package/flowquery-py/src/parsing/data_structures/lookup.py +2 -0
  133. package/flowquery-py/src/parsing/functions/__init__.py +42 -2
  134. package/flowquery-py/src/parsing/functions/coalesce.py +44 -0
  135. package/flowquery-py/src/parsing/functions/date_.py +63 -0
  136. package/flowquery-py/src/parsing/functions/datetime_.py +64 -0
  137. package/flowquery-py/src/parsing/functions/duration.py +159 -0
  138. package/flowquery-py/src/parsing/functions/element_id.py +50 -0
  139. package/flowquery-py/src/parsing/functions/head.py +39 -0
  140. package/flowquery-py/src/parsing/functions/id_.py +49 -0
  141. package/flowquery-py/src/parsing/functions/last.py +39 -0
  142. package/flowquery-py/src/parsing/functions/localdatetime.py +62 -0
  143. package/flowquery-py/src/parsing/functions/localtime.py +59 -0
  144. package/flowquery-py/src/parsing/functions/max_.py +49 -0
  145. package/flowquery-py/src/parsing/functions/min_.py +49 -0
  146. package/flowquery-py/src/parsing/functions/nodes.py +48 -0
  147. package/flowquery-py/src/parsing/functions/predicate_sum.py +3 -6
  148. package/flowquery-py/src/parsing/functions/properties.py +50 -0
  149. package/flowquery-py/src/parsing/functions/relationships.py +46 -0
  150. package/flowquery-py/src/parsing/functions/schema.py +9 -5
  151. package/flowquery-py/src/parsing/functions/tail.py +37 -0
  152. package/flowquery-py/src/parsing/functions/temporal_utils.py +186 -0
  153. package/flowquery-py/src/parsing/functions/time_.py +59 -0
  154. package/flowquery-py/src/parsing/functions/timestamp.py +39 -0
  155. package/flowquery-py/src/parsing/functions/to_float.py +46 -0
  156. package/flowquery-py/src/parsing/functions/to_integer.py +46 -0
  157. package/flowquery-py/src/parsing/functions/trim.py +35 -0
  158. package/flowquery-py/src/parsing/operations/group_by.py +2 -0
  159. package/flowquery-py/src/parsing/parser.py +12 -2
  160. package/flowquery-py/tests/compute/test_runner.py +1082 -4
  161. package/flowquery-vscode/flowQueryEngine/flowquery.min.js +1 -1
  162. package/package.json +1 -1
  163. package/src/graph/database.ts +42 -4
  164. package/src/graph/relationship.ts +12 -4
  165. package/src/graph/relationship_data.ts +1 -1
  166. package/src/graph/relationship_match_collector.ts +6 -2
  167. package/src/graph/relationship_reference.ts +1 -1
  168. package/src/parsing/data_structures/lookup.ts +8 -4
  169. package/src/parsing/functions/coalesce.ts +50 -0
  170. package/src/parsing/functions/date.ts +65 -0
  171. package/src/parsing/functions/datetime.ts +65 -0
  172. package/src/parsing/functions/duration.ts +143 -0
  173. package/src/parsing/functions/element_id.ts +51 -0
  174. package/src/parsing/functions/function_factory.ts +21 -0
  175. package/src/parsing/functions/head.ts +42 -0
  176. package/src/parsing/functions/id.ts +51 -0
  177. package/src/parsing/functions/last.ts +42 -0
  178. package/src/parsing/functions/localdatetime.ts +65 -0
  179. package/src/parsing/functions/localtime.ts +60 -0
  180. package/src/parsing/functions/max.ts +37 -0
  181. package/src/parsing/functions/min.ts +37 -0
  182. package/src/parsing/functions/nodes.ts +54 -0
  183. package/src/parsing/functions/predicate_sum.ts +17 -12
  184. package/src/parsing/functions/properties.ts +56 -0
  185. package/src/parsing/functions/relationships.ts +52 -0
  186. package/src/parsing/functions/schema.ts +7 -4
  187. package/src/parsing/functions/tail.ts +39 -0
  188. package/src/parsing/functions/temporal_utils.ts +180 -0
  189. package/src/parsing/functions/time.ts +60 -0
  190. package/src/parsing/functions/timestamp.ts +41 -0
  191. package/src/parsing/functions/to_float.ts +50 -0
  192. package/src/parsing/functions/to_integer.ts +50 -0
  193. package/src/parsing/functions/trim.ts +25 -0
  194. package/src/parsing/operations/group_by.ts +4 -1
  195. package/src/parsing/parser.ts +15 -2
  196. package/tests/compute/runner.test.ts +1005 -3
  197. 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
- rel_type: str = self.token.value or ""
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.type = rel_type
620
+ relationship.types = rel_types
611
621
  return relationship
612
622
 
613
623
  def _parse_properties(self) -> Iterator[Tuple[str, Expression]]: