diff --git a/src/RedisGraph/Edge.php b/src/RedisGraph/Edge.php index 17f792f..190d215 100644 --- a/src/RedisGraph/Edge.php +++ b/src/RedisGraph/Edge.php @@ -6,20 +6,20 @@ final class Edge { - private string $type; + private string $queryPredicate; private Node $sourceNode; private Node $destinationNode; private ?string $relation; private ?iterable $properties = []; public function __construct( - string $type, + string $queryPredicate, Node $sourceNode, ?string $relation, Node $destinationNode, ?iterable $properties = [] ) { - $this->type = $type; + $this->queryPredicate = $queryPredicate; $this->sourceNode = $sourceNode; $this->destinationNode = $destinationNode; $this->relation = $relation; @@ -44,12 +44,12 @@ public function withProperties(iterable $properties): self public function getType(): string { - return $this->type; + return $this->queryPredicate; } public function toString(): string { - if ($this->type === 'MERGE') { + if ($this->queryPredicate === 'MERGE') { return $this->toStringWithMerge(); } return $this->toStringWithCreate(); @@ -105,4 +105,9 @@ private function getPropValueWithDataType($propValue) } return (int) $propValue; } + + public function getQueryPredicate(): string + { + return $this->queryPredicate; + } } diff --git a/src/RedisGraph/GraphConstructor.php b/src/RedisGraph/GraphConstructor.php index fd769e5..f4456ed 100644 --- a/src/RedisGraph/GraphConstructor.php +++ b/src/RedisGraph/GraphConstructor.php @@ -9,7 +9,9 @@ class GraphConstructor { private string $name; + /** @var Node[] */ private array $nodes = []; + /** @var Edge[] */ private array $edges = []; public function __construct(string $name) @@ -47,10 +49,10 @@ public function getCommitQueryWithMerge(): QueryInterface { $query = ''; foreach ($this->nodes as $index => $node) { - $query .= 'MERGE ' . $node->toString() . ' '; + $query .= $node->getQueryPredicate() . ' ' . $node->toString() . ' '; } foreach ($this->edges as $index => $edge) { - $query .= 'MERGE ' . $edge->toString() . ' '; + $query .= $edge->getQueryPredicate() . ' ' . $edge->toString() . ' '; } return new Query($this->name, trim($query)); } diff --git a/src/RedisGraph/Node.php b/src/RedisGraph/Node.php index 5e7c4f5..37cedb0 100644 --- a/src/RedisGraph/Node.php +++ b/src/RedisGraph/Node.php @@ -9,12 +9,14 @@ final class Node private ?string $label; private ?iterable $properties; private ?string $alias = null; + private ?string $queryPredicate; - public function __construct(?string $label = null, ?iterable $properties = null) + public function __construct(?string $label = null, ?iterable $properties = null, ?string $queryPredicate = null) { $this->label = $label; $this->alias = randomString(); $this->properties = $properties; + $this->queryPredicate = $queryPredicate; } public static function create(): self @@ -53,6 +55,13 @@ public function withAlias(string $alias): self return $new; } + public function withQueryPredicate(string $queryPredicate): self + { + $new = clone $this; + $new->queryPredicate = $queryPredicate; + return $new; + } + public function getAlias(): string { return $this->alias; @@ -104,4 +113,9 @@ private function getPropValueWithDataType($propValue) } return (int) $propValue; } + + public function getQueryPredicate(): string + { + return $this->queryPredicate ?? 'MATCH'; + } } diff --git a/tests/Module/GraphConstructorTest.php b/tests/Module/GraphConstructorTest.php index 55a1a72..70bc2b1 100644 --- a/tests/Module/GraphConstructorTest.php +++ b/tests/Module/GraphConstructorTest.php @@ -56,9 +56,11 @@ public function shouldCreateQueryObjectWithMergeSuccessfully(): void $propertiesDestination = ['name' => 'Japan']; $edgeProperties = ['purpose' => 'pleasure', 'duration' => 'two weeks']; - $person = Node::createWithLabel($labelSource)->withProperties($propertiesSource)->withAlias('CatOwner'); + $person = Node::createWithLabel($labelSource)->withProperties($propertiesSource)->withAlias('CatOwner') + ->withQueryPredicate('MERGE'); $country = Node::createWithLabelAndProperties($labelDestination, $propertiesDestination) - ->withAlias('CatCountry'); + ->withAlias('CatCountry') + ->withQueryPredicate('MERGE'); $edge = Edge::merge($person, 'visited', $country)->withProperties($edgeProperties); @@ -75,4 +77,36 @@ public function shouldCreateQueryObjectWithMergeSuccessfully(): void ); $this->assertEquals('TRAVELLERS', $query->getName()); } + + /** + * @test + */ + public function shouldCreateQueryObjectWithMergeOnlyEdgesSuccessfully(): void + { + $labelSource = 'person'; + $labelDestination = 'country'; + + $propertiesSource = ['name' => 'John Doe', 'age' => 33, 'gender' => 'male', 'status' => 'single']; + $propertiesDestination = ['name' => 'Japan']; + $edgeProperties = ['purpose' => 'pleasure', 'duration' => 'two weeks']; + + $person = Node::createWithLabel($labelSource)->withProperties($propertiesSource)->withAlias('CatOwner'); + $country = Node::createWithLabelAndProperties($labelDestination, $propertiesDestination) + ->withAlias('CatCountry'); + + $edge = Edge::merge($person, 'visited', $country)->withProperties($edgeProperties); + + $graph = new GraphConstructor('TRAVELLERS'); + $graph->addNode($person); + $graph->addNode($country); + $graph->addEdge($edge); + $query = $graph->getCommitQueryWithMerge(); + $this->assertEquals( + 'MATCH (CatOwner:person {name: "John Doe", age: 33, gender: "male", status: "single"}) ' . + 'MATCH (CatCountry:country {name: "Japan"}) ' . + 'MERGE (CatOwner)-[:visited {purpose: "pleasure", duration: "two weeks"}]->(CatCountry)', + $query->getQueryString() + ); + $this->assertEquals('TRAVELLERS', $query->getName()); + } } diff --git a/tests/Module/RedisGraphTest.php b/tests/Module/RedisGraphTest.php index 0165df5..df5c514 100644 --- a/tests/Module/RedisGraphTest.php +++ b/tests/Module/RedisGraphTest.php @@ -83,16 +83,20 @@ public function shouldReturnQueryExecuteResultsSuccessfully(): void $propertiesDestination = ['name' => 'Japan']; $edgeProperties = ['purpose' => 'pleasure', 'duration' => 'one weeks']; - $person2 = Node::createWithLabel($labelSource)->withProperties($propertiesSource); - $country2 = Node::createWithLabelAndProperties($labelDestination, $propertiesDestination); + $person2 = Node::createWithLabel($labelSource)->withProperties($propertiesSource) + ->withQueryPredicate('MERGE'); + $country2 = Node::createWithLabelAndProperties($labelDestination, $propertiesDestination) + ->withQueryPredicate('MERGE'); $edge2 = Edge::merge($person2, 'visited', $country2)->withProperties($edgeProperties); $propertiesSource = ['name' => 'Kedibey', 'age' => 13, 'gender' => 'male', 'status' => 'single']; $propertiesDestination = ['name' => 'Turkey']; $edgeProperties = ['purpose' => 'living', 'duration' => 'whole life']; - $person3 = Node::createWithLabel($labelSource)->withProperties($propertiesSource); - $country3 = Node::createWithLabelAndProperties($labelDestination, $propertiesDestination); + $person3 = Node::createWithLabel($labelSource)->withProperties($propertiesSource) + ->withQueryPredicate('MERGE'); + $country3 = Node::createWithLabelAndProperties($labelDestination, $propertiesDestination) + ->withQueryPredicate('MERGE'); $edge3 = Edge::merge($person3, 'visited', $country3)->withProperties($edgeProperties); $graph = new GraphConstructor('TRAVELLERS'); @@ -141,6 +145,49 @@ public function shouldReturnQueryExecuteResultsSuccessfully(): void $this->assertEquals('p.name', $labels[0]); $this->assertEquals('John Doe', $resultSet[0][0]); + // adding a relationship + $graph = clone $graph; + $edge4 = Edge::merge($person3, 'moved_to', $country3)->withProperties($edgeProperties); + $graph->addEdge($edge4); + $commitQuery = $graph->getCommitQueryWithMerge(); + $this->redisGraph->commit($commitQuery); + + $matchQueryString = 'MATCH (p:person)-[v:moved_to {purpose:"living"}]->(c:country) + RETURN p.name, p.age, v.duration, c.name'; + $matchQuery = new Query('TRAVELLERS', $matchQueryString); + + $explain = $this->redisGraph->explain($matchQuery); + $this->assertStringContainsString('Results', $explain); + $this->assertStringContainsString('Filter', $explain); + $this->assertStringContainsString('Conditional Traverse', $explain); + $this->assertStringContainsString('Node By Label Scan', $explain); + + $result = $this->redisGraph->query($matchQuery); + ob_start(); + $result->prettyPrint(); + $content = ob_get_clean(); + echo $content; + $expectedLines = <<assertStringContainsString($lines[0], $content, 'PrettyPrint'); + $this->assertStringContainsString($lines[1], $content, 'PrettyPrint'); + $this->assertStringContainsString($lines[2], $content, 'PrettyPrint'); + $this->assertStringContainsString($lines[3], $content, 'PrettyPrint'); + + $resultSet = $result->getResultSet(); + $labels = $result->getLabels(); + $this->assertEquals('p.name', $labels[0]); + $this->assertEquals('v.duration', $labels[2]); + $this->assertEquals('Kedibey', $resultSet[0][0]); + + // @todo delete could be done in setup to reduce test flake $delete = $this->redisGraph->delete('TRAVELLERS'); $this->assertStringContainsString('Graph removed', $delete);