diff --git a/CHANGELOG.md b/CHANGELOG.md index b1018c1..d28e9be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,18 @@ 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). +- Remove public property `Query\Builder::$paginating` [#15](https://github.com/GromNaN/laravel-mongodb-private/pull/15) 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 diff --git a/README.md b/README.md index 6a67525..71e7768 100644 --- a/README.md +++ b/README.md @@ -332,6 +332,12 @@ $users = ->get(); ``` +**NOT statements** + +```php +$users = User::whereNot('age', '>', 18)->get(); +``` + **whereIn** ```php diff --git a/src/Query/Builder.php b/src/Query/Builder.php index b6924bb..6321b86 100644 --- a/src/Query/Builder.php +++ b/src/Query/Builder.php @@ -59,13 +59,6 @@ class Builder extends BaseBuilder */ public $options = []; - /** - * Indicate if we are executing a pagination query. - * - * @var bool - */ - public $paginating = false; - /** * All of the available clause operators. * @@ -574,16 +567,6 @@ public function whereBetween($column, iterable $values, $boolean = 'and', $not = return $this; } - /** - * @inheritdoc - */ - public function forPage($page, $perPage = 15) - { - $this->paginating = true; - - return $this->skip(($page - 1) * $perPage)->take($perPage); - } - /** * @inheritdoc */ @@ -1019,19 +1002,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]]; } diff --git a/tests/Query/BuilderTest.php b/tests/Query/BuilderTest.php index f600fa7..7cd6f05 100644 --- a/tests/Query/BuilderTest.php +++ b/tests/Query/BuilderTest.php @@ -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), @@ -65,6 +76,183 @@ 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::testForPage() */ + yield 'forPage' => [ + ['find' => [[], ['limit' => 20, 'skip' => 40]]], + fn (Builder $builder) => $builder->forPage(3, 20), + ]; + /** @see DatabaseQueryBuilderTest::testOrderBys() */ yield 'orderBy multiple columns' => [ ['find' => [[], ['sort' => ['email' => 1, 'age' => -1]]]],