PageRenderTime 53ms CodeModel.GetById 24ms app.highlight 23ms RepoModel.GetById 1ms app.codeStats 0ms

/tests/functional/WindowsAzure/Table/TableServiceFunctionalQueryTest.php

http://github.com/WindowsAzure/azure-sdk-for-php
PHP | 648 lines | 462 code | 100 blank | 86 comment | 45 complexity | 8b90d23c432b96a06d0372ca1a466d63 MD5 | raw file
  1<?php
  2
  3/**
  4 * LICENSE: Licensed under the Apache License, Version 2.0 (the "License");
  5 * you may not use this file except in compliance with the License.
  6 * You may obtain a copy of the License at
  7 * http://www.apache.org/licenses/LICENSE-2.0
  8 *
  9 * Unless required by applicable law or agreed to in writing, software
 10 * distributed under the License is distributed on an "AS IS" BASIS,
 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 * See the License for the specific language governing permissions and
 13 * limitations under the License.
 14 * 
 15 * PHP version 5
 16 *
 17 * @category  Microsoft
 18 * @package   Tests\Functional\WindowsAzure\Table
 19 * @author    Azure PHP SDK <azurephpsdk@microsoft.com>
 20 * @copyright 2012 Microsoft Corporation
 21 * @license   http://www.apache.org/licenses/LICENSE-2.0  Apache License 2.0
 22 * @link      https://github.com/windowsazure/azure-sdk-for-php
 23 */
 24
 25namespace Tests\Functional\WindowsAzure\Table;
 26
 27use WindowsAzure\Common\ServiceException;
 28use WindowsAzure\Table\Models\BatchOperations;
 29use WindowsAzure\Table\Models\EdmType;
 30use WindowsAzure\Table\Models\Entity;
 31use WindowsAzure\Table\Models\Query;
 32use WindowsAzure\Table\Models\QueryEntitiesOptions;
 33use WindowsAzure\Table\Models\Filters\Filter;
 34
 35class TableServiceFunctionalQueryTest extends FunctionalTestBase {
 36    private static $entitiesInTable;
 37    private static $Partitions = array('Alpha', 'Bravo', 'Charlie', 'Delta', 'Echo');
 38    private static $curPartition;
 39    private static $curRowKey;
 40
 41    public static function setUpBeforeClass() {
 42        parent::setUpBeforeClass();
 43        
 44        $table = TableServiceFunctionalTestData::$TEST_TABLE_NAMES[0];
 45        self::$entitiesInTable = self::getEntitiesToQueryOver();  
 46        $baseWithRestProxy = new FunctionalTestBase();
 47        $parts = array();
 48        foreach(self::$entitiesInTable as $entity)  {
 49            if (array_key_exists($entity->getPartitionKey(), $parts) === false) {
 50                $parts[$entity->getPartitionKey()] = array();                
 51            }
 52            array_push($parts[$entity->getPartitionKey()], $entity);
 53        }
 54        foreach($parts as $part) {
 55            $batch = new BatchOperations();
 56            foreach($part as $entity)  {
 57                $batch->addInsertEntity($table, $entity);
 58            }
 59            $baseWithRestProxy->restProxy->batch($batch);
 60        }
 61    }
 62    
 63    private static function getNewEntity() {
 64        if (is_null(self::$curPartition) || self::$curPartition == count(self::$Partitions) - 1) {
 65            self::$curPartition = 0;
 66            self::$curRowKey = TableServiceFunctionalTestData::getNewKey();
 67        }
 68        else {
 69            self::$curPartition++;
 70        }
 71
 72        $entity = new Entity();
 73        $entity->setPartitionKey(self::$Partitions[self::$curPartition]);
 74        $entity->setRowKey(self::$curRowKey);
 75        return $entity;
 76    }
 77
 78    private static function getEntitiesToQueryOver() {
 79        $ret = array();
 80
 81        array_push($ret, self::getNewEntity());
 82        
 83        $entity = self::getNewEntity();
 84        $entity->addProperty('BOOLEAN', EdmType::BOOLEAN, true);
 85        array_push($ret, $entity);
 86        
 87        $entity = self::getNewEntity();
 88        $entity->addProperty('BOOLEAN', EdmType::BOOLEAN, false);
 89        array_push($ret, $entity);
 90
 91        $entity = self::getNewEntity();
 92        $entity->addProperty('DATETIME', EdmType::DATETIME, new \DateTime());
 93        array_push($ret, $entity);
 94
 95        $entity = self::getNewEntity();
 96        $entity->addProperty('DATETIME', EdmType::DATETIME, new \DateTime('2012-01-02'));
 97        array_push($ret, $entity);
 98
 99        $entity = self::getNewEntity();
100        $entity->addProperty('DOUBLE', EdmType::DOUBLE, 2.71828183);
101        array_push($ret, $entity);
102
103        $entity = self::getNewEntity();
104        $entity->addProperty('DOUBLE', EdmType::DOUBLE, 3.14159265);
105        array_push($ret, $entity);
106
107        $entity = self::getNewEntity();
108        $entity->addProperty('GUID', EdmType::GUID, '90ab64d6-d3f8-49ec-b837-b8b5b6367b74');
109        array_push($ret, $entity);
110
111        $entity = self::getNewEntity();
112        $entity->addProperty('GUID', EdmType::GUID, '00000000-1111-2222-3333-444444444444');
113        array_push($ret, $entity);
114
115        $entity = self::getNewEntity();
116        $entity->addProperty('INT32', EdmType::INT32, 23);
117        array_push($ret, $entity);
118
119        $entity = self::getNewEntity();
120        $entity->addProperty('INT32', EdmType::INT32, 42);
121        array_push($ret, $entity);
122
123        $entity = self::getNewEntity();
124        $entity->addProperty('INT64', EdmType::INT64, '-1');
125        array_push($ret, $entity);
126
127        $entity = self::getNewEntity();
128        $entity->addProperty('INT64', EdmType::INT64, strval(TableServiceFunctionalTestData::LongBigValue));
129        array_push($ret, $entity);
130
131        $entity = self::getNewEntity();
132        $entity->addProperty('STRING', EdmType::STRING, 'foo');
133        array_push($ret, $entity);
134
135        $entity = self::getNewEntity();
136        $entity->addProperty('STRING', EdmType::STRING, 'o hai');
137        array_push($ret, $entity);
138
139        $entity = self::getNewEntity();
140
141        $e = self::getNewEntity();
142        $e->addProperty('test', EdmType::BOOLEAN, true);
143        $e->addProperty('test2', EdmType::STRING, 'value');
144        $e->addProperty('test3', EdmType::INT32, 3);
145        $e->addProperty('test4', EdmType::INT64, '12345678901');
146        $e->addProperty('test5', EdmType::DATETIME, new \DateTime());
147        array_push($ret, $e);
148
149        $booleans = TableServiceFunctionalTestData::getInterestingGoodBooleans();
150        $dates = TableServiceFunctionalTestData::getInterestingGoodDates();
151        $doubles = TableServiceFunctionalTestData::getInterestingGoodDoubles();
152        $guids = TableServiceFunctionalTestData::getInterestingGoodGuids();
153        $ints = TableServiceFunctionalTestData::getInterestingGoodInts();
154        $longs = TableServiceFunctionalTestData::getInterestingGoodLongs();
155        $binaries = TableServiceFunctionalTestData::getInterestingGoodBinaries();
156        $strings = TableServiceFunctionalTestData::getInterestingGoodStrings();
157
158        // The random here is not to generate random values, but to
159        // get a good mix of values in the table entities.
160        mt_srand(123);
161
162        for ($i = 0; $i < 20; $i++) {
163            $e = self::getNewEntity();
164            TableServiceFunctionalTestData::addProperty($e, 'BINARY', EdmType::BINARY, $binaries);
165            TableServiceFunctionalTestData::addProperty($e, 'BOOLEAN', EdmType::BOOLEAN, $booleans);
166            TableServiceFunctionalTestData::addProperty($e, 'DATETIME', EdmType::DATETIME, $dates);
167            TableServiceFunctionalTestData::addProperty($e, 'DOUBLE', EdmType::DOUBLE, $doubles);
168            TableServiceFunctionalTestData::addProperty($e, 'GUID', EdmType::GUID, $guids);
169            TableServiceFunctionalTestData::addProperty($e, 'INT32', EdmType::INT32, $ints);
170            TableServiceFunctionalTestData::addProperty($e, 'INT64', EdmType::INT64, $longs);
171            TableServiceFunctionalTestData::addProperty($e, 'STRING', EdmType::STRING, $strings);
172            array_push($ret, $e);
173        }
174
175        return $ret;
176    }
177
178    public static function getInterestingQueryEntitiesOptions() {
179        $ret = array();
180        $e = self::$entitiesInTable[count(self::$entitiesInTable) - 3];
181
182        $options = new QueryEntitiesOptions();
183        array_push($ret, $options);
184
185        $options = new QueryEntitiesOptions();
186        $query = new Query();
187        $options->setQuery($query);
188        array_push($ret, $options);
189
190        $options = new QueryEntitiesOptions();
191        $query = new Query();
192        $query->setTop(2);
193        $options->setQuery($query);
194        array_push($ret, $options);
195
196        $options = new QueryEntitiesOptions();
197        $query = new Query();
198        $query->setTop(-2);
199        $options->setQuery($query);
200        array_push($ret, $options);
201
202        $options = new QueryEntitiesOptions();
203        $query = new Query();
204        $query->addSelectField('TableName');
205        $options->setQuery($query);
206        array_push($ret, $options);
207
208        $options = new QueryEntitiesOptions();
209        $query = new Query();
210        $filter = Filter::applyPropertyName('BOOLEAN');
211        $query->setFilter($filter);
212        $options->setQuery($query);
213        array_push($ret, $options);
214
215        $options = new QueryEntitiesOptions();
216        $query = new Query();
217        $filter = Filter::applyConstant(false, EdmType::BOOLEAN);
218        $query->setFilter($filter);
219        $options->setQuery($query);
220        array_push($ret, $options);
221
222        $options = new QueryEntitiesOptions();
223        $query = new Query();
224        $filter = Filter::applyEq(Filter::applyConstant(23, EdmType::INT32), Filter::applyPropertyName('INT32'));
225        $query->setFilter($filter);
226        $options->setQuery($query);
227        array_push($ret, $options);
228
229        $options = new QueryEntitiesOptions();
230        $query = new Query();
231        $filter = Filter::applyNe(Filter::applyConstant(23, EdmType::INT32), Filter::applyPropertyName('INT32'));
232        $query->setFilter($filter);
233        $options->setQuery($query);
234        array_push($ret, $options);
235
236        $options = new QueryEntitiesOptions();
237        $query = new Query();
238        $filter = Filter::applyNot(Filter::applyEq(Filter::applyConstant(23, EdmType::INT32), Filter::applyPropertyName('INT32')));
239        $query->setFilter($filter);
240        $options->setQuery($query);
241        array_push($ret, $options);
242
243        $options = new QueryEntitiesOptions();
244        $options->setNextPartitionKey($e->getPartitionKey());
245        $options->setNextRowKey($e->getRowKey());
246        array_push($ret, $options);
247
248        // Ask for an entity that does not exist.
249        $options = new QueryEntitiesOptions();
250        $options->setNextPartitionKey(self::$Partitions[2] . 'X');
251        $options->setNextRowKey($e->getRowKey() . 'X');
252        array_push($ret, $options);
253
254        return $ret;
255    }
256
257    public static function getInterestingQueryEntitiesOptionsOfDepth($depth) {
258        $ret = array();
259
260        // The random here is not to generate random values, but to
261        // get a good mix of values in the table entities.
262        mt_srand(456 + $depth);
263        for ($i = 1; $i < 20; $i++) {
264            $filter = self::generateFilterWithBooleanParameters($depth, 0);
265            $options = new QueryEntitiesOptions();
266            $query = new Query();
267            $query->setFilter($filter);
268            $options->setQuery($query);
269            array_push($ret, $options);
270        }
271
272        return $ret;
273    }
274
275    private static function generateFilterWithBooleanParameters($targetDepth, $depth) {
276        // Use the filter grammar to construct a tree.
277        // The random here is not to generate random values, but to
278        // get a good mix of values in the table entities.
279
280        // TODO: Treat raw string special
281        if ($depth == $targetDepth) {
282            switch (mt_rand(0,2)) {
283                case 0:
284                    return self::generateBinaryFilterWithAnyParameters();
285                case 1:
286                    return Filter::applyConstant(mt_rand(0,1) == 1, EdmType::BOOLEAN);
287                case 2:
288                    $e = self::getEntityFromTable();
289                    $boolPropNames = array();
290                    foreach($e->getProperties() as $key => $p)  {
291                        if ($p->getEdmType() == EdmType::BOOLEAN) {
292                            array_push($boolPropNames, $key);
293                        }
294                    }
295                    if (count($boolPropNames) == 0) {
296                        return Filter::applyConstant(mt_rand(0,1) == 1, EdmType::BOOLEAN);
297                    }
298                    else {
299                        $key = $boolPropNames[mt_rand(0, count($boolPropNames) - 1)];
300                        return Filter::applyPropertyName($key);
301                    }
302                default:
303                    return null;
304            }
305        }
306        else {
307            switch (mt_rand(0,8)) {
308                case 0: 
309                case 1: 
310                case 2: 
311                case 3: 
312                    return Filter::applyAnd(
313                            self::generateFilterWithBooleanParameters($targetDepth, $depth + 1), 
314                            self::generateFilterWithBooleanParameters($targetDepth, $depth + 1));
315                case 4:
316                case 5:
317                case 6:
318                case 7: 
319                    return Filter::applyOr(
320                            self::generateFilterWithBooleanParameters($targetDepth, $depth + 1), 
321                            self::generateFilterWithBooleanParameters($targetDepth, $depth + 1));
322                case 8: 
323                    return Filter::applyNot(self::generateFilterWithBooleanParameters($targetDepth, $depth + 1));
324                default:
325                    return null;
326            }
327        }
328    }
329
330    private static function generateBinaryFilterWithAnyParameters() {
331        // get a good mix of values in the table entities.
332
333        // Pull out one of the constants.
334        $e = self::getEntityFromTable();
335        $keys = array_keys($e->getProperties());
336        $propId = mt_rand(0, count($keys) - 1);
337        $key = $keys[$propId];
338
339        $prop = $e->getProperty($key);
340        $f1 = Filter::applyConstant($prop->getValue(), $prop->getEdmType());
341        $f2 = Filter::applyPropertyName($key);
342
343        if (mt_rand(0,1) == 1) {
344            // Try swapping.
345            $t = $f1;
346            $f1 = $f2;
347            $f2 = $t;
348        }
349
350        return self::getBinaryFilterFromIndex(mt_rand(0, 5), $f1, $f2);
351    }
352
353    private static function getEntityFromTable() {
354        $entId = mt_rand(0, count(self::$entitiesInTable) - 1);
355        $e = self::$entitiesInTable[$entId];
356        return $e;
357    }
358
359    private static function getBinaryFilterFromIndex($index, $f1, $f2) {
360        switch ($index) {
361            case 0: return Filter::applyEq($f1, $f2);
362            case 1: return Filter::applyGe($f1, $f2);
363            case 2: return Filter::applyGt($f1, $f2);
364            case 3: return Filter::applyLe($f1, $f2);
365            case 4: return Filter::applyLt($f1, $f2);
366            case 5: return Filter::applyNe($f1, $f2);
367            default: return null;
368        }
369    }
370
371    /**
372    * @covers WindowsAzure\Table\TableRestProxy::queryEntities
373    */
374    public function testQueryEntities() {
375        // The emulator has problems with non-standard queries tested here.
376        $this->skipIfEmulated();
377
378        $interestingqueryEntitiesOptions = self::getInterestingQueryEntitiesOptions();
379        foreach($interestingqueryEntitiesOptions as $options)  {
380            $this->queryEntitiesWorker($options);
381        }
382    }
383
384    /**
385    * @covers WindowsAzure\Table\TableRestProxy::queryEntities
386    */
387    public function testQueryEntitiesBooleanLevel1() {
388        // The emulator has problems with non-standard queries tested here.
389        $this->skipIfEmulated();
390
391        $interestingqueryEntitiesOptions = self::addBinaryFilter('BOOLEAN', EdmType::BOOLEAN, TableServiceFunctionalTestData::getInterestingGoodBooleans());
392        foreach($interestingqueryEntitiesOptions as $options)  {
393            $this->queryEntitiesWorker($options);
394        }
395    }
396
397    /**
398    * @covers WindowsAzure\Table\TableRestProxy::queryEntities
399    */
400    public function testQueryEntitiesDateTimeLevel1() {
401        // The emulator has problems with non-standard queries tested here.
402        $this->skipIfEmulated();
403
404        $interestingqueryEntitiesOptions = self::addBinaryFilter('DATETIME', EdmType::DATETIME, TableServiceFunctionalTestData::getInterestingGoodDates());
405        foreach($interestingqueryEntitiesOptions as $options)  {
406            $this->queryEntitiesWorker($options);
407        }
408    }
409
410    /**
411    * @covers WindowsAzure\Table\TableRestProxy::queryEntities
412    */
413    public function testQueryEntitiesDoubleLevel1() {
414        // The emulator has problems with non-standard queries tested here.
415        $this->skipIfEmulated();
416
417        $interestingqueryEntitiesOptions = self::addBinaryFilter('DOUBLE', EdmType::DOUBLE, TableServiceFunctionalTestData::getInterestingGoodDoubles());
418        foreach($interestingqueryEntitiesOptions as $options)  {
419            $this->queryEntitiesWorker($options);
420        }
421    }
422
423    /**
424    * @covers WindowsAzure\Table\TableRestProxy::queryEntities
425    */
426    public function testQueryEntitiesGuidLevel1() {
427        // The emulator has problems with non-standard queries tested here.
428        $this->skipIfEmulated();
429
430        $interestingqueryEntitiesOptions = self::addBinaryFilter('GUID', EdmType::GUID, TableServiceFunctionalTestData::getInterestingGoodGuids());
431        foreach($interestingqueryEntitiesOptions as $options)  {
432            $this->queryEntitiesWorker($options);
433        }
434    }
435
436    /**
437    * @covers WindowsAzure\Table\TableRestProxy::queryEntities
438    */
439    public function testQueryEntitiesIntLevel1() {
440        // The emulator has problems with non-standard queries tested here.
441        $this->skipIfEmulated();
442
443         $interestingqueryEntitiesOptions = self::addBinaryFilter('INT32', EdmType::INT32, TableServiceFunctionalTestData::getInterestingGoodInts());
444        foreach($interestingqueryEntitiesOptions as $options)  {
445            $this->queryEntitiesWorker($options);
446        }
447    }
448
449    /**
450    * @covers WindowsAzure\Table\TableRestProxy::queryEntities
451    */
452    public function testQueryEntitiesLongLevel1() {
453        // The emulator has problems with non-standard queries tested here.
454        $this->skipIfEmulated();
455
456        $interestingqueryEntitiesOptions = self::addBinaryFilter('INT64', EdmType::INT64, TableServiceFunctionalTestData::getInterestingGoodLongs());
457        foreach($interestingqueryEntitiesOptions as $options)  {
458            $this->queryEntitiesWorker($options);
459        }
460    }
461
462    /**
463    * @covers WindowsAzure\Table\TableRestProxy::queryEntities
464    */
465    public function testQueryEntitiesStringLevel1() {
466        // The emulator has problems with non-standard queries tested here.
467        $this->skipIfEmulated();
468
469        $interestingqueryEntitiesOptions = self::addBinaryFilter('STRING', EdmType::STRING, TableServiceFunctionalTestData::getInterestingGoodStrings());
470        foreach($interestingqueryEntitiesOptions as $options)  {
471            $this->queryEntitiesWorker($options);
472        }
473    }
474
475    /**
476    * @covers WindowsAzure\Table\TableRestProxy::queryEntities
477    */
478    public function testQueryEntitiesBinaryLevel1() {
479        // The emulator has problems with non-standard queries tested here.
480        $this->skipIfEmulated();
481
482        $interestingqueryEntitiesOptions = self::addBinaryFilter('BINARY', EdmType::BINARY, TableServiceFunctionalTestData::getInterestingGoodBinaries());
483        foreach($interestingqueryEntitiesOptions as $options)  {
484            $this->queryEntitiesWorker($options);
485        }
486    }
487
488    /**
489    * @covers WindowsAzure\Table\TableRestProxy::queryEntities
490    */
491    public function testQueryEntitiesLevel2() {
492        // The emulator has problems with non-standard queries tested here.
493        $this->skipIfEmulated();
494
495        $interestingqueryEntitiesOptions = self::getInterestingQueryEntitiesOptionsOfDepth(2);
496        foreach($interestingqueryEntitiesOptions as $options)  {
497            $this->queryEntitiesWorker($options);
498        }
499    }
500
501    /**
502    * @covers WindowsAzure\Table\TableRestProxy::queryEntities
503    */
504    public function testQueryEntitiesLevel3() {
505        // The emulator has problems with non-standard queries tested here.
506        $this->skipIfEmulated();
507
508        $interestingqueryEntitiesOptions = self::getInterestingQueryEntitiesOptionsOfDepth(3);
509        foreach($interestingqueryEntitiesOptions as $options)  {
510            $this->queryEntitiesWorker($options);
511        }
512    }
513
514    /**
515    * @covers WindowsAzure\Table\TableRestProxy::queryEntities
516    */
517    private function queryEntitiesWorker($options) {
518        $table = TableServiceFunctionalTestData::$TEST_TABLE_NAMES[0];
519
520        try {
521            $ret = (is_null($options) ? $this->restProxy->queryEntities($table) : $this->restProxy->queryEntities($table, $options));
522
523            if (is_null($options)) {
524                $options = new QueryEntitiesOptions();
525            }
526
527            if (!is_null($options->getQuery()) && !is_null($options->getQuery()->getTop()) && $options->getQuery()->getTop() <= 0) {
528                $this->assertTrue(false, 'Expect non-positive Top in $options->query to throw');
529            }
530
531            $this->verifyqueryEntitiesWorker($ret, $options);
532
533            // In principle, should check if there is a continuation, then use it.
534            // However, the user cannot easily control when this happens, so I'm
535            // not sure how useful it is.
536            // To test that scenario, set NextTable in the $options.
537        }
538        catch (ServiceException $e) {
539            if (!is_null($options->getQuery()) && !is_null($options->getQuery()->getTop()) && $options->getQuery()->getTop() <= 0) {
540                $this->assertEquals(400, $e->getCode(), 'getCode');
541            }
542            else {
543                $this->assertEquals(500, $e->getCode(), 'getCode');
544            }
545        }
546    }
547
548    private function verifyqueryEntitiesWorker($ret, $options) {
549        $this->assertNotNull($ret->getEntities(), 'getTables');
550
551        $expectedData = array();
552        foreach(self::$entitiesInTable as $e)  {
553            array_push($expectedData, $e);
554        }
555        
556        sort($expectedData);
557
558        $projected = false;
559
560        if (!is_null($options->getNextPartitionKey()) && !is_null($options->getNextRowKey())) {
561            $expectedDataTmp = array();
562            foreach($expectedData as $e)  {
563                if ( ($e->getPartitionKey() >  $options->getNextPartitionKey()) ||
564                    (($e->getPartitionKey() == $options->getNextPartitionKey()) &&
565                     ($e->getRowKey()       >= $options->getNextRowKey()))) {
566                    array_push($expectedDataTmp, $e);
567                }
568            }
569            $expectedData = $expectedDataTmp;
570        }
571
572        $q = $options->getQuery();
573        $expectedFilter = $q->getFilter();
574        $projected = (count($q->getSelectFields()) != 0);
575
576        $expectedData = TableServiceFunctionalTestUtils::filterEntityList($expectedFilter, $expectedData);
577
578        if (!is_null($q->getTop()) && $q->getTop() < count($expectedData)) {
579            $expectedDataTmp = array();
580            for ($i = 0; $i < $q->getTop(); $i++) {
581                array_push($expectedDataTmp, $expectedData[$i]);
582            }
583            $expectedData = $expectedDataTmp;
584        }
585
586        $this->compareEntityLists($ret->getEntities(), $expectedData, $projected);
587    }
588
589    private function compareEntityLists($actualData, $expectedData, $projected) {
590        // Need to sort the lists.
591        $actualData = self::sortEntitiesByCompositeKey($actualData);
592        $expectedData = self::sortEntitiesByCompositeKey($expectedData);
593
594        $this->assertEquals(count($expectedData), count($actualData), 'count(getEntities)');
595        for ($i = 0; $i < count($expectedData); $i++) {
596            $e1 = $expectedData[$i];
597            $e2 = $actualData[$i];
598            if (!$projected) {
599                $this->assertTrue(
600                    ($e1->getPartitionKey() == $e2->getPartitionKey()) && ($e1->getRowKey() == $e2->getRowKey()), 
601                    '(' . $e1->getPartitionKey() . ',' . $e1->getRowKey() . ') == (' . $e2->getPartitionKey() . ',' . $e2->getRowKey() . ')');
602            }
603            // Don't need to verify the whole entities, done elsewhere
604        }
605    }
606    
607    private static function addBinaryFilter($name, $edmType, $values) {
608        $counter = 0;
609        $ret = array();
610        foreach($values as $o)  {
611            $f = self::getBinaryFilterFromIndex($counter, Filter::applyPropertyName($name), Filter::applyConstant($o, $edmType));
612            $q = new Query();
613            $q->setFilter($f);
614            $qeo = new QueryEntitiesOptions();
615            $qeo->setQuery($q);
616            array_push($ret, $qeo);
617            $counter = ($counter + 1) % 6;
618        }
619        return $ret;
620    }
621
622    public static function sortEntitiesByCompositeKey($originalArray) {
623        $tmpArray = array();
624        $isordered = true;
625        $prevIndex = '/';
626        foreach($originalArray as $e) {
627            $index = $e->getPartitionKey() . '/' . $e->getRowKey();
628            $tmpArray[$index] = $e;
629            if ($isordered) {
630                $isordered = $prevIndex <= $index;
631            }
632            $prevIndex = $index;
633        }
634        
635        if ($isordered) {
636            return $originalArray;
637        }
638           
639        ksort($tmpArray);
640        $ret = array();
641        foreach($tmpArray as $e) {
642            array_push($ret, $e);
643        }
644        return $ret;
645    }    
646}
647
648?>