Skip to content

Commit

Permalink
PHPORM-49 Implement Query\Builder::whereNot by encapsulating into `…
Browse files Browse the repository at this point in the history
…$not` (mongodb#13)

`Query\Builder::whereNot` was simply ignoring the "not" and breaking the built query.
  • Loading branch information
GromNaN committed Jul 19, 2023
1 parent cf103ba commit 9d9c7c8
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 7 deletions.
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,17 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

- Add classes to cast ObjectId and UUID instances [#1](https://github.com/GromNaN/laravel-mongodb-private/pull/1) by [@alcaeus](https://github.com/alcaeus).
- Add `Query\Builder::toMql()` to simplify comprehensive query tests [#6](https://github.com/GromNaN/laravel-mongodb-private/pull/6) by [@GromNaN](https://github.com/GromNaN).
- Fix `Query\Builder::whereNot` to use MongoDB [`$not`](https://www.mongodb.com/docs/manual/reference/operator/query/not/) operator [#13](https://github.com/GromNaN/laravel-mongodb-private/pull/13) by [@GromNaN](https://github.com/GromNaN).
- Fix `Query\Builder::whereBetween` to accept `Carbon\Period` object [#10](https://github.com/GromNaN/laravel-mongodb-private/pull/10) by [@GromNaN](https://github.com/GromNaN).
- Throw an exception for unsupported `Query\Builder` methods [#9](https://github.com/GromNaN/laravel-mongodb-private/pull/9) by [@GromNaN](https://github.com/GromNaN).
- Throw an exception when `Query\Builder::orderBy()` is used with invalid direction [#7](https://github.com/GromNaN/laravel-mongodb-private/pull/7) by [@GromNaN](https://github.com/GromNaN).
- Throw an exception when `Query\Builder::push()` is used incorrectly [#5](https://github.com/GromNaN/laravel-mongodb-private/pull/5) by [@GromNaN](https://github.com/GromNaN).

## [3.9.2] - 2022-09-01

### Addded
### Added
- Add single word name mutators [#2438](https://github.com/jenssegers/laravel-mongodb/pull/2438) by [@RosemaryOrchard](https://github.com/RosemaryOrchard) & [@mrneatly](https://github.com/mrneatly).

### Fixed
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,12 @@ $users =
->get();
```

**NOT statements**

```php
$users = User::whereNot('age', '>', 18)->get();
```

**whereIn**

```php
Expand Down
19 changes: 13 additions & 6 deletions src/Query/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -1019,19 +1019,26 @@ protected function compileWheres(): array
}
}

// The next item in a "chain" of wheres devices the boolean of the
// first item. So if we see that there are multiple wheres, we will
// use the operator of the next where.
if ($i == 0 && count($wheres) > 1 && $where['boolean'] == 'and') {
$where['boolean'] = $wheres[$i + 1]['boolean'];
// In a sequence of "where" clauses, the logical operator of the
// first "where" is determined by the 2nd "where".
// $where['boolean'] = "and", "or", "and not" or "or not"
if ($i == 0 && count($wheres) > 1
&& str_starts_with($where['boolean'], 'and')
&& str_starts_with($wheres[$i + 1]['boolean'], 'or')
) {
$where['boolean'] = 'or'.(str_ends_with($where['boolean'], 'not') ? ' not' : '');
}

// We use different methods to compile different wheres.
$method = "compileWhere{$where['type']}";
$result = $this->{$method}($where);

if (str_ends_with($where['boolean'], 'not')) {
$result = ['$not' => $result];
}

// Wrap the where with an $or operator.
if ($where['boolean'] == 'or') {
if (str_starts_with($where['boolean'], 'or')) {
$result = ['$or' => [$result]];
}

Expand Down
182 changes: 182 additions & 0 deletions tests/Query/BuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,17 @@ public static function provideQueryBuilderToMql(): iterable
fn (Builder $builder) => $builder->where('foo', 'bar'),
];

yield 'where with single array of conditions' => [
['find' => [
['$and' => [
['foo' => 1],
['bar' => 2],
]],
[], // options
]],
fn (Builder $builder) => $builder->where(['foo' => 1, 'bar' => 2]),
];

yield 'find > date' => [
['find' => [['foo' => ['$gt' => new UTCDateTime($date)]], []]],
fn (Builder $builder) => $builder->where('foo', '>', $date),
Expand All @@ -65,6 +76,177 @@ public static function provideQueryBuilderToMql(): iterable
fn (Builder $builder) => $builder->limit(10)->offset(5)->select('foo', 'bar'),
];

/** @see DatabaseQueryBuilderTest::testBasicWhereNot() */
yield 'whereNot (multiple)' => [
['find' => [
['$and' => [
['$not' => ['name' => 'foo']],
['$not' => ['name' => ['$ne' => 'bar']]],
]],
[], // options
]],
fn (Builder $builder) => $builder
->whereNot('name', 'foo')
->whereNot('name', '<>', 'bar'),
];

/** @see DatabaseQueryBuilderTest::testBasicOrWheres() */
yield 'where orWhere' => [
['find' => [
['$or' => [
['id' => 1],
['email' => 'foo'],
]],
[], // options
]],
fn (Builder $builder) => $builder
->where('id', '=', 1)
->orWhere('email', '=', 'foo'),
];

/** @see DatabaseQueryBuilderTest::testBasicOrWhereNot() */
yield 'orWhereNot' => [
['find' => [
['$or' => [
['$not' => ['name' => 'foo']],
['$not' => ['name' => ['$ne' => 'bar']]],
]],
[], // options
]],
fn (Builder $builder) => $builder
->orWhereNot('name', 'foo')
->orWhereNot('name', '<>', 'bar'),
];

yield 'whereNot orWhere' => [
['find' => [
['$or' => [
['$not' => ['name' => 'foo']],
['name' => ['$ne' => 'bar']],
]],
[], // options
]],
fn (Builder $builder) => $builder
->whereNot('name', 'foo')
->orWhere('name', '<>', 'bar'),
];

/** @see DatabaseQueryBuilderTest::testWhereNot() */
yield 'whereNot callable' => [
['find' => [
['$not' => ['name' => 'foo']],
[], // options
]],
fn (Builder $builder) => $builder
->whereNot(fn (Builder $q) => $q->where('name', 'foo')),
];

yield 'where whereNot' => [
['find' => [
['$and' => [
['name' => 'bar'],
['$not' => ['email' => 'foo']],
]],
[], // options
]],
fn (Builder $builder) => $builder
->where('name', '=', 'bar')
->whereNot(function (Builder $q) {
$q->where('email', '=', 'foo');
}),
];

yield 'whereNot (nested)' => [
['find' => [
['$not' => [
'$and' => [
['name' => 'foo'],
['$not' => ['email' => ['$ne' => 'bar']]],
],
]],
[], // options
]],
fn (Builder $builder) => $builder
->whereNot(function (Builder $q) {
$q->where('name', '=', 'foo')
->whereNot('email', '<>', 'bar');
}),
];

yield 'orWhere orWhereNot' => [
['find' => [
['$or' => [
['name' => 'bar'],
['$not' => ['email' => 'foo']],
]],
[], // options
]],
fn (Builder $builder) => $builder
->orWhere('name', '=', 'bar')
->orWhereNot(function (Builder $q) {
$q->where('email', '=', 'foo');
}),
];

yield 'where orWhereNot' => [
['find' => [
['$or' => [
['name' => 'bar'],
['$not' => ['email' => 'foo']],
]],
[], // options
]],
fn (Builder $builder) => $builder
->where('name', '=', 'bar')
->orWhereNot('email', '=', 'foo'),
];

/** @see DatabaseQueryBuilderTest::testWhereNotWithArrayConditions() */
yield 'whereNot with arrays of single condition' => [
['find' => [
['$not' => [
'$and' => [
['foo' => 1],
['bar' => 2],
],
]],
[], // options
]],
fn (Builder $builder) => $builder
->whereNot([['foo', 1], ['bar', 2]]),
];

yield 'whereNot with single array of conditions' => [
['find' => [
['$not' => [
'$and' => [
['foo' => 1],
['bar' => 2],
],
]],
[], // options
]],
fn (Builder $builder) => $builder
->whereNot(['foo' => 1, 'bar' => 2]),
];

yield 'whereNot with arrays of single condition with operator' => [
['find' => [
['$not' => [
'$and' => [
['foo' => 1],
['bar' => ['$lt' => 2]],
],
]],
[], // options
]],
fn (Builder $builder) => $builder
->whereNot([
['foo', 1],
['bar', '<', 2],
]),
];

/** @see DatabaseQueryBuilderTest::testOrderBys() */
yield 'orderBy multiple columns' => [
['find' => [[], ['sort' => ['email' => 1, 'age' => -1]]]],
Expand Down

0 comments on commit 9d9c7c8

Please sign in to comment.