diff --git a/.gitignore b/.gitignore index 60718bd..1335ee0 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ .phpunit.result.cache composer.lock .DS_Store +.idea diff --git a/composer.json b/composer.json index d0f5908..fbe41b9 100755 --- a/composer.json +++ b/composer.json @@ -29,10 +29,10 @@ ], "require": { "php": "^8.1|^8.2|^8.3|^8.4", - "illuminate/contracts": "^10|^11", - "illuminate/database": "^10|^11", - "illuminate/support": "^10|^11", - "laravel/scout": "^10|^11" + "illuminate/contracts": "^10|^11|^12", + "illuminate/database": "^10|^11|^12", + "illuminate/support": "^10|^11|^12", + "laravel/scout": "^10|^11|^12" }, "require-dev": { "laravel/pint": "^1.18", diff --git a/src/PostgresEngine.php b/src/PostgresEngine.php index 48cbf2d..f3d6ba0 100755 --- a/src/PostgresEngine.php +++ b/src/PostgresEngine.php @@ -365,17 +365,34 @@ protected function performSearch(Builder $builder, ?int $perPage = 0, int $page ->selectRaw('COUNT(*) OVER () AS total_count') ->whereRaw("{$indexColumn} @@ \"tsquery\""); + // Apply query callback if set + if ($builder->queryCallback) { + call_user_func($builder->queryCallback, $builder); + } + // Apply where clauses that were set on the builder instance if any foreach ($builder->wheres as $key => $value) { + if ($key == '__soft_deleted') { + if ($this->usesSoftDeletes($builder->model)) { + if ($value == 1) { + $query->whereNotNull($builder->model->getDeletedAtColumn()); + } else { + $query->whereNull($builder->model->getDeletedAtColumn()); + } + } + continue; + } $query->where($key, $value); } - // If parsed documents are being stored in the model's table - if (! $this->isExternalIndex($builder->model)) { - // and the model uses soft deletes we need to exclude trashed rows - if ($this->usesSoftDeletes($builder->model)) { - $query->whereNull($builder->model->getDeletedAtColumn()); - } + // Apply whereIn clauses that were set on the builder instance if any + foreach ($builder->whereIns as $key => $value) { + $query->whereIn($key, $value); + } + + // Apply whereNoIn clauses that were set on the builder instance if any + foreach ($builder->whereNotIns as $key => $value) { + $query->whereNotIn($key, $value); } // Apply order by clauses that were set on the builder instance if any diff --git a/tests/PostgresEngineTest.php b/tests/PostgresEngineTest.php index 75cb30a..f475d36 100644 --- a/tests/PostgresEngineTest.php +++ b/tests/PostgresEngineTest.php @@ -32,25 +32,25 @@ public function update_adds_object_to_index() [$engine, $db] = $this->getEngine(); $db->shouldReceive('query') - ->andReturn($query = Mockery::mock('stdClass')); + ->andReturn($query = Mockery::mock('stdClass'))->once(); $query->shouldReceive('selectRaw') ->with( 'to_tsvector(COALESCE(?, get_current_ts_config()), ?) || setweight(to_tsvector(COALESCE(?, get_current_ts_config()), ?), ?) AS tsvector', [null, 'Foo', null, '', 'B'] ) - ->andReturnSelf(); + ->andReturnSelf()->once(); $query->shouldReceive('value') ->with('tsvector') - ->andReturn('foo'); + ->andReturn('foo')->once(); $db->shouldReceive('table') ->andReturn($table = Mockery::mock('stdClass')); $table->shouldReceive('where') ->with('id', '=', 1) - ->andReturnSelf(); + ->andReturnSelf()->once(); $table->shouldReceive('update') - ->with(['searchable' => 'foo']); + ->with(['searchable' => 'foo'])->once(); $engine->update(Collection::make([new TestModel])); } @@ -62,7 +62,7 @@ public function update_do_nothing_if_index_maintenance_turned_off_globally() { [$engine] = $this->getEngine(['maintain_index' => false]); - $engine->update(Collection::make([new TestModel])); + $this->assertNull($engine->update(Collection::make([new TestModel]))); } /** @@ -73,12 +73,15 @@ public function delete_removes_object_from_index() [$engine, $db] = $this->getEngine(); $db->shouldReceive('table') - ->andReturn($table = Mockery::mock('stdClass')); + ->andReturn($table = Mockery::mock('stdClass')) + ->once(); $table->shouldReceive('whereIn') ->with('id', [1]) - ->andReturnSelf(); + ->andReturnSelf() + ->once(); $table->shouldReceive('update') - ->with(['searchable' => null]); + ->with(['searchable' => null]) + ->once(); $engine->delete(Collection::make([new TestModel])); } @@ -142,7 +145,8 @@ public function search() ->shouldReceive('getBindings')->andReturn([null, 'foo', 1, 'qux']); $db->shouldReceive('select') - ->with(null, $table->getBindings()); + ->with(null, $table->getBindings()) + ->once(); $builder = new Builder(new TestModel, 'foo'); $builder->where('bar', 1) @@ -166,7 +170,8 @@ public function search_with_order_by() ->shouldReceive('getBindings')->andReturn([null, 'foo']); $db->shouldReceive('select') - ->with(null, $table->getBindings()); + ->with(null, $table->getBindings()) + ->once(); $builder = new Builder(new TestModel, 'foo'); $builder->orderBy('bar', 'desc') @@ -175,6 +180,85 @@ public function search_with_order_by() $engine->search($builder); } + /** + * @test + */ + public function search_with_queryCallback() + { + [$engine, $db] = $this->getEngine(); + + $skip = 0; + $limit = 5; + $table = $this->setDbExpectations($db); + + $table->shouldReceive('skip')->with($skip)->andReturnSelf() + ->shouldReceive('limit')->with($limit)->andReturnSelf() + ->shouldReceive('where')->with('bar', 1)->andReturnSelf() + ->shouldReceive('where')->with('baz', 'qux') + ->shouldReceive('getBindings')->andReturn([null, 'foo', 1, 'qux']); + + $db->shouldReceive('select') + ->with(null, $table->getBindings()) + ->once(); + + $builder = new Builder(new TestModel, 'foo'); + $builder->query(function ($q) { + $q->where('bar', 1) + ->where('baz', 'qux') + ->take(5); + }); + + $engine->search($builder); + } + + /** + * @test + */ + public function search_with_whereIn() + { + [$engine, $db] = $this->getEngine(); + + $skip = 0; + $limit = 5; + $table = $this->setDbExpectations($db); + + $table->shouldReceive('whereIn')->with('bar', [1])->andReturnSelf() + ->shouldReceive('getBindings')->andReturn([null, 'foo', [1]]); + + $db->shouldReceive('select') + ->with(null, $table->getBindings()) + ->once(); + + $builder = new Builder(new TestModel, 'foo'); + $builder->whereIn('bar', [1]); + + $engine->search($builder); + } + + /** + * @test + */ + public function search_with_whereNotIn() + { + [$engine, $db] = $this->getEngine(); + + $skip = 0; + $limit = 5; + $table = $this->setDbExpectations($db); + + $table->shouldReceive('whereNotIn')->with('bar', [1])->andReturnSelf() + ->shouldReceive('getBindings')->andReturn([null, 'foo', [1]]); + + $db->shouldReceive('select') + ->with(null, $table->getBindings()) + ->once(); + + $builder = new Builder(new TestModel, 'foo'); + $builder->whereNotIn('bar', [1]); + + $engine->search($builder); + } + /** * @test */ @@ -191,7 +275,7 @@ public function search_with_global_config() ->shouldReceive('where')->with('bar', 1) ->shouldReceive('getBindings')->andReturn(['simple', 'foo', 1]); - $db->shouldReceive('select')->with(null, $table->getBindings()); + $db->shouldReceive('select')->with(null, $table->getBindings())->once(); $builder = new Builder(new TestModel, 'foo'); $builder->where('bar', 1)->take(5); @@ -215,7 +299,7 @@ public function search_with_model_config() ->shouldReceive('where')->with('bar', 1) ->shouldReceive('getBindings')->andReturn(['english', 'foo', 1]); - $db->shouldReceive('select')->with(null, $table->getBindings()); + $db->shouldReceive('select')->with(null, $table->getBindings())->once(); $model = new TestModel; $model->searchableOptions['config'] = 'english'; @@ -241,7 +325,7 @@ public function search_with_soft_deletes() ->shouldReceive('whereNull')->with('deleted_at') ->shouldReceive('getBindings')->andReturn([null, 'foo', 1]); - $db->shouldReceive('select')->with(null, $table->getBindings()); + $db->shouldReceive('select')->with(null, $table->getBindings())->once(); $builder = new Builder(new SoftDeletableTestModel, 'foo'); $builder->where('bar', 1)->take(5);