Skip to content

Commit d902f4d

Browse files
Merge branch 'feature-requests-wout' into 'main'
Voeg support voor minimale hoeveelheid hops zonder maximum toe See merge request community/libraries/php-cypher-dsl!3
2 parents bc4716c + 632b041 commit d902f4d

File tree

2 files changed

+207
-7
lines changed

2 files changed

+207
-7
lines changed

src/Patterns/Path.php

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121

2222
namespace WikibaseSolutions\CypherDSL\Patterns;
2323

24+
use DomainException;
2425
use InvalidArgumentException;
26+
use LogicException;
2527
use WikibaseSolutions\CypherDSL\Traits\EscapeTrait;
2628
use WikibaseSolutions\CypherDSL\Traits\PathTypeTrait;
2729
use WikibaseSolutions\CypherDSL\Types\AnyType;
@@ -79,6 +81,11 @@ class Path implements PathType
7981
*/
8082
private int $maxHops;
8183

84+
/**
85+
* @var int The exact number of `relationship->node` hops away to search
86+
*/
87+
private int $exactHops;
88+
8289
/**
8390
* Path constructor.
8491
*
@@ -127,6 +134,18 @@ public function named($variable): self
127134
*/
128135
public function withMinHops(int $minHops): self
129136
{
137+
if ($minHops < 1) {
138+
throw new DomainException("minHops cannot be less than 1");
139+
}
140+
141+
if (isset($this->maxHops) && $minHops > $this->maxHops) {
142+
throw new DomainException("minHops cannot be greater than maxHops");
143+
}
144+
145+
if (isset($this->exactHops)) {
146+
throw new LogicException("Cannot use minHops in combination with exactHops");
147+
}
148+
130149
$this->minHops = $minHops;
131150

132151
return $this;
@@ -142,11 +161,46 @@ public function withMinHops(int $minHops): self
142161
*/
143162
public function withMaxHops(int $maxHops): self
144163
{
164+
if ($maxHops < 1) {
165+
throw new DomainException("maxHops cannot be less than 1");
166+
}
167+
168+
if (isset($this->minHops) && $maxHops < $this->minHops) {
169+
throw new DomainException("maxHops cannot be less than minHops");
170+
}
171+
172+
if (isset($this->exactHops)) {
173+
throw new LogicException("Cannot use maxHops in combination with exactHops");
174+
}
175+
145176
$this->maxHops = $maxHops;
146177

147178
return $this;
148179
}
149180

181+
/**
182+
* Set the exact number of `relationship->node` hops away to search.
183+
*
184+
* @see https://neo4j.com/docs/cypher-manual/current/clauses/match/#varlength-rels
185+
*
186+
* @param int $exactHops
187+
* @return Path
188+
*/
189+
public function withExactHops(int $exactHops) : self
190+
{
191+
if ($exactHops < 1) {
192+
throw new DomainException("exactHops cannot be less than 1");
193+
}
194+
195+
if (isset($this->minHops) || isset($this->maxHops)) {
196+
throw new LogicException("Cannot use exactHops in combination with minHops or maxHops");
197+
}
198+
199+
$this->exactHops = $exactHops;
200+
201+
return $this;
202+
}
203+
150204
/**
151205
* @param string $type
152206
* @return Path
@@ -200,9 +254,13 @@ private function conditionToString(): string
200254
$conditionInner .= $this->minHops;
201255
}
202256

257+
$conditionInner .= '..';
258+
203259
if (isset($this->maxHops)) {
204-
$conditionInner .= sprintf("..%s", $this->maxHops);
260+
$conditionInner .= $this->maxHops;
205261
}
262+
} elseif (isset($this->exactHops)) {
263+
$conditionInner .= '*' . $this->exactHops;
206264
}
207265

208266
if (isset($this->properties)) {
@@ -216,4 +274,4 @@ private function conditionToString(): string
216274

217275
return sprintf("[%s]", $conditionInner);
218276
}
219-
}
277+
}

tests/Unit/Patterns/RelationshipTest.php

Lines changed: 147 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,15 @@
2121

2222
namespace WikibaseSolutions\CypherDSL\Tests\Unit\Patterns;
2323

24+
use DomainException;
25+
use LogicException;
2426
use PHPUnit\Framework\MockObject\MockObject;
2527
use PHPUnit\Framework\TestCase;
2628
use WikibaseSolutions\CypherDSL\Literals\Decimal;
2729
use WikibaseSolutions\CypherDSL\Literals\StringLiteral;
2830
use WikibaseSolutions\CypherDSL\Patterns\Node;
2931
use WikibaseSolutions\CypherDSL\Patterns\Path;
32+
use WikibaseSolutions\CypherDSL\Query;
3033
use WikibaseSolutions\CypherDSL\Tests\Unit\TestHelper;
3134
use WikibaseSolutions\CypherDSL\Types\StructuralTypes\StructuralType;
3235

@@ -271,6 +274,8 @@ public function testVariableLengthRelationshipsWithProperties(array $properties,
271274
* @param string $name
272275
* @param string $type
273276
* @param array $properties
277+
* @param int|null $minHops
278+
* @param int|null $maxHops
274279
* @param array $direction
275280
* @param string $expected
276281
*/
@@ -290,11 +295,148 @@ public function testVariableLengthRelationshipsWithNameAndTypeAndProperties(stri
290295
$this->assertSame($expected, $r->toQuery());
291296
}
292297

298+
public function testExactLengthRelationships()
299+
{
300+
$r = new Path($this->a, $this->b, Path::DIR_RIGHT);
301+
$r->named("tom")
302+
->withType("Person")
303+
->withProperties(['name' => Query::literal('Tom Hanks')]);
304+
305+
$r->withExactHops(10);
306+
307+
$this->assertSame("(a)-[tom:Person*10 {name: 'Tom Hanks'}]->(b)", $r->toQuery());
308+
}
309+
310+
public function testMinAndExactHops()
311+
{
312+
$r = new Path($this->a, $this->b, Path::DIR_RIGHT);
313+
$r->withMinHops(1);
314+
315+
$this->expectException(LogicException::class);
316+
317+
$r->withExactHops(1);
318+
}
319+
320+
public function testMaxAndExactHops()
321+
{
322+
$r = new Path($this->a, $this->b, Path::DIR_RIGHT);
323+
$r->withMaxHops(1);
324+
325+
$this->expectException(LogicException::class);
326+
327+
$r->withExactHops(1);
328+
}
329+
330+
public function testMinMaxAndExactHops()
331+
{
332+
$r = new Path($this->a, $this->b, Path::DIR_RIGHT);
333+
$r->withMinHops(1);
334+
$r->withMaxHops(1);
335+
336+
$this->expectException(LogicException::class);
337+
338+
$r->withExactHops(1);
339+
}
340+
341+
public function testExactAndMinHops()
342+
{
343+
$r = new Path($this->a, $this->b, Path::DIR_RIGHT);
344+
$r->withExactHops(1);
345+
346+
$this->expectException(LogicException::class);
347+
348+
$r->withMinHops(1);
349+
}
350+
351+
public function testExactAndMaxHops()
352+
{
353+
$r = new Path($this->a, $this->b, Path::DIR_RIGHT);
354+
$r->withExactHops(1);
355+
356+
$this->expectException(LogicException::class);
357+
358+
$r->withMaxHops(1);
359+
}
360+
361+
public function testMaxHopsLessThanMinHops()
362+
{
363+
$r = new Path($this->a, $this->b, Path::DIR_RIGHT);
364+
$r->withMinHops(100);
365+
366+
$this->expectException(DomainException::class);
367+
368+
$r->withMaxHops(1);
369+
}
370+
371+
public function testMinHopsGreaterThanMaxHops()
372+
{
373+
$r = new Path($this->a, $this->b, Path::DIR_RIGHT);
374+
$r->withMaxHops(1);
375+
376+
$this->expectException(DomainException::class);
377+
378+
$r->withMinHops(100);
379+
}
380+
381+
public function testMinHopsLessThanOne()
382+
{
383+
$r = new Path($this->a, $this->b, Path::DIR_RIGHT);
384+
385+
$this->expectException(DomainException::class);
386+
387+
$r->withMinHops(0);
388+
}
389+
390+
public function testMinHopsLessThanZero()
391+
{
392+
$r = new Path($this->a, $this->b, Path::DIR_RIGHT);
393+
394+
$this->expectException(DomainException::class);
395+
396+
$r->withMinHops(-1);
397+
}
398+
399+
public function testMaxHopsLessThanOne()
400+
{
401+
$r = new Path($this->a, $this->b, Path::DIR_RIGHT);
402+
403+
$this->expectException(DomainException::class);
404+
405+
$r->withMaxHops(0);
406+
}
407+
408+
public function testMaxHopsLessThanZero()
409+
{
410+
$r = new Path($this->a, $this->b, Path::DIR_RIGHT);
411+
412+
$this->expectException(DomainException::class);
413+
414+
$r->withMaxHops(-1);
415+
}
416+
417+
public function testExactHopsLessThanOne()
418+
{
419+
$r = new Path($this->a, $this->b, Path::DIR_RIGHT);
420+
421+
$this->expectException(DomainException::class);
422+
423+
$r->withExactHops(0);
424+
}
425+
426+
public function testExactHopsLessThanZero()
427+
{
428+
$r = new Path($this->a, $this->b, Path::DIR_RIGHT);
429+
430+
$this->expectException(DomainException::class);
431+
432+
$r->withExactHops(-1);
433+
}
434+
293435
public function provideVariableLengthRelationshipsWithNameData(): array
294436
{
295437
return [
296438
['', 1, 100, Path::DIR_UNI, '(a)-[*1..100]-(b)'],
297-
['a', 10, null, Path::DIR_UNI, '(a)-[a*10]-(b)'],
439+
['a', 10, null, Path::DIR_UNI, '(a)-[a*10..]-(b)'],
298440
['a', null, 10, Path::DIR_LEFT, '(a)<-[a*..10]-(b)'],
299441
];
300442
}
@@ -303,7 +445,7 @@ public function provideVariableLengthRelationshipsWithTypeData(): array
303445
{
304446
return [
305447
['', 1, 100, Path::DIR_LEFT, '(a)<-[*1..100]-(b)'],
306-
['a', 10, null, Path::DIR_LEFT, '(a)<-[:a*10]-(b)'],
448+
['a', 10, null, Path::DIR_LEFT, '(a)<-[:a*10..]-(b)'],
307449
[':', null, 10, Path::DIR_LEFT, '(a)<-[:`:`*..10]-(b)']
308450
];
309451
}
@@ -312,7 +454,7 @@ public function provideVariableLengthRelationshipsWithPropertiesData(): array
312454
{
313455
return [
314456
[[], 10, 100, Path::DIR_LEFT, "(a)<-[*10..100 {}]-(b)"],
315-
[[new StringLiteral('a')], 10, null, Path::DIR_LEFT, "(a)<-[*10 {`0`: 'a'}]-(b)"],
457+
[[new StringLiteral('a')], 10, null, Path::DIR_LEFT, "(a)<-[*10.. {`0`: 'a'}]-(b)"],
316458
[['a' => new StringLiteral('b')], null, 10, Path::DIR_LEFT, "(a)<-[*..10 {a: 'b'}]-(b)"]
317459
];
318460
}
@@ -322,11 +464,11 @@ public function provideVariableLengthRelationshipsWithNameAndTypeAndPropertiesDa
322464
return [
323465
['a', 'a', [], 10, 100, Path::DIR_LEFT, "(a)<-[a:a*10..100 {}]-(b)"],
324466
['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)"],
467+
['', 'a', ['a' => new StringLiteral('b')], 10, null, Path::DIR_LEFT, "(a)<-[:a*10.. {a: 'b'}]-(b)"],
326468
[':', 'a', ['a' => new StringLiteral('b'), new StringLiteral('c')], null, null, Path::DIR_LEFT, "(a)<-[`:`:a {a: 'b', `0`: 'c'}]-(b)"],
327469
['a', 'b', [new StringLiteral('a')], 10, 100, Path::DIR_LEFT, "(a)<-[a:b*10..100 {`0`: 'a'}]-(b)"],
328470
['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)"]
471+
['a', ':', ['a' => new StringLiteral('b'), new StringLiteral('c')], 10, null, Path::DIR_LEFT, "(a)<-[a:`:`*10.. {a: 'b', `0`: 'c'}]-(b)"]
330472
];
331473
}
332474

0 commit comments

Comments
 (0)