Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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
1 change: 1 addition & 0 deletions .php-cs-fixer.cache
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"php":"8.3.6","version":"3.68.0:v3.68.0#73f78d8b2b34a0dd65fedb434a602ee4c2c8ad4c","indent":" ","lineEnding":"\n","rules":{"binary_operator_spaces":{"default":"at_least_single_space"},"blank_line_after_opening_tag":true,"blank_line_between_import_groups":true,"blank_lines_before_namespace":true,"braces_position":{"allow_single_line_empty_anonymous_classes":true},"class_definition":{"inline_constructor_arguments":false,"space_before_parenthesis":true},"compact_nullable_type_declaration":true,"declare_equal_normalize":true,"lowercase_cast":true,"lowercase_static_reference":true,"new_with_parentheses":true,"no_blank_lines_after_class_opening":true,"no_extra_blank_lines":{"tokens":["use"]},"no_leading_import_slash":true,"no_whitespace_in_blank_line":true,"ordered_class_elements":{"order":["use_trait"]},"ordered_imports":{"imports_order":["class","function","const"],"sort_algorithm":"none"},"return_type_declaration":true,"short_scalar_cast":true,"single_import_per_statement":{"group_to_single_imports":false},"single_space_around_construct":{"constructs_followed_by_a_single_space":["abstract","as","case","catch","class","const_import","do","else","elseif","final","finally","for","foreach","function","function_import","if","insteadof","interface","namespace","new","private","protected","public","static","switch","trait","try","use","use_lambda","while"],"constructs_preceded_by_a_single_space":["as","else","elseif","use_lambda"]},"single_trait_insert_per_statement":true,"ternary_operator_spaces":true,"unary_operator_spaces":{"only_dec_inc":true},"visibility_required":true,"blank_line_after_namespace":true,"constant_case":true,"control_structure_braces":true,"control_structure_continuation_position":true,"elseif":true,"function_declaration":true,"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"method_argument_space":{"attribute_placement":"ignore","on_multiline":"ensure_fully_multiline"},"no_break_comment":true,"no_closing_tag":true,"no_multiple_statements_per_line":true,"no_space_around_double_colon":true,"no_spaces_after_function_name":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":{"elements":["property"]},"single_line_after_imports":true,"spaces_inside_parentheses":true,"statement_indentation":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"encoding":true,"full_opening_tag":true,"strict_param":true},"hashes":{"tests\/resources\/expected\/complex-query-profile.php":"003fa2fdf787e68d6fb3e72d698df763","tests\/Unit\/Neo4jExceptionUnitTest.php":"0d7842780eeb9729d501831ee55df0d8","tests\/Unit\/ResultRowTest.php":"f5ee9f21d2439793a290e8ab946a7f32","tests\/Unit\/Neo4jQueryAPIUnitTest.php":"54be7f7b0f9dcf13d62c4912127583b9","tests\/Integration\/Neo4jQueryAPIIntegrationTest.php":"44977ffd6c09c505b00c8ef4857b8bfd","tests\/Integration\/Neo4jOGMTest.php":"73136b2d28fbb4fa298467d1ab3e18c8","src\/OGM.php":"93aae9c7afc8dbfd5aa00bc1d264ad19","src\/Neo4jQueryAPI.php":"9f2e978ed0191040e303f84846a7f556","src\/Results\/ResultRow.php":"ad55ec1bd999a8f6ad6b18874c4017b5","src\/Results\/ResultSet.php":"5f7748a356bf0fb30403e3c5a411bd24","src\/Exception\/Neo4jException.php":"dfb0f6933b9d3913c5495ba6d801d5f1","src\/Objects\/Path.php":"88c95962a6316ba7aa2fa3f0f6e31627","src\/Objects\/Node.php":"4a8ab7b8bd1981ee4d35d8c52b81c7c3","src\/Objects\/ProfiledQueryPlanArguments.php":"1be7b230a034a72c13349a5670a34a2f","src\/Objects\/Person.php":"f2f469937660f5454761e4f31154e081","src\/Objects\/Point.php":"169715b2157e08482e420374e6ca4cc3","src\/Objects\/ProfiledQueryPlan.php":"4709c2a11e73003fc1c027a6a8a62971","src\/Objects\/Bookmarks.php":"50f89ca88b2df817433ce8237ccc0f18","src\/Objects\/ResultCounters.php":"a9372c98fe7bede10cb004af30ea502f","src\/Objects\/Relationship.php":"f6347c0260780d4f5d2dc407dc97e25e","src\/Transaction.php":"e456922858b31d87b17ca47d25d58474"}}
18 changes: 18 additions & 0 deletions .php-cs-fixer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php declare(strict_types=1);

use PhpCsFixer\Config;
use PhpCsFixer\Finder;

return (new Config())
->setRiskyAllowed(true) // Allow risky fixers
->setRules([
'@PSR12' => true,
'strict_param' => true, // This is a risky rule
])
->setFinder(
Finder::create()
->in(__DIR__)
->exclude([
'vendor',
])
);
96 changes: 48 additions & 48 deletions src/Neo4jQueryAPI.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@

use Exception;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\RequestException;
use Neo4j\QueryAPI\Objects\ChildQueryPlan;
use Neo4j\QueryAPI\Objects\QueryArguments;
use Neo4j\QueryAPI\Objects\ProfiledQueryPlanArguments;
use Neo4j\QueryAPI\Objects\ResultCounters;
use Neo4j\QueryAPI\Objects\ProfiledQueryPlan;
use Neo4j\QueryAPI\Results\ResultRow;
Expand All @@ -26,18 +23,9 @@ public function __construct(Client $client)
{
$this->client = $client;
}
/**
* @api
*/

public static function login(string $address, string $username, string $password): self
{
if (empty($address)) {
throw new RuntimeException('Address cannot be empty');
}
if (empty($username) || empty($password)) {
throw new RuntimeException('Missing username or password');
}

$client = new Client([
'base_uri' => rtrim($address, '/'),
'timeout' => 10.0,
Expand All @@ -54,7 +42,6 @@ public static function login(string $address, string $username, string $password
/**
* @throws Neo4jException
* @throws RequestExceptionInterface
* @api
*/
public function run(string $cypher, array $parameters = [], string $database = 'neo4j', Bookmarks $bookmark = null): ResultSet
{
Expand All @@ -69,15 +56,21 @@ public function run(string $cypher, array $parameters = [], string $database = '
$payload['bookmarks'] = $bookmark->getBookmarks();
}

$response = $this->client->post('/db/' . $database . '/query/v2', [
$response = $this->client->request('POST', '/db/' . $database . '/query/v2', [
'json' => $payload,
]);

$data = json_decode($response->getBody()->getContents(), true);
$contents = $response->getBody()->getContents();
$data = json_decode($contents, true, flags: JSON_THROW_ON_ERROR);
$ogm = new OGM();

$keys = $data['data']['fields'];
$values = $data['data']['values'];
$keys = $data['data']['fields'] ?? [];
$values = $data['data']['values'] ?? []; // Ensure $values is an array

if (!is_array($values)) {
throw new RuntimeException('Unexpected response format: values is not an array.');
}

$rows = array_map(function ($resultRow) use ($ogm, $keys) {
$data = [];
foreach ($keys as $index => $key) {
Expand All @@ -86,10 +79,7 @@ public function run(string $cypher, array $parameters = [], string $database = '
}
return new ResultRow($data);
}, $values);

if (isset($data['profiledQueryPlan'])) {
$profile = $this->createProfileData($data['profiledQueryPlan']);
}
$profile = isset($data['profiledQueryPlan']) ? $this->createProfileData($data['profiledQueryPlan']) : null;

$resultCounters = new ResultCounters(
containsUpdates: $data['counters']['containsUpdates'] ?? false,
Expand Down Expand Up @@ -119,19 +109,14 @@ public function run(string $cypher, array $parameters = [], string $database = '
if ($response !== null) {
$contents = $response->getBody()->getContents();
$errorResponse = json_decode($contents, true);

throw Neo4jException::fromNeo4jResponse($errorResponse, $e);
}
throw $e;
}
throw new RuntimeException('Error executing query: ' . $e->getMessage(), 0, $e);
}

/**
* @api
*/
public function beginTransaction(string $database = 'neo4j'): Transaction
{
unset($database);
$response = $this->client->post("/db/neo4j/query/v2/tx");

$clusterAffinity = $response->getHeaderLine('neo4j-cluster-affinity');
Expand All @@ -143,27 +128,43 @@ public function beginTransaction(string $database = 'neo4j'): Transaction

private function createProfileData(array $data): ProfiledQueryPlan
{
$ogm = new OGM();

// Map arguments using OGM
$arguments = $data['arguments'];
$mappedArguments = [];
foreach ($arguments as $key => $value) {
if (is_array($value) && array_key_exists('$type', $value) && array_key_exists('_value', $value)) {
$mappedArguments[$key] = $ogm->map($value);
} else {
$mappedArguments[$key] = $value;
}
}

$queryArguments = new QueryArguments(
$arguments['globalMemory'] ?? 0,
$arguments['plannerImpl'] ?? '',
$arguments['memory'] ?? 0,
$arguments['stringRepresentation'] ?? '',
is_string($arguments['runtime'] ?? '') ? $arguments['runtime'] : json_encode($arguments['runtime']),
$arguments['runtimeImpl'] ?? '',
$arguments['dbHits'] ?? 0,
$arguments['batchSize'] ?? 0,
$arguments['details'] ?? '',
$arguments['plannerVersion'] ?? '',
$arguments['pipelineInfo'] ?? '',
$arguments['runtimeVersion'] ?? '',
$arguments['id'] ?? 0,
$arguments['estimatedRows'] ?? 0.0,
is_string($arguments['planner'] ?? '') ? $arguments['planner'] : json_encode($arguments['planner']),
$arguments['rows'] ?? 0
$queryArguments = new ProfiledQueryPlanArguments(
globalMemory: $mappedArguments['GlobalMemory'] ?? null,
plannerImpl: $mappedArguments['planner-impl'] ?? null,
memory: $mappedArguments['Memory'] ?? null,
stringRepresentation: $mappedArguments['string-representation'] ?? null,
runtime: $mappedArguments['runtime'] ?? null,
time: $mappedArguments['Time'] ?? null,
pageCacheMisses: $mappedArguments['PageCacheMisses'] ?? null,
pageCacheHits: $mappedArguments['PageCacheHits'] ?? null,
runtimeImpl: $mappedArguments['runtime-impl'] ?? null,
version: $mappedArguments['version'] ?? null,
dbHits: $mappedArguments['DbHits'] ?? null,
batchSize: $mappedArguments['batch-size'] ?? null,
details: $mappedArguments['Details'] ?? null,
plannerVersion: $mappedArguments['planner-version'] ?? null,
pipelineInfo: $mappedArguments['PipelineInfo'] ?? null,
runtimeVersion: $mappedArguments['runtime-version'] ?? null,
id: $mappedArguments['Id'] ?? null,
estimatedRows: $mappedArguments['EstimatedRows'] ?? null,
planner: $mappedArguments['planner'] ?? null,
rows: $mappedArguments['Rows' ?? null]
);


$profiledQueryPlan = new ProfiledQueryPlan(
$data['dbHits'],
$data['records'],
Expand All @@ -176,14 +177,13 @@ private function createProfileData(array $data): ProfiledQueryPlan
$queryArguments
);

// Process children recursively
foreach ($data['children'] as $child) {
$childQueryPlan = $this->createProfileData($child);

$profiledQueryPlan->addChild($childQueryPlan);
}

return $profiledQueryPlan;
}


}
47 changes: 9 additions & 38 deletions src/Objects/ProfiledQueryPlan.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Neo4j\QueryAPI\Objects;

class ProfiledQueryPlan
class ProfiledQueryPlan extends \Neo4j\QueryAPI\Objects\Bookmarks
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did this extend from bookmarks?

{
private int $dbHits;
private int $records;
Expand All @@ -12,10 +12,10 @@ class ProfiledQueryPlan
private float $pageCacheHitRatio;
private int $time;
private string $operatorType;
private QueryArguments $arguments;
private ProfiledQueryPlanArguments $arguments;

/**
* @var list<ProfiledQueryPlan>
* @var list<ProfiledQueryPlan|ProfiledQueryPlanArguments>
*/
private array $children;

Expand All @@ -28,7 +28,8 @@ public function __construct(
?float $pageCacheHitRatio = 0.0,
?int $time = 0,
?string $operatorType = '',
QueryArguments $arguments
ProfiledQueryPlanArguments $arguments,
array $children = [] // Accept an array of children, default to empty array
) {
$this->dbHits = $dbHits ?? 0;
$this->records = $records ?? 0;
Expand All @@ -39,93 +40,63 @@ public function __construct(
$this->time = $time ?? 0;
$this->operatorType = $operatorType ?? '';
$this->arguments = $arguments;
$this->children = $children ?? [];
}
/**
* @api
*/

public function getDbHits(): int
{
return $this->dbHits;
}
/**
* @api
*/

public function getRecords(): int
{
return $this->records;
}
/**
* @api
*/

public function hasPageCacheStats(): bool
{
return $this->hasPageCacheStats;
}
/**
* @api
*/

public function getPageCacheHits(): int
{
return $this->pageCacheHits;
}
/**
* @api
*/

public function getPageCacheMisses(): int
{
return $this->pageCacheMisses;
}
/**
* @api
*/

public function getPageCacheHitRatio(): float
{
return $this->pageCacheHitRatio;
}
/**
* @api
*/

public function getTime(): int
{
return $this->time;
}
/**
* @api
*/

public function getOperatorType(): string
{
return $this->operatorType;
}
/**
* @api
*/

public function getArguments(): QueryArguments
public function getArguments(): ProfiledQueryPlanArguments
{
return $this->arguments;
}

/**
* @api
* @return list<ProfiledQueryPlan>
* @return list<ProfiledQueryPlan|ProfiledQueryPlanArguments>
*/
public function getChildren(): array
{
return $this->children;
}
/**
* @api
*/

public function addChild(ProfiledQueryPlan $child): void
public function addChild(ProfiledQueryPlan|ProfiledQueryPlanArguments $child): void
{
$this->children[] = $child;
}
Expand Down
Loading
Loading