Skip to content

Commit 75be26d

Browse files
authored
Merge branch 'main' into call-clause
2 parents 504b37a + 8fffe51 commit 75be26d

File tree

4 files changed

+222
-0
lines changed

4 files changed

+222
-0
lines changed

src/Clauses/UnionClause.php

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
namespace WikibaseSolutions\CypherDSL\Clauses;
4+
5+
use WikibaseSolutions\CypherDSL\Query;
6+
7+
/**
8+
* This class represents the union clause.
9+
*
10+
* @see https://neo4j.com/docs/cypher-manual/current/clauses/union/
11+
*/
12+
class UnionClause extends Clause
13+
{
14+
/** @var bool Whether the union should include all results or remove the duplicates instead. */
15+
private bool $all;
16+
17+
/**
18+
* @param bool $all Whether the union should include all results or remove the duplicates instead.
19+
*/
20+
public function __construct(bool $all = false)
21+
{
22+
$this->all = $all;
23+
}
24+
25+
/**
26+
* Combines two queries with a union.
27+
*
28+
* @param Query $left The query preceding the union clause.
29+
* @param Query $right The query after the union clause.
30+
* @param bool $all Whether the union should include all results or remove the duplicates instead.
31+
*/
32+
public static function union(Query $left, Query $right, bool $all = false): Query
33+
{
34+
$tbr = Query::new();
35+
36+
foreach ($left->getClauses() as $clause) {
37+
$tbr->addClause($clause);
38+
}
39+
40+
$tbr->addClause(new self($all));
41+
42+
foreach ($right->getClauses() as $clause) {
43+
$tbr->addClause($clause);
44+
}
45+
46+
return $tbr;
47+
}
48+
49+
/**
50+
* Returns whether the union includes all results or removes the duplicates instead.
51+
*
52+
* @return bool
53+
*/
54+
public function includesAll(): bool
55+
{
56+
return $this->all;
57+
}
58+
59+
/**
60+
* @inheritDoc
61+
*/
62+
public function canBeEmpty(): bool
63+
{
64+
return true;
65+
}
66+
67+
/**
68+
* @inheritDoc
69+
*/
70+
protected function getSubject(): string
71+
{
72+
return $this->all ? 'ALL' : '';
73+
}
74+
75+
/**
76+
* @inheritDoc
77+
*/
78+
protected function getClause(): string
79+
{
80+
return 'UNION';
81+
}
82+
}

src/Query.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
use WikibaseSolutions\CypherDSL\Clauses\ReturnClause;
3939
use WikibaseSolutions\CypherDSL\Clauses\SetClause;
4040
use WikibaseSolutions\CypherDSL\Clauses\SkipClause;
41+
use WikibaseSolutions\CypherDSL\Clauses\UnionClause;
4142
use WikibaseSolutions\CypherDSL\Clauses\WhereClause;
4243
use WikibaseSolutions\CypherDSL\Clauses\WithClause;
4344
use WikibaseSolutions\CypherDSL\Functions\FunctionCall;
@@ -57,6 +58,7 @@
5758
use WikibaseSolutions\CypherDSL\Types\PropertyTypes\StringType;
5859
use WikibaseSolutions\CypherDSL\Types\StructuralTypes\NodeType;
5960
use WikibaseSolutions\CypherDSL\Types\StructuralTypes\PathType;
61+
use function is_callable;
6062

6163
/**
6264
* Builder class for building complex Cypher queries.
@@ -638,6 +640,35 @@ public function callProcedure(string $procedure, array $arguments = [], array $y
638640
return $this;
639641
}
640642

643+
644+
/**
645+
* Combines the result of this query with another one via a UNION clause.
646+
*
647+
* @param callable(Query):void|Query $queryOrCallable The callable decorating a fresh query instance or the query instance to be attached after the union clause.
648+
* @param bool $all Whether the union should include all results or remove the duplicates instead.
649+
*
650+
* @return Query
651+
*
652+
* @see https://neo4j.com/docs/cypher-manual/current/clauses/union/
653+
*/
654+
public function union($queryOrCallable, bool $all = false): self
655+
{
656+
$this->clauses[] = new UnionClause($all);
657+
658+
if (is_callable($queryOrCallable)) {
659+
$query = Query::new();
660+
$queryOrCallable($query);
661+
} else {
662+
$query = $queryOrCallable;
663+
}
664+
665+
foreach ($query->getClauses() as $clause) {
666+
$this->clauses[] = $clause;
667+
}
668+
669+
return $this;
670+
}
671+
641672
/**
642673
* Creates a CALL sub query clause.
643674
*
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
namespace WikibaseSolutions\CypherDSL\Tests\Unit\Clauses;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use WikibaseSolutions\CypherDSL\Clauses\UnionClause;
7+
use WikibaseSolutions\CypherDSL\Query;
8+
9+
class UnionClauseTest extends TestCase
10+
{
11+
public function testNoCombine(): void
12+
{
13+
$union = new UnionClause();
14+
15+
$this->assertFalse($union->includesAll());
16+
17+
$this->assertEquals('UNION', $union->toQuery());
18+
}
19+
20+
public function testAll(): void
21+
{
22+
$union = new UnionClause(true);
23+
24+
$this->assertTrue($union->includesAll());
25+
26+
$this->assertEquals('UNION ALL', $union->toQuery());
27+
}
28+
29+
public function testUnionFactory(): void
30+
{
31+
$nodeX = Query::node('X')->named('x');
32+
$nodeY = Query::node('Y')->named('y');
33+
34+
$left = Query::new()->match($nodeX)->returning($nodeX->getVariable());
35+
$right = Query::new()->match($nodeY)->returning($nodeY->getVariable());
36+
37+
$query = UnionClause::union($left, $right, false);
38+
39+
$this->assertEquals('MATCH (x:X) RETURN x UNION MATCH (y:Y) RETURN y', $query->toQuery());
40+
}
41+
42+
public function testUnionFactoryAll(): void
43+
{
44+
$nodeX = Query::node('X')->named('x');
45+
$nodeY = Query::node('Y')->named('y');
46+
47+
$left = Query::new()->match($nodeX)->returning($nodeX->getVariable());
48+
$right = Query::new()->match($nodeY)->returning($nodeY->getVariable());
49+
50+
$query = UnionClause::union($left, $right, true);
51+
52+
$this->assertEquals('MATCH (x:X) RETURN x UNION ALL MATCH (y:Y) RETURN y', $query->toQuery());
53+
}
54+
}

tests/Unit/QueryTest.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use WikibaseSolutions\CypherDSL\Assignment;
2828
use WikibaseSolutions\CypherDSL\Clauses\Clause;
2929
use WikibaseSolutions\CypherDSL\Clauses\MatchClause;
30+
use WikibaseSolutions\CypherDSL\Clauses\UnionClause;
3031
use WikibaseSolutions\CypherDSL\Exists;
3132
use WikibaseSolutions\CypherDSL\ExpressionList;
3233
use WikibaseSolutions\CypherDSL\Literals\Boolean;
@@ -1033,6 +1034,60 @@ public function testWikiExamples(): void
10331034
$this->assertSame("((nineties.released >= 1990) AND (nineties IS NOT NULL))", $expression->toQuery());
10341035
}
10351036

1037+
public function testUnionQueryAll(): void
1038+
{
1039+
$nodeX = Query::node('X')->named('x');
1040+
$nodeY = Query::node('Y')->named('y');
1041+
1042+
$query = Query::new()->match($nodeX)->returning($nodeX->getVariable());
1043+
$right = Query::new()->match($nodeY)->returning($nodeY->getVariable());
1044+
1045+
$query = $query->union($right, true);
1046+
1047+
$this->assertEquals('MATCH (x:X) RETURN x UNION ALL MATCH (y:Y) RETURN y', $query->toQuery());
1048+
}
1049+
1050+
public function testUnionQuery(): void
1051+
{
1052+
$nodeX = Query::node('X')->named('x');
1053+
$nodeY = Query::node('Y')->named('y');
1054+
1055+
$query = Query::new()->match($nodeX)->returning($nodeX->getVariable());
1056+
$right = Query::new()->match($nodeY)->returning($nodeY->getVariable());
1057+
1058+
$query = $query->union($right, false);
1059+
1060+
$this->assertEquals('MATCH (x:X) RETURN x UNION MATCH (y:Y) RETURN y', $query->toQuery());
1061+
}
1062+
1063+
public function testUnionDecorator(): void
1064+
{
1065+
$nodeX = Query::node('X')->named('x');
1066+
1067+
$query = Query::new()->match($nodeX)->returning($nodeX->getVariable());
1068+
1069+
$query = $query->union(function (Query $query) {
1070+
$nodeY = Query::node('Y')->named('y');
1071+
$query->match($nodeY)->returning($nodeY->getVariable());
1072+
});
1073+
1074+
$this->assertEquals('MATCH (x:X) RETURN x UNION MATCH (y:Y) RETURN y', $query->toQuery());
1075+
}
1076+
1077+
public function testUnionDecoratorAll(): void
1078+
{
1079+
$nodeX = Query::node('X')->named('x');
1080+
1081+
$query = Query::new()->match($nodeX)->returning($nodeX->getVariable());
1082+
1083+
$query = $query->union(function (Query $query) {
1084+
$nodeY = Query::node('Y')->named('y');
1085+
$query->match($nodeY)->returning($nodeY->getVariable());
1086+
}, true);
1087+
1088+
$this->assertEquals('MATCH (x:X) RETURN x UNION ALL MATCH (y:Y) RETURN y', $query->toQuery());
1089+
}
1090+
10361091
public function testAutomaticIdentifierGeneration(): void
10371092
{
10381093
$node = Query::node();

0 commit comments

Comments
 (0)