From c778e0142785a368fb6e2a85075e6839586ae17c Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Thu, 28 Sep 2017 16:01:01 -0500 Subject: [PATCH 1/7] Support for Aggregate Queries --- src/Parse/ParseQuery.php | 59 +++++++ tests/Parse/ParseQueryAggregateTest.php | 213 ++++++++++++++++++++++++ 2 files changed, 272 insertions(+) create mode 100644 tests/Parse/ParseQueryAggregateTest.php diff --git a/src/Parse/ParseQuery.php b/src/Parse/ParseQuery.php index d949c9b8..04ac8500 100755 --- a/src/Parse/ParseQuery.php +++ b/src/Parse/ParseQuery.php @@ -415,6 +415,65 @@ public function count($useMasterKey = false) return $result['count']; } + /** + * Execute a distinct query and return unique values. + * + * @param string $key field to find distinct values + * + * @return array + */ + public function distinct($key) + { + $sessionToken = null; + if (ParseUser::getCurrentUser()) { + $sessionToken = ParseUser::getCurrentUser()->getSessionToken(); + } + $opts = []; + if (!empty($this->where)) { + $opts['where'] = $this->where; + } + $opts['distinct'] = $key; + $queryString = $this->buildQueryString($opts); + $result = ParseClient::_request( + 'GET', + 'aggregate/'.$this->className.'?'.$queryString, + $sessionToken, + null, + true + ); + + return $result['results']; + } + + /** + * Execute a aggregate query and returns aggregate results. + * + * @param array $pipeline stages to process query + * + * @return array + */ + public function aggregate($pipeline) + { + $sessionToken = null; + if (ParseUser::getCurrentUser()) { + $sessionToken = ParseUser::getCurrentUser()->getSessionToken(); + } + $stages = []; + foreach ($pipeline as $stage => $value) { + $stages[$stage] = json_encode($value); + } + $queryString = $this->buildQueryString($stages); + $result = ParseClient::_request( + 'GET', + 'aggregate/'.$this->className.'?'.$queryString, + $sessionToken, + null, + true + ); + + return $result['results']; + } + /** * Execute a find query and return the results. * diff --git a/tests/Parse/ParseQueryAggregateTest.php b/tests/Parse/ParseQueryAggregateTest.php new file mode 100644 index 00000000..4411683c --- /dev/null +++ b/tests/Parse/ParseQueryAggregateTest.php @@ -0,0 +1,213 @@ +set('score', 10); + $obj2->set('score', 10); + $obj3->set('score', 10); + $obj4->set('score', 20); + + $obj1->set('name', 'foo'); + $obj2->set('name', 'foo'); + $obj3->set('name', 'bar'); + $obj4->set('name', 'dpl'); + + $objects = [$obj1, $obj2, $obj3, $obj4]; + ParseObject::saveAll($objects); + } + + public function testDistinctQuery() + { + $this->loadObjects(); + $query = new ParseQuery('TestObject'); + $results = $query->distinct('score'); + + $this->assertEquals(2, count($results)); + $this->assertEquals($results[0], 10); + $this->assertEquals($results[1], 20); + } + + public function testDistinctWhereQuery() + { + $this->loadObjects(); + $query = new ParseQuery('TestObject'); + $query->equalTo('name', 'foo'); + $results = $query->distinct('score'); + + $this->assertEquals(1, count($results)); + $this->assertEquals($results[0], 10); + } + + public function testDistinctClassNotExistQuery() + { + $this->loadObjects(); + $query = new ParseQuery('UnknownClass'); + $results = $query->distinct('score'); + + $this->assertEquals(0, count($results)); + } + + public function testDistinctFieldNotExistQuery() + { + $this->loadObjects(); + $query = new ParseQuery('TestObject'); + $results = $query->distinct('unknown'); + + $this->assertEquals(0, count($results)); + } + + public function testAggregateGroupQuery() + { + $pipeline = [ + 'group' => [ + 'objectId' => '$name' + ] + ]; + $this->loadObjects(); + $query = new ParseQuery('TestObject'); + $results = $query->aggregate($pipeline); + + $this->assertEquals(3, count($results)); + } + + public function testAggregateGroupClassNotExistQuery() + { + $pipeline = [ + 'group' => [ + 'objectId' => '$name' + ] + ]; + $this->loadObjects(); + $query = new ParseQuery('UnknownClass'); + $results = $query->aggregate($pipeline); + + $this->assertEquals(0, count($results)); + } + + public function testAggregateGroupFieldNotExistQuery() + { + $pipeline = [ + 'group' => [ + 'objectId' => '$unknown' + ] + ]; + $this->loadObjects(); + $query = new ParseQuery('UnknownClass'); + $results = $query->aggregate($pipeline); + + $this->assertEquals(0, count($results)); + } + + public function testAggregateMatchQuery() + { + $pipeline = [ + 'match' => [ + 'score' => [ '$gt' => 15 ] + ] + ]; + $this->loadObjects(); + $query = new ParseQuery('TestObject'); + $results = $query->aggregate($pipeline); + + $this->assertEquals(1, count($results)); + $this->assertEquals(20, $results[0]['score']); + } + + public function testAggregateProjectQuery() + { + $pipeline = [ + 'project' => [ + 'name' => 1 + ] + ]; + $this->loadObjects(); + $query = new ParseQuery('TestObject'); + $results = $query->aggregate($pipeline); + + foreach ($results as $result) { + $this->assertEquals(array_key_exists('name', $result), true); + $this->assertEquals(array_key_exists('objectId', $result), true); + $this->assertEquals(array_key_exists('score', $result), false); + } + } + + public function testAggregatePipelineInvalid() + { + $pipeline = [ + 'unknown' => [] + ]; + $this->loadObjects(); + $query = new ParseQuery('TestObject'); + $this->setExpectedException( + 'Parse\ParseException', + 'Invalid parameter for query: unknown', + 102 + ); + $results = $query->aggregate($pipeline); + } + + public function testAggregateGroupInvalid() + { + $pipeline = [ + 'group' => [ + '_id' => '$name' + ] + ]; + $this->loadObjects(); + $query = new ParseQuery('TestObject'); + $this->setExpectedException( + 'Parse\ParseException', + 'Invalid parameter for query: group. Please use objectId instead of _id', + 102 + ); + $results = $query->aggregate($pipeline); + } + + public function testAggregateGroupObjectIdRequired() + { + $pipeline = [ + 'group' => [] + ]; + $this->loadObjects(); + $query = new ParseQuery('TestObject'); + $this->setExpectedException( + 'Parse\ParseException', + 'Invalid parameter for query: group. objectId is required', + 102 + ); + $results = $query->aggregate($pipeline); + } +} From fd93d097d97257acd55399936c930d888219aece Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Thu, 28 Sep 2017 16:07:34 -0500 Subject: [PATCH 2/7] lint!!! --- tests/Parse/ParseQueryAggregateTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Parse/ParseQueryAggregateTest.php b/tests/Parse/ParseQueryAggregateTest.php index 4411683c..4dfd9fa5 100644 --- a/tests/Parse/ParseQueryAggregateTest.php +++ b/tests/Parse/ParseQueryAggregateTest.php @@ -196,7 +196,7 @@ public function testAggregateGroupInvalid() $results = $query->aggregate($pipeline); } - public function testAggregateGroupObjectIdRequired() + public function testAggregateGroupObjectIdRequired() { $pipeline = [ 'group' => [] From 50f31bb511592994bf7467d2f9d700b529c552d3 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Sat, 30 Sep 2017 14:23:41 -0500 Subject: [PATCH 3/7] nit and tests on user class --- src/Parse/ParseQuery.php | 10 ++-- tests/Parse/ParseQueryAggregateTest.php | 63 +++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 5 deletions(-) diff --git a/src/Parse/ParseQuery.php b/src/Parse/ParseQuery.php index 04ac8500..0378210f 100755 --- a/src/Parse/ParseQuery.php +++ b/src/Parse/ParseQuery.php @@ -425,8 +425,8 @@ public function count($useMasterKey = false) public function distinct($key) { $sessionToken = null; - if (ParseUser::getCurrentUser()) { - $sessionToken = ParseUser::getCurrentUser()->getSessionToken(); + if ($user = ParseUser::getCurrentUser()) { + $sessionToken = $user->getSessionToken(); } $opts = []; if (!empty($this->where)) { @@ -446,7 +446,7 @@ public function distinct($key) } /** - * Execute a aggregate query and returns aggregate results. + * Execute an aggregate query and returns aggregate results. * * @param array $pipeline stages to process query * @@ -455,8 +455,8 @@ public function distinct($key) public function aggregate($pipeline) { $sessionToken = null; - if (ParseUser::getCurrentUser()) { - $sessionToken = ParseUser::getCurrentUser()->getSessionToken(); + if ($user = ParseUser::getCurrentUser()) { + $sessionToken = $user->getSessionToken(); } $stages = []; foreach ($pipeline as $stage => $value) { diff --git a/tests/Parse/ParseQueryAggregateTest.php b/tests/Parse/ParseQueryAggregateTest.php index 4dfd9fa5..e7926616 100644 --- a/tests/Parse/ParseQueryAggregateTest.php +++ b/tests/Parse/ParseQueryAggregateTest.php @@ -89,6 +89,35 @@ public function testDistinctFieldNotExistQuery() $this->assertEquals(0, count($results)); } + public function testDisinctOnUsers() + { + Helper::clearClass(ParseUser::$parseClassName); + $user1 = new ParseUser(); + $user1->setUsername('foo'); + $user1->setPassword('password'); + $user1->set('score', 10); + $user1->signUp(); + + $user2 = new ParseUser(); + $user2->setUsername('bar'); + $user2->setPassword('password'); + $user2->set('score', 10); + $user2->signUp(); + + $user3 = new ParseUser(); + $user3->setUsername('hello'); + $user3->setPassword('password'); + $user3->set('score', 20); + $user3->signUp(); + + $query = ParseUser::query(); + $results = $query->distinct('score'); + + $this->assertEquals(2, count($results)); + $this->assertEquals($results[0], 10); + $this->assertEquals($results[1], 20); + } + public function testAggregateGroupQuery() { $pipeline = [ @@ -210,4 +239,38 @@ public function testAggregateGroupObjectIdRequired() ); $results = $query->aggregate($pipeline); } + + public function testAggregateOnUsers() + { + Helper::clearClass(ParseUser::$parseClassName); + $user1 = new ParseUser(); + $user1->setUsername('foo'); + $user1->setPassword('password'); + $user1->set('score', 10); + $user1->signUp(); + + $user2 = new ParseUser(); + $user2->setUsername('bar'); + $user2->setPassword('password'); + $user2->set('score', 10); + $user2->signUp(); + + $user3 = new ParseUser(); + $user3->setUsername('hello'); + $user3->setPassword('password'); + $user3->set('score', 20); + $user3->signUp(); + + $pipeline = [ + 'match' => [ + 'score' => [ '$gt' => 15 ] + ] + ]; + + $query = ParseUser::query(); + $results = $query->aggregate($pipeline); + + $this->assertEquals(1, count($results)); + $this->assertEquals($results[0], 20); + } } From 39a45f3d427ea0505ab693d9bfa02413af853cd6 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Wed, 15 Nov 2017 08:59:30 -0600 Subject: [PATCH 4/7] fixed tests --- tests/Parse/ParseQueryAggregateTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Parse/ParseQueryAggregateTest.php b/tests/Parse/ParseQueryAggregateTest.php index e7926616..8cbfceeb 100644 --- a/tests/Parse/ParseQueryAggregateTest.php +++ b/tests/Parse/ParseQueryAggregateTest.php @@ -271,6 +271,6 @@ public function testAggregateOnUsers() $results = $query->aggregate($pipeline); $this->assertEquals(1, count($results)); - $this->assertEquals($results[0], 20); + $this->assertEquals($results[0]['score'], 20); } } From 0da75ea888e37ad21f8c7672fd5c7410cf6a4be9 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Wed, 15 Nov 2017 09:12:07 -0600 Subject: [PATCH 5/7] flaky test --- tests/Parse/ParseQueryAggregateTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/Parse/ParseQueryAggregateTest.php b/tests/Parse/ParseQueryAggregateTest.php index 8cbfceeb..cabfc97f 100644 --- a/tests/Parse/ParseQueryAggregateTest.php +++ b/tests/Parse/ParseQueryAggregateTest.php @@ -55,7 +55,10 @@ public function testDistinctQuery() $query = new ParseQuery('TestObject'); $results = $query->distinct('score'); + print_r($results); $this->assertEquals(2, count($results)); + $this->assertEquals(in_array(10, $results), true); + $this->assertEquals(in_array(20, $results), true); $this->assertEquals($results[0], 10); $this->assertEquals($results[1], 20); } From c8daf82c7f6098e7d7e53b12be1c53922d140d06 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Wed, 15 Nov 2017 09:23:23 -0600 Subject: [PATCH 6/7] clean up --- tests/Parse/ParseQueryAggregateTest.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/Parse/ParseQueryAggregateTest.php b/tests/Parse/ParseQueryAggregateTest.php index cabfc97f..63d53f19 100644 --- a/tests/Parse/ParseQueryAggregateTest.php +++ b/tests/Parse/ParseQueryAggregateTest.php @@ -55,12 +55,9 @@ public function testDistinctQuery() $query = new ParseQuery('TestObject'); $results = $query->distinct('score'); - print_r($results); $this->assertEquals(2, count($results)); $this->assertEquals(in_array(10, $results), true); $this->assertEquals(in_array(20, $results), true); - $this->assertEquals($results[0], 10); - $this->assertEquals($results[1], 20); } public function testDistinctWhereQuery() From 36abc00ff99e4ab466173b988de75d43bb639695 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Wed, 15 Nov 2017 14:15:40 -0600 Subject: [PATCH 7/7] documentation --- README.md | 65 +++++++++++++++++++++++-- tests/Parse/ParseQueryAggregateTest.php | 2 +- 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 919f5c5b..5e986c44 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,8 @@ from your PHP app or script. Designed to work with the self-hosted Parse Server - [Users](#users) - [ACLs/Security](#acls) - [Queries](#queries) + - [Aggregate](#aggregate) + - [Distinct](#distinct) - [Relative Time](#relative-time) - [Cloud Functions](#cloud-functions) - [Cloud Jobs](#cloud-jobs) @@ -42,7 +44,7 @@ from your PHP app or script. Designed to work with the self-hosted Parse Server - [Contributing / Testing](#contributing--testing) ## Installation -There are various ways to install and use this sdk. We'll elaborate on a couple here. +There are various ways to install and use this sdk. We'll elaborate on a couple here. Note that the Parse PHP SDK requires PHP 5.4 or newer. ### Install with Composer @@ -224,7 +226,7 @@ use Parse\ParseAudience; ### Parse Objects Parse Objects hold your data, can be saved, queried for, serialized and more! -Objects are at the core of this sdk, they allow you to persist your data from php without having to worry about any databasing code. +Objects are at the core of this sdk, they allow you to persist your data from php without having to worry about any databasing code. ```php $object = ParseObject::create("TestObject"); @@ -254,7 +256,7 @@ $decodedObject = ParseObject::decode($encoded); ### Users -Users are a special kind of object. +Users are a special kind of object. This class allows individuals to access your applications with their unique information and allows you to identify them distinctly. Users may also be linked with 3rd party accounts such as facebook, twitter, etc. @@ -302,7 +304,7 @@ $acl->setRoleWriteAccessWithName("PHPFans", true); ### Queries -Queries allow you to recall objects that you've saved to parse-server. +Queries allow you to recall objects that you've saved to parse-server. Query methods and parameters allow allow a varying degree of querying for objects, from all objects of a class to objects created within a particular date range and more. ```php @@ -327,6 +329,59 @@ $first = $query->first(); $query->each(function($obj) { echo $obj->getObjectId(); }); + +``` +#### Aggregate + +Queries can be made using aggregates, allowing you to retrieve objects over a set of input values. +Keep in mind that `_id` does not exist in parse-server. Please replace with `objectId`. MasterKey is Required + +For a list of available operators please refer to Mongo Aggregate Documentation. + + Mongo 3.2 Aggregate Operators + +```php +// group pipeline is similar to distinct, can apply $sum, $avg, $max, $min +// accumulate sum and store in total field +$pipeline = [ + 'group' => [ + 'objectId' => null, + 'total' => [ '$sum' => '$score'] + ] +]; +$results = $query->aggregate($pipeline); + +// project pipeline is similar to keys, add or remove existing fields +// includes name key +$pipeline = [ + 'project' => [ + 'name' => 1 + ] +]; +$results = $query->aggregate($pipeline); + +// match pipeline is similar to equalTo +// filter out objects with score greater than 15 + $pipeline = [ + 'match' => [ + 'score' => [ '$gt' => 15 ] + ] +]; +$results = $query->aggregate($pipeline); +``` + +#### Distinct + +Queries can be made using distinct, allowing you find unique values for a specified field. +Keep in mind that MasterKey is required. +```php +// finds score that are unique +$results = $query->distinct('score'); + +// can be used with equalTo +$query = new ParseQuery('TestObject'); +$query->equalTo('name', 'foo'); +$results = $query->distinct('score'); ``` #### Relative Time @@ -557,7 +612,7 @@ $globalConfigFeatures = ParseServerInfo::getGlobalConfigFeatures(); * "delete" : true * } */ - + // you can always get all feature data $data = ParseServerInfo::getFeatures(); ``` diff --git a/tests/Parse/ParseQueryAggregateTest.php b/tests/Parse/ParseQueryAggregateTest.php index 63d53f19..f78bda7c 100644 --- a/tests/Parse/ParseQueryAggregateTest.php +++ b/tests/Parse/ParseQueryAggregateTest.php @@ -89,7 +89,7 @@ public function testDistinctFieldNotExistQuery() $this->assertEquals(0, count($results)); } - public function testDisinctOnUsers() + public function testDistinctOnUsers() { Helper::clearClass(ParseUser::$parseClassName); $user1 = new ParseUser();