From 53a2795947ac29e16ece35f8a77ca1e848fc9062 Mon Sep 17 00:00:00 2001 From: Yash Sahu <54198301+yash30201@users.noreply.github.com> Date: Wed, 15 Nov 2023 05:35:26 +0000 Subject: [PATCH] feat(Firestore): SUM / AVG aggregation feature (#6557) --- Firestore/src/Aggregate.php | 71 ++++++- Firestore/src/AggregateQuerySnapshot.php | 10 +- Firestore/src/Query.php | 58 ++++++ Firestore/tests/System/AggregateQueryTest.php | 196 ++++++++++++++++-- Firestore/tests/System/TransactionTest.php | 17 +- Firestore/tests/Unit/AggregateQueryTest.php | 47 +++-- Firestore/tests/Unit/AggregateTest.php | 32 ++- Firestore/tests/Unit/QueryTest.php | 29 ++- Firestore/tests/Unit/TransactionTest.php | 27 ++- 9 files changed, 430 insertions(+), 57 deletions(-) diff --git a/Firestore/src/Aggregate.php b/Firestore/src/Aggregate.php index 8a8312bf2f23..9362a2cb6cb6 100644 --- a/Firestore/src/Aggregate.php +++ b/Firestore/src/Aggregate.php @@ -37,6 +37,16 @@ class Aggregate */ private const TYPE_COUNT = 'count'; + /** + * Default placeholder for all sum aggregate props. + */ + private const TYPE_SUM = 'sum'; + + /** + * Default placeholder for all average aggregate props. + */ + private const TYPE_AVG = 'avg'; + /** * @var array Aggregation properties for an AggregateQuery. */ @@ -63,9 +73,64 @@ private function __construct($aggregationType) */ public static function count() { - $count = new Aggregate(self::TYPE_COUNT); - $count->props[$count->aggregationType] = []; - return $count; + return self::createAggregate(self::TYPE_COUNT); + } + + /** + * Creates sum aggregation properties. + * + * Example: + * ``` + * $sum = Aggregate::sum('field_to_aggregate_upon'); + * ``` + * Result of SUM aggregation can be a integer or a float. + * Sum of integers which exceed maxinum integer value returns a float. + * Sum of numbers exceeding max float value returns `INF`. + * Sum of data which contains `NaN` returns `NaN`. + * Non numeric values are ignored. + * + * @param string $field The relative path of the field to aggregate upon. + * @return Aggregate + */ + public static function sum($field) + { + return self::createAggregate(self::TYPE_SUM, $field); + } + + /** + * Creates average aggregation properties. + * + * Example: + * ``` + * $avg = Aggregate::avg('field_to_aggregate_upon'); + * ``` + * + * Result of AVG aggregation can be a float or a null. + * Average of empty valid data set return `null`. + * Average of numbers exceeding max float value returns `INF`. + * Average of data which contains `NaN` returns `NaN`. + * Non numeric values are ignored. + * + * @param string|null $field The relative path of the field to aggregate upon. + * @return Aggregate + */ + public static function avg($field) + { + return self::createAggregate(self::TYPE_AVG, $field); + } + + private static function createAggregate(string $type, $field = null) + { + $aggregate = new Aggregate($type); + $aggregate->props[$aggregate->aggregationType] = []; + if (!is_null($field)) { + $aggregate->props[$aggregate->aggregationType] = [ + 'field' => [ + 'fieldPath' => $field + ] + ]; + } + return $aggregate; } /** diff --git a/Firestore/src/AggregateQuerySnapshot.php b/Firestore/src/AggregateQuerySnapshot.php index 8ce772658173..f568c590ac80 100644 --- a/Firestore/src/AggregateQuerySnapshot.php +++ b/Firestore/src/AggregateQuerySnapshot.php @@ -96,7 +96,7 @@ public function getReadTime() * Get the Query Aggregation value. * * @param string $alias The aggregation alias. - * @return int + * @return mixed * @throws \InvalidArgumentException If provided alias does not exist in result. */ public function get($alias) @@ -106,7 +106,13 @@ public function get($alias) } $result = $this->aggregateFields[$alias]; if (is_array($result)) { - return $result['integerValue']; + $key = array_key_first($result); + if ($key == 'nullValue') { + return null; + } + // `$result` would contain only one of + // (@see https://cloud.google.com/firestore/docs/reference/rest/v1/Value) + return $result[$key]; } return $result; } diff --git a/Firestore/src/Query.php b/Firestore/src/Query.php index a91b5e16f45e..8147d7e89285 100644 --- a/Firestore/src/Query.php +++ b/Firestore/src/Query.php @@ -195,6 +195,64 @@ public function count(array $options = []) return $aggregationResult->get('count'); } + /** + * Gets the sum of all documents matching the provided query filters. + * + * Example: + * ``` + * $sum = $query->sum(); + * ``` + * + * Sum of integers which exceed maximum integer value returns a float. + * Sum of numbers exceeding max float value returns `INF`. + * Sum of data which contains `NaN` returns `NaN`. + * Non numeric values are ignored. + * + * @param string $field The relative path of the field to aggregate upon. + * @param array $options [optional] { + * Configuration options is an array. + * + * @type Timestamp $readTime Reads entities as they were at the given timestamp. + * } + * @return int|float + */ + public function sum(string $field, array $options = []) + { + $aggregateQuery = $this->addAggregation(Aggregate::sum($field)->alias('sum')); + + $aggregationResult = $aggregateQuery->getSnapshot($options); + return $aggregationResult->get('sum'); + } + + /** + * Gets the average of all documents matching the provided query filters. + * + * Example: + * ``` + * $avg = $query->avg(); + * ``` + * + * Average of empty valid data set return `null`. + * Average of numbers exceeding max float value returns `INF`. + * Average of data which contains `NaN` returns `NaN`. + * Non numeric values are ignored. + * + * @param string $field The relative path of the field to aggregate upon. + * @param array $options [optional] { + * Configuration options is an array. + * + * @type Timestamp $readTime Reads entities as they were at the given timestamp. + * } + * @return float|null + */ + public function avg(string $field, array $options = []) + { + $aggregateQuery = $this->addAggregation(Aggregate::avg($field)->alias('avg')); + + $aggregationResult = $aggregateQuery->getSnapshot($options); + return $aggregationResult->get('avg'); + } + /** * Returns an aggregate query provided an aggregation with existing query filters. * diff --git a/Firestore/tests/System/AggregateQueryTest.php b/Firestore/tests/System/AggregateQueryTest.php index 3c69ed6e903f..c10afd156bd3 100644 --- a/Firestore/tests/System/AggregateQueryTest.php +++ b/Firestore/tests/System/AggregateQueryTest.php @@ -17,8 +17,11 @@ namespace Google\Cloud\Firestore\Tests\System; +use Exception; +use Google\Cloud\Core\Exception\BadRequestException; use Google\Cloud\Core\Timestamp; use Google\Cloud\Firestore\Aggregate; +use Google\Cloud\Firestore\Filter; use Google\Cloud\Firestore\Query; /** @@ -94,6 +97,24 @@ public function testLimits($type, $arg, $expectedResults, $docsToAdd = []) $this->assertQuery($query, $type, $arg, $expectedResults[1]); } + /** + * @dataProvider getMultipleAggregationCases + */ + public function testMultipleAggregationsOverDifferentFields($aggregates, $expectedResults, $docsToAdd = []) + { + $this->insertDocs($docsToAdd); + + $query = $this->query; + foreach ($aggregates as $aggregate) { + $query = $query->addAggregation($aggregate); + } + + $querySnapshot = $query->getSnapshot(); + foreach ($expectedResults as $key => $value) { + $this->compareResult($value, $querySnapshot->get($key)); + } + } + private function insertDocs(array $docs) { $docsRefs = []; @@ -103,15 +124,37 @@ private function insertDocs(array $docs) return $docsRefs; } + private function compareResult($expected, $actual) + { + if (!is_null($expected) && is_nan($expected)) { + $this->assertNan($actual); + } elseif (is_double($expected)) { + $this->assertEqualsWithDelta($expected, $actual, 0.01); + } else { + $this->assertEquals($expected, $actual); + } + } + private function assertQuery(Query $query, $type, $arg, $expected) { + if ($expected instanceof Exception) { + $this->expectException(get_class($expected)); + $this->expectExceptionMessage($expected->getMessage()); + } + $actual = $arg ? $query->$type($arg) : $query->$type(); - $this->assertEquals($expected, $actual); + + $this->compareResult($expected, $actual); $this->assertQueryWithMultipleAggregations($query, $type, $arg, $expected); } private function assertQueryWithMultipleAggregations(Query $query, $type, $arg, $expected) { + if ($expected instanceof Exception) { + $this->expectException(get_class($expected)); + $this->expectExceptionMessage($expected->getMessage()); + } + $aggregations = [ ($arg ? Aggregate::$type($arg) : Aggregate::$type())->alias('a1'), ($arg ? Aggregate::$type($arg) : Aggregate::$type())->alias('a2'), @@ -129,10 +172,9 @@ private function assertQueryWithMultipleAggregations(Query $query, $type, $arg, $snapshot = $query->getSnapshot(); - foreach ($expectedResults as $k => $v) { - $expectedResult = $v; - $actualResult = $snapshot->get($k); - $this->assertEquals($expectedResult, $actualResult); + foreach ($expectedResults as $key => $expectedResult) { + $actualResult = $snapshot->get($key); + $this->compareResult($expected, $actualResult); } $this->assertEquals(0, strlen($snapshot->getTransaction())); @@ -154,15 +196,61 @@ private function assertQueryWithMultipleAggregations(Query $query, $type, $arg, * string $aggregationType, * string $targetFieldName, * string $fieldMask, - * mixed $expectedResult, + * mixed $expectedResult, // can also be instance of Exception * array $docsToAddBeforeTestRunning * ] */ public function getSelectCases() { return [ - ['count', null, ['foo', 'good'], 1, [['foo' => 'bar', 'hello' => 'world', 'good' => 'night']]], - ['count', null, [], 1, [['foo' => 'bar']]], + // With mask + [ + 'count', + null, + ['foo', 'good'], + 1, + [['foo' => 'bar', 'hello' => 'world', 'good' => 'night']] + ], + [ + 'sum', + 'foo', + ['foo'], + new BadRequestException('Cannot apply property masks when aggregation fields are present.'), + [['foo' => 1, 'zoo' => 100], ['foo' => 2, 'zoo' => 200]] + ], + [ + 'avg', + 'foo', + ['foo'], + new BadRequestException('Cannot apply property masks when aggregation fields are present.'), + [['foo' => 1, 'zoo' => 100], ['foo' => 2, 'zoo' => 200]] + ], + + // With empty mask + [ + 'count', + null, + [], 1, + [['foo' => 'bar']] + ], + [ + 'sum', + 'foo', + [], + new BadRequestException( + 'Aggregation over non-key properties is not supported for base query that only returns keys.' + ), + [['foo' => 1, 'zoo' => 100], ['foo' => 2, 'zoo' => 200]] + ], + [ + 'avg', + 'foo', + [], + new BadRequestException( + 'Aggregation over non-key properties is not supported for base query that only returns keys' + ), + [['foo' => 1, 'zoo' => 100], ['foo' => 2, 'zoo' => 200]] + ] ]; } @@ -186,23 +274,47 @@ public function getWhereCases() ['value' => ['foo', 'bar']], ['value' => ['foo']] ]; + $numDoc = [ + ['value' => [1, 2]], + ['value' => [30]] + ]; + $maxValueExceedDoc = [ + ['value' => PHP_FLOAT_MAX], + ['value' => PHP_FLOAT_MAX] + ]; $randomVal = base64_encode(random_bytes(10)); + $randomInt = random_int(1, PHP_INT_MAX); return [ // For testing where: equality for random value ['count', null, '=', $randomVal, 1, [['value' => $randomVal]]], + ['sum', 'value', '=', $randomInt, $randomInt, [['value' => $randomInt]]], + ['avg', 'value', '=', $randomInt, $randomInt, [['value' => $randomInt]]], // For testing where: equality for null ['count', null, '=', null, 1, [['value' => null]]], + ['sum', 'value', '=', null, 0, [['value' => null]]], + ['avg', 'value', '=', null, null, [['value' => null]]], // For testing where: equality for NAN ['count', null, '=', NAN, 1, [['value' => NAN]]], + ['sum', 'value', '=', NAN, NAN, [['value' => NAN]]], + ['avg', 'value', '=', NAN, NAN, [['value' => NAN]]], // For testing where: in array ['count', null, 'in', [['foo']], 1, $arrayDoc], ['count', null, 'in', [['foo'], ['foo', 'bar']], 2, $arrayDoc], - ['count', null, 'in', [['bar']], 0, $arrayDoc], - ['count', null, 'in', [['foo', 'bar']], 1, $arrayDoc], ['count', null, 'in', [['bar', 'foo']], 0, $arrayDoc], + // Array is considered non numeric for Sum and Avg + ['sum', 'value', 'in', [[1, 2]], 0, $numDoc], + ['avg', 'value', 'in', [[1, 2]], null, $numDoc], + + // For testing aggregation on empty query set + ['sum', 'value', '=', 'non_existent', 0, []], + ['avg', 'value', '=', 'non_existent', null, []], + + // When sum is greater than FLOAT_MAX + ['sum', 'value', '>', 0, INF, $maxValueExceedDoc], + ['avg', 'value', '>', 0, INF, $maxValueExceedDoc], ]; } @@ -220,9 +332,16 @@ public function getWhereCases() */ public function getSnapshotCursorCases() { - $docsToAdd = [['value' => 0], ['value' => 1], ['value' => 2], ['value' => 3]]; + $docsToAdd = [ + ['value' => 1], + ['value' => 2], + ['value' => 3], + ['value' => 4] + ]; return [ - ['count', null, [4, 3, 3, 4], $docsToAdd] + ['count', null, [4, 3, 3, 4], $docsToAdd], + ['sum', 'value', [10, 9, 6, 10], $docsToAdd], + ['avg', 'value', [2.5, 3, 2, 2.5], $docsToAdd] ]; } @@ -240,9 +359,58 @@ public function getSnapshotCursorCases() */ public function getLimitCases() { - $docsToAdd = [['value' => 0], ['value' => 1], ['value' => 2], ['value' => 3], ['value' => 4]]; + $docsToAdd = [ + ['value' => 1], + ['value' => 2], + ['value' => 3], + ['value' => 4], + ['value' => 5] + ]; + return [ + ['count', null, [2, 2], $docsToAdd], + ['sum', 'value', [9, 7], $docsToAdd], + ['avg', 'value', [4.5, 3.5], $docsToAdd] + ]; + } + + /** + * This dataProvider returns the test cases to test how + * multiple aggregations work with multiple fields. + * + * The values are of the form + * [ + * array $aggregates, + * array $expectedResults, + * array $docsToAddBeforeTestRunning + * ] + */ + public function getMultipleAggregationCases() + { + $docsToAdd = [ + ['key1' => 1, 'key2' => 2], + ['key1' => 3, 'key2' => 4], + ['key1' => 10], + ['key1' => 20], + ['key2' => 100], + ]; + $case1 = [ + Aggregate::count()->alias('count'), + Aggregate::avg('key2')->alias('avg'), + ]; + $case2 = [ + Aggregate::count()->alias('count'), + Aggregate::sum('key1')->alias('sum'), + ]; + $case3 = [ + Aggregate::count()->alias('count'), + Aggregate::sum('key1')->alias('sum'), + Aggregate::avg('key1')->alias('avg'), + ]; + return [ - ['count', null, [2, 2], $docsToAdd] + [$case1, ['count' => 3, 'avg' => 35.33], $docsToAdd], + [$case2, ['count' => 4, 'sum' => 34], $docsToAdd], + [$case3, ['count' => 4, 'sum' => 34, 'avg' => 8.5], $docsToAdd] ]; } } diff --git a/Firestore/tests/System/TransactionTest.php b/Firestore/tests/System/TransactionTest.php index fb24ee2435d9..b42a3ed229e9 100644 --- a/Firestore/tests/System/TransactionTest.php +++ b/Firestore/tests/System/TransactionTest.php @@ -137,7 +137,7 @@ public function testAggregateQuery($type, $arg, $expected, $docsToAdd) $collection->add($docToAdd); } - $query = $collection->where('value', '!=', []) + $query = $collection->where('value', '!=', 'non_existent_value') ->addAggregation( ($arg ? Aggregate::$type($arg) : Aggregate::$type())->alias('res') ); @@ -172,7 +172,7 @@ public function testAggregateQueryWithMultipleAggregation($type, $arg, $expected $collection->add($docToAdd); } - $query = $collection->where('value', '!=', []) + $query = $collection->where('value', '!=', 'non_existent_value') ->addAggregation( ($arg ? Aggregate::$type($arg) : Aggregate::$type())->alias('res_1') ); @@ -253,12 +253,19 @@ public function testAbort() public function getAggregateCases() { $docsToAdd = [ - ['value' => ['foobar']], - ['value' => ['foo', 'bar']], - ['value' => ['foo']] + ['value' => 'foobar'], + ['value' => 'bar'], + ['value' => 'foo'] + ]; + $numsToAdd = [ + ['value' => 1], + ['value' => 2], + ['value' => 3] ]; return [ ['count', null, 3, $docsToAdd], + ['sum', 'value', 6, $numsToAdd], + ['avg', 'value', 2, $numsToAdd] ]; } } diff --git a/Firestore/tests/Unit/AggregateQueryTest.php b/Firestore/tests/Unit/AggregateQueryTest.php index fb8be07c8627..57fc50742ffc 100644 --- a/Firestore/tests/Unit/AggregateQueryTest.php +++ b/Firestore/tests/Unit/AggregateQueryTest.php @@ -67,11 +67,16 @@ public function setUp(): void ], ['connection', 'query', 'aggregates']); } - public function testGetSnapshot() + /** + * @dataProvider aggregationTypes + */ + public function testGetSnapshot($type, $arg, $expected) { $expectedProps = [ - ['count' => []] + [$type => $expected] ]; + $aggregate = $arg ? Aggregate::$type($arg) : Aggregate::$type(); + $this->aggregateQuery->___setProperty('aggregates', [$aggregate]); $this->connection->runAggregationQuery(Argument::that(function ($args) use ($expectedProps) { return $expectedProps == $args['structuredAggregationQuery']['aggregations']; @@ -80,7 +85,7 @@ public function testGetSnapshot() ->willReturn(new \ArrayIterator([[ 'result' => [ 'aggregateFields' => [ - 'count' => ['integerValue' => 123456] + $type => ['testResultType' => 123456] ] ] ]])); @@ -88,30 +93,46 @@ public function testGetSnapshot() $response = $this->aggregateQuery->getSnapshot(); $this->assertInstanceOf(AggregateQuerySnapshot::class, $response); - $this->assertEquals(123456, $response->get('count')); + $this->assertEquals(123456, $response->get($type)); } - public function testAddAggregation() + /** + * @dataProvider aggregationTypes + */ + public function testAddAggregation($type, $arg, $expected) { $expectedProps = [ - ['count' => []], - ['count' => [], 'alias' => 'total'], - ['count' => [], 'alias' => 'count_with_another_alias'], + [$type => $expected], + [$type => $expected, 'alias' => 'total'], + [$type => $expected, 'alias' => 'type_with_another_alias'], ]; - $this->aggregateQuery->addAggregation(Aggregate::count()->alias('total')); - $this->aggregateQuery->addAggregation( - Aggregate::count() - ->alias('count_with_another_alias') - ); + // Filling the array using array_fill or similar method results in + // having values by reference, hence not suited for test's purpose. + $aggregates = [ + $arg ? Aggregate::$type($arg) : Aggregate::$type(), + ($arg ? Aggregate::$type($arg) : Aggregate::$type())->alias('total'), + ($arg ? Aggregate::$type($arg) : Aggregate::$type())->alias('type_with_another_alias') + ]; + $this->aggregateQuery->___setProperty('aggregates', $aggregates); $this->connection->runAggregationQuery(Argument::that(function ($args) use ($expectedProps) { return $expectedProps == $args['structuredAggregationQuery']['aggregations']; })) ->shouldBeCalledTimes(1) ->willReturn(new \ArrayIterator([])); $this->aggregateQuery->___setProperty('connection', $this->connection->reveal()); + $response = $this->aggregateQuery->getSnapshot(); $this->assertInstanceOf(AggregateQuerySnapshot::class, $response); } + + public function aggregationTypes() + { + return [ + ['count', null, []], + ['sum', 'testField', ['field' => ['fieldPath' => 'testField']]], + ['avg', 'testField', ['field' => ['fieldPath' => 'testField']]] + ]; + } } diff --git a/Firestore/tests/Unit/AggregateTest.php b/Firestore/tests/Unit/AggregateTest.php index 3742d3ffddb4..d49b449e866f 100644 --- a/Firestore/tests/Unit/AggregateTest.php +++ b/Firestore/tests/Unit/AggregateTest.php @@ -26,27 +26,43 @@ */ class AggregateTest extends TestCase { - public function testCountType() + /** + * @dataProvider aggregationTypes + */ + public function testAggregationType($type, $arg, $expected) { $expectedQuery = [ - 'count' => [] + $type => $expected ]; - $aggregation = Aggregate::count(); + $aggregate = $arg ? Aggregate::$type($arg) : Aggregate::$type(); - $this->assertEquals($expectedQuery, $aggregation->getProps()); + $this->assertEquals($expectedQuery, $aggregate->getProps()); } - public function testAlias() + /** + * @dataProvider aggregationTypes + */ + public function testAlias($type, $arg, $expected) { $alias = uniqid(); $expectedQuery = [ - 'count' => [], + $type => $expected, 'alias' => $alias ]; - $aggregation = Aggregate::count()->alias($alias); + $aggregate = $arg ? Aggregate::$type($arg) : Aggregate::$type(); + $aggregate->alias($alias); - $this->assertEquals($expectedQuery, $aggregation->getProps()); + $this->assertEquals($expectedQuery, $aggregate->getProps()); + } + + public function aggregationTypes() + { + return [ + ['count', null, []], + ['sum', 'testField', ['field' => ['fieldPath' => 'testField']]], + ['avg', 'testField', ['field' => ['fieldPath' => 'testField']]] + ]; } } diff --git a/Firestore/tests/Unit/QueryTest.php b/Firestore/tests/Unit/QueryTest.php index 5fff8ad9dba3..36ea6943f4f6 100644 --- a/Firestore/tests/Unit/QueryTest.php +++ b/Firestore/tests/Unit/QueryTest.php @@ -156,7 +156,10 @@ public function testDocumentsMetadata() $this->assertInstanceOf(Timestamp::class, $res->readTime()); } - public function testCount() + /** + * @dataProvider aggregationTypes + */ + public function testAggregation($type, $arg) { $this->connection->runAggregationQuery(Argument::any()) ->shouldBeCalled() @@ -164,7 +167,7 @@ public function testCount() [ 'result' => [ 'aggregateFields' => [ - 'count' => ['integerValue' => 1] + $type => ['integerValue' => 1] ] ] ] @@ -172,11 +175,14 @@ public function testCount() $this->query->___setProperty('connection', $this->connection->reveal()); - $res = $this->query->count(); + $res = $arg ? $this->query->$type($arg) : $this->query->$type(); $this->assertEquals(1, $res); } - public function testCountWithReadTime() + /** + * @dataProvider aggregationTypes + */ + public function testAggregationWithReadTime($type, $arg) { $readTime = new Timestamp(new \DateTimeImmutable('now')); $this->connection->runAggregationQuery(Argument::withEntry('readTime', $readTime)) @@ -185,7 +191,7 @@ public function testCountWithReadTime() [ 'result' => [ 'aggregateFields' => [ - 'count' => ['integerValue' => 1] + $type => ['testValue' => 1] ] ] ] @@ -193,7 +199,9 @@ public function testCountWithReadTime() $this->query->___setProperty('connection', $this->connection->reveal()); - $res = $this->query->count(['readTime' => $readTime]); + $res = $arg ? + $this->query->$type($arg, ['readTime' => $readTime]) : + $this->query->$type(['readTime' => $readTime]); $this->assertEquals(1, $res); } @@ -1602,4 +1610,13 @@ public function sentinels() [FieldValue::arrayRemove([])] ]; } + + public function aggregationTypes() + { + return [ + ['count', null], + ['sum', 'testField'], + ['avg', 'testField'] + ]; + } } diff --git a/Firestore/tests/Unit/TransactionTest.php b/Firestore/tests/Unit/TransactionTest.php index 385a82d41ebd..8de6087f1d19 100644 --- a/Firestore/tests/Unit/TransactionTest.php +++ b/Firestore/tests/Unit/TransactionTest.php @@ -106,7 +106,10 @@ public function testRunQuery() $this->assertInstanceOf(QuerySnapshot::class, $res); } - public function testRunAggregateQuery() + /** + * @dataProvider aggregationTypes + */ + public function testRunAggregateQuery($type, $arg) { $this->connection->runAggregationQuery(Argument::any()) ->shouldBeCalled() @@ -116,7 +119,7 @@ public function testRunAggregateQuery() $this->connection->reveal(), self::DOCUMENT, ['query' => []], - Aggregate::count() + $arg ? Aggregate::$type($arg) : Aggregate::$type() ); $this->transaction->___setProperty('connection', $this->connection->reveal()); @@ -126,7 +129,10 @@ public function testRunAggregateQuery() $this->assertInstanceOf(AggregateQuerySnapshot::class, $res); } - public function testGetAggregateSnapshotReadTime() + /** + * @dataProvider aggregationTypes + */ + public function testGetAggregateSnapshotReadTime($type, $arg) { $timestamp = [ 'seconds' => 100, @@ -137,7 +143,7 @@ public function testGetAggregateSnapshotReadTime() $this->connection->reveal(), self::DOCUMENT, ['query' => []], - Aggregate::count() + $arg ? Aggregate::$type($arg) : Aggregate::$type() ); $this->connection->runAggregationQuery( Argument::withEntry('readTime', $timestamp) @@ -145,7 +151,7 @@ public function testGetAggregateSnapshotReadTime() [ 'result' => [ 'aggregateFields' => [ - 'count' => ['integerValue' => 1] + $type => ['integerValue' => 1] ] ] ] @@ -160,7 +166,7 @@ public function testGetAggregateSnapshotReadTime() ) ]); - $this->assertEquals(1, $res->get('count')); + $this->assertEquals(1, $res->get($type)); } public function testGetAggregateSnapshotReadTimeInvalidReadTime() @@ -363,6 +369,15 @@ public function documents() ]; } + public function aggregationTypes() + { + return [ + ['count', null], + ['sum', 'testField'], + ['avg', 'testField'] + ]; + } + public function testDocumentsOrdered() { $timestamp = (new Timestamp(new \DateTimeImmutable))->formatAsString();