Skip to content

Commit e96f23c

Browse files
authored
Merge pull request #32 from microsoft/improvements
Improvements
2 parents 390e884 + a385d97 commit e96f23c

48 files changed

Lines changed: 4203 additions & 7 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

flowquery-py/src/parsing/data_structures/lookup.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ def is_operand(self) -> bool:
3838

3939
def value(self) -> Any:
4040
obj = self.variable.value()
41+
if obj is None:
42+
return None
4143
key = self.index.value()
4244
# Try dict-like access first, then fall back to attribute access for objects
4345
try:

flowquery-py/src/parsing/functions/__init__.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@
33
from .aggregate_function import AggregateFunction
44
from .async_function import AsyncFunction
55
from .avg import Avg
6+
from .coalesce import Coalesce
67
from .collect import Collect
78
from .count import Count
9+
from .date_ import DateFunction
10+
from .datetime_ import Datetime
11+
from .duration import Duration
12+
from .element_id import ElementId
813
from .function import Function
914
from .function_factory import FunctionFactory
1015
from .function_metadata import (
@@ -19,23 +24,36 @@
1924
get_registered_function_metadata,
2025
)
2126
from .functions import Functions
27+
from .head import Head
28+
from .id_ import Id
2229
from .join import Join
2330
from .keys import Keys
31+
from .last import Last
32+
from .localdatetime import LocalDatetime
33+
from .localtime import LocalTime
34+
from .max_ import Max
35+
from .min_ import Min
36+
from .nodes import Nodes
2437
from .predicate_function import PredicateFunction
2538
from .predicate_sum import PredicateSum
39+
from .properties import Properties
2640
from .rand import Rand
2741
from .range_ import Range
2842
from .reducer_element import ReducerElement
43+
from .relationships import Relationships
2944
from .replace import Replace
3045
from .round_ import Round
3146
from .schema import Schema
3247
from .size import Size
3348
from .split import Split
3449
from .string_distance import StringDistance
3550
from .stringify import Stringify
36-
37-
# Built-in functions
3851
from .sum import Sum
52+
from .tail import Tail
53+
from .time_ import Time
54+
from .timestamp import Timestamp
55+
from .to_float import ToFloat
56+
from .to_integer import ToInteger
3957
from .to_json import ToJson
4058
from .to_lower import ToLower
4159
from .to_string import ToString
@@ -64,10 +82,23 @@
6482
# Built-in functions
6583
"Sum",
6684
"Avg",
85+
"DateFunction",
86+
"Datetime",
87+
"Coalesce",
6788
"Collect",
6889
"Count",
90+
"Duration",
91+
"ElementId",
92+
"Head",
93+
"Id",
6994
"Join",
95+
"Last",
7096
"Keys",
97+
"Max",
98+
"Min",
99+
"Nodes",
100+
"Properties",
101+
"Relationships",
71102
"Rand",
72103
"Range",
73104
"Replace",
@@ -76,11 +107,18 @@
76107
"Split",
77108
"StringDistance",
78109
"Stringify",
110+
"Tail",
111+
"Time",
112+
"Timestamp",
113+
"ToFloat",
114+
"ToInteger",
79115
"ToJson",
80116
"ToLower",
81117
"ToString",
82118
"Trim",
83119
"Type",
120+
"LocalDatetime",
121+
"LocalTime",
84122
"Functions",
85123
"Schema",
86124
"PredicateSum",
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""Coalesce 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": "Returns the first non-null value from a list of expressions",
11+
"category": "scalar",
12+
"parameters": [
13+
{"name": "expressions", "description": "Two or more expressions to evaluate", "type": "any"}
14+
],
15+
"output": {"description": "The first non-null value, or null if all values are null", "type": "any"},
16+
"examples": [
17+
"RETURN coalesce(null, 'hello', 'world')",
18+
"MATCH (n) RETURN coalesce(n.nickname, n.name) AS displayName"
19+
]
20+
})
21+
class Coalesce(Function):
22+
"""Coalesce function.
23+
24+
Returns the first non-null value from a list of expressions.
25+
Equivalent to Neo4j's coalesce() function.
26+
"""
27+
28+
def __init__(self) -> None:
29+
super().__init__("coalesce")
30+
self._expected_parameter_count = None # variable number of parameters
31+
32+
def value(self) -> Any:
33+
children = self.get_children()
34+
if len(children) == 0:
35+
raise ValueError("coalesce() requires at least one argument")
36+
for child in children:
37+
try:
38+
val = child.value()
39+
except (KeyError, AttributeError):
40+
# Treat missing properties/keys as null, matching Neo4j behavior
41+
val = None
42+
if val is not None:
43+
return val
44+
return None
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"""Date function."""
2+
3+
from datetime import datetime
4+
from typing import Any
5+
6+
from .function import Function
7+
from .function_metadata import FunctionDef
8+
from .temporal_utils import build_date_object, parse_temporal_arg
9+
10+
11+
@FunctionDef({
12+
"description": (
13+
"Returns a date value. With no arguments returns the current date. "
14+
"Accepts an ISO 8601 date string or a map of components (year, month, day)."
15+
),
16+
"category": "scalar",
17+
"parameters": [
18+
{
19+
"name": "input",
20+
"description": "Optional. An ISO 8601 date string (YYYY-MM-DD) or a map of components.",
21+
"type": "string",
22+
"required": False,
23+
},
24+
],
25+
"output": {
26+
"description": (
27+
"A date object with properties: year, month, day, "
28+
"epochMillis, dayOfWeek, dayOfYear, quarter, formatted"
29+
),
30+
"type": "object",
31+
},
32+
"examples": [
33+
"RETURN date() AS today",
34+
"RETURN date('2025-06-15') AS d",
35+
"RETURN date({year: 2025, month: 6, day: 15}) AS d",
36+
"WITH date() AS d RETURN d.year, d.month, d.dayOfWeek",
37+
],
38+
})
39+
class DateFunction(Function):
40+
"""Date function.
41+
42+
Returns a date value (no time component).
43+
When called with no arguments, returns the current date.
44+
When called with a string argument, parses it as an ISO 8601 date.
45+
46+
Equivalent to Neo4j's date() function.
47+
"""
48+
49+
def __init__(self) -> None:
50+
super().__init__("date")
51+
self._expected_parameter_count = None
52+
53+
def value(self) -> Any:
54+
children = self.get_children()
55+
if len(children) > 1:
56+
raise ValueError("date() accepts at most one argument")
57+
58+
if len(children) == 1:
59+
d = parse_temporal_arg(children[0].value(), "date")
60+
else:
61+
d = datetime.now()
62+
63+
return build_date_object(d)
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"""Datetime 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_datetime_object, parse_temporal_arg
9+
10+
11+
@FunctionDef({
12+
"description": (
13+
"Returns a datetime value. With no arguments returns the current UTC datetime. "
14+
"Accepts an ISO 8601 string or a map of components (year, month, day, hour, minute, second, millisecond)."
15+
),
16+
"category": "scalar",
17+
"parameters": [
18+
{
19+
"name": "input",
20+
"description": "Optional. An ISO 8601 datetime string or a map of components.",
21+
"type": "string",
22+
"required": False,
23+
},
24+
],
25+
"output": {
26+
"description": (
27+
"A datetime object with properties: year, month, day, hour, minute, second, millisecond, "
28+
"epochMillis, epochSeconds, dayOfWeek, dayOfYear, quarter, formatted"
29+
),
30+
"type": "object",
31+
},
32+
"examples": [
33+
"RETURN datetime() AS now",
34+
"RETURN datetime('2025-06-15T12:30:00Z') AS dt",
35+
"RETURN datetime({year: 2025, month: 6, day: 15, hour: 12}) AS dt",
36+
"WITH datetime() AS dt RETURN dt.year, dt.month, dt.day",
37+
],
38+
})
39+
class Datetime(Function):
40+
"""Datetime function.
41+
42+
Returns a datetime value (date + time + timezone offset).
43+
When called with no arguments, returns the current UTC datetime.
44+
When called with a string argument, parses it as an ISO 8601 datetime.
45+
When called with a map argument, constructs a datetime from components.
46+
47+
Equivalent to Neo4j's datetime() function.
48+
"""
49+
50+
def __init__(self) -> None:
51+
super().__init__("datetime")
52+
self._expected_parameter_count = None
53+
54+
def value(self) -> Any:
55+
children = self.get_children()
56+
if len(children) > 1:
57+
raise ValueError("datetime() accepts at most one argument")
58+
59+
if len(children) == 1:
60+
d = parse_temporal_arg(children[0].value(), "datetime")
61+
else:
62+
d = datetime.now(timezone.utc)
63+
64+
return build_datetime_object(d, utc=True)

0 commit comments

Comments
 (0)