Skip to content

Commit 4c79e5c

Browse files
Merge pull request #20 from pmatseykanets/configurable-querying-method
Making querying method configurable
2 parents 99199f8 + a8601ff commit 4c79e5c

9 files changed

+216
-39
lines changed

README.md

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,17 @@ This package makes it easy to use native PostgreSQL Full Text Search capabilitie
1212
## Contents
1313

1414
- [Installation](#installation)
15-
- [Scout 4.x](#scout-4x)
16-
- [Scout 2.x, 3.x](#scout-2x-3x)
17-
- [Scout 1.x](#scout-1x)
18-
- [Laravel](#laravel)
19-
- [Lumen](#lumen)
15+
- [Scout 4.x](#scout-4x)
16+
- [Scout 2.x, 3.x](#scout-2x-3x)
17+
- [Scout 1.x](#scout-1x)
18+
- [Laravel](#laravel)
19+
- [Lumen](#lumen)
2020
- [Configuration](#configuration)
21-
- [Configuring the Engine](#configuring-the-engine)
22-
- [Configuring PostgreSQL](#configuring-postgresql)
23-
- [Prepare the Schema](#prepare-the-schema)
24-
- [Configuring Searchable Data](#configuring-searchable-data)
25-
- [Configuring the Model](#configuring-the-model)
21+
- [Configuring the Engine](#configuring-the-engine)
22+
- [Configuring PostgreSQL](#configuring-postgresql)
23+
- [Prepare the Schema](#prepare-the-schema)
24+
- [Configuring Searchable Data](#configuring-searchable-data)
25+
- [Configuring the Model](#configuring-the-model)
2626
- [Usage](#usage)
2727
- [Testing](#testing)
2828
- [Security](#security)
@@ -36,22 +36,26 @@ This package makes it easy to use native PostgreSQL Full Text Search capabilitie
3636
You can install the package via composer:
3737

3838
### Scout 4.x (Laravel 5.4+)
39+
3940
``` bash
4041
composer require pmatseykanets/laravel-scout-postgres
4142
```
4243

4344
### Scout 2.x, 3.x
45+
4446
``` bash
4547
composer require pmatseykanets/laravel-scout-postgres:1.0.0
4648
```
4749

4850
### Scout 1.x
51+
4952
``` bash
5053
composer require pmatseykanets/laravel-scout-postgres:0.2.1
5154
```
5255

5356

5457
### Laravel
58+
5559
If you're using Laravel < 5.5 or if you have package auto-discovery turned off you have to manually register the service provider:
5660

5761
```php
@@ -63,6 +67,7 @@ If you're using Laravel < 5.5 or if you have package auto-discovery turned off y
6367
```
6468

6569
### Lumen
70+
6671
Scout service provider uses `config_path` helper that is not included in the Lumen.
6772
To fix this include the following snippet either directly in `bootstrap.app` or in your autoloaded helpers file i.e. `app/helpers.php`.
6873

@@ -82,6 +87,7 @@ if (! function_exists('config_path')) {
8287
```
8388

8489
Create the `scout.php` config file in `app/config` folder with the following contents
90+
8591
```php
8692
<?php
8793

@@ -124,6 +130,10 @@ Specify the database connection that should be used to access indexed documents
124130
// You can explicitly specify what PostgreSQL text search config to use by scout.
125131
// Use \dF in psql to see all available configurations in your database.
126132
'config' => 'english',
133+
// You may set the default querying method
134+
// Possible values: plainquery, phrasequery, tsquery
135+
// plainquery is used if this option is omitted.
136+
'search_using' => 'tsquery'
127137
],
128138
...
129139
```
@@ -154,13 +164,13 @@ class CreatePostsTable extends Migration
154164
$table->integer('user_id');
155165
$table->timestamps();
156166
});
157-
167+
158168
DB::statement('ALTER TABLE posts ADD searchable tsvector NULL');
159169
DB::statement('CREATE INDEX posts_searchable_index ON posts USING GIN (searchable)');
160170
// Or alternatively
161171
// DB::statement('CREATE INDEX posts_searchable_index ON posts USING GIST (searchable)');
162172
}
163-
173+
164174
public function down()
165175
{
166176
Schema::drop('posts');
@@ -193,7 +203,7 @@ class Post extends Model
193203
{
194204
use Searchable;
195205

196-
...
206+
// ...
197207
public function searchableOptions()
198208
{
199209
return [
@@ -244,7 +254,9 @@ public function searchableAdditionalArray()
244254
];
245255
}
246256
```
257+
247258
You may want to make your searchable column hidden so it's not standing in your way
259+
248260
```php
249261
protected $hidden = [
250262
'searchable',
@@ -255,11 +267,10 @@ protected $hidden = [
255267

256268
Please see the [official documentation](http://laravel.com/docs/master/scout) on how to use Laravel Scout.
257269

258-
259270
## Testing
260271

261272
``` bash
262-
$ composer test
273+
composer test
263274
```
264275

265276
## Security

src/PostgresEngine.php

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
use Laravel\Scout\Engines\Engine;
77
use Illuminate\Database\Eloquent\Model;
88
use Illuminate\Database\Eloquent\Collection;
9+
use ScoutEngines\Postgres\TsQuery\ToTsQuery;
10+
use ScoutEngines\Postgres\TsQuery\PlainToTsQuery;
11+
use ScoutEngines\Postgres\TsQuery\PhraseToTsQuery;
912
use Illuminate\Database\ConnectionResolverInterface;
1013

1114
class PostgresEngine extends Engine
@@ -155,7 +158,7 @@ public function delete($models)
155158

156159
$ids = $models->pluck($key)->all();
157160

158-
return $this->database
161+
$this->database
159162
->table($model->searchableAs())
160163
->whereIn($key, $ids)
161164
->update([$indexColumn => null]);
@@ -213,29 +216,20 @@ protected function performSearch(Builder $builder, $perPage = 0, $page = 1)
213216
{
214217
// We have to preserve the model in order to allow for
215218
// correct behavior of mapIds() method which currently
216-
// does not revceive a model instance
219+
// does not receive a model instance
217220
$this->preserveModel($builder->model);
218221

219222
$bindings = collect([]);
220223

221-
// The choices of parser, dictionaries and which types of tokens to index are determined
222-
// by the selected text search configuration which can be set globally in config/scout.php
223-
// file or individually for each model in searchableOptions()
224-
// See https://www.postgresql.org/docs/current/static/textsearch-controls.html
225-
$tsQuery = 'plainto_tsquery(COALESCE(?, get_current_ts_config()), ?) AS query';
226-
$bindings->push($this->searchConfig($builder->model) ?: null)
227-
->push($builder->query);
228-
229224
$indexColumn = $this->getIndexColumn($builder->model);
230225

231-
// Build the query
226+
// Build the SQL query
232227
$query = $this->database
233228
->table($builder->index ?: $builder->model->searchableAs())
234-
->crossJoin($this->database->raw($tsQuery))
235229
->select($builder->model->getKeyName())
236230
->selectRaw("{$this->rankingExpression($builder->model, $indexColumn)} AS rank")
237231
->selectRaw('COUNT(*) OVER () AS total_count')
238-
->whereRaw("$indexColumn @@ query");
232+
->whereRaw("$indexColumn @@ \"tsquery\"");
239233

240234
// Apply where clauses that were set on the builder instance if any
241235
foreach ($builder->wheres as $key => $value) {
@@ -267,10 +261,45 @@ protected function performSearch(Builder $builder, $perPage = 0, $page = 1)
267261
->limit($perPage);
268262
}
269263

264+
// The choices of parser, dictionaries and which types of tokens to index are determined
265+
// by the selected text search configuration which can be set globally in config/scout.php
266+
// file or individually for each model in searchableOptions()
267+
// See https://www.postgresql.org/docs/current/static/textsearch-controls.html
268+
$tsQuery = $builder->callback
269+
? call_user_func($builder->callback, $builder, $this->searchConfig($builder->model))
270+
: $this->defaultQueryMethod($builder->query, $this->searchConfig($builder->model));
271+
272+
$query->crossJoin($this->database->raw($tsQuery->sql().' AS "tsquery"'));
273+
274+
// Transfer bindings
275+
foreach ($tsQuery->bindings() as $binding) {
276+
$bindings->prepend($binding);
277+
}
278+
270279
return $this->database
271280
->select($query->toSql(), $bindings->all());
272281
}
273282

283+
/**
284+
* Returns the default query method.
285+
*
286+
* @param string $query
287+
* @param string $config
288+
* @return \ScoutEngines\Postgres\TsQuery\TsQueryable
289+
*/
290+
public function defaultQueryMethod($query, $config)
291+
{
292+
switch (strtolower($this->config('search_using', 'plain'))) {
293+
case 'tsquery':
294+
return new ToTsQuery($query, $config);
295+
case 'phrasequery':
296+
return new PhraseToTsQuery($query, $config);
297+
case 'plainquery':
298+
default:
299+
return new PlainToTsQuery($query, $config);
300+
}
301+
}
302+
274303
/**
275304
* Pluck and return the primary keys of the given results.
276305
*
@@ -345,7 +374,7 @@ protected function connect()
345374
*/
346375
protected function rankingExpression(Model $model, $indexColumn)
347376
{
348-
$args = collect([$indexColumn, 'query']);
377+
$args = collect([$indexColumn, '"tsquery"']);
349378

350379
if ($weights = $this->rankWeights($model)) {
351380
$args->prepend("'$weights'");
@@ -504,7 +533,7 @@ protected function preserveModel(Model $model)
504533
*/
505534
protected function searchConfig(Model $model)
506535
{
507-
return $this->option($model, 'config', $this->config('config', ''));
536+
return $this->option($model, 'config', $this->config('config', '')) ?: null;
508537
}
509538

510539
/**

src/PostgresEngineServiceProvider.php

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,49 @@
22

33
namespace ScoutEngines\Postgres;
44

5+
use Laravel\Scout\Builder;
56
use Laravel\Scout\EngineManager;
67
use Illuminate\Support\ServiceProvider;
8+
use ScoutEngines\Postgres\TsQuery\ToTsQuery;
9+
use ScoutEngines\Postgres\TsQuery\PlainToTsQuery;
10+
use ScoutEngines\Postgres\TsQuery\PhraseToTsQuery;
711

812
class PostgresEngineServiceProvider extends ServiceProvider
913
{
1014
public function boot()
1115
{
12-
app(EngineManager::class)->extend('pgsql', function () {
13-
return new PostgresEngine($this->app['db'], config('scout.pgsql', []));
16+
$this->app->make(EngineManager::class)->extend('pgsql', function () {
17+
return new PostgresEngine($this->app['db'], $this->app['config']->get('scout.pgsql', []));
1418
});
19+
20+
if (! Builder::hasMacro('usingPhraseQuery')) {
21+
Builder::macro('usingPhraseQuery', function () {
22+
$this->callback = function ($builder, $config) {
23+
return new PhraseToTsQuery($builder->query, $config);
24+
};
25+
26+
return $this;
27+
});
28+
}
29+
30+
if (! Builder::hasMacro('usingPlainQuery')) {
31+
Builder::macro('usingPlainQuery', function () {
32+
$this->callback = function ($builder, $config) {
33+
return new PlainToTsQuery($builder->query, $config);
34+
};
35+
36+
return $this;
37+
});
38+
}
39+
40+
if (! Builder::hasMacro('usingTsQuery')) {
41+
Builder::macro('usingTsQuery', function () {
42+
$this->callback = function ($builder, $config) {
43+
return new ToTsQuery($builder->query, $config);
44+
};
45+
46+
return $this;
47+
});
48+
}
1549
}
1650
}

src/TsQuery/BaseTsQueryable.php

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
namespace ScoutEngines\Postgres\TsQuery;
4+
5+
abstract class BaseTsQueryable implements TsQueryable
6+
{
7+
/**
8+
* Text Search Query.
9+
*
10+
* @var string
11+
*/
12+
public $query;
13+
14+
/**
15+
* PostgreSQL Text search configuration.
16+
*
17+
* @var string|null
18+
*/
19+
public $config;
20+
21+
/**
22+
* PostgreSQL Text Search Function.
23+
*
24+
* @var string
25+
*/
26+
protected $tsFunction = '';
27+
28+
/**
29+
* Create a new instance.
30+
*
31+
* @param string $query
32+
* @param string $config
33+
*/
34+
public function __construct($query, $config = null)
35+
{
36+
$this->query = $query;
37+
$this->config = $config;
38+
}
39+
40+
/**
41+
* Render the SQL representation.
42+
*
43+
* @return string
44+
*/
45+
public function sql()
46+
{
47+
return sprintf('%s(COALESCE(?, get_current_ts_config()), ?)', $this->tsFunction);
48+
}
49+
50+
/**
51+
* Return value bindings for the SQL representation.
52+
*
53+
* @return array
54+
*/
55+
public function bindings()
56+
{
57+
return [$this->query, $this->config];
58+
}
59+
}

src/TsQuery/PhraseToTsQuery.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace ScoutEngines\Postgres\TsQuery;
4+
5+
class PhraseToTsQuery extends BaseTsQueryable
6+
{
7+
protected $tsFunction = 'phraseto_tsquery';
8+
}

src/TsQuery/PlainToTsQuery.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace ScoutEngines\Postgres\TsQuery;
4+
5+
class PlainToTsQuery extends BaseTsQueryable
6+
{
7+
protected $tsFunction = 'plainto_tsquery';
8+
}

src/TsQuery/ToTsQuery.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace ScoutEngines\Postgres\TsQuery;
4+
5+
class ToTsQuery extends BaseTsQueryable
6+
{
7+
protected $tsFunction = 'to_tsquery';
8+
}

0 commit comments

Comments
 (0)