Skip to content

Commit 9de2802

Browse files
committed
Merge remote-tracking branch 'origin/imported-magento-magento2-30928' into 2.4-develop-pr109
2 parents 0a762ac + 6a1a2f3 commit 9de2802

File tree

2 files changed

+252
-34
lines changed

2 files changed

+252
-34
lines changed

app/code/Magento/Elasticsearch/Model/Adapter/Elasticsearch.php

Lines changed: 58 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,18 @@
66

77
namespace Magento\Elasticsearch\Model\Adapter;
88

9-
use Magento\Catalog\Api\Data\ProductAttributeInterface;
9+
use Elasticsearch\Common\Exceptions\Missing404Exception;
10+
use Magento\AdvancedSearch\Model\Client\ClientInterface;
1011
use Magento\Catalog\Api\ProductAttributeRepositoryInterface;
1112
use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\StaticField;
13+
use Magento\Elasticsearch\Model\Adapter\Index\BuilderInterface;
14+
use Magento\Elasticsearch\Model\Adapter\Index\IndexNameResolver;
15+
use Magento\Elasticsearch\Model\Config;
16+
use Magento\Elasticsearch\SearchAdapter\ConnectionManager;
1217
use Magento\Framework\App\ObjectManager;
18+
use Magento\Framework\Exception\LocalizedException;
1319
use Magento\Framework\Stdlib\ArrayManager;
20+
use Psr\Log\LoggerInterface;
1421

1522
/**
1623
* Elasticsearch adapter
@@ -36,7 +43,7 @@ class Elasticsearch
3643
protected $connectionManager;
3744

3845
/**
39-
* @var \Magento\Elasticsearch\Model\Adapter\Index\IndexNameResolver
46+
* @var IndexNameResolver
4047
*/
4148
protected $indexNameResolver;
4249

@@ -46,22 +53,22 @@ class Elasticsearch
4653
protected $fieldMapper;
4754

4855
/**
49-
* @var \Magento\Elasticsearch\Model\Config
56+
* @var Config
5057
*/
5158
protected $clientConfig;
5259

5360
/**
54-
* @var \Magento\AdvancedSearch\Model\Client\ClientInterface
61+
* @var ClientInterface
5562
*/
5663
protected $client;
5764

5865
/**
59-
* @var \Magento\Elasticsearch\Model\Adapter\Index\BuilderInterface
66+
* @var BuilderInterface
6067
*/
6168
protected $indexBuilder;
6269

6370
/**
64-
* @var \Psr\Log\LoggerInterface
71+
* @var LoggerInterface
6572
*/
6673
protected $logger;
6774

@@ -101,27 +108,27 @@ class Elasticsearch
101108
private $arrayManager;
102109

103110
/**
104-
* @param \Magento\Elasticsearch\SearchAdapter\ConnectionManager $connectionManager
111+
* @param ConnectionManager $connectionManager
105112
* @param FieldMapperInterface $fieldMapper
106-
* @param \Magento\Elasticsearch\Model\Config $clientConfig
113+
* @param Config $clientConfig
107114
* @param Index\BuilderInterface $indexBuilder
108-
* @param \Psr\Log\LoggerInterface $logger
115+
* @param LoggerInterface $logger
109116
* @param Index\IndexNameResolver $indexNameResolver
110117
* @param BatchDataMapperInterface $batchDocumentDataMapper
111118
* @param array $options
112119
* @param ProductAttributeRepositoryInterface|null $productAttributeRepository
113120
* @param StaticField|null $staticFieldProvider
114121
* @param ArrayManager|null $arrayManager
115-
* @throws \Magento\Framework\Exception\LocalizedException
122+
* @throws LocalizedException
116123
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
117124
*/
118125
public function __construct(
119-
\Magento\Elasticsearch\SearchAdapter\ConnectionManager $connectionManager,
126+
ConnectionManager $connectionManager,
120127
FieldMapperInterface $fieldMapper,
121-
\Magento\Elasticsearch\Model\Config $clientConfig,
122-
\Magento\Elasticsearch\Model\Adapter\Index\BuilderInterface $indexBuilder,
123-
\Psr\Log\LoggerInterface $logger,
124-
\Magento\Elasticsearch\Model\Adapter\Index\IndexNameResolver $indexNameResolver,
128+
Config $clientConfig,
129+
BuilderInterface $indexBuilder,
130+
LoggerInterface $logger,
131+
IndexNameResolver $indexNameResolver,
125132
BatchDataMapperInterface $batchDocumentDataMapper,
126133
$options = [],
127134
ProductAttributeRepositoryInterface $productAttributeRepository = null,
@@ -146,7 +153,7 @@ public function __construct(
146153
$this->client = $this->connectionManager->getConnection($options);
147154
} catch (\Exception $e) {
148155
$this->logger->critical($e);
149-
throw new \Magento\Framework\Exception\LocalizedException(
156+
throw new LocalizedException(
150157
__('The search failed because of a search engine misconfiguration.')
151158
);
152159
}
@@ -156,14 +163,14 @@ public function __construct(
156163
* Retrieve Elasticsearch server status
157164
*
158165
* @return bool
159-
* @throws \Magento\Framework\Exception\LocalizedException
166+
* @throws LocalizedException
160167
*/
161168
public function ping()
162169
{
163170
try {
164171
$response = $this->client->ping();
165172
} catch (\Exception $e) {
166-
throw new \Magento\Framework\Exception\LocalizedException(
173+
throw new LocalizedException(
167174
__('Could not ping search engine: %1', $e->getMessage())
168175
);
169176
}
@@ -387,22 +394,12 @@ public function updateIndexMapping(int $storeId, string $mappedIndexerId, string
387394
return $this;
388395
}
389396

390-
$attribute = $this->productAttributeRepository->get($attributeCode);
391-
$newAttributeMapping = $this->staticFieldProvider->getField($attribute);
392-
$mappedAttributes = $this->getMappedAttributes($indexName);
393-
394-
$attrToUpdate = array_diff_key($newAttributeMapping, $mappedAttributes);
395-
if (!empty($attrToUpdate)) {
396-
$settings['index']['mapping']['total_fields']['limit'] = $this
397-
->getMappingTotalFieldsLimit(array_merge($mappedAttributes, $attrToUpdate));
398-
$this->client->putIndexSettings($indexName, ['settings' => $settings]);
399-
400-
$this->client->addFieldsMapping(
401-
$attrToUpdate,
402-
$indexName,
403-
$this->clientConfig->getEntityType()
404-
);
405-
$this->setMappedAttributes($indexName, $attrToUpdate);
397+
try {
398+
$this->updateMapping($attributeCode, $indexName);
399+
} catch (Missing404Exception $e) {
400+
unset($this->indexByCode[$mappedIndexerId . '_' . $storeId]);
401+
$indexName = $this->getIndexFromAlias($storeId, $mappedIndexerId);
402+
$this->updateMapping($attributeCode, $indexName);
406403
}
407404

408405
return $this;
@@ -505,4 +502,31 @@ private function getMappingTotalFieldsLimit(array $allAttributeTypes): int
505502
}
506503
return $count + self::MAPPING_TOTAL_FIELDS_BUFFER_LIMIT;
507504
}
505+
506+
/**
507+
* Perform index mapping update
508+
*
509+
* @param string $attributeCode
510+
* @param string $indexName
511+
* @return void
512+
*/
513+
private function updateMapping(string $attributeCode, string $indexName): void
514+
{
515+
$attribute = $this->productAttributeRepository->get($attributeCode);
516+
$newAttributeMapping = $this->staticFieldProvider->getField($attribute);
517+
$mappedAttributes = $this->getMappedAttributes($indexName);
518+
$attrToUpdate = array_diff_key($newAttributeMapping, $mappedAttributes);
519+
if (!empty($attrToUpdate)) {
520+
$settings['index']['mapping']['total_fields']['limit'] = $this
521+
->getMappingTotalFieldsLimit(array_merge($mappedAttributes, $attrToUpdate));
522+
$this->client->putIndexSettings($indexName, ['settings' => $settings]);
523+
524+
$this->client->addFieldsMapping(
525+
$attrToUpdate,
526+
$indexName,
527+
$this->clientConfig->getEntityType()
528+
);
529+
$this->setMappedAttributes($indexName, $attrToUpdate);
530+
}
531+
}
508532
}
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Elasticsearch\Model\Adapter;
9+
10+
use Magento\AdvancedSearch\Model\Client\ClientInterface;
11+
use Magento\Catalog\Model\ResourceModel\Eav\Attribute;
12+
use Magento\Catalog\Setup\CategorySetup;
13+
use Magento\Eav\Model\Entity\Attribute\Backend\ArrayBackend;
14+
use Magento\Elasticsearch\Model\Adapter\Index\BuilderInterface;
15+
use Magento\Elasticsearch\Model\Adapter\Index\IndexNameResolver;
16+
use Magento\Elasticsearch\SearchAdapter\ConnectionManager;
17+
use Magento\Framework\ObjectManagerInterface;
18+
use Magento\Framework\Stdlib\ArrayManager;
19+
use Magento\Indexer\Model\Indexer;
20+
use Magento\Store\Model\StoreManagerInterface;
21+
use Magento\TestFramework\Helper\Bootstrap;
22+
use PHPUnit\Framework\TestCase;
23+
24+
/**
25+
* Elasticsearch adapter model test class
26+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
27+
*/
28+
class ElasticsearchTest extends TestCase
29+
{
30+
/**
31+
* @var ObjectManagerInterface
32+
*/
33+
private $objectManager;
34+
35+
/**
36+
* @var IndexNameResolver
37+
*/
38+
private $indexNameResolver;
39+
40+
/**
41+
* @var ClientInterface
42+
*/
43+
private $client;
44+
45+
/**
46+
* @var StoreManagerInterface
47+
*/
48+
private $storeManager;
49+
50+
/**
51+
* @var Elasticsearch
52+
*/
53+
private $adapter;
54+
55+
/**
56+
* @var BuilderInterface
57+
*/
58+
private $indexBuilder;
59+
60+
/**
61+
* @var ArrayManager
62+
*/
63+
private $arrayManager;
64+
65+
/**
66+
* @var string
67+
*/
68+
private $newIndex;
69+
70+
/**
71+
* @inheritdoc
72+
*/
73+
protected function setUp(): void
74+
{
75+
$this->objectManager = Bootstrap::getObjectManager();
76+
$this->indexNameResolver = $this->objectManager->get(IndexNameResolver::class);
77+
$this->adapter = $this->objectManager->get(Elasticsearch::class);
78+
$this->storeManager = $this->objectManager->create(StoreManagerInterface::class);
79+
$connectionManager = $this->objectManager->create(ConnectionManager::class);
80+
$this->client = $connectionManager->getConnection();
81+
$this->indexBuilder = $this->objectManager->get(BuilderInterface::class);
82+
$this->arrayManager = $this->objectManager->get(ArrayManager::class);
83+
$indexer = $this->objectManager->create(Indexer::class);
84+
$indexer->load('catalogsearch_fulltext');
85+
$indexer->reindexAll();
86+
}
87+
88+
/**
89+
* @inheritdoc
90+
*/
91+
public function tearDown(): void
92+
{
93+
$this->deleteIndex($this->newIndex);
94+
}
95+
96+
/**
97+
* Tests possibility to create mapping if adapter has obsolete index name in cache
98+
*
99+
* @magentoDataFixture Magento/Elasticsearch/_files/select_attribute.php
100+
* @return void
101+
*/
102+
public function testRetryOnIndexNotFoundException(): void
103+
{
104+
$this->updateElasticsearchIndex();
105+
$this->createNewAttribute();
106+
$mapping = $this->client->getMapping(['index' => $this->newIndex]);
107+
$pathField = $this->arrayManager->findPath('properties', $mapping);
108+
$attributes = $this->arrayManager->get($pathField, $mapping, []);
109+
$this->assertArrayHasKey('multiselect_attribute', $attributes);
110+
}
111+
112+
/**
113+
* Prepare and save new attribute
114+
*
115+
* @return void
116+
*/
117+
public function createNewAttribute(): void
118+
{
119+
/** @var CategorySetup $installer */
120+
$installer = $this->objectManager->get(CategorySetup::class);
121+
/** @var Attribute $attribute */
122+
$multiselectAttribute = $this->objectManager->get(Attribute::class);
123+
$multiselectAttribute->setData(
124+
[
125+
'attribute_code' => 'multiselect_attribute',
126+
'entity_type_id' => $installer->getEntityTypeId('catalog_product'),
127+
'is_global' => 1,
128+
'is_user_defined' => 1,
129+
'frontend_input' => 'multiselect',
130+
'is_unique' => 0,
131+
'is_required' => 0,
132+
'is_searchable' => 1,
133+
'is_visible_in_advanced_search' => 0,
134+
'is_comparable' => 0,
135+
'is_filterable' => 1,
136+
'is_filterable_in_search' => 0,
137+
'is_used_for_promo_rules' => 0,
138+
'is_html_allowed_on_front' => 1,
139+
'is_visible_on_front' => 0,
140+
'used_in_product_listing' => 0,
141+
'used_for_sort_by' => 0,
142+
'frontend_label' => ['Multiselect Attribute'],
143+
'backend_type' => 'varchar',
144+
'backend_model' => ArrayBackend::class,
145+
'option' => [
146+
'value' => [
147+
'dog' => ['Dog'],
148+
'cat' => ['Cat'],
149+
],
150+
'order' => [
151+
'dog' => 1,
152+
'cat' => 2,
153+
],
154+
],
155+
]
156+
);
157+
$multiselectAttribute->save();
158+
}
159+
160+
/**
161+
* Prepare new index and delete old. Keep cache alive.
162+
*
163+
* @return void
164+
*/
165+
private function updateElasticsearchIndex(): void
166+
{
167+
$storeId = (int)$this->storeManager->getDefaultStoreView()->getId();
168+
$mappedIndexerId = 'product';
169+
$this->adapter->updateIndexMapping($storeId, $mappedIndexerId, 'select_attribute');
170+
$oldIndex = $this->indexNameResolver->getIndexFromAlias($storeId, $mappedIndexerId);
171+
$this->newIndex = $oldIndex . '1';
172+
$this->deleteIndex($this->newIndex);
173+
$this->indexBuilder->setStoreId($storeId);
174+
$this->client->createIndex($this->newIndex, ['settings' => $this->indexBuilder->build()]);
175+
$this->client->updateAlias(
176+
$this->indexNameResolver->getIndexNameForAlias($storeId, $mappedIndexerId),
177+
$this->newIndex,
178+
$oldIndex
179+
);
180+
$this->client->deleteIndex($oldIndex);
181+
}
182+
183+
/**
184+
* Delete index by name if exists
185+
*
186+
* @param $newIndex
187+
*/
188+
private function deleteIndex($newIndex): void
189+
{
190+
if ($this->client->indexExists($newIndex)) {
191+
$this->client->deleteIndex($newIndex);
192+
}
193+
}
194+
}

0 commit comments

Comments
 (0)