Skip to content

Commit 9cb64ad

Browse files
committed
ACP2E-1028: Current Indexer implementation for large catalogs is causing items to not appear on site.
1 parent 2125a21 commit 9cb64ad

File tree

2 files changed

+91
-67
lines changed

2 files changed

+91
-67
lines changed

app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,8 @@ abstract public function execute($ids);
170170
* @param array $processIds
171171
* @return AbstractAction
172172
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
173-
* @deprecated 102.0.6 Used only for backward compatibility for indexer, which not support indexation by dimensions
173+
* @deprecated 102.0.6
174+
* @see Used only for backward compatibility for indexer, which not support indexation by dimensions
174175
*/
175176
protected function _syncData(array $processIds = [])
176177
{
@@ -386,10 +387,6 @@ protected function _reindexRows($changedIds = [])
386387
$changedIds = array_unique(array_merge($changedIds, ...array_values($parentProductsTypes)));
387388
$productsTypes = array_merge_recursive($productsTypes, $parentProductsTypes);
388389

389-
if ($changedIds) {
390-
$this->deleteIndexData($changedIds);
391-
}
392-
393390
$typeIndexers = $this->getTypeIndexers();
394391
foreach ($typeIndexers as $productType => $indexer) {
395392
$entityIds = $productsTypes[$productType] ?? [];
@@ -403,14 +400,13 @@ protected function _reindexRows($changedIds = [])
403400
$temporaryTable = $this->tableMaintainer->getMainTmpTable($dimensions);
404401
$this->_emptyTable($temporaryTable);
405402
$indexer->executeByDimensions($dimensions, \SplFixedArray::fromArray($entityIds, false));
406-
// copy to index
407-
$this->_insertFromTable(
408-
$temporaryTable,
409-
$this->tableMaintainer->getMainTableByDimensions($dimensions)
410-
);
403+
$mainTable = $this->tableMaintainer->getMainTableByDimensions($dimensions);
404+
$this->_insertFromTable($temporaryTable, $mainTable);
405+
$this->deleteOutdatedData($entityIds, $temporaryTable, $mainTable);
411406
}
412407
} else {
413408
// handle 3d-party indexers for backward compatibility
409+
$this->deleteIndexData($changedIds);
414410
$this->_emptyTable($this->_defaultIndexerResource->getIdxTable());
415411
$this->_copyRelationIndexData($entityIds);
416412
$indexer->reindexEntity($entityIds);
@@ -421,6 +417,30 @@ protected function _reindexRows($changedIds = [])
421417
return $changedIds;
422418
}
423419

420+
/**
421+
* Delete records that do not exist anymore
422+
*
423+
* @param array $entityIds
424+
* @param string $temporaryTable
425+
* @param string $mainTable
426+
* @return void
427+
*/
428+
private function deleteOutdatedData(array $entityIds, string $temporaryTable, string $mainTable): void
429+
{
430+
$joinCondition = [
431+
'tmp_table.entity_id = main_table.entity_id',
432+
'tmp_table.customer_group_id = main_table.customer_group_id',
433+
'tmp_table.website_id = main_table.website_id',
434+
];
435+
$select = $this->getConnection()->select()
436+
->from(['main_table' => $mainTable], null)
437+
->joinLeft(['tmp_table' => $temporaryTable], implode(' AND ', $joinCondition), null)
438+
->where('tmp_table.entity_id IS NULL')
439+
->where('main_table.entity_id IN (?)', $entityIds, \Zend_Db::INT_TYPE);
440+
$query = $select->deleteFromSelect('main_table');
441+
$this->getConnection()->query($query);
442+
}
443+
424444
/**
425445
* Delete Index data index for list of entities
426446
*
@@ -445,7 +465,8 @@ private function deleteIndexData(array $entityIds)
445465
* @param null|array $parentIds
446466
* @param array $excludeIds
447467
* @return \Magento\Catalog\Model\Indexer\Product\Price\AbstractAction
448-
* @deprecated 102.0.6 Used only for backward compatibility for do not broke custom indexer implementation
468+
* @deprecated 102.0.6
469+
* @see Used only for backward compatibility for do not broke custom indexer implementation
449470
* which do not work by dimensions.
450471
* For indexers, which support dimensions all composite products read data directly from main price indexer table
451472
* or replica table for partial or full reindex correspondingly.

app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Price/Action/RowsTest.php

Lines changed: 59 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
namespace Magento\Catalog\Test\Unit\Model\Indexer\Product\Price\Action;
99

10+
use Magento\Framework\Indexer\DimensionalIndexerInterface;
11+
use Magento\Framework\Search\Request\Dimension;
1012
use Magento\Store\Model\StoreManagerInterface;
1113
use Magento\Directory\Model\CurrencyFactory;
1214
use Magento\Catalog\Model\Product\Type;
@@ -94,36 +96,17 @@ class RowsTest extends TestCase
9496

9597
protected function setUp(): void
9698
{
97-
$this->config = $this->getMockBuilder(ScopeConfigInterface::class)
98-
->getMockForAbstractClass();
99-
$this->storeManager = $this->getMockBuilder(StoreManagerInterface::class)
100-
->getMockForAbstractClass();
101-
$this->currencyFactory = $this->getMockBuilder(CurrencyFactory::class)
102-
->disableOriginalConstructor()
103-
->getMock();
104-
$this->localeDate = $this->getMockBuilder(TimezoneInterface::class)
105-
->getMockForAbstractClass();
106-
$this->dateTime = $this->getMockBuilder(DateTime::class)
107-
->disableOriginalConstructor()
108-
->getMock();
109-
$this->catalogProductType = $this->getMockBuilder(Type::class)
110-
->disableOriginalConstructor()
111-
->getMock();
112-
$this->indexerPriceFactory = $this->getMockBuilder(Factory::class)
113-
->disableOriginalConstructor()
114-
->getMock();
115-
$this->defaultIndexerResource = $this->getMockBuilder(DefaultPrice::class)
116-
->disableOriginalConstructor()
117-
->getMock();
118-
$this->tierPriceIndexResource = $this->getMockBuilder(TierPrice::class)
119-
->disableOriginalConstructor()
120-
->getMock();
121-
$this->dimensionCollectionFactory = $this->getMockBuilder(DimensionCollectionFactory::class)
122-
->disableOriginalConstructor()
123-
->getMock();
124-
$this->tableMaintainer = $this->getMockBuilder(TableMaintainer::class)
125-
->disableOriginalConstructor()
126-
->getMock();
99+
$this->config = $this->createMock(ScopeConfigInterface::class);
100+
$this->storeManager = $this->createMock(StoreManagerInterface::class);
101+
$this->currencyFactory = $this->createMock(CurrencyFactory::class);
102+
$this->localeDate = $this->createMock(TimezoneInterface::class);
103+
$this->dateTime = $this->createMock(DateTime::class);
104+
$this->catalogProductType = $this->createMock(Type::class);
105+
$this->indexerPriceFactory = $this->createMock(Factory::class);
106+
$this->defaultIndexerResource = $this->createMock(DefaultPrice::class);
107+
$this->tierPriceIndexResource = $this->createMock(TierPrice::class);
108+
$this->dimensionCollectionFactory = $this->createMock(DimensionCollectionFactory::class);
109+
$this->tableMaintainer = $this->createMock(TableMaintainer::class);
127110
$batchSize = 2;
128111

129112
$this->actionRows = new Rows(
@@ -144,52 +127,72 @@ protected function setUp(): void
144127

145128
public function testEmptyIds()
146129
{
147-
$this->expectException('Magento\Framework\Exception\InputException');
130+
$this->expectException(\Magento\Framework\Exception\InputException::class);
148131
$this->expectExceptionMessage('Bad value was supplied.');
149132
$this->actionRows->execute(null);
150133
}
151134

152135
public function testBatchProcessing()
153136
{
154137
$ids = [1, 2, 3, 4];
155-
$select = $this->getMockBuilder(Select::class)
156-
->disableOriginalConstructor()
157-
->getMock();
158-
$select->expects($this->any())->method('from')->willReturnSelf();
159-
$select->expects($this->any())->method('where')->willReturnSelf();
160-
$select->expects($this->any())->method('join')->willReturnSelf();
161-
$adapter = $this->getMockBuilder(AdapterInterface::class)->getMockForAbstractClass();
162-
$adapter->expects($this->any())->method('select')->willReturn($select);
163-
$this->defaultIndexerResource->expects($this->any())
164-
->method('getConnection')
165-
->willReturn($adapter);
166-
$adapter->expects($this->any())
167-
->method('fetchAll')
168-
->with($select)
169-
->willReturn([]);
170-
$adapter->expects($this->any())
138+
139+
$select = $this->createMock(Select::class);
140+
$select->method('from')->willReturnSelf();
141+
$select->method('joinLeft')->willReturnSelf();
142+
$select->method('where')->willReturnSelf();
143+
$select->method('join')->willReturnSelf();
144+
$adapter = $this->createMock(AdapterInterface::class);
145+
$adapter->method('select')->willReturn($select);
146+
$adapter->method('describeTable')->willReturn([]);
147+
$this->defaultIndexerResource->method('getConnection')->willReturn($adapter);
148+
$adapter->method('fetchAll')->with($select)->willReturn([]);
149+
150+
$adapter->expects($this->exactly(4))
171151
->method('fetchPairs')
172152
->with($select)
173-
->willReturn([]);
174-
$multiDimensionProvider = $this->getMockBuilder(MultiDimensionProvider::class)
175-
->disableOriginalConstructor()
176-
->getMock();
177-
$this->dimensionCollectionFactory->expects($this->exactly(2))
153+
->willReturnOnConsecutiveCalls(
154+
[1 => 'simple', 2 => 'virtual'],
155+
[],
156+
[3 => 'simple', 4 => 'virtual'],
157+
[],
158+
);
159+
$multiDimensionProvider = $this->createMock(MultiDimensionProvider::class);
160+
$this->dimensionCollectionFactory->expects($this->exactly(4))
178161
->method('create')
179162
->willReturn($multiDimensionProvider);
180-
$iterator = new \ArrayIterator([]);
181-
$multiDimensionProvider->expects($this->exactly(2))
163+
$dimension = $this->createMock(Dimension::class);
164+
$dimension->method('getName')->willReturn('default');
165+
$dimension->method('getValue')->willReturn('0');
166+
$iterator = new \ArrayIterator([[$dimension]]);
167+
$multiDimensionProvider->expects($this->exactly(4))
182168
->method('getIterator')
183169
->willReturn($iterator);
184-
$this->catalogProductType->expects($this->any())
170+
$this->catalogProductType->expects($this->once())
185171
->method('getTypesByPriority')
186-
->willReturn([]);
172+
->willReturn(
173+
[
174+
'virtual' => ['price_indexer' => '\Price\Indexer'],
175+
'simple' => ['price_indexer' => '\Price\Indexer'],
176+
]
177+
);
178+
$priceIndexer = $this->createMock(DimensionalIndexerInterface::class);
179+
$this->indexerPriceFactory->expects($this->exactly(2))
180+
->method('create')
181+
->with('\Price\Indexer', ['fullReindexAction' => false])
182+
->willReturn($priceIndexer);
183+
$priceIndexer->expects($this->exactly(4))
184+
->method('executeByDimensions');
185+
$select->expects($this->exactly(4))
186+
->method('deleteFromSelect')
187+
->with('main_table')
188+
->willReturn('');
187189
$adapter->expects($this->exactly(2))
188190
->method('getIndexList')
189191
->willReturn(['entity_id'=>['COLUMNS_LIST'=>['test']]]);
190192
$adapter->expects($this->exactly(2))
191193
->method('getPrimaryKeyName')
192194
->willReturn('entity_id');
195+
193196
$this->actionRows->execute($ids);
194197
}
195198
}

0 commit comments

Comments
 (0)