Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DOCSP-43919: aggregation builder #203

Merged
merged 8 commits into from
Feb 13, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .github/workflows/coding-standards.yml
Original file line number Diff line number Diff line change
@@ -8,9 +8,7 @@ on:

env:
PHP_VERSION: "8.2"
# TODO: change to "stable" once 1.20.0 is released
# DRIVER_VERSION: "stable"
DRIVER_VERSION: "mongodb/mongo-php-driver@master"
DRIVER_VERSION: "stable"

jobs:
phpcs:
4 changes: 1 addition & 3 deletions .github/workflows/static-analysis.yml
Original file line number Diff line number Diff line change
@@ -14,9 +14,7 @@ on:

env:
PHP_VERSION: "8.2"
# TODO: change to "stable" once 1.20.0 is released
# DRIVER_VERSION: "stable"
DRIVER_VERSION: "mongodb/mongo-php-driver@master"
DRIVER_VERSION: "stable"

jobs:
psalm:
1 change: 1 addition & 0 deletions snooty.toml
Original file line number Diff line number Diff line change
@@ -41,6 +41,7 @@ php-library = "MongoDB PHP Library"
[constants]
php-library = "MongoDB PHP Library"
version = "1.20"
source-gh-branch = "v1.x"
full-version = "{+version+}.0"
extension-short = "PHP extension"
mdb-server = "MongoDB Server"
228 changes: 206 additions & 22 deletions source/aggregation.txt
Original file line number Diff line number Diff line change
@@ -47,8 +47,8 @@ The **aggregation pipeline** is the assembly line, **aggregation stages** are th
assembly stations, and **operator expressions** are the
specialized tools.

Aggregation Versus Find Operations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Compare Aggregation and Find Operations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

You can use find operations to perform the following actions:

@@ -72,6 +72,7 @@ Consider the following limitations when performing aggregation operations:
- Returned documents cannot violate the
:manual:`BSON document size limit </reference/limits/#mongodb-limit-BSON-Document-Size>`
of 16 megabytes.

- Pipeline stages have a memory limit of 100 megabytes by default. You can exceed this
limit by creating an options array that sets the ``allowDiskUse`` option to ``true``
and passing the array to the ``MongoDB\Collection::aggregate()`` method.
@@ -82,37 +83,63 @@ Consider the following limitations when performing aggregation operations:
</reference/operator/aggregation/graphLookup/>` stage has a strict
memory limit of 100 megabytes and ignores the ``allowDiskUse`` option.

.. _php-aggregation-example:
Aggregation APIs
----------------

Aggregation Example
-------------------
The {+library-short+} provides the following APIs to create aggregation
pipelines:

- :ref:`php-aggregation-array-api`: Create aggregation pipelines by
passing arrays that specify the aggregation stages.
- :ref:`php-aggregation-builder-api`: Create aggregation pipelines by
using factory methods to make your application more type-safe and debuggable.

The following sections describe each API and provide examples for
creating aggregation pipelines.

.. note::
.. _php-aggregation-array-api:

The examples in this guide use the ``restaurants`` collection in the ``sample_restaurants``
database from the :atlas:`Atlas sample datasets </sample-data>`. To learn how to create a
free MongoDB Atlas cluster and load the sample datasets, see the :atlas:`Get Started with Atlas
</getting-started>` guide.
Array API
---------

To perform an aggregation, pass an array containing the pipeline stages to
the ``MongoDB\Collection::aggregate()`` method.
To perform an aggregation, pass an array containing the pipeline stages
as BSON documents to the ``MongoDB\Collection::aggregate()`` method, as
shown in the following code:

.. code-block:: php

$pipeline = [
['<stage>' => <parameters>],
['<stage>' => <parameters>],
...
];

$cursor = $collection->aggregate($pipeline);

The examples in this section use the ``restaurants`` collection in the ``sample_restaurants``
database from the :atlas:`Atlas sample datasets </sample-data>`. To learn how to create a
free MongoDB Atlas cluster and load the sample datasets, see the :atlas:`Get Started with Atlas
</getting-started>` guide.

Filter and Group Example
~~~~~~~~~~~~~~~~~~~~~~~~

The following code example produces a count of the number of bakeries in each borough
of New York. To do so, it uses an aggregation pipeline that contains the following stages:

- :manual:`$match </reference/operator/aggregation/match/>` stage to filter for documents
in which the ``cuisine`` field contains the value ``'Bakery'``
1. :manual:`$match </reference/operator/aggregation/match/>` stage to filter for documents
in which the ``cuisine`` field contains the value ``'Bakery'``

- :manual:`$group </reference/operator/aggregation/group/>` stage to group the matching
documents by the ``borough`` field, accumulating a count of documents for each distinct
value
#. :manual:`$group </reference/operator/aggregation/group/>` stage to group the matching
documents by the ``borough`` field, accumulating a count of documents for each distinct
value

.. io-code-block::
:copyable:

.. input:: /includes/aggregation/aggregation.php
:start-after: start-match-group
:end-before: end-match-group
:start-after: start-array-match-group
:end-before: end-array-match-group
:language: php
:dedent:

@@ -141,14 +168,14 @@ and pass the database, collection, and pipeline stages as parameters. Then, pass
``MongoDB\Operation\Aggregate`` object to the ``MongoDB\Collection::explain()`` method.

The following example instructs MongoDB to explain the aggregation operation
from the preceding :ref:`php-aggregation-example`:
from the preceding section:

.. io-code-block::
:copyable:

.. input:: /includes/aggregation/aggregation.php
:start-after: start-explain
:end-before: end-explain
:start-after: start-array-explain
:end-before: end-array-explain
:language: php
:dedent:

@@ -161,6 +188,158 @@ from the preceding :ref:`php-aggregation-example`:
"maxIndexedAndSolutionsReached":false,"maxScansToExplodeReached":false,"winningPlan":{
... }

.. _php-aggregation-builder-api:

Aggregation Builder
-------------------

To create an aggregation pipeline by using the Aggregation Builder,
perform the following actions:

1. Create an array to store the pipeline stages.

#. For each stage, call the a factory method from the
``Stage`` that shares the same name as your desired aggregation
stage. For example, to create an ``$unwind`` stage, call the
``Stage::unwind()`` method.

#. Within the body of the ``Stage`` method, use methods from other
builder classes such as ``Query``, ``Expression``, or ``Accumulator``
to express your aggregation specifications.

The following code demonstrates the template for constructing
aggregation pipelines:

.. code-block:: php

$pipeline = [
Stage::<factory method>(
<stage specification>
),
Stage::<factory method>(
<stage specification>
),
...
];

$cursor = $collection->aggregate($pipeline);

The examples in this section are adapted from the {+mdb-server+} manual.
Each example provides a link to the sample data that you can insert into
your database to test the aggregation operation.

Filter and Group Example
~~~~~~~~~~~~~~~~~~~~~~~~

This example uses the sample data given in the :manual:`Calculate Count,
Sum, and Average </reference/operator/aggregation/group/#calculate-count--sum--and-average>`
section of the ``$group`` stage reference in the Server manual.

The following code example calculates the total sales amount, average
sales quantity, and sale count for each day in the year 2014. To do so,
it uses an aggregation pipeline that contains the following stages:

1. :manual:`$match </reference/operator/aggregation/match/>` stage to
filter for documents that contain a ``date`` field in which the year is
2014

#. :manual:`$group </reference/operator/aggregation/group/>` stage to
group the documents by date and calculate the total sales amount,
average sales quantity, and sale count for each group

#. :manual:`$sort </reference/operator/aggregation/sort/>` stage to
sort the results by the total sale amount for each group in descending
order

.. io-code-block::
:copyable:

.. input:: /includes/aggregation/aggregation.php
:start-after: start-builder-match-group
:end-before: end-builder-match-group
:language: php
:dedent:

.. output::
:visible: false

{"_id":"2014-04-04","totalSaleAmount":{"$numberDecimal":"200"},"averageQuantity":15,"count":2}
{"_id":"2014-03-15","totalSaleAmount":{"$numberDecimal":"50"},"averageQuantity":10,"count":1}
{"_id":"2014-03-01","totalSaleAmount":{"$numberDecimal":"40"},"averageQuantity":1.5,"count":2}

Unwind Embedded Arrays Example
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This example uses the sample data given in the :manual:`Unwind Embedded Arrays
</reference/operator/aggregation/unwind/#unwind-embedded-arrays>`
section of the ``$unwind`` stage reference in the Server manual.

The following code example groups sold items by their tags and
calculates the total sales amount for each tag. To do so,
it uses an aggregation pipeline that contains the following stages:

1. :manual:`$unwind </reference/operator/aggregation/unwind/>` stage to
output a separate document for each element in the ``items`` array

#. :manual:`$unwind </reference/operator/aggregation/unwind/>` stage to
output a separate document for each element in the ``items.tags`` arrays

#. :manual:`$group </reference/operator/aggregation/group/>` stage to
group the documents by the tag value and calculate the total sales
amount of items that have each tag

.. io-code-block::
:copyable:

.. input:: /includes/aggregation/aggregation.php
:start-after: start-builder-unwind
:end-before: end-builder-unwind
:language: php
:dedent:

.. output::
:visible: false

{"_id":"office","totalSalesAmount":{"$numberDecimal":"1019.60"}}
{"_id":"school","totalSalesAmount":{"$numberDecimal":"104.85"}}
{"_id":"stationary","totalSalesAmount":{"$numberDecimal":"264.45"}}
{"_id":"electronics","totalSalesAmount":{"$numberDecimal":"800.00"}}
{"_id":"writing","totalSalesAmount":{"$numberDecimal":"60.00"}}

Single Equality Join Example
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This example uses the sample data given in the :manual:`Perform a Single
Equality Join with $lookup
</reference/operator/aggregation/lookup/#perform-a-single-equality-join-with--lookup>`
section of the ``$lookup`` stage reference in the Server manual.

The following code example joins the documents from the ``orders``
collection with the documents from the ``inventory`` collection by using
the ``item`` field from the ``orders`` collection and the ``sku`` field
from the ``inventory`` collection.

To do so, the example uses an aggregation pipeline that contains a
:manual:`$lookup </reference/operator/aggregation/lookup/>` stage that
specifies the collection to retrieve data from and the local and
foreign field names.

.. io-code-block::
:copyable:

.. input:: /includes/aggregation/aggregation.php
:start-after: start-builder-lookup
:end-before: end-builder-lookup
:language: php
:dedent:

.. output::
:visible: false

{"_id":1,"item":"almonds","price":12,"quantity":2,"inventory_docs":[{"_id":1,"sku":"almonds","description":"product 1","instock":120}]}
{"_id":2,"item":"pecans","price":20,"quantity":1,"inventory_docs":[{"_id":4,"sku":"pecans","description":"product 4","instock":70}]}
{"_id":3,"inventory_docs":[{"_id":5,"sku":null,"description":"Incomplete"},{"_id":6}]}

Additional Information
----------------------

@@ -169,6 +348,11 @@ pipelines, see `Complex Aggregation Pipelines with Vanilla PHP and MongoDB
<https://www.mongodb.com/developer/products/mongodb/aggregations-php-mongodb/>`__
in the MongoDB Developer Center.

To view more examples of aggregation pipelines built by using the Aggregation
Builder, see the :github:`Stage class test suite
<mongodb/mongo-php-library/tree/{+source-gh-branch+}/tests/Builder/Stage>` in the
{+library-short+} source code on GitHub.

MongoDB Server Manual
~~~~~~~~~~~~~~~~~~~~~

9 changes: 4 additions & 5 deletions source/aggregation/atlas-search.txt
Original file line number Diff line number Diff line change
@@ -22,8 +22,7 @@ Overview

In this guide, you can learn how to perform searches on your documents
by using the Atlas Search feature. The {+library-short+} allows you to
perform Atlas Search queries by using the :ref:`Aggregation Builder API
<TODO DOCSP-43919>`.
perform Atlas Search queries by using the :ref:`php-aggregation-builder-api`.

.. note:: Deployment Compatibility

@@ -66,12 +65,12 @@ Search queries by using the Aggregation Builder:
To create a ``$search`` stage in your aggregation pipeline, perform the
following actions:

1. Create an array to store the pipeline stages
1. Create an array to store the pipeline stages.

#. Call the ``Stage::search()`` method to create the Atlas Search stage
#. Call the ``Stage::search()`` method to create the Atlas Search stage.

#. Within the body of the ``search()`` method, use methods from the
``Search`` builder class to construct your Search query criteria
``Search`` builder class to construct your Search query criteria.

The following code demonstrates the template for constructing basic Atlas Search
queries:
9 changes: 4 additions & 5 deletions source/aggregation/vector-search.txt
Original file line number Diff line number Diff line change
@@ -22,8 +22,7 @@ Overview

In this guide, you can learn how to perform searches on your documents
by using the Atlas Vector Search feature. The {+library-short+} allows you to
perform Atlas Vector Search queries by using the :ref:`Aggregation Builder API
<TODO DOCSP-43919>`.
perform Atlas Vector Search queries by using the :ref:`php-aggregation-builder-api`.

.. note:: Deployment Compatibility

@@ -67,13 +66,13 @@ Search queries by using the Aggregation Builder:
To create a ``$vectorSearch`` stage in your aggregation pipeline, perform the
following actions:

1. Create an array to store the pipeline stages
1. Create an array to store the pipeline stages.

#. Call the ``Stage::vectorSearch()`` method to create the Atlas Vector
Search stage
Search stage.

#. Within the body of the ``vectorSearch()`` method, specify the
criteria for your vector query
criteria for your vector query.

The following code demonstrates the template for constructing basic Atlas Search
queries:
83 changes: 79 additions & 4 deletions source/includes/aggregation/aggregation.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

require 'vendor/autoload.php';

$uri = getenv('MONGODB_URI') ?: throw new RuntimeException('Set the MONGODB_URI variable to your Atlas URI that connects to the sample dataset');
@@ -8,7 +9,7 @@

// Retrieves documents with a cuisine value of "Bakery", groups them by "borough", and
// counts each borough's matching documents
// start-match-group
// start-array-match-group
$pipeline = [
['$match' => ['cuisine' => 'Bakery']],
['$group' => ['_id' => '$borough', 'count' => ['$sum' => 1]]],
@@ -19,10 +20,10 @@
foreach ($cursor as $doc) {
echo json_encode($doc), PHP_EOL;
}
// end-match-group
// end-array-match-group

// Performs the same aggregation operation as above but asks MongoDB to explain it
// start-explain
// start-array-explain
$pipeline = [
['$match' => ['cuisine' => 'Bakery']],
['$group' => ['_id' => '$borough', 'count' => ['$sum' => 1]]],
@@ -36,5 +37,79 @@

$result = $collection->explain($aggregate);
echo json_encode($result), PHP_EOL;
// end-explain
// end-array-explain

// start-builder-match-group
$pipeline = [
MongoDB\Builder\Stage::match(
date: [
MongoDB\Builder\Query::gte(new MongoDB\BSON\UTCDateTime(new DateTimeImmutable('2014-01-01'))),
MongoDB\Builder\Query::lt(new MongoDB\BSON\UTCDateTime(new DateTimeImmutable('2015-01-01'))),
],
),
MongoDB\Builder\Stage::group(
_id: MongoDB\Builder\Expression::dateToString(MongoDB\Builder\Expression::dateFieldPath('date'), '%Y-%m-%d'),
totalSaleAmount: MongoDB\Builder\Accumulator::sum(
MongoDB\Builder\Expression::multiply(
MongoDB\Builder\Expression::numberFieldPath('price'),
MongoDB\Builder\Expression::numberFieldPath('quantity'),
),
),
averageQuantity: MongoDB\Builder\Accumulator::avg(
MongoDB\Builder\Expression::numberFieldPath('quantity'),
),
count: MongoDB\Builder\Accumulator::sum(1),
),
MongoDB\Builder\Stage::sort(
totalSaleAmount: MongoDB\Builder\Type\Sort::Desc,
),
];

$cursor = $collection->aggregate($pipeline);

foreach ($cursor as $doc) {
echo json_encode($doc), PHP_EOL;
}
// end-builder-match-group

// start-builder-unwind
$pipeline = [
MongoDB\Builder\Stage::unwind(MongoDB\Builder\Expression::arrayFieldPath('items')),
MongoDB\Builder\Stage::unwind(MongoDB\Builder\Expression::arrayFieldPath('items.tags')),
MongoDB\Builder\Stage::group(
_id: MongoDB\Builder\Expression::fieldPath('items.tags'),
totalSalesAmount: MongoDB\Builder\Accumulator::sum(
MongoDB\Builder\Expression::multiply(
MongoDB\Builder\Expression::numberFieldPath('items.price'),
MongoDB\Builder\Expression::numberFieldPath('items.quantity'),
),
),
),
];

$cursor = $collection->aggregate($pipeline);

foreach ($cursor as $doc) {
echo json_encode($doc), PHP_EOL;
}
// end-builder-unwind

$collection = $client->db->orders;

// start-builder-lookup
$pipeline = [
MongoDB\Builder\Stage::lookup(
from: 'inventory',
localField: 'item',
foreignField: 'sku',
as: 'inventory_docs',
),
];

/* Performs the aggregation on the orders collection */
$cursor = $collection->aggregate($pipeline);

foreach ($cursor as $doc) {
echo json_encode($doc), PHP_EOL;
}
// end-builder-lookup
10 changes: 4 additions & 6 deletions source/indexes/atlas-search-index.txt
Original file line number Diff line number Diff line change
@@ -86,13 +86,11 @@ Vector Search indexes in one call:
:end-before: end-create-multiple-indexes

After you create Atlas Search or Atlas Vector Search indexes, you can
perform the corresponding query types on your documents.
perform the corresponding query types on your documents. To learn more,
see the following guides:

..
TODO uncomment when https://github.com/mongodb/docs-php-library/pull/197 is merged
To learn more, see the following guides:
- :ref:`php-atlas-search`
- :ref:`php-vector-search`
- :ref:`php-atlas-search` guide
- :ref:`php-vector-search` guide

.. _php-atlas-search-index-list:

5 changes: 5 additions & 0 deletions source/whats-new.txt
Original file line number Diff line number Diff line change
@@ -38,6 +38,11 @@ What's New in 1.21
The {+library-short+} v1.21 release includes the following features,
improvements, and fixes:

- Introduces the Aggregation Builder, an API to build aggregation
pipelines in a more type-safe way. To learn more and
view examples, see the :ref:`php-aggregation-builder-api` section of
the Aggregation guide.

- Adds the following methods:

- :phpmethod:`MongoDB\Client::getDatabase()`: alias for :phpmethod:`MongoDB\Client::selectDatabase()`