<commit_before><?php

declare(strict_types=1);

namespace Markup\NeedleBundle\Builder;

use Markup\NeedleBundle\Attribute\AttributeInterface;
use Markup\NeedleBundle\Elastic\QueryShapeBuilder;
use Markup\NeedleBundle\Exception\UnformableSearchKeyException;
use Markup\NeedleBundle\Facet\SortOrderProviderInterface;
use Markup\NeedleBundle\Filter\FilterQueryInterface;
use Markup\NeedleBundle\Filter\FilterValueInterface;
use Markup\NeedleBundle\Filter\IntersectionFilterValueInterface;
use Markup\NeedleBundle\Filter\RangeFilterValueInterface;
use Markup\NeedleBundle\Filter\UnionFilterValueInterface;
use Markup\NeedleBundle\Query\ResolvedSelectQueryInterface;
use Markup\NeedleBundle\Sort\SortCollectionInterface;
use Markup\NeedleBundle\Sort\SortInterface;

/**
 * A query builder for Elasticsearch, to adapt the abstract Needle query format.
 */
class ElasticSelectQueryBuilder
{
    use DedupeFilterQueryTrait;

    /**
     * @var QueryShapeBuilder
     */
    private $queryShapeBuilder;

    public function __construct()
    {
        $this->queryShapeBuilder = new QueryShapeBuilder();
    }

    public function buildElasticQueryFromGeneric(
        ResolvedSelectQueryInterface $genericQuery,
        QueryBuildOptions $options
    ): array {
        $query = [];

        if ($genericQuery->hasSearchTerm()) {
            if (!$options->useWildcardSearch()) {
                $matchClause = [
                    'multi_match' => [
                        'query' => $genericQuery->getSearchTerm(),
                    ],
                ];
            } else {
                $matchClause = [
                    'query_string' => [
                        'query' => sprintf('*%s*', trim((string) $genericQuery->getSearchTerm(), '*')),
                    ],
                ];
            }
        } else {
            $matchClause = [
                'match_all' => new \stdClass(),
            ];
        }

        //determine whether we are using the facet values for an underlying "base" (recorded) query
        $shouldUseFacetValuesForRecordedQuery = $genericQuery->shouldUseFacetValuesForRecordedQuery();

        $filterQueries = $genericQuery->getFilterQueries();
        $baseQueries = $filterQueries;
        $extraQueries = [];
        if ($shouldUseFacetValuesForRecordedQuery) {
            $record = $genericQuery->getRecord();
            $recordedQueries = ($record) ? $record->getFilterQueries() : [];
            $baseQueries = $recordedQueries;
            $extraQueries = $this->diffQuerySets($filterQueries, $recordedQueries);
        }

        //if there are base filter queries, add them
        if (count($baseQueries) > 0) {
            $baseQueries = $this->dedupeFilterQueries($baseQueries);

            $mustClause = [$matchClause];
            foreach ($baseQueries as $filterQuery) {
                /** FilterQueryInterface $filterQuery */
                $clause = $this->queryShapeBuilder->getQueryShapeForFilterQuery($filterQuery);
                if ($clause !== null) {
                    $mustClause[] = $clause;<commit_msg>feat: use boosts in ES queries<commit_after><?php

declare(strict_types=1);

namespace Markup\NeedleBundle\Builder;

use Markup\NeedleBundle\Attribute\AttributeInterface;
use Markup\NeedleBundle\Elastic\QueryShapeBuilder;
use Markup\NeedleBundle\Exception\UnformableSearchKeyException;
use Markup\NeedleBundle\Facet\SortOrderProviderInterface;
use Markup\NeedleBundle\Filter\FilterQueryInterface;
use Markup\NeedleBundle\Filter\FilterValueInterface;
use Markup\NeedleBundle\Filter\IntersectionFilterValueInterface;
use Markup\NeedleBundle\Filter\RangeFilterValueInterface;
use Markup\NeedleBundle\Filter\UnionFilterValueInterface;
use Markup\NeedleBundle\Lucene\BoostLucenifier;
use Markup\NeedleBundle\Query\ResolvedSelectQueryInterface;
use Markup\NeedleBundle\Sort\SortCollectionInterface;
use Markup\NeedleBundle\Sort\SortInterface;

/**
 * A query builder for Elasticsearch, to adapt the abstract Needle query format.
 */
class ElasticSelectQueryBuilder
{
    use DedupeFilterQueryTrait;

    /**
     * @var QueryShapeBuilder
     */
    private $queryShapeBuilder;

    public function __construct()
    {
        $this->queryShapeBuilder = new QueryShapeBuilder();
    }

    public function buildElasticQueryFromGeneric(
        ResolvedSelectQueryInterface $genericQuery,
        QueryBuildOptions $options
    ): array {
        $query = [];

        //if there are boost query fields to apply, apply them within the query clause
        $boostQueryFields = $genericQuery->getBoostQueryFields();

        //generate query clause
        if ($genericQuery->hasSearchTerm()) {
            if (!empty($boostQueryFields)) {
                $boostLucenifier = new BoostLucenifier();
                $fieldRefs = [];
                foreach ($boostQueryFields as $boostQueryField) {
                    $fieldRefs[] = $boostLucenifier->lucenifyBoost($boostQueryField);
                }
                $fieldsData = [
                    'fields' => $fieldRefs,
                ];
            } else {
                $fieldsData = [];
            }
            if (!$options->useWildcardSearch()) {
                $matchClause = [
                    'multi_match' => array_merge(
                        [
                            'query' => $genericQuery->getSearchTerm(),
                        ],
                        $fieldsData
                    ),
                ];
            } else {
                $matchClause = [
                    'query_string' => array_merge(
                        [
                            'query' => sprintf('*%s*', trim((string) $genericQuery->getSearchTerm(), '*')),
                        ],
                        $fieldsData
                    ),
                ];
            }
        } else {
            $matchClause = [
                'match_all' => new \stdClass(),
            ];
        }

        //determine whether we are using the facet values for an underlying "base" (recorded) query
        $shouldUseFacetValuesForRecordedQuery = $genericQuery->shouldUseFacetValuesForRecordedQuery();

        $filterQueries = $genericQuery->getFilterQueries();
        $baseQueries = $filterQueries;
        $extraQueries = [];
        if ($shouldUseFacetValuesForRecordedQuery) {
            $record = $genericQuery->getRecord();
            $recordedQueries = ($record) ? $record->getFilterQueries() : [];
            $baseQueries = $recordedQueries;
            $extraQueries = $this->diffQuerySets($filterQueries, $recordedQueries);
        }

        //if there are base filter queries, add them
        if (count($baseQueries) > 0) {
            $baseQueries = $this->dedupeFilterQueries($baseQueries);

            $mustClause = [$matchClause];
            foreach ($baseQueries as $filterQuery) {
                /** FilterQueryInterface $filterQuery */
                $clause = $this->queryShapeBuilder->getQueryShapeForFilterQuery($filterQuery);
                if ($clause !== null) {
                    $mustClause[] = $clause;
{'fraction_non_alphanumeric': 0.10469212343243624, 'fraction_numerical': 0.0005636184303226716, 'mean_word_length': 2.2366621067031462, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 44, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1440832', 'n_tokens_mistral': 1885, 'n_tokens_neox': 1821, 'n_words': 334}