Skip to content

Commit bf9de59

Browse files
committed
resolved merge conflicts
2 parents 065fbdd + b30ef26 commit bf9de59

File tree

16 files changed

+456
-253
lines changed

16 files changed

+456
-253
lines changed

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# Neo4j PHP Client and Driver
22

33
[![GitHub](https://img.shields.io/github/license/neo4j-php/neo4j-php-client)](https://github.com/laudis-technologies/neo4j-php-client/blob/main/LICENSE)
4-
[![Code Climate maintainability](https://img.shields.io/codeclimate/maintainability/laudis-technologies/neo4j-php-client)](https://codeclimate.com/github/laudis-technologies/neo4j-php-client/maintainability)
54
[![Packagist PHP Version Support (custom server)](https://img.shields.io/packagist/php-v/laudis/neo4j-php-client)](https://packagist.org/packages/laudis/neo4j-php-client)
65
[![Latest Stable Version](https://poser.pugx.org/laudis/neo4j-php-client/v)](https://packagist.org/packages/laudis/neo4j-php-client)
76

src/Bolt/BoltConnection.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,14 @@ public function getServerAgent(): string
102102
return $this->config->getServerAgent();
103103
}
104104

105+
/**
106+
* @psalm-mutation-free
107+
*/
108+
public function getServerVersion(): string
109+
{
110+
return explode('/', $this->getServerAgent())[1] ?? '';
111+
}
112+
105113
/**
106114
* @psalm-mutation-free
107115
*/
@@ -431,6 +439,32 @@ public function assertNoFailure(Response $response): void
431439
}
432440
}
433441

442+
/**
443+
* Discard unconsumed results - sends DISCARD to server for each subscribed result.
444+
*/
445+
public function discardUnconsumedResults(): void
446+
{
447+
$this->logger?->log(LogLevel::DEBUG, 'Discarding unconsumed results');
448+
449+
$this->subscribedResults = array_values(array_filter(
450+
$this->subscribedResults,
451+
static fn (WeakReference $ref): bool => $ref->get() !== null
452+
));
453+
454+
if (!empty($this->subscribedResults)) {
455+
try {
456+
$this->discard(null);
457+
$this->logger?->log(LogLevel::DEBUG, 'Sent DISCARD ALL for unconsumed results');
458+
} catch (Throwable $e) {
459+
$this->logger?->log(LogLevel::ERROR, 'Failed to discard results', [
460+
'exception' => $e->getMessage(),
461+
]);
462+
}
463+
}
464+
465+
$this->subscribedResults = [];
466+
}
467+
434468
/**
435469
* Discard unconsumed results - sends DISCARD to server for each subscribed result.
436470
*/

src/Bolt/Session.php

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
use Exception;
1717
use Laudis\Neo4j\Common\GeneratorHelper;
1818
use Laudis\Neo4j\Common\Neo4jLogger;
19-
use Laudis\Neo4j\Common\TransactionHelper;
2019
use Laudis\Neo4j\Contracts\ConnectionPoolInterface;
2120
use Laudis\Neo4j\Contracts\SessionInterface;
2221
use Laudis\Neo4j\Contracts\TransactionInterface;
@@ -39,6 +38,8 @@
3938
*/
4039
final class Session implements SessionInterface
4140
{
41+
private const ROLLBACK_CLASSIFICATIONS = ['ClientError', 'TransientError', 'DatabaseError'];
42+
4243
/** @var list<BoltConnection> */
4344
private array $usedConnections = [];
4445
/** @psalm-readonly */
@@ -101,21 +102,59 @@ public function writeTransaction(callable $tsxHandler, ?TransactionConfiguration
101102
$this->getLogger()?->log(LogLevel::INFO, 'Beginning write transaction', ['config' => $config]);
102103
$config = $this->mergeTsxConfig($config);
103104

104-
return TransactionHelper::retry(
105-
fn () => $this->startTransaction($config, $this->config->withAccessMode(AccessMode::WRITE())),
106-
$tsxHandler
107-
);
105+
return $this->retry($tsxHandler, false, $config);
108106
}
109107

110108
public function readTransaction(callable $tsxHandler, ?TransactionConfiguration $config = null)
111109
{
112110
$this->getLogger()?->log(LogLevel::INFO, 'Beginning read transaction', ['config' => $config]);
113111
$config = $this->mergeTsxConfig($config);
114112

115-
return TransactionHelper::retry(
116-
fn () => $this->startTransaction($config, $this->config->withAccessMode(AccessMode::READ())),
117-
$tsxHandler
118-
);
113+
return $this->retry($tsxHandler, true, $config);
114+
}
115+
116+
/**
117+
* @template U
118+
*
119+
* @param callable(TransactionInterface):U $tsxHandler
120+
*
121+
* @return U
122+
*/
123+
private function retry(callable $tsxHandler, bool $read, TransactionConfiguration $config)
124+
{
125+
while (true) {
126+
$transaction = null;
127+
try {
128+
if ($read) {
129+
$transaction = $this->startTransaction($config, $this->config->withAccessMode(AccessMode::READ()));
130+
} else {
131+
$transaction = $this->startTransaction($config, $this->config->withAccessMode(AccessMode::WRITE()));
132+
}
133+
$tbr = $tsxHandler($transaction);
134+
self::triggerLazyResult($tbr);
135+
$transaction->commit();
136+
137+
return $tbr;
138+
} catch (Neo4jException $e) {
139+
if ($transaction && !in_array($e->getClassification(), self::ROLLBACK_CLASSIFICATIONS)) {
140+
$transaction->rollback();
141+
}
142+
143+
if ($e->getTitle() === 'NotALeader') {
144+
// By closing the pool, we force the connection to be re-acquired and the routing table to be refetched
145+
$this->pool->close();
146+
} elseif ($e->getClassification() !== 'TransientError') {
147+
throw $e;
148+
}
149+
}
150+
}
151+
}
152+
153+
private static function triggerLazyResult(mixed $tbr): void
154+
{
155+
if ($tbr instanceof CypherSequence) {
156+
$tbr->preload();
157+
}
119158
}
120159

121160
public function transaction(callable $tsxHandler, ?TransactionConfiguration $config = null)

src/Databags/Notification.php

Lines changed: 1 addition & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
/**
2020
* @psalm-immutable
2121
*
22-
* @template-extends AbstractCypherObject<string, string|Position>
22+
* @template-extends AbstractCypherObject<string, string|array<string, float|int|null|string>>
2323
*/
2424
final class Notification extends AbstractCypherObject
2525
{
@@ -57,16 +57,6 @@ public function getCodeClassification(): string
5757
return $this->splitCode()['classification'];
5858
}
5959

60-
public function getCodeCategory(): string
61-
{
62-
return $this->splitCode()['category'];
63-
}
64-
65-
public function getCodeTitle(): string
66-
{
67-
return $this->splitCode()['title'];
68-
}
69-
7060
public function getSeverity(): string
7161
{
7262
return $this->severity;
@@ -92,44 +82,10 @@ public function getTitle(): string
9282
return $this->title;
9383
}
9484

95-
public function getCategory(): string
96-
{
97-
return $this->category;
98-
}
99-
10085
/**
101-
* Matches inherited return type: array<string, string|Position>.
102-
*
10386
* @psalm-external-mutation-free
104-
*
105-
* @return array<string, string|Position>
10687
*/
10788
public function toArray(): array
108-
{
109-
return [
110-
'severity' => $this->severity,
111-
'description' => $this->description,
112-
'code' => $this->code,
113-
'position' => $this->position,
114-
'title' => $this->title,
115-
'category' => $this->category,
116-
];
117-
}
118-
119-
/**
120-
* If you still want a version with the position converted to array,
121-
* use this custom method instead of overriding toArray().
122-
*
123-
* @return array{
124-
* severity: string,
125-
* description: string,
126-
* code: string,
127-
* position: array<string, float|int|null|string>,
128-
* title: string,
129-
* category: string
130-
* }
131-
*/
132-
public function toSerializedArray(): array
13389
{
13490
return [
13591
'severity' => $this->severity,

src/Formatter/Specialised/BoltOGMTranslator.php

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,13 @@ private function makeFromBoltNode(BoltNode $node): Node
102102
$properties[$name] = $this->mapValueToType($property);
103103
}
104104

105-
/** @var ?string|null $elementId */
106-
$elementId = null;
107-
if ($node instanceof \Bolt\protocol\v5\structures\Node) {
108-
$elementId = $node->element_id;
105+
/** @var string|null $elementId */
106+
if (property_exists($node, 'element_id')) {
107+
/** @var string|null $elementIdValue */
108+
$elementIdValue = $node->element_id ?? null;
109+
$elementId = is_string($elementIdValue) ? $elementIdValue : (string) $node->id;
110+
} else {
111+
$elementId = (string) $node->id;
109112
}
110113

111114
/**
@@ -180,9 +183,28 @@ private function makeFromBoltRelationship(BoltRelationship $rel): Relationship
180183
}
181184

182185
/** @var string|null $elementId */
183-
$elementId = null;
184-
if ($rel instanceof \Bolt\protocol\v5\structures\Relationship) {
185-
$elementId = $rel->element_id;
186+
if (property_exists($rel, 'element_id')) {
187+
/** @var string|null $elementIdValue */
188+
$elementIdValue = $rel->element_id ?? null;
189+
$elementId = is_string($elementIdValue) ? $elementIdValue : (string) $rel->id;
190+
} else {
191+
$elementId = (string) $rel->id;
192+
}
193+
194+
if (property_exists($rel, 'startNodeElementId')) {
195+
/** @var string|null $startNodeElementIdValue */
196+
$startNodeElementIdValue = $rel->startNodeElementId ?? null;
197+
$startNodeElementId = is_string($startNodeElementIdValue) ? $startNodeElementIdValue : (string) $rel->startNodeId;
198+
} else {
199+
$startNodeElementId = (string) $rel->startNodeId;
200+
}
201+
202+
if (property_exists($rel, 'endNodeElementId')) {
203+
/** @var string|null $endNodeElementIdValue */
204+
$endNodeElementIdValue = $rel->endNodeElementId ?? null;
205+
$endNodeElementId = is_string($endNodeElementIdValue) ? $endNodeElementIdValue : (string) $rel->endNodeId;
206+
} else {
207+
$endNodeElementId = (string) $rel->endNodeId;
186208
}
187209

188210
return new Relationship(
@@ -191,7 +213,9 @@ private function makeFromBoltRelationship(BoltRelationship $rel): Relationship
191213
$rel->endNodeId,
192214
$rel->type,
193215
new CypherMap($map),
194-
$elementId
216+
$elementId,
217+
$startNodeElementId,
218+
$endNodeElementId
195219
);
196220
}
197221

@@ -207,9 +231,13 @@ private function makeFromBoltUnboundRelationship(BoltUnboundRelationship $rel):
207231
$map[$key] = $this->mapValueToType($property);
208232
}
209233

210-
$elementId = null;
211-
if ($rel instanceof \Bolt\protocol\v5\structures\UnboundRelationship) {
212-
$elementId = $rel->element_id;
234+
/** @var string|null $elementId */
235+
if (property_exists($rel, 'element_id')) {
236+
/** @var string|null $elementIdValue */
237+
$elementIdValue = $rel->element_id ?? null;
238+
$elementId = is_string($elementIdValue) ? $elementIdValue : (string) $rel->id;
239+
} else {
240+
$elementId = (string) $rel->id;
213241
}
214242

215243
return new UnboundRelationship(

src/Types/Relationship.php

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ public function __construct(
3434
string $type,
3535
CypherMap $properties,
3636
?string $elementId,
37+
private readonly ?string $startNodeElementId = null,
38+
private readonly ?string $endNodeElementId = null,
3739
) {
3840
parent::__construct($id, $type, $properties, $elementId);
3941
}
@@ -54,6 +56,22 @@ public function getEndNodeId(): int
5456
return $this->endNodeId;
5557
}
5658

59+
/**
60+
* Returns the element ID of the start node.
61+
*/
62+
public function getStartNodeElementId(): ?string
63+
{
64+
return $this->startNodeElementId;
65+
}
66+
67+
/**
68+
* Returns the element ID of the end node.
69+
*/
70+
public function getEndNodeElementId(): ?string
71+
{
72+
return $this->endNodeElementId;
73+
}
74+
5775
/**
5876
* @psalm-suppress ImplementedReturnTypeMismatch False positive.
5977
*
@@ -62,7 +80,9 @@ public function getEndNodeId(): int
6280
* type: string,
6381
* startNodeId: int,
6482
* endNodeId: int,
65-
* properties: CypherMap<OGMTypes>
83+
* properties: CypherMap<OGMTypes>,
84+
* startNodeElementId: ?string,
85+
* endNodeElementId: ?string
6686
* }
6787
*/
6888
public function toArray(): array
@@ -71,6 +91,8 @@ public function toArray(): array
7191

7292
$tbr['startNodeId'] = $this->getStartNodeId();
7393
$tbr['endNodeId'] = $this->getEndNodeId();
94+
$tbr['startNodeElementId'] = $this->getStartNodeElementId();
95+
$tbr['endNodeElementId'] = $this->getEndNodeElementId();
7496

7597
return $tbr;
7698
}

testkit-backend/src/Handlers/AbstractRunner.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public function handle($request): ResultResponse|DriverErrorResponse
5959
$params = [];
6060
if ($request->getParams() !== null) {
6161
foreach ($request->getParams() as $key => $value) {
62-
$params[$key] = $this->decodeToValue($value);
62+
$params[$key] = self::decodeToValue($value);
6363
}
6464
}
6565

@@ -68,7 +68,7 @@ public function handle($request): ResultResponse|DriverErrorResponse
6868
$actualMeta = [];
6969
if ($metaData !== null) {
7070
foreach ($metaData as $key => $meta) {
71-
$actualMeta[$key] = $this->decodeToValue($meta);
71+
$actualMeta[$key] = self::decodeToValue($meta);
7272
}
7373
}
7474
$config = TransactionConfiguration::default()->withMetadata($actualMeta)->withTimeout($request->getTimeout());
@@ -105,7 +105,7 @@ public function handle($request): ResultResponse|DriverErrorResponse
105105
*
106106
* @return scalar|AbstractCypherObject|iterable|null
107107
*/
108-
private function decodeToValue(array $param)
108+
public static function decodeToValue(array $param)
109109
{
110110
$value = $param['data']['value'];
111111
if (is_iterable($value)) {
@@ -118,7 +118,7 @@ private function decodeToValue(array $param)
118118
*/
119119
foreach ($value as $k => $v) {
120120
/** @psalm-suppress MixedArgument */
121-
$map[(string) $k] = $this->decodeToValue($v);
121+
$map[(string) $k] = self::decodeToValue($v);
122122
}
123123

124124
return new CypherMap($map);
@@ -131,7 +131,7 @@ private function decodeToValue(array $param)
131131
*/
132132
foreach ($value as $v) {
133133
/** @psalm-suppress MixedArgument */
134-
$list[] = $this->decodeToValue($v);
134+
$list[] = self::decodeToValue($v);
135135
}
136136

137137
return new CypherList($list);

0 commit comments

Comments
 (0)