Skip to content

Commit

Permalink
Feature/comparison operators (#2)
Browse files Browse the repository at this point in the history
Allow the use of comparison operators in search engines
  • Loading branch information
drasko95 committed Jun 2, 2024
1 parent 5285dfe commit 647b689
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 10 deletions.
22 changes: 22 additions & 0 deletions src/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ class Builder
*/
public $whereNotIns = [];

/**
* The "where" comparisons added to the query.
*
* @var array
*/
public $whereComparisons = [];

/**
* The "limit" that should be applied to the search.
*
Expand Down Expand Up @@ -167,6 +174,21 @@ public function whereNotIn($field, array $values)
return $this;
}

/**
* Add a "where comparison" constraint to the search query.
*
* @param string $field
* @param string $operator
* @param mixed $value
* @return $this
*/
public function whereComparison($field, $operator, $value)
{
$this->whereComparisons[] = compact('field', 'operator', 'value');

return $this;
}

/**
* Include soft deleted records in the results.
*
Expand Down
3 changes: 3 additions & 0 deletions src/Engines/AlgoliaEngine.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@ protected function filters(Builder $builder)
return collect($values)->map(function ($value) use ($key) {
return $key.'='.$value;
})->all();
})->values())
->merge(collect($builder->whereComparisons)->map(function ($comparison) {
return $comparison['field'].$comparison['operator'].$comparison['value'];
})->values())->values()->all();
}

Expand Down
5 changes: 5 additions & 0 deletions src/Engines/CollectionEngine.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ protected function searchModels(Builder $builder)
$query->whereNotIn($key, $values);
}
})
->when(! $builder->callback && count($builder->whereComparisons) > 0, function ($query) use ($builder) {
foreach ($builder->whereComparisons as $comparison) {
$query->where($comparison['field'], $comparison['operator'], $comparison['value']);
}
})
->when($builder->orders, function ($query) use ($builder) {
foreach ($builder->orders as $order) {
$query->orderBy($order['column'], $order['direction']);
Expand Down
4 changes: 4 additions & 0 deletions src/Engines/DatabaseEngine.php
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,10 @@ protected function addAdditionalConstraints(Builder $builder, $query)
foreach ($builder->whereNotIns as $key => $values) {
$query->whereNotIn($key, $values);
}
})->when(! $builder->callback && count($builder->whereComparisons) > 0, function ($query) use ($builder) {
foreach ($builder->whereComparisons as $comparison) {
$query->where($comparison['field'], $comparison['operator'], $comparison['value']);
}
})->when(! is_null($builder->queryCallback), function ($query) use ($builder) {
call_user_func($builder->queryCallback, $query);
});
Expand Down
32 changes: 25 additions & 7 deletions src/Engines/MeilisearchEngine.php
Original file line number Diff line number Diff line change
Expand Up @@ -177,13 +177,7 @@ protected function performSearch(Builder $builder, array $searchParams = [])
protected function filters(Builder $builder)
{
$filters = collect($builder->wheres)->map(function ($value, $key) {
if (is_bool($value)) {
return sprintf('%s=%s', $key, $value ? 'true' : 'false');
}

return is_numeric($value)
? sprintf('%s=%s', $key, $value)
: sprintf('%s="%s"', $key, $value);
return sprintf('%s=%s', $key, $this->formatValue($value));
});

$whereInOperators = [
Expand All @@ -207,9 +201,33 @@ protected function filters(Builder $builder)
}
}

collect($builder->whereComparisons)->each(function ($comparison) use ($filters) {
$filters->push(sprintf(
'%s%s%s',
$comparison['field'],
$comparison['operator'],
$this->formatValue($comparison['value']))
);
});

return $filters->values()->implode(' AND ');
}

/**
* Format the value for the filter depending on its type.
*
* @param mixed $value
* @return string
*/
protected function formatValue($value)
{
if (is_bool($value)) {
return $value ? 'true' : 'false';
}

return is_numeric($value) ? $value : sprintf('"%s"', $value);
}

/**
* Get the sort array for the query.
*
Expand Down
12 changes: 9 additions & 3 deletions src/Engines/TypesenseEngine.php
Original file line number Diff line number Diff line change
Expand Up @@ -300,9 +300,15 @@ protected function filters(Builder $builder): string
->values()
->implode(' && ');

return $whereFilter.(
($whereFilter !== '' && $whereInFilter !== '') ? ' && ' : ''
).$whereInFilter;
$whereComparisonFilter = collect($builder->whereComparisons)
->map(fn ($comparison) => sprintf('%s:%s%s', $comparison['field'], $comparison['operator'], $comparison['value']))
->values()
->implode(' && ');

return collect([$whereFilter, $whereInFilter, $whereComparisonFilter])
->filter()
->values()
->implode(' && ');
}

/**
Expand Down
19 changes: 19 additions & 0 deletions tests/Unit/AlgoliaEngineTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,25 @@ public function test_search_sends_correct_parameters_to_algolia_for_empty_where_
$engine->search($builder);
}

public function test_search_sends_correct_parameters_to_algolia_for_where_comparison_search()
{
$client = m::mock(SearchClient::class);
$client->shouldReceive('initIndex')->with('table')->andReturn($index = m::mock(stdClass::class));
$index->shouldReceive('search')->with('zonda', [
'numericFilters' => ['foo=1', ['bar=1', 'bar=2'], 'baz>2', 'qux<=3'],
]);

$engine = new AlgoliaEngine($client);
$builder = new Builder(new SearchableModel, 'zonda');
$builder
->where('foo', 1)
->whereIn('bar', [1, 2])
->whereComparison('baz', '>', 2)
->whereComparison('qux', '<=', 3);

$engine->search($builder);
}

public function test_map_correctly_maps_results_to_models()
{
$client = m::mock(SearchClient::class);
Expand Down
43 changes: 43 additions & 0 deletions tests/Unit/MeilisearchEngineTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,30 @@ public function test_where_not_in_conditions_are_applied()
$engine->search($builder);
}

public function test_where_comparison_conditions_are_applied()
{
$builder = new Builder(new SearchableModel(), '');
$builder->where('foo', 'bar');
$builder->where('bar', 'baz');
$builder->whereIn('qux', [1, 2]);
$builder->whereIn('quux', [1, 2]);
$builder->whereNotIn('eaea', [3]);
$builder->whereComparison('gt', '>', 10);
$builder->whereComparison('lt', '<', 20);
$builder->whereComparison('gte', '>=', 30);
$builder->whereComparison('lte', '<=', 40);

$client = m::mock(Client::class);
$client->shouldReceive('index')->once()->andReturn($index = m::mock(Indexes::class));
$index->shouldReceive('rawSearch')->once()->with($builder->query, array_filter([
'filter' => 'foo="bar" AND bar="baz" AND qux IN [1, 2] AND quux IN [1, 2] AND eaea NOT IN [3] AND gt>10 AND lt<20 AND gte>=30 AND lte<=40',
'hitsPerPage' => $builder->limit,
]))->andReturn([]);

$engine = new MeilisearchEngine($client);
$engine->search($builder);
}

public function test_where_in_conditions_are_applied_without_other_conditions()
{
$builder = new Builder(new SearchableModel(), '');
Expand Down Expand Up @@ -602,6 +626,25 @@ public function test_where_not_in_conditions_are_applied_without_other_condition
$engine->search($builder);
}

public function test_where_comparison_conditions_are_applied_without_other_conditions()
{
$builder = new Builder(new SearchableModel(), '');
$builder->whereComparison('gt', '>', 10);
$builder->whereComparison('lt', '<', 20);
$builder->whereComparison('gte', '>=', 30);
$builder->whereComparison('lte', '<=', 40);

$client = m::mock(Client::class);
$client->shouldReceive('index')->once()->andReturn($index = m::mock(Indexes::class));
$index->shouldReceive('rawSearch')->once()->with($builder->query, array_filter([
'filter' => 'gt>10 AND lt<20 AND gte>=30 AND lte<=40',
'hitsPerPage' => $builder->limit,
]))->andReturn([]);

$engine = new MeilisearchEngine($client);
$engine->search($builder);
}

public function test_empty_where_in_conditions_are_applied_correctly()
{
$builder = new Builder(new SearchableModel(), '');
Expand Down

0 comments on commit 647b689

Please sign in to comment.