Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions src/Authentication/BasicAuth.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public function __construct(
/**
* @throws Exception
*
* @return array{server: string, connection_id: string, hints: list}
* @return array{server: string, connection_id: string, hints: list, patch_bolt?: list<string>}
*/
public function authenticateBolt(BoltConnection $connection, string $userAgent): array
{
Expand All @@ -44,6 +44,9 @@ public function authenticateBolt(BoltConnection $connection, string $userAgent):
$protocol = $connection->protocol();
if (method_exists($protocol, 'logon')) {
$helloMetadata = ['user_agent' => $userAgent];
if ($connection->getProtocol()->needsBoltUtcPatchInHello()) {
$helloMetadata['patch_bolt'] = ['utc'];
}

$responseHello = $factory->createHelloMessage($helloMetadata)->send()->getResponse();

Expand All @@ -55,7 +58,7 @@ public function authenticateBolt(BoltConnection $connection, string $userAgent):

$response = $factory->createLogonMessage($credentials)->send()->getResponse();

/** @var array{server: string, connection_id: string, hints: list} */
/** @var array{server: string, connection_id: string, hints: list, patch_bolt?: list<string>} */
return array_merge($responseHello->content, $response->content);
}

Expand All @@ -65,10 +68,13 @@ public function authenticateBolt(BoltConnection $connection, string $userAgent):
'principal' => $this->username,
'credentials' => $this->password,
];
if ($connection->getProtocol()->needsBoltUtcPatchInHello()) {
$helloMetadata['patch_bolt'] = ['utc'];
}

$response = $factory->createHelloMessage($helloMetadata)->send()->getResponse();

/** @var array{server: string, connection_id: string, hints: list} */
/** @var array{server: string, connection_id: string, hints: list, patch_bolt?: list<string>} */
return $response->content;
}

Expand Down
4 changes: 2 additions & 2 deletions src/Authentication/KerberosAuth.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public function __construct(
/**
* @throws Exception
*
* @return array{server: string, connection_id: string, hints: list}
* @return array{server: string, connection_id: string, hints: list, patch_bolt?: list<string>}
*/
public function authenticateBolt(BoltConnection $connection, string $userAgent): array
{
Expand All @@ -56,7 +56,7 @@ public function authenticateBolt(BoltConnection $connection, string $userAgent):
])->send()->getResponse();

/**
* @var array{server: string, connection_id: string, hints: list}
* @var array{server: string, connection_id: string, hints: list, patch_bolt?: list<string>}
*/
return $response->content;
}
Expand Down
6 changes: 3 additions & 3 deletions src/Authentication/NoAuth.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public function __construct(
/**
* @throws Exception
*
* @return array{server: string, connection_id: string, hints: list}
* @return array{server: string, connection_id: string, hints: list, patch_bolt?: list<string>}
*/
public function authenticateBolt(BoltConnection $connection, string $userAgent): array
{
Expand All @@ -46,7 +46,7 @@ public function authenticateBolt(BoltConnection $connection, string $userAgent):

$response = $factory->createLogonMessage(['scheme' => 'none'])->send()->getResponse();

/** @var array{server: string, connection_id: string, hints: list} */
/** @var array{server: string, connection_id: string, hints: list, patch_bolt?: list<string>} */
return $response->content;
}

Expand All @@ -57,7 +57,7 @@ public function authenticateBolt(BoltConnection $connection, string $userAgent):

$response = $factory->createHelloMessage($helloMetadata)->send()->getResponse();

/** @var array{server: string, connection_id: string, hints: list} */
/** @var array{server: string, connection_id: string, hints: list, patch_bolt?: list<string>} */
return $response->content;
}

Expand Down
4 changes: 2 additions & 2 deletions src/Authentication/OpenIDConnectAuth.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public function authenticateHttp(RequestInterface $request, UriInterface $uri, s
/**
* @throws Exception
*
* @return array{server: string, connection_id: string, hints: list}
* @return array{server: string, connection_id: string, hints: list, patch_bolt?: list<string>}
*/
public function authenticateBolt(BoltConnection $connection, string $userAgent): array
{
Expand All @@ -61,7 +61,7 @@ public function authenticateBolt(BoltConnection $connection, string $userAgent):
])->send()->getResponse();

/**
* @var array{server: string, connection_id: string, hints: list}
* @var array{server: string, connection_id: string, hints: list, patch_bolt?: list<string>}
*/
return $response->content;
}
Expand Down
21 changes: 17 additions & 4 deletions src/Bolt/BoltConnection.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,17 @@
use Bolt\error\ConnectException as BoltConnectException;
use Bolt\protocol\Response;
use Bolt\protocol\V3;
use Bolt\protocol\V4;
use Bolt\protocol\V4_1;
use Bolt\protocol\V4_2;
use Bolt\protocol\V4_3;
use Bolt\protocol\V4_4;
use Bolt\protocol\V5;
use Bolt\protocol\V5_1;
use Bolt\protocol\V5_2;
use Bolt\protocol\V5_3;
use Bolt\protocol\V5_4;
use Bolt\protocol\V6;
use Exception;
use Laudis\Neo4j\Common\ConnectionConfiguration;
use Laudis\Neo4j\Common\Neo4jLogger;
Expand All @@ -45,7 +50,7 @@
use WeakReference;

/**
* @implements ConnectionInterface<array{0: V3|V4_4|V5|V5_1|V5_2|V5_3|V5_4|null, 1: Connection}>
* @implements ConnectionInterface<array{0: V3|V4|V4_1|V4_2|V4_3|V4_4|V5|V5_1|V5_2|V5_3|V5_4|V6|null, 1: Connection}>
*
* @psalm-import-type BoltMeta from SummarizedResultFormatter
*/
Expand Down Expand Up @@ -85,7 +90,7 @@ class BoltConnection implements ConnectionInterface
private ?Neo4jException $deferredPullFailure = null;

/**
* @return array{0: V3|V4_4|V5|V5_1|V5_2|V5_3|V5_4|null, 1: Connection}
* @return array{0: V3|V4|V4_1|V4_2|V4_3|V4_4|V5|V5_1|V5_2|V5_3|V5_4|V6|null, 1: Connection}
*/
public function getImplementation(): array
{
Expand All @@ -96,7 +101,7 @@ public function getImplementation(): array
* @psalm-mutation-free
*/
public function __construct(
private V3|V4_4|V5|V5_1|V5_2|V5_3|V5_4|null $boltProtocol,
private V3|V4|V4_1|V4_2|V4_3|V4_4|V5|V5_1|V5_2|V5_3|V5_4|V6|null $boltProtocol,
private readonly Connection $connection,
private readonly AuthenticateInterface $auth,
private readonly string $userAgent,
Expand Down Expand Up @@ -145,6 +150,14 @@ public function getProtocol(): ConnectionProtocol
return $this->config->getProtocol();
}

/**
* @psalm-mutation-free
*/
public function isBoltUtcPatchNegotiated(): bool
{
return $this->config->isBoltUtcPatchNegotiated();
}

/**
* @psalm-mutation-free
*/
Expand Down Expand Up @@ -321,7 +334,7 @@ public function rollback(): void
$this->assertNoFailure($response);
}

public function protocol(): V3|V4_4|V5|V5_1|V5_2|V5_3|V5_4
public function protocol(): V3|V4|V4_1|V4_2|V4_3|V4_4|V5|V5_1|V5_2|V5_3|V5_4|V6
{
if (!isset($this->boltProtocol)) {
throw new Exception('Connection is closed');
Expand Down
6 changes: 5 additions & 1 deletion src/Bolt/BoltUnmanagedTransaction.php
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,11 @@ public function run(string $statement, iterable $parameters = []): SummarizedRes
*/
public function runStatement(Statement $statement): SummarizedResult
{
$parameters = ParameterHelper::formatParameters($statement->getParameters(), $this->connection->getProtocol());
$parameters = ParameterHelper::formatParameters(
$statement->getParameters(),
$this->connection->getProtocol(),
$this->connection->isBoltUtcPatchNegotiated(),
);
$start = microtime(true);

// Only drain an outstanding autocommit result (STREAMING). In an explicit transaction (TX_STREAMING)
Expand Down
22 changes: 17 additions & 5 deletions src/Bolt/ProtocolFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,42 @@
use Bolt\Bolt;
use Bolt\connection\IConnection;
use Bolt\protocol\V3;
use Bolt\protocol\V4;
use Bolt\protocol\V4_1;
use Bolt\protocol\V4_2;
use Bolt\protocol\V4_3;
use Bolt\protocol\V4_4;
use Bolt\protocol\V5;
use Bolt\protocol\V5_1;
use Bolt\protocol\V5_2;
use Bolt\protocol\V5_3;
use Bolt\protocol\V5_4;
use Bolt\protocol\V6;
use RuntimeException;

class ProtocolFactory
{
public function createProtocol(IConnection $connection): V3|V4_4|V5|V5_1|V5_2|V5_3|V5_4
/**
* Bolt 4.3+ range proposal: 4.4 and the three minors below (4.3, 4.2, 4.1) in one uint32 (00 03 04 04).
*
* @see https://neo4j.com/docs/bolt/current/bolt/handshake/
*/
private const HANDSHAKE_BOLT_4_4_DOWN_TO_4_1 = 0x00030404;

public function createProtocol(IConnection $connection): V3|V4|V4_1|V4_2|V4_3|V4_4|V5|V5_1|V5_2|V5_3|V5_4|V6
{
$boltOptoutEnv = getenv('BOLT_ANALYTICS_OPTOUT');
if ($boltOptoutEnv === false) {
putenv('BOLT_ANALYTICS_OPTOUT=1');
}

$bolt = new Bolt($connection);
// Newest first; include 3.0 for legacy servers and TestKit stub (BOLT 3)
$bolt->setProtocolVersions('5.4.4', 4.4, '3.0');
// Newest first: 6, 5.4.4, Bolt 4.4–4.1 range (single uint32), 3.0 — fits 4 slots and satisfies 4.2 / 4.3 stubs
$bolt->setProtocolVersions(6, '5.4.4', self::HANDSHAKE_BOLT_4_4_DOWN_TO_4_1, '3.0');
$protocol = $bolt->build();

if (!($protocol instanceof V3 || $protocol instanceof V4_4 || $protocol instanceof V5 || $protocol instanceof V5_1 || $protocol instanceof V5_2 || $protocol instanceof V5_3 || $protocol instanceof V5_4)) {
throw new RuntimeException('Client only supports bolt version 3.0 to 5.4');
if (!($protocol instanceof V3 || $protocol instanceof V4 || $protocol instanceof V4_1 || $protocol instanceof V4_2 || $protocol instanceof V4_3 || $protocol instanceof V4_4 || $protocol instanceof V5 || $protocol instanceof V5_1 || $protocol instanceof V5_2 || $protocol instanceof V5_3 || $protocol instanceof V5_4 || $protocol instanceof V6)) {
throw new RuntimeException('Client only supports bolt version 3.0 to 6.x');
}

return $protocol;
Expand Down
5 changes: 5 additions & 0 deletions src/BoltFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ public function createConnection(ConnectionRequestData $data, SessionConfigurati
$connection->setRecvTimeoutHint((float) $response['hints']['connection.recv_timeout_seconds']);
}

$patchBolt = $response['patch_bolt'] ?? null;
$config->setBoltUtcPatchNegotiated(
is_array($patchBolt) && in_array('utc', $patchBolt, true)
);

return $connection;
}

Expand Down
2 changes: 1 addition & 1 deletion src/ClientBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
/**
* Immutable factory for creating a client.
*
* @psalm-import-type OGMTypes from SummarizedResultFormatter
* @psalm-import-type OGMTypes from \Laudis\Neo4j\Types\OGMTypesAlias
*/
final class ClientBuilder
{
Expand Down
14 changes: 14 additions & 0 deletions src/Common/ConnectionConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public function __construct(
private readonly ?AccessMode $accessMode,
private readonly ?DatabaseInfo $databaseInfo,
private readonly string $encryptionLevel,
private bool $boltUtcPatchNegotiated = false,
) {
}

Expand Down Expand Up @@ -71,4 +72,17 @@ public function getEncryptionLevel(): string
{
return $this->encryptionLevel;
}

/**
* True when the server echoed patch_bolt containing "utc" (Bolt 4.3–4.x UTC DateTime wire format).
*/
public function isBoltUtcPatchNegotiated(): bool
{
return $this->boltUtcPatchNegotiated;
}

public function setBoltUtcPatchNegotiated(bool $boltUtcPatchNegotiated): void
{
$this->boltUtcPatchNegotiated = $boltUtcPatchNegotiated;
}
}
2 changes: 1 addition & 1 deletion src/Contracts/AuthenticateInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ interface AuthenticateInterface
/**
* Authenticates a Bolt connection with the provided configuration Uri and userAgent.
*
* @return array{server: string, connection_id: string, hints: list}
* @return array{server: string, connection_id: string, hints: list, patch_bolt?: list<string>}
*/
public function authenticateBolt(BoltConnection $connection, string $userAgent): array;

Expand Down
5 changes: 2 additions & 3 deletions src/Databags/SummarizedResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,15 @@

use Closure;
use Generator;
use Laudis\Neo4j\Formatter\SummarizedResultFormatter;
use Laudis\Neo4j\Types\CypherList;
use Laudis\Neo4j\Types\CypherMap;

/**
* A result containing the values and the summary.
*
* @psalm-import-type OGMTypes from SummarizedResultFormatter
* @psalm-import-type OGMTypes from \Laudis\Neo4j\Types\OGMTypesAlias
*
* @extends CypherList<CypherMap<OGMTypes>>
* @extends \Laudis\Neo4j\Types\CypherList<\Laudis\Neo4j\Types\CypherMap<OGMTypes>>
*/
final class SummarizedResult extends CypherList
{
Expand Down
15 changes: 14 additions & 1 deletion src/Enum/ConnectionProtocol.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use Bolt\protocol\V5_2;
use Bolt\protocol\V5_3;
use Bolt\protocol\V5_4;
use Bolt\protocol\V6;
use JsonSerializable;
use Laudis\TypedEnum\TypedEnum;

Expand All @@ -41,6 +42,7 @@
* @method static ConnectionProtocol BOLT_V5_2()
* @method static ConnectionProtocol BOLT_V5_3()
* @method static ConnectionProtocol BOLT_V5_4()
* @method static ConnectionProtocol BOLT_V6()
*
* @extends TypedEnum<string>
*
Expand All @@ -61,19 +63,30 @@ final class ConnectionProtocol extends TypedEnum implements JsonSerializable
private const BOLT_V5_2 = '5.2';
private const BOLT_V5_3 = '5.3';
private const BOLT_V5_4 = '5.4';
private const BOLT_V6 = '6';

/**
* @pure
*
* @psalm-suppress ImpureMethodCall
*/
public static function determineBoltVersion(V3|V4|V4_1|V4_2|V4_3|V4_4|V5|V5_1|V5_2|V5_3|V5_4 $bolt): self
public static function determineBoltVersion(V3|V4|V4_1|V4_2|V4_3|V4_4|V5|V5_1|V5_2|V5_3|V5_4|V6 $bolt): self
{
$version = self::resolve($bolt->getVersion());

return $version[0] ?? self::BOLT_V44();
}

/**
* Bolt 4.3–4.x: negotiate fixed DateTime via HELLO (TestKit echo_date_time_patched.script).
*
* @psalm-suppress ImpureMethodCall see compare()
*/
public function needsBoltUtcPatchInHello(): bool
{
return $this->compare(self::BOLT_V43()) >= 0 && $this->compare(self::BOLT_V5()) < 0;
}

public function compare(ConnectionProtocol $protocol): int
{
$x = 0;
Expand Down
Loading
Loading