Skip to content

Fix support for subqueries using the query builder #2717

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

Merged
merged 2 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
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
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# Changelog
All notable changes to this project will be documented in this file.

## [4.1.2]

## [4.1.1]
* Fix support for subqueries using the query builder by @GromNaN in [#2717](https://github.com/mongodb/laravel-mongodb/pull/2717)

## [4.1.1] - 2024-01-17

* Fix casting issues by [@stubbo](https://github.com/stubbo) in [#2705](https://github.com/mongodb/laravel-mongodb/pull/2705)
* Move documentation to the mongodb.com domain at [https://www.mongodb.com/docs/drivers/php/laravel-mongodb/current/](https://www.mongodb.com/docs/drivers/php/laravel-mongodb/current/)
Expand Down
52 changes: 40 additions & 12 deletions docs/query-builder.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Query Builder

The database driver plugs right into the original query builder.

When using MongoDB connections, you will be able to build fluent queries to
When using MongoDB connections, you will be able to build fluent queries to
perform database operations.

For your convenience, there is a ``collection`` alias for ``table`` and
Expand Down Expand Up @@ -85,7 +85,7 @@ Available operations

$users = User::whereIn('age', [16, 18, 20])->get();

When using ``whereNotIn`` objects will be returned if the field is
When using ``whereNotIn`` objects will be returned if the field is
non-existent. Combine with ``whereNotNull('age')`` to omit those documents.

**whereBetween**
Expand Down Expand Up @@ -137,7 +137,7 @@ The usage is the same as ``whereMonth`` / ``whereDay`` / ``whereYear`` / ``where

**groupBy**

Selected columns that are not grouped will be aggregated with the ``$last``
Selected columns that are not grouped will be aggregated with the ``$last``
function.

.. code-block:: php
Expand Down Expand Up @@ -230,7 +230,7 @@ You may also specify more columns to update:
MongoDB-specific operators
~~~~~~~~~~~~~~~~~~~~~~~~~~

In addition to the Laravel Eloquent operators, all available MongoDB query
In addition to the Laravel Eloquent operators, all available MongoDB query
operators can be used with ``where``:

.. code-block:: php
Expand Down Expand Up @@ -279,7 +279,7 @@ Selects documents where values match a specified regular expression.

.. note::

You can also use the Laravel regexp operations. These will automatically
You can also use the Laravel regexp operations. These will automatically
convert your regular expression string to a ``MongoDB\BSON\Regex`` object.

.. code-block:: php
Expand All @@ -292,9 +292,37 @@ The inverse of regexp:

User::where('name', 'not regexp', '/.*doe/i')->get();

**ElemMatch**

The :manual:`$elemMatch </reference/operator/query/elemMatch//>` operator
matches documents that contain an array field with at least one element that
matches all the specified query criteria.

The following query matches only those documents where the results array
contains at least one element that is both greater than or equal to 80 and
is less than 85:

.. code-block:: php

User::where('results', 'elemMatch', ['gte' => 80, 'lt' => 85])->get();

A closure can be used to create more complex sub-queries.

The following query matches only those documents where the results array
contains at least one element with both product equal to "xyz" and score
greater than or equal to 8:

.. code-block:: php

User::where('results', 'elemMatch', function (Builder $builder) {
$builder
->where('product', 'xyz')
->andWhere('score', '>', 50);
})->get();

**Type**

Selects documents if a field is of the specified type. For more information
Selects documents if a field is of the specified type. For more information
check: :manual:`$type </reference/operator/query/type/#op._S_type/>` in the
MongoDB Server documentation.

Expand All @@ -304,7 +332,7 @@ MongoDB Server documentation.

**Mod**

Performs a modulo operation on the value of a field and selects documents with
Performs a modulo operation on the value of a field and selects documents with
a specified result.

.. code-block:: php
Expand Down Expand Up @@ -366,10 +394,10 @@ MongoDB-specific Geo operations
You can make a ``geoNear`` query on MongoDB.
You can omit specifying the automatic fields on the model.
The returned instance is a collection, so you can call the `Collection <https://laravel.com/docs/9.x/collections>`__ operations.
Make sure that your model has a ``location`` field, and a
Make sure that your model has a ``location`` field, and a
`2ndSphereIndex <https://www.mongodb.com/docs/manual/core/2dsphere>`__.
The data in the ``location`` field must be saved as `GeoJSON <https://www.mongodb.com/docs/manual/reference/geojson/>`__.
The ``location`` points must be saved as `WGS84 <https://www.mongodb.com/docs/manual/reference/glossary/#std-term-WGS84>`__
The ``location`` points must be saved as `WGS84 <https://www.mongodb.com/docs/manual/reference/glossary/#std-term-WGS84>`__
reference system for geometry calculation. That means that you must
save ``longitude and latitude``, in that order specifically, and to find near
with calculated distance, you ``must do the same way``.
Expand Down Expand Up @@ -405,7 +433,7 @@ with calculated distance, you ``must do the same way``.
Inserts, updates and deletes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Inserting, updating and deleting records works just like the original Eloquent.
Inserting, updating and deleting records works just like the original Eloquent.
Please check `Laravel Docs' Eloquent section <https://laravel.com/docs/6.x/eloquent>`__.

Here, only the MongoDB-specific operations are specified.
Expand All @@ -431,14 +459,14 @@ These expressions will be injected directly into the query.
'$where' => '/.*123.*/.test(this["hyphenated-field"])',
])->get();

You can also perform raw expressions on the internal MongoCollection object.
You can also perform raw expressions on the internal MongoCollection object.
If this is executed on the model class, it will return a collection of models.

If this is executed on the query builder, it will return the original response.

**Cursor timeout**

To prevent ``MongoCursorTimeout`` exceptions, you can manually set a timeout
To prevent ``MongoCursorTimeout`` exceptions, you can manually set a timeout
value that will be applied to the cursor:

.. code-block:: php
Expand Down
7 changes: 7 additions & 0 deletions src/Query/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -1363,6 +1363,13 @@ protected function compileWhereRaw(array $where): mixed
return $where['sql'];
}

protected function compileWhereSub(array $where): mixed
{
$where['value'] = $where['query']->compileWheres();

return $this->compileWhereBasic($where);
}

/**
* Set custom options for the query.
*
Expand Down
25 changes: 25 additions & 0 deletions tests/Query/BuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1127,6 +1127,31 @@ function (Builder $builder) {
],
fn (Builder $builder) => $builder->groupBy('foo'),
];

yield 'sub-query' => [
[
'find' => [
[
'filters' => [
'$elemMatch' => [
'$and' => [
['search_by' => 'by search'],
['value' => 'foo'],
],
],
],
],
[], // options
],
],
fn (Builder $builder) => $builder->where(
'filters',
'elemMatch',
function (Builder $elemMatchQuery): void {
$elemMatchQuery->where([ 'search_by' => 'by search', 'value' => 'foo' ]);
},
),
];
}

/** @dataProvider provideExceptions */
Expand Down