Skip to content

Commit da0472d

Browse files
authored
Merge pull request #10 from nagels-tech/create-query-profile-1951106664
Create query profile
2 parents 303be57 + b615899 commit da0472d

File tree

10 files changed

+1250
-317
lines changed

10 files changed

+1250
-317
lines changed

.php-cs-fixer.cache

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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\/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\/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\/Bookmarks.php":"50f89ca88b2df817433ce8237ccc0f18","src\/Objects\/ResultCounters.php":"a9372c98fe7bede10cb004af30ea502f","src\/Objects\/Relationship.php":"f6347c0260780d4f5d2dc407dc97e25e","src\/Transaction.php":"e456922858b31d87b17ca47d25d58474","tests\/resources\/expected\/complex-query-profile.php":"cc2b1e7e731c30a8855d9fa368cd55f3","src\/Neo4jQueryAPI.php":"8bffb787a834b58523e89fc9e5c19fe3","src\/Objects\/ProfiledQueryPlan.php":"d9ba608f3177426ea34d73276d75f20b"}}

.php-cs-fixer.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php declare(strict_types=1);
2+
3+
use PhpCsFixer\Config;
4+
use PhpCsFixer\Finder;
5+
6+
return (new Config())
7+
->setRiskyAllowed(true) // Allow risky fixers
8+
->setRules([
9+
'@PSR12' => true,
10+
'strict_param' => true, // This is a risky rule
11+
])
12+
->setFinder(
13+
Finder::create()
14+
->in(__DIR__)
15+
->exclude([
16+
'vendor',
17+
])
18+
);

src/Neo4jQueryAPI.php

Lines changed: 51 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@
44

55
use Exception;
66
use GuzzleHttp\Client;
7-
use GuzzleHttp\Exception\GuzzleException;
8-
use GuzzleHttp\Exception\RequestException;
9-
use Neo4j\QueryAPI\Objects\ChildQueryPlan;
10-
use Neo4j\QueryAPI\Objects\QueryArguments;
7+
use Neo4j\QueryAPI\Objects\ProfiledQueryPlanArguments;
118
use Neo4j\QueryAPI\Objects\ResultCounters;
129
use Neo4j\QueryAPI\Objects\ProfiledQueryPlan;
1310
use Neo4j\QueryAPI\Results\ResultRow;
@@ -26,18 +23,9 @@ public function __construct(Client $client)
2623
{
2724
$this->client = $client;
2825
}
29-
/**
30-
* @api
31-
*/
26+
3227
public static function login(string $address, string $username, string $password): self
3328
{
34-
if (empty($address)) {
35-
throw new RuntimeException('Address cannot be empty');
36-
}
37-
if (empty($username) || empty($password)) {
38-
throw new RuntimeException('Missing username or password');
39-
}
40-
4129
$client = new Client([
4230
'base_uri' => rtrim($address, '/'),
4331
'timeout' => 10.0,
@@ -54,7 +42,6 @@ public static function login(string $address, string $username, string $password
5442
/**
5543
* @throws Neo4jException
5644
* @throws RequestExceptionInterface
57-
* @api
5845
*/
5946
public function run(string $cypher, array $parameters = [], string $database = 'neo4j', Bookmarks $bookmark = null): ResultSet
6047
{
@@ -69,15 +56,21 @@ public function run(string $cypher, array $parameters = [], string $database = '
6956
$payload['bookmarks'] = $bookmark->getBookmarks();
7057
}
7158

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

76-
$data = json_decode($response->getBody()->getContents(), true);
63+
$contents = $response->getBody()->getContents();
64+
$data = json_decode($contents, true, flags: JSON_THROW_ON_ERROR);
7765
$ogm = new OGM();
7866

79-
$keys = $data['data']['fields'];
80-
$values = $data['data']['values'];
67+
$keys = $data['data']['fields'] ?? [];
68+
$values = $data['data']['values'] ?? []; // Ensure $values is an array
69+
70+
if (!is_array($values)) {
71+
throw new RuntimeException('Unexpected response format: values is not an array.');
72+
}
73+
8174
$rows = array_map(function ($resultRow) use ($ogm, $keys) {
8275
$data = [];
8376
foreach ($keys as $index => $key) {
@@ -86,10 +79,7 @@ public function run(string $cypher, array $parameters = [], string $database = '
8679
}
8780
return new ResultRow($data);
8881
}, $values);
89-
90-
if (isset($data['profiledQueryPlan'])) {
91-
$profile = $this->createProfileData($data['profiledQueryPlan']);
92-
}
82+
$profile = isset($data['profiledQueryPlan']) ? $this->createProfileData($data['profiledQueryPlan']) : null;
9383

9484
$resultCounters = new ResultCounters(
9585
containsUpdates: $data['counters']['containsUpdates'] ?? false,
@@ -119,19 +109,14 @@ public function run(string $cypher, array $parameters = [], string $database = '
119109
if ($response !== null) {
120110
$contents = $response->getBody()->getContents();
121111
$errorResponse = json_decode($contents, true);
122-
123112
throw Neo4jException::fromNeo4jResponse($errorResponse, $e);
124113
}
114+
throw $e;
125115
}
126-
throw new RuntimeException('Error executing query: ' . $e->getMessage(), 0, $e);
127116
}
128117

129-
/**
130-
* @api
131-
*/
132118
public function beginTransaction(string $database = 'neo4j'): Transaction
133119
{
134-
unset($database);
135120
$response = $this->client->post("/db/neo4j/query/v2/tx");
136121

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

144129
private function createProfileData(array $data): ProfiledQueryPlan
145130
{
131+
$ogm = new OGM();
132+
133+
// Map arguments using OGM
146134
$arguments = $data['arguments'];
135+
$mappedArguments = [];
136+
foreach ($arguments as $key => $value) {
137+
if (is_array($value) && array_key_exists('$type', $value) && array_key_exists('_value', $value)) {
138+
$mappedArguments[$key] = $ogm->map($value);
139+
} else {
140+
$mappedArguments[$key] = $value;
141+
}
142+
}
147143

148-
$queryArguments = new QueryArguments(
149-
$arguments['globalMemory'] ?? 0,
150-
$arguments['plannerImpl'] ?? '',
151-
$arguments['memory'] ?? 0,
152-
$arguments['stringRepresentation'] ?? '',
153-
is_string($arguments['runtime'] ?? '') ? $arguments['runtime'] : json_encode($arguments['runtime']),
154-
$arguments['runtimeImpl'] ?? '',
155-
$arguments['dbHits'] ?? 0,
156-
$arguments['batchSize'] ?? 0,
157-
$arguments['details'] ?? '',
158-
$arguments['plannerVersion'] ?? '',
159-
$arguments['pipelineInfo'] ?? '',
160-
$arguments['runtimeVersion'] ?? '',
161-
$arguments['id'] ?? 0,
162-
$arguments['estimatedRows'] ?? 0.0,
163-
is_string($arguments['planner'] ?? '') ? $arguments['planner'] : json_encode($arguments['planner']),
164-
$arguments['rows'] ?? 0
144+
$queryArguments = new ProfiledQueryPlanArguments(
145+
globalMemory: $mappedArguments['GlobalMemory'] ?? null,
146+
plannerImpl: $mappedArguments['planner-impl'] ?? null,
147+
memory: $mappedArguments['Memory'] ?? null,
148+
stringRepresentation: $mappedArguments['string-representation'] ?? null,
149+
runtime: $mappedArguments['runtime'] ?? null,
150+
time: $mappedArguments['Time'] ?? null,
151+
pageCacheMisses: $mappedArguments['PageCacheMisses'] ?? null,
152+
pageCacheHits: $mappedArguments['PageCacheHits'] ?? null,
153+
runtimeImpl: $mappedArguments['runtime-impl'] ?? null,
154+
version: $mappedArguments['version'] ?? null,
155+
dbHits: $mappedArguments['DbHits'] ?? null,
156+
batchSize: $mappedArguments['batch-size'] ?? null,
157+
details: $mappedArguments['Details'] ?? null,
158+
plannerVersion: $mappedArguments['planner-version'] ?? null,
159+
pipelineInfo: $mappedArguments['PipelineInfo'] ?? null,
160+
runtimeVersion: $mappedArguments['runtime-version'] ?? null,
161+
id: $mappedArguments['Id'] ?? null,
162+
estimatedRows: $mappedArguments['EstimatedRows'] ?? null,
163+
planner: $mappedArguments['planner'] ?? null,
164+
rows: $mappedArguments['Rows' ?? null]
165165
);
166166

167+
$identifiers = $data['identifiers'] ?? [];
167168
$profiledQueryPlan = new ProfiledQueryPlan(
168169
$data['dbHits'],
169170
$data['records'],
@@ -173,17 +174,17 @@ private function createProfileData(array $data): ProfiledQueryPlan
173174
$data['pageCacheHitRatio'],
174175
$data['time'],
175176
$data['operatorType'],
176-
$queryArguments
177+
$queryArguments,
178+
children: [],
179+
identifiers: $identifiers
177180
);
178-
181+
// Process children recursively
179182
foreach ($data['children'] as $child) {
180183
$childQueryPlan = $this->createProfileData($child);
181-
182184
$profiledQueryPlan->addChild($childQueryPlan);
183185
}
184186

185187
return $profiledQueryPlan;
186188
}
187189

188-
189190
}

src/Objects/ProfiledQueryPlan.php

Lines changed: 43 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,30 @@ class ProfiledQueryPlan
1212
private float $pageCacheHitRatio;
1313
private int $time;
1414
private string $operatorType;
15-
private QueryArguments $arguments;
15+
private ProfiledQueryPlanArguments $arguments;
1616

1717
/**
18-
* @var list<ProfiledQueryPlan>
18+
* @var list<ProfiledQueryPlan|ProfiledQueryPlanArguments>
1919
*/
2020
private array $children;
2121

22+
/**
23+
* @var string[]
24+
*/
25+
private array $identifiers;
26+
2227
public function __construct(
23-
?int $dbHits = 0, // Default to 0 if null
24-
?int $records = 0,
25-
?bool $hasPageCacheStats = false,
26-
?int $pageCacheHits = 0,
27-
?int $pageCacheMisses = 0,
28-
?float $pageCacheHitRatio = 0.0,
29-
?int $time = 0,
30-
?string $operatorType = '',
31-
QueryArguments $arguments
28+
?int $dbHits,
29+
?int $records,
30+
?bool $hasPageCacheStats,
31+
?int $pageCacheHits,
32+
?int $pageCacheMisses,
33+
?float $pageCacheHitRatio,
34+
?int $time,
35+
?string $operatorType,
36+
ProfiledQueryPlanArguments $arguments,
37+
?array $children = [],
38+
array $identifiers = [] // Default to an empty array
3239
) {
3340
$this->dbHits = $dbHits ?? 0;
3441
$this->records = $records ?? 0;
@@ -39,94 +46,86 @@ public function __construct(
3946
$this->time = $time ?? 0;
4047
$this->operatorType = $operatorType ?? '';
4148
$this->arguments = $arguments;
49+
$this->children = $children ?? [];
50+
$this->identifiers = $identifiers;
4251
}
43-
/**
44-
* @api
45-
*/
4652

4753
public function getDbHits(): int
4854
{
4955
return $this->dbHits;
5056
}
51-
/**
52-
* @api
53-
*/
5457

5558
public function getRecords(): int
5659
{
5760
return $this->records;
5861
}
59-
/**
60-
* @api
61-
*/
6262

6363
public function hasPageCacheStats(): bool
6464
{
6565
return $this->hasPageCacheStats;
6666
}
67-
/**
68-
* @api
69-
*/
7067

7168
public function getPageCacheHits(): int
7269
{
7370
return $this->pageCacheHits;
7471
}
75-
/**
76-
* @api
77-
*/
7872

7973
public function getPageCacheMisses(): int
8074
{
8175
return $this->pageCacheMisses;
8276
}
83-
/**
84-
* @api
85-
*/
8677

8778
public function getPageCacheHitRatio(): float
8879
{
8980
return $this->pageCacheHitRatio;
9081
}
91-
/**
92-
* @api
93-
*/
9482

9583
public function getTime(): int
9684
{
9785
return $this->time;
9886
}
99-
/**
100-
* @api
101-
*/
10287

10388
public function getOperatorType(): string
10489
{
10590
return $this->operatorType;
10691
}
107-
/**
108-
* @api
109-
*/
11092

111-
public function getArguments(): QueryArguments
93+
public function getArguments(): ProfiledQueryPlanArguments
11294
{
11395
return $this->arguments;
11496
}
11597

11698
/**
117-
* @api
118-
* @return list<ProfiledQueryPlan>
99+
* @return list<ProfiledQueryPlan|ProfiledQueryPlanArguments>
119100
*/
120101
public function getChildren(): array
121102
{
122103
return $this->children;
123104
}
105+
106+
public function addChild(ProfiledQueryPlan|ProfiledQueryPlanArguments $child): void
107+
{
108+
$this->children[] = $child;
109+
}
110+
124111
/**
125-
* @api
112+
* @return string[]
126113
*/
114+
public function getIdentifiers(): array
115+
{
116+
return $this->identifiers;
117+
}
118+
119+
/**
120+
* @param string[] $identifiers
121+
*/
122+
public function setIdentifiers(array $identifiers): void
123+
{
124+
$this->identifiers = $identifiers;
125+
}
127126

128-
public function addChild(ProfiledQueryPlan $child): void
127+
public function addIdentifier(string $identifier): void
129128
{
130-
$this->children[] = $child;
129+
$this->identifiers[] = $identifier;
131130
}
132131
}

0 commit comments

Comments
 (0)