Skip to content
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

IDBPR-2554 Add whereMoreLikeThis and addFunctionScore #85

Merged
merged 5 commits into from
Feb 11, 2025
Merged
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
24 changes: 24 additions & 0 deletions src/Concerns/DecoratesBoolQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
namespace Ensi\LaravelElasticQuery\Concerns;

use Closure;
use Ensi\LaravelElasticQuery\Contracts\DSLAware;
use Ensi\LaravelElasticQuery\Contracts\FunctionScoreItem;
use Ensi\LaravelElasticQuery\Contracts\FunctionScoreOptions;
use Ensi\LaravelElasticQuery\Contracts\MatchOptions;
use Ensi\LaravelElasticQuery\Contracts\MoreLikeOptions;
use Ensi\LaravelElasticQuery\Contracts\MoreLikeThis;
use Ensi\LaravelElasticQuery\Contracts\MultiMatchOptions;
use Ensi\LaravelElasticQuery\Contracts\WildcardOptions;
use Ensi\LaravelElasticQuery\Filtering\BoolQueryBuilder;
Expand Down Expand Up @@ -141,4 +146,23 @@ public function addMustBool(callable $fn): static

return $this;
}

public function whereMoreLikeThis(array $fields, MoreLikeThis $likeThis, ?MoreLikeOptions $options = null): static
{
$this->forwardCallTo($this->boolQuery(), __FUNCTION__, func_get_args());

return $this;
}

/**
* @param array<FunctionScoreItem> $functions
* @param ?DSLAware $query
* @param ?FunctionScoreOptions $options
*/
public function addFunctionScore(array $functions, ?DSLAware $query = null, ?FunctionScoreOptions $options = null): static
{
$this->forwardCallTo($this->boolQuery(), __FUNCTION__, func_get_args());

return $this;
}
}
9 changes: 9 additions & 0 deletions src/Contracts/BoolQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,13 @@ public function whereWildcard(string $field, string $query, ?WildcardOptions $op
public function orWhereWildcard(string $field, string $query, ?WildcardOptions $options = null): static;

public function addMustBool(callable $fn): static;

public function whereMoreLikeThis(array $fields, MoreLikeThis $likeThis, ?MoreLikeOptions $options = null): static;

/**
* @param array<FunctionScoreItem> $functions
* @param ?DSLAware $query
* @param ?FunctionScoreOptions $options
*/
public function addFunctionScore(array $functions, ?DSLAware $query = null, ?FunctionScoreOptions $options = null): static;
}
25 changes: 25 additions & 0 deletions src/Contracts/BoostMode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace Ensi\LaravelElasticQuery\Contracts;

class BoostMode
{
public const MULTIPLY = 'multiply';
public const REPLACE = 'replace';
public const SUM = 'sum';
public const AVG = 'avg';
public const MAX = 'max';
public const MIN = 'min';

public static function cases(): array
{
return [
self::MULTIPLY,
self::SUM,
self::AVG,
self::REPLACE,
self::MAX,
self::MIN,
];
}
}
22 changes: 22 additions & 0 deletions src/Contracts/FunctionScoreItem.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Ensi\LaravelElasticQuery\Contracts;

use Illuminate\Contracts\Support\Arrayable;

class FunctionScoreItem implements Arrayable
{
public function __construct(
private int $weight,
private Criteria $filter,
) {
}

public function toArray(): array
{
return [
'weight' => $this->weight,
'filter' => $this->filter->toDSL(),
];
}
}
31 changes: 31 additions & 0 deletions src/Contracts/FunctionScoreOptions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace Ensi\LaravelElasticQuery\Contracts;

use Illuminate\Contracts\Support\Arrayable;
use Webmozart\Assert\Assert;

class FunctionScoreOptions implements Arrayable
{
public function __construct(private array $options = [])
{
}

public static function make(
?string $scoreMode = null,
?string $boostMode = null,
): static {
Assert::nullOrOneOf($scoreMode, ScoreMode::cases());
Assert::nullOrOneOf($boostMode, BoostMode::cases());

return new static(array_filter([
'score_mode' => $scoreMode,
'boost_mode' => $boostMode,
]));
}

public function toArray(): array
{
return $this->options;
}
}
42 changes: 42 additions & 0 deletions src/Contracts/MoreLikeOptions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace Ensi\LaravelElasticQuery\Contracts;

use Illuminate\Contracts\Support\Arrayable;

class MoreLikeOptions implements Arrayable
{
public function __construct(private array $options = [])
{
}

public static function make(
?int $maxQueryTerms = null,
?int $minTermFreq = null,
?int $minDocFreq = null,
?int $maxDocFreq = null,
?int $minWordLength = null,
?int $maxWordLength = null,
?array $stopWords = null,
?string $analyzer = null,
?string $minimumShouldMatch = null,
): static {

return new static(array_filter([
'max_query_terms' => $maxQueryTerms,
'min_term_freq' => $minTermFreq,
'min_doc_freq' => $minDocFreq,
'max_doc_freq' => $maxDocFreq,
'min_word_length' => $minWordLength,
'max_word_length' => $maxWordLength,
'stop_words' => $stopWords,
'analyzer' => $analyzer,
'minimum_should_match' => $minimumShouldMatch,
]));
}

public function toArray(): array
{
return $this->options;
}
}
32 changes: 32 additions & 0 deletions src/Contracts/MoreLikeThis.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Ensi\LaravelElasticQuery\Contracts;

use Illuminate\Contracts\Support\Arrayable;

class MoreLikeThis implements Arrayable
{
private array $this = [];

public function addId(string $id, ?string $index = null): static
{
$this->this[] = array_filter([
'_id' => $id,
'_index' => $index,
]);

return $this;
}

public function addString(string $token): static
{
$this->this[] = $token;

return $this;
}

public function toArray(): array
{
return $this->this;
}
}
25 changes: 25 additions & 0 deletions src/Contracts/ScoreMode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace Ensi\LaravelElasticQuery\Contracts;

class ScoreMode
{
public const MULTIPLY = 'multiply';
public const SUM = 'sum';
public const AVG = 'avg';
public const FIRST = 'first';
public const MAX = 'max';
public const MIN = 'min';

public static function cases(): array
{
return [
self::MULTIPLY,
self::SUM,
self::AVG,
self::FIRST,
self::MAX,
self::MIN,
];
}
}
26 changes: 26 additions & 0 deletions src/Filtering/BoolQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,17 @@
use Ensi\LaravelElasticQuery\Concerns\SupportsPath;
use Ensi\LaravelElasticQuery\Contracts\BoolQuery;
use Ensi\LaravelElasticQuery\Contracts\Criteria;
use Ensi\LaravelElasticQuery\Contracts\DSLAware;
use Ensi\LaravelElasticQuery\Contracts\FunctionScoreItem;
use Ensi\LaravelElasticQuery\Contracts\FunctionScoreOptions;
use Ensi\LaravelElasticQuery\Contracts\MatchOptions;
use Ensi\LaravelElasticQuery\Contracts\MoreLikeOptions;
use Ensi\LaravelElasticQuery\Contracts\MoreLikeThis;
use Ensi\LaravelElasticQuery\Contracts\MultiMatchOptions;
use Ensi\LaravelElasticQuery\Contracts\WildcardOptions;
use Ensi\LaravelElasticQuery\Filtering\Criterias\Exists;
use Ensi\LaravelElasticQuery\Filtering\Criterias\FunctionScore;
use Ensi\LaravelElasticQuery\Filtering\Criterias\MoreLike;
use Ensi\LaravelElasticQuery\Filtering\Criterias\MultiMatch;
use Ensi\LaravelElasticQuery\Filtering\Criterias\Nested;
use Ensi\LaravelElasticQuery\Filtering\Criterias\OneMatch;
Expand Down Expand Up @@ -245,6 +252,25 @@ protected function makeWildcard(string $field, string $query, ?WildcardOptions $
return new Wildcard($this->absolutePath($field), $query, $options ?: new WildcardOptions());
}

public function whereMoreLikeThis(array $fields, MoreLikeThis $likeThis, ?MoreLikeOptions $options = null): static
{
$this->must->add(new MoreLike($fields, $likeThis, $options));

return $this;
}

/**
* @param array<FunctionScoreItem> $functions
* @param ?DSLAware $query
* @param ?FunctionScoreOptions $options
*/
public function addFunctionScore(array $functions, ?DSLAware $query = null, ?FunctionScoreOptions $options = null): static
{
$this->should->add(new FunctionScore($functions, $query, $options));

return $this;
}

public function addMustBool(callable $fn): static
{
$this->must->add(static::make(builder: $fn));
Expand Down
39 changes: 39 additions & 0 deletions src/Filtering/Criterias/FunctionScore.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace Ensi\LaravelElasticQuery\Filtering\Criterias;

use Ensi\LaravelElasticQuery\Contracts\Criteria;
use Ensi\LaravelElasticQuery\Contracts\DSLAware;
use Ensi\LaravelElasticQuery\Contracts\FunctionScoreItem;
use Ensi\LaravelElasticQuery\Contracts\FunctionScoreOptions;
use stdClass;
use Webmozart\Assert\Assert;

class FunctionScore implements Criteria
{
/**
* @param array<FunctionScoreItem> $functions
* @param FunctionScoreOptions|null $options
*/
public function __construct(
private array $functions,
private ?DSLAware $query = null,
private ?FunctionScoreOptions $options = null,
) {
array_map(fn ($function) => Assert::isInstanceOfAny($function, [FunctionScoreItem::class]), $functions);
}

public function toDSL(): array
{
$body = [
'query' => $this->query?->toDSL() ?? ['match_all' => new stdClass()],
'functions' => array_map(fn (FunctionScoreItem $function) => $function->toArray(), $this->functions),
];

if ($this->options) {
$body = array_merge($this->options->toArray(), $body);
}

return ['function_score' => $body];
}
}
34 changes: 34 additions & 0 deletions src/Filtering/Criterias/MoreLike.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace Ensi\LaravelElasticQuery\Filtering\Criterias;

use Ensi\LaravelElasticQuery\Contracts\Criteria;
use Ensi\LaravelElasticQuery\Contracts\MoreLikeOptions;
use Ensi\LaravelElasticQuery\Contracts\MoreLikeThis;
use Webmozart\Assert\Assert;

class MoreLike implements Criteria
{
public function __construct(
private array $fields,
private MoreLikeThis $likeThis,
private ?MoreLikeOptions $options = null,
) {
Assert::minCount($fields, 1);
array_map(Assert::stringNotEmpty(...), $fields);
}

public function toDSL(): array
{
$body = [
'fields' => $this->fields,
'like' => $this->likeThis->toArray(),
];

if ($this->options) {
$body = array_merge($this->options->toArray(), $body);
}

return ['more_like_this' => $body];
}
}
Loading