Skip to content

Commit

Permalink
Add GeoSearch Index and search
Browse files Browse the repository at this point in the history
  • Loading branch information
menthol committed Dec 28, 2019
1 parent 3e95445 commit 2deed3e
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 11 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,16 @@ In your `config/scout.php` add:
],
'asYouType' => false,
'searchBoolean' => env('TNTSEARCH_BOOLEAN', false),
'geoIndex' => env('TNTSEARCH_GEOINDEX', false),
],
```
To prevent your search indexes being commited to your project repository,
add the following line to your `.gitignore` file.

```/storage/*.index```
```
/storage/*.index
/storage/*.geoindex
```

The `asYouType` option can be set per model basis, see the example below.

Expand Down
30 changes: 24 additions & 6 deletions src/Console/ImportCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Illuminate\Console\Command;
use Illuminate\Contracts\Events\Dispatcher;
use TeamTNT\TNTSearch\TNTGeoSearch;
use TeamTNT\TNTSearch\TNTSearch;
use Illuminate\Support\Facades\Schema;

Expand Down Expand Up @@ -40,12 +41,6 @@ public function handle(Dispatcher $events)
$config = config('scout.tntsearch') + config("database.connections.$driver");
$db = app('db')->connection($driver);

$tnt->loadConfig($config);
$tnt->setDatabaseHandle($db->getPdo());

$indexer = $tnt->createIndex($model->searchableAs().'.index');
$indexer->setPrimaryKey($model->getKeyName());

$availableColumns = Schema::connection($driver)->getColumnListing($model->getTable());
$desiredColumns = array_keys($model->toSearchableArray());

Expand All @@ -58,9 +53,32 @@ public function handle(Dispatcher $events)
->addSelect($fields);
}

$tnt->loadConfig($config);
$tnt->setDatabaseHandle($db->getPdo());

$indexer = $tnt->createIndex($model->searchableAs().'.index');
$indexer->setPrimaryKey($model->getKeyName());

$indexer->query($query->toSql());

$indexer->run();

if (!empty($config['geoIndex'])) {
$geotnt = new TNTGeoSearch();

$geotnt->loadConfig($config);
$geotnt->setDatabaseHandle($db->getPdo());

$geoIndexer = $geotnt->getIndex();
$geoIndexer->loadConfig($geotnt->config);
$geoIndexer->createIndex($model->searchableAs().'.geoindex');
$geoIndexer->setPrimaryKey($model->getKeyName());

$geoIndexer->query($query->toSql());

$geoIndexer->run();
}

$this->info('All ['.$class.'] records have been imported.');
}
}
78 changes: 76 additions & 2 deletions src/Engines/TNTSearchEngine.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Laravel\Scout\Builder;
use Laravel\Scout\Engines\Engine;
use TeamTNT\TNTSearch\Exceptions\IndexNotFoundException;
use TeamTNT\TNTSearch\TNTGeoSearch;
use TeamTNT\TNTSearch\TNTSearch;

class TNTSearchEngine extends Engine
Expand All @@ -17,6 +18,11 @@ class TNTSearchEngine extends Engine
*/
protected $tnt;

/**
* @var TNTGeoSearch
*/
protected $geotnt;

/**
* @var Builder
*/
Expand All @@ -26,10 +32,12 @@ class TNTSearchEngine extends Engine
* Create a new engine instance.
*
* @param TNTSearch $tnt
* @param TNTGeoSearch|null $geotnt
*/
public function __construct(TNTSearch $tnt)
public function __construct(TNTSearch $tnt, TNTGeoSearch $geotnt = null)
{
$this->tnt = $tnt;
$this->geotnt = $geotnt;
}

/**
Expand All @@ -46,21 +54,51 @@ public function update($models)
$index = $this->tnt->getIndex();
$index->setPrimaryKey($models->first()->getKeyName());

$geoindex = null;
if ($this->geotnt) {
$this->geotnt->selectIndex("{$models->first()->searchableAs()}.geoindex");
$geoindex = $this->geotnt->getIndex();
$geoindex->loadConfig($this->geotnt->config);
$geoindex->setPrimaryKey($models->first()->getKeyName());
$geoindex->indexBeginTransaction();
}

$index->indexBeginTransaction();
$models->each(function ($model) use ($index) {
$models->each(function ($model) use ($index, $geoindex) {
$array = $model->toSearchableArray();

if (empty($array)) {
return;
}

if ($geoindex) {
$latitude = isset($array['latitude']) ? (float) $array['latitude'] : null;
$longitude = isset($array['longitude']) ? (float) $array['longitude'] : null;
unset($array['longitude']);
unset($array['latitude']);
}

if ($model->getKey()) {
$index->update($model->getKey(), $array);
if ($geoindex) {
$geoindex->prepareAndExecuteStatement(
'DELETE FROM locations WHERE doc_id = :documentId;',
[['key' => ':documentId', 'value' => $model->getKey()]]
);
}
} else {
$index->insert($array);
}
if ($geoindex && !empty($latitude) && !empty($longitude)) {
$array['latitude'] = $latitude;
$array['longitude'] = $longitude;
$geoindex->insert($array);
}
});
$index->indexEndTransaction();
if ($this->geotnt) {
$geoindex->indexEndTransaction();
}
}

/**
Expand All @@ -78,6 +116,17 @@ public function delete($models)
$index = $this->tnt->getIndex();
$index->setPrimaryKey($model->getKeyName());
$index->delete($model->getKey());

if ($this->geotnt) {
$this->geotnt->selectIndex("{$model->searchableAs()}.geoindex");
$index = $this->geotnt->getIndex();
$index->loadConfig($this->geotnt->config);
$index->setPrimaryKey($model->getKeyName());
$index->prepareAndExecuteStatement(
'DELETE FROM locations WHERE doc_id = :documentId;',
[['key' => ':documentId', 'value' => $model->getKey()]]
);
}
});
}

Expand Down Expand Up @@ -145,6 +194,9 @@ protected function performSearch(Builder $builder, array $options = [])
$index = $builder->index ?: $builder->model->searchableAs();
$limit = $builder->limit ?: 10000;
$this->tnt->selectIndex("{$index}.index");
if ($this->geotnt) {
$this->geotnt->selectIndex("{$index}.geoindex");
}

$this->builder = $builder;

Expand All @@ -160,6 +212,14 @@ protected function performSearch(Builder $builder, array $options = [])
$options
);
}

if (is_array($builder->query)) {
$location = $builder->query['location'];
$distance = $builder->query['distance'];
$limit = array_key_exists('limit', $builder->query) ? $builder->query['limit'] : 10;
return $this->geotnt->findNearest($location, $distance, $limit);
}

if (isset($this->tnt->config['searchBoolean']) ? $this->tnt->config['searchBoolean'] : false) {
return $this->tnt->searchBoolean($builder->query, $limit);
} else {
Expand Down Expand Up @@ -260,6 +320,13 @@ public function initIndex($model)
$indexer->setDatabaseHandle($model->getConnection()->getPdo());
$indexer->setPrimaryKey($model->getKeyName());
}
if ($this->geotnt && !file_exists($this->tnt->config['storage']."/{$indexName}.geoindex")) {
$indexer = $this->geotnt->getIndex();
$indexer->loadConfig($this->geotnt->config);
$indexer->createIndex("$indexName.geoindex");
$indexer->setDatabaseHandle($model->getConnection()->getPdo());
$indexer->setPrimaryKey($model->getKeyName());
}
}

/**
Expand Down Expand Up @@ -401,5 +468,12 @@ public function flush($model)
if (file_exists($pathToIndex)) {
unlink($pathToIndex);
}

if ($this->geotnt){
$pathToGeoIndex = $this->geotnt->config['storage']."/{$indexName}.geoindex";
if (file_exists($pathToGeoIndex)) {
unlink($pathToGeoIndex);
}
}
}
}
11 changes: 10 additions & 1 deletion src/TNTSearchScoutServiceProvider.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php namespace TeamTNT\Scout;

use TeamTNT\TNTSearch\TNTGeoSearch;
use TeamTNT\TNTSearch\TNTSearch;
use Laravel\Scout\EngineManager;
use Laravel\Scout\Builder;
Expand Down Expand Up @@ -28,7 +29,15 @@ public function boot()
$this->setFuzziness($tnt);
$this->setAsYouType($tnt);

return new TNTSearchEngine($tnt);
$geotnt = null;
if (!empty($config['geoIndex'])) {
$geotnt = new TNTGeoSearch();

$geotnt->loadConfig($config);
$geotnt->setDatabaseHandle(app('db')->connection()->getPdo());
}

return new TNTSearchEngine($tnt, $geotnt);
});

if ($this->app->runningInConsole()) {
Expand Down
31 changes: 30 additions & 1 deletion tests/TNTSearchEngineTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,36 @@ public function test_update_adds_objects_to_index()
$index->shouldReceive('update');
$index->shouldReceive('indexEndTransaction');

$engine = new TNTSearchEngine($client);
$geoClient = Mockery::mock('TeamTNT\TNTSearch\TNTGeoSearch');

$config = [
'storage' => '/',
'fuzziness' => false,
'fuzzy' => [
'prefix_length' => 2,
'max_expansions' => 50,
'distance' => 2
],
'asYouType' => false,
'searchBoolean' => false,
];

$geoClient->shouldReceive('getIndex')
->andReturn($geoIndex = Mockery::mock('TeamTNT\TNTSearch\Indexer\TNTGeoIndexer'))
->andSet('config', $config);
$geoIndex->shouldReceive('loadConfig');
$geoIndex->shouldReceive('createIndex')
->with('table.geoindex');
$geoIndex->shouldReceive('setDatabaseHandle');
$geoIndex->shouldReceive('setPrimaryKey');
$geoClient->shouldReceive('selectIndex');
$geoIndex->shouldReceive('indexBeginTransaction');
$geoIndex->shouldReceive('prepareAndExecuteStatement');
$geoIndex->shouldReceive('insert');
$geoIndex->shouldReceive('indexEndTransaction');


$engine = new TNTSearchEngine($client, $geoClient);
$engine->update(Collection::make([new TNTSearchEngineTestModel()]));
}
}
Expand Down

0 comments on commit 2deed3e

Please sign in to comment.