@@ -39,6 +39,21 @@ def _seed_graph(db) -> None:
3939 )
4040
4141
42+ def _seed_path_mode_graph (db ) -> None :
43+ db .command ("sql" , "CREATE VERTEX TYPE Node" )
44+ db .command ("sql" , "CREATE EDGE TYPE LINK" )
45+
46+ with db .transaction ():
47+ db .command (
48+ "opencypher" ,
49+ "CREATE (a:Node {name: 'A'})-[:LINK]->(b:Node {name: 'B'})"
50+ "-[:LINK]->(c:Node {name: 'C'})"
51+ "-[:LINK]->(d:Node {name: 'D'})"
52+ "-[:LINK]->(a),"
53+ "(a)-[:LINK]->(e:Node {name: 'E'})" ,
54+ )
55+
56+
4257def test_opencypher_basic_match (temp_db_path ):
4358 """Test basic OpenCypher MATCH/WHERE."""
4459 with arcadedb .create_database (temp_db_path ) as db :
@@ -87,19 +102,108 @@ def test_opencypher_variable_length_path(temp_db_path):
87102 assert names == ["Bob" , "Charlie" , "David" ]
88103
89104
105+ def test_opencypher_trail_is_default_path_mode (temp_db_path ):
106+ """Test that TRAIL remains the default path mode for variable-length traversals."""
107+ with arcadedb .create_database (temp_db_path ) as db :
108+ _ensure_opencypher (db )
109+ _seed_path_mode_graph (db )
110+
111+ trail_names = [
112+ record .get ("name" )
113+ for record in db .query (
114+ "opencypher" ,
115+ "MATCH TRAIL (a:Node {name: 'A'})-[:LINK*1..5]->(b) "
116+ "RETURN b.name AS name" ,
117+ )
118+ ]
119+ default_names = [
120+ record .get ("name" )
121+ for record in db .query (
122+ "opencypher" ,
123+ "MATCH (a:Node {name: 'A'})-[:LINK*1..5]->(b) " "RETURN b.name AS name" ,
124+ )
125+ ]
126+
127+ assert len (default_names ) == len (trail_names )
128+ assert "A" in trail_names
129+
130+
131+ def test_opencypher_acyclic_blocks_vertex_revisit (temp_db_path ):
132+ """Test that ACYCLIC traversal does not revisit vertices on a cycle."""
133+ with arcadedb .create_database (temp_db_path ) as db :
134+ _ensure_opencypher (db )
135+ _seed_path_mode_graph (db )
136+
137+ names = [
138+ record .get ("name" )
139+ for record in db .query (
140+ "opencypher" ,
141+ "MATCH ACYCLIC (a:Node {name: 'A'})-[:LINK*1..5]->(b) "
142+ "RETURN b.name AS name" ,
143+ )
144+ ]
145+
146+ assert "A" not in names
147+ assert {"B" , "C" , "D" , "E" }.issubset (set (names ))
148+
149+
150+ def test_opencypher_walk_produces_more_results_than_trail (temp_db_path ):
151+ """Test that WALK allows more variable-length matches than TRAIL on a cycle."""
152+ with arcadedb .create_database (temp_db_path ) as db :
153+ _ensure_opencypher (db )
154+ _seed_path_mode_graph (db )
155+
156+ walk_count = len (
157+ list (
158+ db .query (
159+ "opencypher" ,
160+ "MATCH WALK (a:Node {name: 'A'})-[:LINK*1..6]->(b) RETURN b" ,
161+ )
162+ )
163+ )
164+ trail_count = len (
165+ list (
166+ db .query (
167+ "opencypher" ,
168+ "MATCH TRAIL (a:Node {name: 'A'})-[:LINK*1..6]->(b) RETURN b" ,
169+ )
170+ )
171+ )
172+
173+ assert walk_count > trail_count
174+
175+
176+ def test_opencypher_walk_requires_max_hops (temp_db_path ):
177+ """Test that WALK requires an explicit maximum hop bound."""
178+ with arcadedb .create_database (temp_db_path ) as db :
179+ _ensure_opencypher (db )
180+ _seed_path_mode_graph (db )
181+
182+ with pytest .raises (Exception , match = "WALK" ):
183+ list (
184+ db .query (
185+ "opencypher" ,
186+ "MATCH WALK (a:Node {name: 'A'})-[:LINK*]->(b) RETURN b" ,
187+ )
188+ )
189+
190+
90191def test_opencypher_aggregation (temp_db_path ):
91192 """Test aggregation and ordering."""
92193 with arcadedb .create_database (temp_db_path ) as db :
93194 _ensure_opencypher (db )
94195 _seed_graph (db )
95196
96- result = db .query (
97- "opencypher" ,
98- "MATCH (p:Person)-[:WORKS_FOR]->(c:Company) "
99- "WITH c, count(p) as employees "
100- "RETURN c.name as company, employees ORDER BY employees DESC" ,
197+ rows = list (
198+ db .query (
199+ "opencypher" ,
200+ "MATCH (p:Person)-[:WORKS_FOR]->(c:Company) "
201+ "WITH c, count(p) as employees "
202+ "RETURN c.name as company, employees ORDER BY employees DESC" ,
203+ )
101204 )
102- row = next (result )
205+
206+ row = rows [0 ]
103207
104208 assert row .get ("company" ) == "Acme"
105209 assert row .get ("employees" ) == 2
0 commit comments