Skip to content

Commit 4e99cb8

Browse files
Allow property directly on node
1 parent e535ed5 commit 4e99cb8

File tree

6 files changed

+104
-28
lines changed

6 files changed

+104
-28
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535
},
3636
"require": {
3737
"php": ">=7.4",
38-
"ext-ctype": "*"
38+
"ext-ctype": "*",
39+
"ext-openssl": "*"
3940
},
4041
"require-dev": {
4142
"phpunit/phpunit": "~9.0",

src/Patterns/Node.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@
2222
namespace WikibaseSolutions\CypherDSL\Patterns;
2323

2424
use InvalidArgumentException;
25+
use WikibaseSolutions\CypherDSL\Property;
2526
use WikibaseSolutions\CypherDSL\PropertyMap;
2627
use WikibaseSolutions\CypherDSL\Traits\EscapeTrait;
28+
use WikibaseSolutions\CypherDSL\Traits\IdentifierGenerationTrait;
2729
use WikibaseSolutions\CypherDSL\Traits\NodeTypeTrait;
2830
use WikibaseSolutions\CypherDSL\Types\AnyType;
2931
use WikibaseSolutions\CypherDSL\Types\CompositeTypes\MapType;
@@ -39,6 +41,7 @@ class Node implements NodeType
3941
{
4042
use EscapeTrait;
4143
use NodeTypeTrait;
44+
use IdentifierGenerationTrait;
4245

4346
/**
4447
* @var string[]
@@ -154,7 +157,6 @@ public function getName(): ?Variable
154157
* @param $variable
155158
* @return $this
156159
* @see Node::named()
157-
*
158160
*/
159161
public function setName($variable): self
160162
{
@@ -171,6 +173,18 @@ public function hasName(): bool
171173
return isset($this->variable);
172174
}
173175

176+
/**
177+
* Returns the property of the given name for this expression. For instance, if this expression is the
178+
* variable "foo", a function call like $expression->property("bar") would yield "foo.bar".
179+
*
180+
* @param string $property
181+
* @return Property
182+
*/
183+
public function property(string $property): Property
184+
{
185+
return new Property($this->variableIfNode($this), $property);
186+
}
187+
174188
/**
175189
* Returns the string representation of this relationship that can be used directly
176190
* in a query.

src/Query.php

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
use WikibaseSolutions\CypherDSL\Patterns\Node;
4646
use WikibaseSolutions\CypherDSL\Patterns\Path;
4747
use WikibaseSolutions\CypherDSL\Traits\EscapeTrait;
48+
use WikibaseSolutions\CypherDSL\Traits\IdentifierGenerationTrait;
4849
use WikibaseSolutions\CypherDSL\Types\AnyType;
4950
use WikibaseSolutions\CypherDSL\Types\CompositeTypes\ListType;
5051
use WikibaseSolutions\CypherDSL\Types\CompositeTypes\MapType;
@@ -61,6 +62,7 @@
6162
class Query implements QueryConvertable
6263
{
6364
use EscapeTrait;
65+
use IdentifierGenerationTrait;
6466

6567
// A reference to the Literal class
6668
const literal = Literal::class;
@@ -270,19 +272,8 @@ public function returning($expressions, bool $distinct = false): self
270272
throw new TypeError("\$expressions should only consist of AnyType objects");
271273
}
272274

273-
274-
// check if expression is node, then replace with variable
275-
if ($expression instanceof Node) {
276-
// check if Node has Name setted
277-
if (!$expression->hasName()) {
278-
$expression->setName(uniqid());
279-
}
280-
281-
$expression = $expression->getName();
282-
}
283-
284275
$alias = is_integer($maybeAlias) ? "" : $maybeAlias;
285-
$returnClause->addColumn($expression, $alias);
276+
$returnClause->addColumn($this->variableIfNode($expression), $alias);
286277
}
287278

288279
$returnClause->setDistinct($distinct);
@@ -575,8 +566,8 @@ public function where(AnyType $expression): self
575566
/**
576567
* Creates the WITH clause.
577568
*
578-
* @param AnyType[]|AnyType $expressions The entries to add; if the array-key is
579-
* non-numerical, it is used as the alias
569+
* @param AnyType[]|AnyType $expressions The entries to add; if the array-key is non-numerical, it is used as the alias
570+
*
580571
*
581572
* @return Query
582573
* @see https://neo4j.com/docs/cypher-manual/current/clauses/with/
@@ -595,17 +586,8 @@ public function with($expressions): self
595586
throw new TypeError("\$expressions should only consist of AnyType objects");
596587
}
597588

598-
// check if expression is node, then replace with variable
599-
if ($expression instanceof Node) {
600-
// check if Node has Name setted
601-
if (!$expression->hasName()) {
602-
$expression->named(uniqid());
603-
}
604-
$expression = $expression->getName();
605-
}
606-
607589
$alias = is_integer($maybeAlias) ? "" : $maybeAlias;
608-
$withClause->addEntry($expression, $alias);
590+
$withClause->addEntry($this->variableIfNode($expression), $alias);
609591
}
610592

611593
$this->clauses[] = $withClause;
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace WikibaseSolutions\CypherDSL\Traits;
4+
5+
use WikibaseSolutions\CypherDSL\Patterns\Node;
6+
7+
trait IdentifierGenerationTrait
8+
{
9+
/**
10+
* Cast the given value to a Variable if it is a node.
11+
*
12+
* @param mixed $value
13+
* @return mixed
14+
*/
15+
public function variableIfNode($value)
16+
{
17+
if (!($value instanceof Node)) {
18+
return $value;
19+
}
20+
21+
if (!$value->hasName()) {
22+
$value->setName($this->generateUUID());
23+
}
24+
25+
return $value->getName();
26+
}
27+
28+
/**
29+
* Generates a unique random identifier.
30+
*
31+
* @note It is not entirely guaranteed that this function gives a truly unique identifier. However, because the
32+
* number of possible IDs is so huge, it should not be a problem.
33+
*
34+
* @param int $length
35+
* @return string
36+
*/
37+
private function generateUUID(int $length = 32): string
38+
{
39+
return substr(bin2hex(openssl_random_pseudo_bytes(ceil($length / 2))), 0, $length);
40+
}
41+
}

tests/Unit/Patterns/NodeTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,21 @@ public function testAddingProperties()
192192
$this->assertSame("({foo: 'baz', baz: 'bar', qux: 'baz'})", $node->toQuery());
193193
}
194194

195+
public function testPropertyWithName()
196+
{
197+
$node = new Node();
198+
$node->named('example');
199+
200+
$this->assertSame('example.foo', $node->property('foo')->toQuery());
201+
}
202+
203+
public function testPropertyWithoutName()
204+
{
205+
$node = new Node();
206+
207+
$this->assertMatchesRegularExpression("/^[0-9a-f]+\.foo$/", $node->property('foo')->toQuery());
208+
}
209+
195210
public function provideOnlyLabelData(): array
196211
{
197212
return [

tests/Unit/QueryTest.php

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,11 +230,18 @@ public function testReturning()
230230

231231
public function testReturningWithNode()
232232
{
233-
$node = Query::node("(m)");
233+
$node = Query::node("m");
234234

235235
$statement = (new Query())->returning($node)->build();
236236

237-
$this->assertMatchesRegularExpression("/(RETURN [0-Z])\w+/", $statement);
237+
$this->assertMatchesRegularExpression("/(RETURN [0-9a-f]+)/", $statement);
238+
239+
$node = Query::node("m");
240+
$node->named('example');
241+
242+
$statement = (new Query())->returning($node)->build();
243+
244+
$this->assertSame('RETURN example', $statement);
238245
}
239246

240247
public function testCreate()
@@ -388,6 +395,22 @@ public function testWith()
388395
$this->assertSame("WITH a < b AS foobar", $statement);
389396
}
390397

398+
public function testWithWithNode()
399+
{
400+
$node = Query::node('m');
401+
402+
$statement = (new Query())->with($node)->build();
403+
404+
$this->assertMatchesRegularExpression("/(WITH [0-9a-f]+)/", $statement);
405+
406+
$node = Query::node("m");
407+
$node->named('example');
408+
409+
$statement = (new Query())->with($node)->build();
410+
411+
$this->assertSame('WITH example', $statement);
412+
}
413+
391414
public function testCallProcedure()
392415
{
393416
$procedure = "apoc.json";

0 commit comments

Comments
 (0)