Skip to content

Commit ef00478

Browse files
Fix parsing priority issue with NULL/TRUE/FALSE. Add support for IN Clause. (#66)
* Fix parsing priority issue with NULL/TRUE/FALSE. Remove deprecated Python builds. * Add support for IN clause * Add additional Python versions to build workflow * Add id() function * Rename function in spec to scalar_function
1 parent fff7651 commit ef00478

File tree

3 files changed

+63
-10
lines changed

3 files changed

+63
-10
lines changed

.github/workflows/python-package.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
runs-on: ubuntu-latest
1616
strategy:
1717
matrix:
18-
python-version: [3.7, 3.8, 3.9, '3.10', '3.11']
18+
python-version: [3.9, '3.10', '3.11', '3.12', '3.13']
1919

2020
steps:
2121
- uses: actions/checkout@v2

grandcypher/__init__.py

+23-9
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,15 @@
6363
| "(" compound_condition boolean_arithmetic compound_condition ")"
6464
| compound_condition boolean_arithmetic compound_condition
6565
66-
condition : entity_id op entity_id_or_value
66+
condition : (entity_id | scalar_function) op entity_id_or_value
67+
| (entity_id | scalar_function) op_list value_list
6768
| "not"i condition -> condition_not
6869
6970
?entity_id_or_value : entity_id
7071
| value
71-
| "NULL"i -> null
72-
| "TRUE"i -> true
73-
| "FALSE"i -> false
72+
| NULL -> null
73+
| TRUE -> true
74+
| FALSE -> false
7475
7576
op : "==" -> op_eq
7677
| "=" -> op_eq
@@ -80,22 +81,24 @@
8081
| ">="-> op_gte
8182
| "<="-> op_lte
8283
| "is"i -> op_is
83-
| "in"i -> op_in
8484
| "contains"i -> op_contains
8585
| "starts with"i -> op_starts_with
8686
| "ends with"i -> op_ends_with
8787
88+
op_list : "in"i -> op_in
8889
8990
9091
9192
return_clause : "return"i distinct_return? return_item ("," return_item)*
92-
return_item : (entity_id | aggregation_function | entity_id "." attribute_id) ( "AS"i alias )?
93+
return_item : (entity_id | aggregation_function | scalar_function | entity_id "." attribute_id) ( "AS"i alias )?
9394
alias : CNAME
9495
9596
aggregation_function : AGGREGATE_FUNC "(" entity_id ( "." attribute_id )? ")"
9697
AGGREGATE_FUNC : "COUNT" | "SUM" | "AVG" | "MAX" | "MIN"
9798
attribute_id : CNAME
9899
100+
scalar_function : "id"i "(" entity_id ")" -> id_function
101+
99102
distinct_return : "DISTINCT"i
100103
limit_clause : "limit"i NUMBER
101104
skip_clause : "skip"i NUMBER
@@ -131,6 +134,7 @@
131134
| LEFT_ANGLE? "-[" CNAME ":" TYPE "*" MIN_HOP "]-" RIGHT_ANGLE?
132135
| LEFT_ANGLE? "-[" CNAME ":" TYPE "*" MIN_HOP ".." MAX_HOP "]-" RIGHT_ANGLE?
133136
137+
value_list : "[" [value ("," value)*] "]"
134138
type_list : TYPE ( "|" TYPE )*
135139
136140
LEFT_ANGLE : "<"
@@ -149,9 +153,13 @@
149153
key : CNAME
150154
?value : ESTRING
151155
| NUMBER
152-
| "NULL"i -> null
153-
| "TRUE"i -> true
154-
| "FALSE"i -> false
156+
| NULL -> null
157+
| TRUE -> true
158+
| FALSE -> false
159+
160+
NULL.1 : "NULL"i
161+
TRUE.1 : "TRUE"i
162+
FALSE.1 : "FALSE"i
155163
156164
157165
%import common.CNAME -> CNAME
@@ -1175,6 +1183,12 @@ def condition_not(self, processed_condition):
11751183
ESTRING = v_args(inline=True)(eval)
11761184
NUMBER = v_args(inline=True)(eval)
11771185

1186+
def id_function(self, entity_id):
1187+
return entity_id[0].value
1188+
1189+
def value_list(self, items):
1190+
return list(items)
1191+
11781192
def op(self, operator):
11791193
return operator
11801194

grandcypher/test_queries.py

+39
Original file line numberDiff line numberDiff line change
@@ -2166,3 +2166,42 @@ def test_multigraph_match_with_single_or_operator(self):
21662166
res = GrandCypher(host).run(qry)
21672167
assert res["n1.name"] == ["Bob", "Bob"]
21682168
assert res["n2.name"] == ["Carol", "Derek"]
2169+
2170+
class TestFunction:
2171+
@pytest.mark.parametrize("graph_type", ACCEPTED_GRAPH_TYPES)
2172+
def test_id(self, graph_type):
2173+
host = graph_type()
2174+
host.add_node(1, name="Ford Prefect")
2175+
host.add_node(2, name="Arthur Dent")
2176+
host.add_node(3, name="John Smith")
2177+
host.add_edge(1, 2)
2178+
host.add_edge(2, 3)
2179+
2180+
qry = """
2181+
MATCH (A)
2182+
WHERE id(A) == 1 OR id(A) == 2
2183+
RETURN id(A)
2184+
"""
2185+
2186+
res = GrandCypher(host).run(qry)
2187+
assert res["A"] == [1, 2]
2188+
2189+
2190+
class TestList:
2191+
@pytest.mark.parametrize("graph_type", ACCEPTED_GRAPH_TYPES)
2192+
def test_in(self, graph_type):
2193+
host = graph_type()
2194+
host.add_node(1, name="Ford Prefect")
2195+
host.add_node(2, name="Arthur Dent")
2196+
host.add_node(3, name="John Smith")
2197+
host.add_edge(1, 2)
2198+
host.add_edge(2, 3)
2199+
2200+
qry = """
2201+
MATCH (A)
2202+
WHERE id(A) IN [1, 3]
2203+
RETURN A
2204+
"""
2205+
2206+
res = GrandCypher(host).run(qry)
2207+
assert res["A"] == [1, 3]

0 commit comments

Comments
 (0)