Skip to content

Commit a7e770f

Browse files
authored
Merge pull request #39 from eouy/fix-bad-partitionkeyrangeid
FIX: Exception "x-ms-documentdb-partitionkeyrangeid header contains i…
2 parents 34a4858 + 126eab3 commit a7e770f

File tree

1 file changed

+79
-41
lines changed

1 file changed

+79
-41
lines changed

src/CosmosDb.php

Lines changed: 79 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Jupitern\CosmosDb;
44

5+
use GuzzleHttp\Exception\ClientException;
56
use GuzzleHttp\Exception\GuzzleException;
67
use Psr\Http\Message\ResponseInterface;
78

@@ -10,6 +11,7 @@ class CosmosDb
1011
private string $host;
1112
private string $private_key;
1213
public array $httpClientOptions;
14+
private array $pkRanges = [];
1315

1416
/**
1517
* __construct
@@ -153,11 +155,12 @@ public function getInfo(): string
153155
*/
154156
public function query(string $rid_id, string $rid_col, string $query, bool $isCrossPartition = false, $partitionValue = null): array
155157
{
156-
$headers = $this->getAuthHeaders('POST', 'docs', $rid_col);
157-
$headers['Content-Length'] = strlen($query);
158-
$headers['Content-Type'] = 'application/query+json';
159-
$headers['x-ms-max-item-count'] = -1;
160-
$headers['x-ms-documentdb-isquery'] = 'True';
158+
$headers = $this->getAuthHeaders('POST', 'docs', $rid_col) + [
159+
'Content-Length' => strlen($query),
160+
'Content-Type' => 'application/query+json',
161+
'x-ms-max-item-count' => -1,
162+
'x-ms-documentdb-isquery' => 'True',
163+
];
161164

162165
if ($isCrossPartition) {
163166
$headers['x-ms-documentdb-query-enablecrosspartition'] = 'True';
@@ -166,6 +169,64 @@ public function query(string $rid_id, string $rid_col, string $query, bool $isCr
166169
if ($partitionValue) {
167170
$headers['x-ms-documentdb-partitionkey'] = '["' . $partitionValue . '"]';
168171
}
172+
173+
try {
174+
return $this->getQueryResults($rid_id, $rid_col, $query, $headers);
175+
}
176+
catch (ClientException $e) {
177+
$responseError = \json_decode($e->getResponse()->getBody()->getContents());
178+
179+
if(!($isCrossPartition && $responseError->code === "BadRequest" && strpos($responseError->message, "cross partition query can not be directly served by the gateway") !== false)) {
180+
throw $e;
181+
}
182+
}
183+
184+
// -- Retry the request with PK Ranges --
185+
// The provided cross partition query can not be directly served by the gateway.
186+
// This is a first chance (internal) exception that all newer clients will know how to
187+
// handle gracefully. This exception is traced, but unless you see it bubble up as an
188+
// exception (which only happens on older SDK clients), then you can safely ignore this message.
189+
$headers["x-ms-documentdb-partitionkeyrangeid"] = $this->getPkFullRange($rid_id, $rid_col);
190+
191+
try {
192+
return $this->getQueryResults($rid_id, $rid_col, $query, $headers);
193+
} catch (ClientException $e) {
194+
$responseError = \json_decode($e->getResponse()->getBody()->getContents());
195+
196+
if($responseError->code === "BadRequest" && strpos($responseError->message, "x-ms-documentdb-partitionkeyrangeid header contains invalid value") !== false) {
197+
// -- Retry a request for each Partition Key Range --
198+
// Azure CosmosDB does not support the x-ms-documentdb-partitionkeyrangeid header
199+
// in PkFullRange format.
200+
// We need to make a request for each partition one by one.
201+
// BUT: Each partition will be in separate array element, and the order clause will
202+
// be dispersed across the array elements.
203+
$pkRanges = $this->getPkRanges($rid_id, $rid_col);
204+
$results = [];
205+
foreach ($pkRanges->PartitionKeyRanges as $rid) {
206+
$headers["x-ms-documentdb-partitionkeyrangeid"] = $rid->id;
207+
$results = array_merge($results, $this->getQueryResults($rid_id, $rid_col, $query, $headers));
208+
}
209+
}
210+
else {
211+
throw $e;
212+
}
213+
}
214+
215+
return $results;
216+
}
217+
218+
/**
219+
* getQueryResults
220+
*
221+
* @param string $rid_id Resource ID
222+
* @param string $rid_col Resource Collection ID
223+
* @param string $query Query
224+
* @param array $headers Request headers
225+
* @return array JSON response
226+
* @throws GuzzleException
227+
*/
228+
private function getQueryResults(string $rid_id, string $rid_col, string $query, array $headers): array
229+
{
169230
/*
170231
* Fix for https://github.com/jupitern/cosmosdb/issues/21 (credits to https://github.com/ElvenSpellmaker).
171232
*
@@ -178,37 +239,11 @@ public function query(string $rid_id, string $rid_col, string $query, bool $isCr
178239
* all results are loaded.
179240
*/
180241
$results = [];
181-
try {
182-
$result = $this->request("/dbs/{$rid_id}/colls/{$rid_col}/docs", "POST", $headers, $query);
183-
$results[] = $result->getBody()->getContents();
184-
while ($result->getHeader('x-ms-continuation') !== []) {
185-
$headers['x-ms-continuation'] = $result->getHeader('x-ms-continuation');
186-
$result = $this->request("/dbs/{$rid_id}/colls/{$rid_col}/docs", "POST", $headers, $query);
187-
$results[] = $result->getBody()->getContents();
188-
}
189-
}
190-
catch (\GuzzleHttp\Exception\ClientException $e) {
191-
$responseError = \json_decode($e->getResponse()->getBody()->getContents());
192-
193-
// -- Retry the request with PK Ranges --
194-
// The provided cross partition query can not be directly served by the gateway.
195-
// This is a first chance (internal) exception that all newer clients will know how to
196-
// handle gracefully. This exception is traced, but unless you see it bubble up as an
197-
// exception (which only happens on older SDK clients), then you can safely ignore this message.
198-
if ($isCrossPartition && $responseError->code === "BadRequest" && strpos($responseError->message, "cross partition query can not be directly served by the gateway") !== false) {
199-
$headers["x-ms-documentdb-partitionkeyrangeid"] = $this->getPkFullRange($rid_id, $rid_col);
200-
$result = $this->request("/dbs/{$rid_id}/colls/{$rid_col}/docs", "POST", $headers, $query);
201-
$results[] = $result->getBody()->getContents();
202-
while ($result->getHeader('x-ms-continuation') !== []) {
203-
$headers['x-ms-continuation'] = $result->getHeader('x-ms-continuation');
204-
$result = $this->request("/dbs/{$rid_id}/colls/{$rid_col}/docs", "POST", $headers, $query);
205-
$results[] = $result->getBody()->getContents();
206-
}
207-
} else {
208-
throw $e;
209-
}
210-
}
211-
242+
do {
243+
$response = $this->request("/dbs/{$rid_id}/colls/{$rid_col}/docs", "POST", $headers, $query);
244+
$results[] = $response->getBody()->getContents();
245+
$headers['x-ms-continuation'] = $response->getHeader('x-ms-continuation');
246+
} while ($headers['x-ms-continuation'] !== []);
212247
return $results;
213248
}
214249

@@ -222,11 +257,18 @@ public function query(string $rid_id, string $rid_col, string $query, bool $isCr
222257
*/
223258
public function getPkRanges(string $rid_id, string $rid_col): mixed
224259
{
260+
if(isset($this->pkRanges[$rid_id][$rid_col])) {
261+
return $this->pkRanges[$rid_id][$rid_col];
262+
}
263+
225264
$headers = $this->getAuthHeaders('GET', 'pkranges', $rid_col);
226265
$headers['Accept'] = 'application/json';
227266
$headers['x-ms-max-item-count'] = -1;
228267
$result = $this->request("/dbs/{$rid_id}/colls/{$rid_col}/pkranges", "GET", $headers);
229-
return json_decode($result->getBody()->getContents());
268+
269+
$this->pkRanges[$rid_id][$rid_col] = json_decode($result->getBody()->getContents());
270+
271+
return $this->pkRanges[$rid_id][$rid_col];
230272
}
231273

232274
/**
@@ -508,10 +550,6 @@ public function getDocument(string $rid_id, string $rid_col, string $rid_doc): s
508550
{
509551
$headers = $this->getAuthHeaders('GET', 'docs', $rid_doc);
510552
$headers['Content-Length'] = '0';
511-
$options = array(
512-
CURLOPT_HTTPHEADER => $headers,
513-
CURLOPT_HTTPGET => true,
514-
);
515553
return $this->request("/dbs/{$rid_id}/colls/{$rid_col}/docs/{$rid_doc}", "GET", $headers)->getBody()->getContents();
516554
}
517555

0 commit comments

Comments
 (0)