2
2
3
3
namespace Jupitern \CosmosDb ;
4
4
5
+ use GuzzleHttp \Exception \ClientException ;
5
6
use GuzzleHttp \Exception \GuzzleException ;
6
7
use Psr \Http \Message \ResponseInterface ;
7
8
@@ -10,6 +11,7 @@ class CosmosDb
10
11
private string $ host ;
11
12
private string $ private_key ;
12
13
public array $ httpClientOptions ;
14
+ private array $ pkRanges = [];
13
15
14
16
/**
15
17
* __construct
@@ -153,11 +155,12 @@ public function getInfo(): string
153
155
*/
154
156
public function query (string $ rid_id , string $ rid_col , string $ query , bool $ isCrossPartition = false , $ partitionValue = null ): array
155
157
{
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
+ ];
161
164
162
165
if ($ isCrossPartition ) {
163
166
$ headers ['x-ms-documentdb-query-enablecrosspartition ' ] = 'True ' ;
@@ -166,6 +169,64 @@ public function query(string $rid_id, string $rid_col, string $query, bool $isCr
166
169
if ($ partitionValue ) {
167
170
$ headers ['x-ms-documentdb-partitionkey ' ] = '[" ' . $ partitionValue . '"] ' ;
168
171
}
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
+ {
169
230
/*
170
231
* Fix for https://github.com/jupitern/cosmosdb/issues/21 (credits to https://github.com/ElvenSpellmaker).
171
232
*
@@ -178,37 +239,11 @@ public function query(string $rid_id, string $rid_col, string $query, bool $isCr
178
239
* all results are loaded.
179
240
*/
180
241
$ 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 ' ] !== []);
212
247
return $ results ;
213
248
}
214
249
@@ -222,11 +257,18 @@ public function query(string $rid_id, string $rid_col, string $query, bool $isCr
222
257
*/
223
258
public function getPkRanges (string $ rid_id , string $ rid_col ): mixed
224
259
{
260
+ if (isset ($ this ->pkRanges [$ rid_id ][$ rid_col ])) {
261
+ return $ this ->pkRanges [$ rid_id ][$ rid_col ];
262
+ }
263
+
225
264
$ headers = $ this ->getAuthHeaders ('GET ' , 'pkranges ' , $ rid_col );
226
265
$ headers ['Accept ' ] = 'application/json ' ;
227
266
$ headers ['x-ms-max-item-count ' ] = -1 ;
228
267
$ 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 ];
230
272
}
231
273
232
274
/**
@@ -508,10 +550,6 @@ public function getDocument(string $rid_id, string $rid_col, string $rid_doc): s
508
550
{
509
551
$ headers = $ this ->getAuthHeaders ('GET ' , 'docs ' , $ rid_doc );
510
552
$ headers ['Content-Length ' ] = '0 ' ;
511
- $ options = array (
512
- CURLOPT_HTTPHEADER => $ headers ,
513
- CURLOPT_HTTPGET => true ,
514
- );
515
553
return $ this ->request ("/dbs/ {$ rid_id }/colls/ {$ rid_col }/docs/ {$ rid_doc }" , "GET " , $ headers )->getBody ()->getContents ();
516
554
}
517
555
0 commit comments