Skip to content

Commit bc4716c

Browse files
Add support for variable length relationships
1 parent 903e7b9 commit bc4716c

File tree

3 files changed

+190
-1
lines changed

3 files changed

+190
-1
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "wikibase-solutions/php-cypher-dsl",
33
"description": "A PHP DSL for the Cypher Query Language",
4-
"version": "2.2.3",
4+
"version": "2.3.0",
55
"type": "library",
66
"keywords": [
77
"neo4j",

src/Patterns/Path.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,16 @@ class Path implements PathType
6969
*/
7070
private Variable $variable;
7171

72+
/**
73+
* @var int The minimum number of `relationship->node` hops away to search
74+
*/
75+
private int $minHops;
76+
77+
/**
78+
* @var int The maximum number of `relationship->node` hops away to search
79+
*/
80+
private int $maxHops;
81+
7282
/**
7383
* Path constructor.
7484
*
@@ -107,6 +117,36 @@ public function named($variable): self
107117
return $this;
108118
}
109119

120+
/**
121+
* Set the minimum number of `relationship->node` hops away to search.
122+
*
123+
* @see https://neo4j.com/docs/cypher-manual/current/clauses/match/#varlength-rels
124+
*
125+
* @param int $minHops
126+
* @return Path
127+
*/
128+
public function withMinHops(int $minHops): self
129+
{
130+
$this->minHops = $minHops;
131+
132+
return $this;
133+
}
134+
135+
/**
136+
* Set the maximum number of `relationship->node` hops away to search.
137+
*
138+
* @see https://neo4j.com/docs/cypher-manual/current/clauses/match/#varlength-rels
139+
*
140+
* @param int $maxHops
141+
* @return Path
142+
*/
143+
public function withMaxHops(int $maxHops): self
144+
{
145+
$this->maxHops = $maxHops;
146+
147+
return $this;
148+
}
149+
110150
/**
111151
* @param string $type
112152
* @return Path
@@ -152,6 +192,19 @@ private function conditionToString(): string
152192
$conditionInner .= sprintf(":%s", implode("|", $escapedTypes));
153193
}
154194

195+
if (isset($this->minHops) || isset($this->maxHops)) {
196+
// We have either a minHop, maxHop or both
197+
$conditionInner .= "*";
198+
199+
if (isset($this->minHops)) {
200+
$conditionInner .= $this->minHops;
201+
}
202+
203+
if (isset($this->maxHops)) {
204+
$conditionInner .= sprintf("..%s", $this->maxHops);
205+
}
206+
}
207+
155208
if (isset($this->properties)) {
156209
if ($conditionInner !== "") {
157210
// Add some padding between the property list and the preceding structure

tests/Unit/Patterns/RelationshipTest.php

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,142 @@ public function testWithMultipleTypes(string $name, array $types, array $propert
194194
$this->assertSame($expected, $r->toQuery());
195195
}
196196

197+
/**
198+
* @dataProvider provideVariableLengthRelationshipsWithNameData
199+
* @param string $name
200+
* @param int|null $minHops
201+
* @param int|null $maxHops
202+
* @param array $direction
203+
* @param string $expected
204+
*/
205+
public function testVariableLengthRelationshipsWithName(string $name, ?int $minHops, ?int $maxHops, array $direction, string $expected)
206+
{
207+
$r = new Path($this->a, $this->b, $direction);
208+
$r->named($name);
209+
210+
if (isset($minHops)) {
211+
$r->withMinHops($minHops);
212+
}
213+
214+
if (isset($maxHops)) {
215+
$r->withMaxHops($maxHops);
216+
}
217+
218+
$this->assertSame($expected, $r->toQuery());
219+
}
220+
221+
/**
222+
* @dataProvider provideVariableLengthRelationshipsWithTypeData
223+
* @param string $type
224+
* @param int|null $minHops
225+
* @param int|null $maxHops
226+
* @param array $direction
227+
* @param string $expected
228+
*/
229+
public function testVariableLengthRelationshipsWithType(string $type, ?int $minHops, ?int $maxHops, array $direction, string $expected)
230+
{
231+
$r = new Path($this->a, $this->b, $direction);
232+
$r->withType($type);
233+
234+
if (isset($minHops)) {
235+
$r->withMinHops($minHops);
236+
}
237+
238+
if (isset($maxHops)) {
239+
$r->withMaxHops($maxHops);
240+
}
241+
242+
$this->assertSame($expected, $r->toQuery());
243+
}
244+
245+
/**
246+
* @dataProvider provideVariableLengthRelationshipsWithPropertiesData
247+
* @param array $properties
248+
* @param int|null $minHops
249+
* @param int|null $maxHops
250+
* @param array $direction
251+
* @param string $expected
252+
*/
253+
public function testVariableLengthRelationshipsWithProperties(array $properties, ?int $minHops, ?int $maxHops, array $direction, string $expected)
254+
{
255+
$r = new Path($this->a, $this->b, $direction);
256+
$r->withProperties($properties);
257+
258+
if (isset($minHops)) {
259+
$r->withMinHops($minHops);
260+
}
261+
262+
if (isset($maxHops)) {
263+
$r->withMaxHops($maxHops);
264+
}
265+
266+
$this->assertSame($expected, $r->toQuery());
267+
}
268+
269+
/**
270+
* @dataProvider provideVariableLengthRelationshipsWithNameAndTypeAndPropertiesData
271+
* @param string $name
272+
* @param string $type
273+
* @param array $properties
274+
* @param array $direction
275+
* @param string $expected
276+
*/
277+
public function testVariableLengthRelationshipsWithNameAndTypeAndProperties(string $name, string $type, array $properties, ?int $minHops, ?int $maxHops, array $direction, string $expected)
278+
{
279+
$r = new Path($this->a, $this->b, $direction);
280+
$r->named($name)->withType($type)->withProperties($properties);
281+
282+
if (isset($minHops)) {
283+
$r->withMinHops($minHops);
284+
}
285+
286+
if (isset($maxHops)) {
287+
$r->withMaxHops($maxHops);
288+
}
289+
290+
$this->assertSame($expected, $r->toQuery());
291+
}
292+
293+
public function provideVariableLengthRelationshipsWithNameData(): array
294+
{
295+
return [
296+
['', 1, 100, Path::DIR_UNI, '(a)-[*1..100]-(b)'],
297+
['a', 10, null, Path::DIR_UNI, '(a)-[a*10]-(b)'],
298+
['a', null, 10, Path::DIR_LEFT, '(a)<-[a*..10]-(b)'],
299+
];
300+
}
301+
302+
public function provideVariableLengthRelationshipsWithTypeData(): array
303+
{
304+
return [
305+
['', 1, 100, Path::DIR_LEFT, '(a)<-[*1..100]-(b)'],
306+
['a', 10, null, Path::DIR_LEFT, '(a)<-[:a*10]-(b)'],
307+
[':', null, 10, Path::DIR_LEFT, '(a)<-[:`:`*..10]-(b)']
308+
];
309+
}
310+
311+
public function provideVariableLengthRelationshipsWithPropertiesData(): array
312+
{
313+
return [
314+
[[], 10, 100, Path::DIR_LEFT, "(a)<-[*10..100 {}]-(b)"],
315+
[[new StringLiteral('a')], 10, null, Path::DIR_LEFT, "(a)<-[*10 {`0`: 'a'}]-(b)"],
316+
[['a' => new StringLiteral('b')], null, 10, Path::DIR_LEFT, "(a)<-[*..10 {a: 'b'}]-(b)"]
317+
];
318+
}
319+
320+
public function provideVariableLengthRelationshipsWithNameAndTypeAndPropertiesData(): array
321+
{
322+
return [
323+
['a', 'a', [], 10, 100, Path::DIR_LEFT, "(a)<-[a:a*10..100 {}]-(b)"],
324+
['b', 'a', [new StringLiteral('a')], null, 10, Path::DIR_LEFT, "(a)<-[b:a*..10 {`0`: 'a'}]-(b)"],
325+
['', 'a', ['a' => new StringLiteral('b')], 10, null, Path::DIR_LEFT, "(a)<-[:a*10 {a: 'b'}]-(b)"],
326+
[':', 'a', ['a' => new StringLiteral('b'), new StringLiteral('c')], null, null, Path::DIR_LEFT, "(a)<-[`:`:a {a: 'b', `0`: 'c'}]-(b)"],
327+
['a', 'b', [new StringLiteral('a')], 10, 100, Path::DIR_LEFT, "(a)<-[a:b*10..100 {`0`: 'a'}]-(b)"],
328+
['a', '', ['a' => new StringLiteral('b')], null, 10, Path::DIR_LEFT, "(a)<-[a*..10 {a: 'b'}]-(b)"],
329+
['a', ':', ['a' => new StringLiteral('b'), new StringLiteral('c')], 10, null, Path::DIR_LEFT, "(a)<-[a:`:`*10 {a: 'b', `0`: 'c'}]-(b)"]
330+
];
331+
}
332+
197333
public function provideWithNameData(): array
198334
{
199335
return [

0 commit comments

Comments
 (0)