Skip to content

Commit 1dea563

Browse files
committed
MAGETWO-60339: Request generator uses clause 'should' instead of 'must'
1 parent 7a6e341 commit 1dea563

File tree

5 files changed

+283
-37
lines changed

5 files changed

+283
-37
lines changed

app/code/Magento/CatalogSearch/Model/Search/RequestGenerator.php

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -78,16 +78,20 @@ private function generateRequest($attributeType, $container, $useFulltext)
7878
if ($attribute->getData($attributeType)) {
7979
if (!in_array($attribute->getAttributeCode(), ['price', 'category_ids'], true)) {
8080
$queryName = $attribute->getAttributeCode() . '_query';
81-
8281
$request['queries'][$container]['queryReference'][] = [
83-
'clause' => 'should',
82+
'clause' => 'must',
8483
'ref' => $queryName,
8584
];
8685
$filterName = $attribute->getAttributeCode() . self::FILTER_SUFFIX;
8786
$request['queries'][$queryName] = [
8887
'name' => $queryName,
8988
'type' => QueryInterface::TYPE_FILTER,
90-
'filterReference' => [['ref' => $filterName]],
89+
'filterReference' => [
90+
[
91+
'clause' => 'must',
92+
'ref' => $filterName,
93+
]
94+
],
9195
];
9296
$bucketName = $attribute->getAttributeCode() . self::BUCKET_SUFFIX;
9397
$generator = $this->generatorResolver->getGeneratorForType($attribute->getBackendType());
@@ -100,14 +104,7 @@ private function generateRequest($attributeType, $container, $useFulltext)
100104
// Some fields have their own specific handlers
101105
continue;
102106
}
103-
104-
// Match search by custom price attribute isn't supported
105-
if ($useFulltext && $attribute->getFrontendInput() !== 'price') {
106-
$request['queries']['search']['match'][] = [
107-
'field' => $attribute->getAttributeCode(),
108-
'boost' => $attribute->getSearchWeight() ?: 1,
109-
];
110-
}
107+
$request = $this->processPriceAttribute($useFulltext, $attribute, $request);
111108
}
112109

113110
return $request;
@@ -148,10 +145,9 @@ private function generateAdvancedSearchRequest()
148145
//same fields have special semantics
149146
continue;
150147
}
151-
152148
$queryName = $attribute->getAttributeCode() . '_query';
153149
$request['queries']['advanced_search_container']['queryReference'][] = [
154-
'clause' => 'should',
150+
'clause' => 'must',
155151
'ref' => $queryName,
156152
];
157153
switch ($attribute->getBackendType()) {
@@ -164,9 +160,12 @@ private function generateAdvancedSearchRequest()
164160
$request['queries'][$queryName] = [
165161
'name' => $queryName,
166162
'type' => QueryInterface::TYPE_FILTER,
167-
'filterReference' => [['ref' => $filterName]],
163+
'filterReference' => [
164+
[
165+
'ref' => $filterName,
166+
],
167+
],
168168
];
169-
170169
$request['filters'][$filterName] = [
171170
'type' => FilterInterface::TYPE_TERM,
172171
'name' => $filterName,
@@ -194,7 +193,11 @@ private function generateAdvancedSearchRequest()
194193
$request['queries'][$queryName] = [
195194
'name' => $queryName,
196195
'type' => QueryInterface::TYPE_FILTER,
197-
'filterReference' => [['ref' => $filterName]],
196+
'filterReference' => [
197+
[
198+
'ref' => $filterName,
199+
],
200+
],
198201
];
199202
$request['filters'][$filterName] = [
200203
'field' => $attribute->getAttributeCode(),
@@ -209,9 +212,12 @@ private function generateAdvancedSearchRequest()
209212
$request['queries'][$queryName] = [
210213
'name' => $queryName,
211214
'type' => QueryInterface::TYPE_FILTER,
212-
'filterReference' => [['ref' => $filterName]],
215+
'filterReference' => [
216+
[
217+
'ref' => $filterName,
218+
],
219+
],
213220
];
214-
215221
$request['filters'][$filterName] = [
216222
'type' => FilterInterface::TYPE_TERM,
217223
'name' => $filterName,
@@ -223,4 +229,25 @@ private function generateAdvancedSearchRequest()
223229

224230
return $request;
225231
}
232+
233+
/**
234+
* Modify request for price attribute.
235+
*
236+
* @param bool $useFulltext
237+
* @param Attribute $attribute
238+
* @param array $request
239+
* @return array
240+
*/
241+
private function processPriceAttribute($useFulltext, $attribute, $request)
242+
{
243+
// Match search by custom price attribute isn't supported
244+
if ($useFulltext && $attribute->getFrontendInput() !== 'price') {
245+
$request['queries']['search']['match'][] = [
246+
'field' => $attribute->getAttributeCode(),
247+
'boost' => $attribute->getSearchWeight() ?: 1,
248+
];
249+
}
250+
251+
return $request;
252+
}
226253
}

app/code/Magento/CatalogSearch/Test/Unit/Model/Search/RequestGeneratorTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,16 @@ public function testGenerate($countResult, $attributeOptions)
170170
$this->countVal($result['catalog_view_container']['queries']),
171171
'Queries count for "catalog_view_container" doesn\'t match'
172172
);
173+
foreach ($result as $key => $value) {
174+
if (isset($value['queries'][$key]['queryReference'])) {
175+
foreach ($value['queries'][$key]['queryReference'] as $reference) {
176+
$this->assertEquals(
177+
'must',
178+
$reference['clause']
179+
);
180+
}
181+
}
182+
}
173183
}
174184

175185
/**

dev/tests/integration/testsuite/Magento/Framework/Search/Adapter/Mysql/AdapterTest.php

Lines changed: 87 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
*/
66
namespace Magento\Framework\Search\Adapter\Mysql;
77

8+
use Magento\Catalog\Model\Product;
9+
use Magento\Catalog\Model\ResourceModel\Eav\Attribute;
810
use Magento\CatalogSearch\Model\ResourceModel\EngineInterface;
11+
use Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection;
912
use Magento\Framework\App\Config\MutableScopeConfigInterface;
1013
use Magento\Search\Model\EngineResolver;
1114
use Magento\TestFramework\Helper\Bootstrap;
@@ -375,18 +378,18 @@ public function advancedSearchDataProvider()
375378
*/
376379
public function testCustomFilterableAttribute()
377380
{
378-
/** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute */
379-
$attribute = $this->objectManager->get(\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class)
380-
->loadByCode(\Magento\Catalog\Model\Product::ENTITY, 'select_attribute');
381-
/** @var \Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection $selectOptions */
381+
/** @var Attribute $attribute */
382+
$attribute = $this->objectManager->get(Attribute::class)
383+
->loadByCode(Product::ENTITY, 'select_attribute');
384+
/** @var Collection $selectOptions */
382385
$selectOptions = $this->objectManager
383-
->create(\Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection::class)
386+
->create(Collection::class)
384387
->setAttributeFilter($attribute->getId());
385388

386-
$attribute->loadByCode(\Magento\Catalog\Model\Product::ENTITY, 'multiselect_attribute');
387-
/** @var \Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection $multiselectOptions */
389+
$attribute->loadByCode(Product::ENTITY, 'multiselect_attribute');
390+
/** @var Collection $multiselectOptions */
388391
$multiselectOptions = $this->objectManager
389-
->create(\Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection::class)
392+
->create(Collection::class)
390393
->setAttributeFilter($attribute->getId());
391394

392395
$this->requestBuilder->bind('select_attribute', $selectOptions->getLastItem()->getId());
@@ -395,7 +398,72 @@ public function testCustomFilterableAttribute()
395398
$this->requestBuilder->bind('price.to', 100);
396399
$this->requestBuilder->bind('category_ids', 2);
397400
$this->requestBuilder->setRequestName('filterable_custom_attributes');
401+
$queryResponse = $this->executeQuery();
402+
$this->assertEquals(1, $queryResponse->count());
403+
}
404+
405+
/**
406+
* Data provider for testFilterByAttributeValues.
407+
*
408+
* @return array
409+
*/
410+
public function filterByAttributeValuesDataProvider()
411+
{
412+
return [
413+
'quick_search_container' => [
414+
'quick_search_container',
415+
[
416+
// Make sure search uses "should" cause.
417+
'search_term' => 'Simple Product',
418+
],
419+
],
420+
'advanced_search_container' => [
421+
'advanced_search_container',
422+
[
423+
// Make sure "wildcard" feature works.
424+
'sku' => 'simple_product',
425+
]
426+
],
427+
'catalog_view_container' => [
428+
'catalog_view_container',
429+
[
430+
'category_ids' => 2
431+
]
432+
]
433+
];
434+
}
398435

436+
/**
437+
* Test filtering by two attributes.
438+
*
439+
* @magentoDataFixture Magento/Framework/Search/_files/filterable_attributes.php
440+
* @magentoConfigFixture current_store catalog/search/engine mysql
441+
* @dataProvider filterByAttributeValuesDataProvider
442+
* @param string $requestName
443+
* @param array $additionalData
444+
* @return void
445+
*/
446+
public function testFilterByAttributeValues($requestName, $additionalData)
447+
{
448+
/** @var Attribute $attribute */
449+
$attribute = $this->objectManager->get(Attribute::class)
450+
->loadByCode(Product::ENTITY, 'select_attribute_1');
451+
/** @var Collection $selectOptions1 */
452+
$selectOptions1 = $this->objectManager
453+
->create(Collection::class)
454+
->setAttributeFilter($attribute->getId());
455+
$attribute->loadByCode(Product::ENTITY, 'select_attribute_2');
456+
/** @var Collection $selectOptions2 */
457+
$selectOptions2 = $this->objectManager
458+
->create(Collection::class)
459+
->setAttributeFilter($attribute->getId());
460+
$this->requestBuilder->bind('select_attribute_1', $selectOptions1->getLastItem()->getId());
461+
$this->requestBuilder->bind('select_attribute_2', $selectOptions2->getLastItem()->getId());
462+
// Binds for specific containers.
463+
foreach ($additionalData as $key => $value) {
464+
$this->requestBuilder->bind($key, $value);
465+
}
466+
$this->requestBuilder->setRequestName($requestName);
399467
$queryResponse = $this->executeQuery();
400468
$this->assertEquals(1, $queryResponse->count());
401469
}
@@ -425,12 +493,12 @@ public function testAdvancedSearchDateField($rangeFilter, $expectedRecordsCount)
425493
*/
426494
public function testAdvancedSearchCompositeProductWithOutOfStockOption()
427495
{
428-
/** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute */
429-
$attribute = $this->objectManager->get(\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class)
430-
->loadByCode(\Magento\Catalog\Model\Product::ENTITY, 'test_configurable');
431-
/** @var \Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection $selectOptions */
496+
/** @var Attribute $attribute */
497+
$attribute = $this->objectManager->get(Attribute::class)
498+
->loadByCode(Product::ENTITY, 'test_configurable');
499+
/** @var Collection $selectOptions */
432500
$selectOptions = $this->objectManager
433-
->create(\Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection::class)
501+
->create(Collection::class)
434502
->setAttributeFilter($attribute->getId());
435503

436504
$firstOption = $selectOptions->getFirstItem();
@@ -456,12 +524,12 @@ public function testAdvancedSearchCompositeProductWithOutOfStockOption()
456524
*/
457525
public function testAdvancedSearchCompositeProductWithDisabledChild()
458526
{
459-
/** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute */
460-
$attribute = $this->objectManager->get(\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class)
461-
->loadByCode(\Magento\Catalog\Model\Product::ENTITY, 'test_configurable');
462-
/** @var \Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection $selectOptions */
527+
/** @var Attribute $attribute */
528+
$attribute = $this->objectManager->get(Attribute::class)
529+
->loadByCode(Product::ENTITY, 'test_configurable');
530+
/** @var Collection $selectOptions */
463531
$selectOptions = $this->objectManager
464-
->create(\Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection::class)
532+
->create(Collection::class)
465533
->setAttributeFilter($attribute->getId());
466534

467535
$firstOption = $selectOptions->getFirstItem();
@@ -518,7 +586,7 @@ public function testSearchQueryBoost()
518586
* Now we're going to change search weight of one of the attributes to ensure that it will affect
519587
* how products are ordered in the search result
520588
*/
521-
/** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute */
589+
/** @var Attribute $attribute */
522590
$attribute = $productAttributeRepository->get('name');
523591
$attribute->setSearchWeight(20);
524592
$productAttributeRepository->save($attribute);
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
use Magento\Catalog\Model\Product;
8+
use Magento\Catalog\Model\Product\Action;
9+
use Magento\Catalog\Model\Product\Attribute\Source\Status;
10+
use Magento\Catalog\Model\Product\Type;
11+
use Magento\Catalog\Model\Product\Visibility;
12+
use Magento\Catalog\Model\ResourceModel\Eav\Attribute;
13+
use Magento\Catalog\Setup\CategorySetup;
14+
use Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection;
15+
use Magento\TestFramework\Helper\Bootstrap;
16+
17+
/* Create attribute */
18+
/** @var $installer CategorySetup */
19+
$installer = Bootstrap::getObjectManager()->create(
20+
CategorySetup::class,
21+
['resourceName' => 'catalog_setup']
22+
);
23+
$selectOptions = [];
24+
$selectAttributes = [];
25+
foreach (range(1, 2) as $index) {
26+
/** @var $selectAttribute Attribute */
27+
$selectAttribute = Bootstrap::getObjectManager()->create(
28+
Attribute::class
29+
);
30+
$selectAttribute->setData(
31+
[
32+
'attribute_code' => 'select_attribute_' . $index,
33+
'entity_type_id' => $installer->getEntityTypeId('catalog_product'),
34+
'is_global' => 1,
35+
'is_user_defined' => 1,
36+
'frontend_input' => 'select',
37+
'is_unique' => 0,
38+
'is_required' => 0,
39+
'is_searchable' => 1,
40+
'is_visible_in_advanced_search' => 1,
41+
'is_comparable' => 0,
42+
'is_filterable' => 1,
43+
'is_filterable_in_search' => 1,
44+
'is_used_for_promo_rules' => 0,
45+
'is_html_allowed_on_front' => 1,
46+
'is_visible_on_front' => 0,
47+
'used_in_product_listing' => 0,
48+
'used_for_sort_by' => 0,
49+
'frontend_label' => ['Select Attribute'],
50+
'backend_type' => 'int',
51+
'option' => [
52+
'value' => ['option_0' => ['Option 1'], 'option_1' => ['Option 2']],
53+
'order' => ['option_0' => 1, 'option_1' => 2],
54+
],
55+
]
56+
);
57+
$selectAttribute->save();
58+
/* Assign attribute to attribute set */
59+
$installer->addAttributeToGroup('catalog_product', 'Default', 'General', $selectAttribute->getId());
60+
/** @var $selectOptions Collection */
61+
$selectOption = Bootstrap::getObjectManager()->create(
62+
Collection::class
63+
);
64+
$selectOption->setAttributeFilter($selectAttribute->getId());
65+
$selectAttributes[$index] = $selectAttribute;
66+
$selectOptions[$index] = $selectOption;
67+
}
68+
/* Create simple products per each first attribute option */
69+
foreach ($selectOptions[1] as $option) {
70+
/** @var $product Product */
71+
$product = Bootstrap::getObjectManager()->create(
72+
Product::class
73+
);
74+
$product->setTypeId(
75+
Type::TYPE_SIMPLE
76+
)->setAttributeSetId(
77+
$installer->getAttributeSetId('catalog_product', 'Default')
78+
)->setWebsiteIds(
79+
[1]
80+
)->setName(
81+
'Simple Product ' . $option->getId()
82+
)->setSku(
83+
'simple_product_' . $option->getId()
84+
)->setPrice(
85+
99
86+
)->setCategoryIds(
87+
[2]
88+
)->setVisibility(
89+
Visibility::VISIBILITY_BOTH
90+
)->setStatus(
91+
Status::STATUS_ENABLED
92+
)->setStockData(
93+
['use_config_manage_stock' => 1, 'qty' => 5, 'is_in_stock' => 1]
94+
)->save();
95+
Bootstrap::getObjectManager()->get(
96+
Action::class
97+
)->updateAttributes(
98+
[$product->getId()],
99+
[
100+
$selectAttributes[1]->getAttributeCode() => $option->getId(),
101+
$selectAttributes[2]->getAttributeCode() => $selectOptions[2]->getLastItem()->getId(),
102+
],
103+
$product->getStoreId()
104+
);
105+
}

0 commit comments

Comments
 (0)