Magento 2: what is the search_request.xml file used for?

All we need is an easy explanation of the problem, so here it is.

Under app/code/Magento/CatalogSearch/etc/ there’s a search_request.xml file with the following content:

<requests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:noNamespaceSchemaLocation="urn:magento:framework:Search/etc/search_request.xsd">
    <request query="quick_search_container" index="catalogsearch_fulltext">
        <dimensions>
            <dimension name="scope" value="default"/>
        </dimensions>
        <queries>
            <query xsi:type="boolQuery" name="quick_search_container" boost="1">
                <queryReference clause="should" ref="search" />
                <queryReference clause="must" ref="category"/>
                <queryReference clause="must" ref="price"/>
                <queryReference clause="must" ref="visibility"/>
            </query>
            <query xsi:type="matchQuery" value="$search_term$" name="search">
                <match field="sku"/>
                <match field="*"/>
            </query>
            <query xsi:type="filteredQuery" name="category">
                <filterReference clause="must" ref="category_filter"/>
            </query>
            <query xsi:type="filteredQuery" name="price">
                <filterReference clause="must" ref="price_filter"/>
            </query>
            <query xsi:type="filteredQuery" name="visibility">
                <filterReference clause="must" ref="visibility_filter"/>
            </query>
        </queries>
        <filters>
            <filter xsi:type="termFilter" name="category_filter" field="category_ids" value="$category_ids$"/>
            <filter xsi:type="rangeFilter" name="price_filter" field="price" from="$price.from$" to="$price.to$"/>
            <filter xsi:type="termFilter" name="visibility_filter" field="visibility" value="$visibility$"/>
        </filters>
        <aggregations>
            <bucket name="price_bucket" field="price" xsi:type="dynamicBucket" method="$price_dynamic_algorithm$">
                <metrics>
                    <metric type="count"/>
                </metrics>
            </bucket>
            <bucket name="category_bucket" field="category_ids" xsi:type="termBucket">
                <metrics>
                    <metric type="count"/>
                </metrics>
            </bucket>
        </aggregations>
        <from>0</from>
        <size>10000</size>
    </request>
    <request query="advanced_search_container" index="catalogsearch_fulltext">
        <dimensions>
            <dimension name="scope" value="default"/>
        </dimensions>
        <queries>
            <query xsi:type="boolQuery" name="advanced_search_container" boost="1">
                <queryReference clause="should" ref="sku_query"/>
                <queryReference clause="should" ref="price_query"/>
                <queryReference clause="should" ref="category_query"/>
            </query>
            <query name="sku_query" xsi:type="filteredQuery">
                <filterReference clause="must" ref="sku_query_filter"/>
            </query>
            <query name="price_query" xsi:type="filteredQuery">
                <filterReference clause="must" ref="price_query_filter"/>
            </query>
            <query name="category_query" xsi:type="filteredQuery">
                <filterReference clause="must" ref="category_filter"/>
            </query>
        </queries>
        <filters>
            <filter xsi:type="wildcardFilter" name="sku_query_filter" field="sku" value="$sku$"/>
            <filter xsi:type="rangeFilter" name="price_query_filter" field="price" from="$price.from$" to="$price.to$"/>
            <filter xsi:type="termFilter" name="category_filter" field="category_ids" value="$category_ids$"/>
        </filters>
        <from>0</from>
        <size>10000</size>
    </request>
    <request query="catalog_view_container" index="catalogsearch_fulltext">
        <dimensions>
            <dimension name="scope" value="default"/>
        </dimensions>
        <queries>
            <query xsi:type="boolQuery" name="catalog_view_container" boost="1">
                <queryReference clause="must" ref="category"/>
                <queryReference clause="must" ref="price"/>
                <queryReference clause="must" ref="visibility"/>
            </query>
            <query xsi:type="filteredQuery" name="category">
                <filterReference clause="must" ref="category_filter"/>
            </query>
            <query xsi:type="filteredQuery" name="price">
                <filterReference clause="must" ref="price_filter"/>
            </query>
            <query xsi:type="filteredQuery" name="visibility">
                <filterReference clause="must" ref="visibility_filter"/>
            </query>
        </queries>
        <filters>
            <filter xsi:type="termFilter" name="category_filter" field="category_ids" value="$category_ids$"/>
            <filter xsi:type="rangeFilter" name="price_filter" field="price" from="$price.from$" to="$price.to$"/>
            <filter xsi:type="termFilter" name="visibility_filter" field="visibility" value="$visibility$"/>
        </filters>
        <aggregations>
            <bucket name="price_bucket" field="price" xsi:type="dynamicBucket" method="$price_dynamic_algorithm$">
                <metrics>
                    <metric type="count"/>
                </metrics>
            </bucket>
            <bucket name="category_bucket" field="category_ids" xsi:type="termBucket">
                <metrics>
                    <metric type="count"/>
                </metrics>
            </bucket>
        </aggregations>
        <from>0</from>
        <size>10000</size>
    </request>
</requests>

I’m assuming it is used for the search, I’m wondering how Magento processes this file and what are the following fields used for:

  • request
  • dimensions
  • queries
  • filters
  • aggregations
  • from
  • size

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

Magento 2 provides a declarative way to describe search request which should be executed. search_request.xml is a part of Query API. This declaration describes:

  1. What index should be queried
  2. What full-text queries should be executed (queries section)
  3. What filters should be applied (filters section)
  4. For which attributes faceted search should be built (Layered Navigation in terms of Magento) (aggregations section)

Naming convention here is very similar to ElasticSearch.
For example, if you will look at XSD file (Search/etc/search_request.xsd) you will see that there are three possible query types:

  1. Bool Query (analogue for Elasticsearch BoolQuery)
  2. Match Query (analogue for Elasticsearch Match Query)
  3. Filter Query (analogue for Elasticsearch Filtered Query)

There is presentation which describes Magento 2 new Search approach and architecture

For now Magento indexes just product data. And there are 3 scenarios (read 3 search queries for that data):

  1. Quick Search (<request query="quick_search_container" index="catalogsearch_fulltext">)
  2. Advanced Search (<request query="advanced_search_container" index="catalogsearch_fulltext">)
  3. Category View (<request query="catalog_view_container" index="catalogsearch_fulltext">). As Magento implies Category View scenatio – as a search query without full text query in it.

Method 2

Looks like it’s used to define attributes certain filters should be applied to and then searchable attributes are applied against this XML to configure the searchRequest – see:

Magento\CatalogSearch\Model\Search\RequestGenerator

private function generateRequest($attributeType, $container, $useFulltext)
{
    $request = [];
    foreach ($this->getSearchableAttributes() as $attribute) {
        if ($attribute->getData($attributeType)) {
            if (!in_array($attribute->getAttributeCode(), ['price', 'category_ids'])) {
                $queryName = $attribute->getAttributeCode() . '_query';

                $request['queries'][$container]['queryReference'][] = [
                    'clause' => 'should',
                    'ref' => $queryName,
                ];
                $filterName = $attribute->getAttributeCode() . self::FILTER_SUFFIX;
                $request['queries'][$queryName] = [
                    'name' => $queryName,
                    'type' => QueryInterface::TYPE_FILTER,
                    'filterReference' => [['ref' => $filterName]],
                ];
                $bucketName = $attribute->getAttributeCode() . self::BUCKET_SUFFIX;
                if ($attribute->getBackendType() == 'decimal') {
                    $request['filters'][$filterName] = [
                        'type' => FilterInterface::TYPE_RANGE,
                        'name' => $filterName,
                        'field' => $attribute->getAttributeCode(),
                        'from' => '$' . $attribute->getAttributeCode() . '.from$',
                        'to' => '$' . $attribute->getAttributeCode() . '.to$',
                    ];
                    $request['aggregations'][$bucketName] = [
                        'type' => BucketInterface::TYPE_DYNAMIC,
                        'name' => $bucketName,
                        'field' => $attribute->getAttributeCode(),
                        'method' => 'manual',
                        'metric' => [["type" => "count"]],
                    ];
                } else {
                    $request['filters'][$filterName] = [
                        'type' => FilterInterface::TYPE_TERM,
                        'name' => $filterName,
                        'field' => $attribute->getAttributeCode(),
                        'value' => '$' . $attribute->getAttributeCode() . '$',
                    ];
                    $request['aggregations'][$bucketName] = [
                        'type' => BucketInterface::TYPE_TERM,
                        'name' => $bucketName,
                        'field' => $attribute->getAttributeCode(),
                        'metric' => [["type" => "count"]],
                    ];
                }
            }
        }
        /** @var $attribute Attribute */
        if (in_array($attribute->getAttributeCode(), ['price', 'sku'])
            || !$attribute->getIsSearchable()
        ) {
            //same fields have special semantics
            continue;
        }
        if ($useFulltext) {
            $request['queries']['search']['match'][] = [
                'field' => $attribute->getAttributeCode(),
                'boost' => $attribute->getSearchWeight() ?: 1,
            ];
        }
    }
    return $request;
}

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply