Skip to content

[PoC] Add GeoSearch Index and search #259

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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'] : 10000;
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