Skip to content
167 changes: 167 additions & 0 deletions app/code/Magento/Eav/Model/Mview/ChangeLogBatchWalker.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/

namespace Magento\Eav\Model\Mview;

use Magento\Framework\App\ResourceConnection;
use Magento\Framework\DB\Sql\Expression;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Mview\View\ChangeLogBatchWalkerInterface;
use Magento\Framework\Mview\View\ChangelogInterface;

/**
* Class BatchIterator
*/
class ChangeLogBatchWalker implements ChangeLogBatchWalkerInterface
{
private const GROUP_CONCAT_MAX_VARIABLE = 'group_concat_max_len';
/** ID is defined as small int. Default size of it is 5 */
private const DEFAULT_ID_SIZE = 5;

/**
* @var ResourceConnection
*/
private $resourceConnection;

/**
* @var array
*/
private $entityTypeCodes;

/**
* @var ChangelogInterface
*/
private $changelog;
/**
* @var int
*/
private $fromVersionId;

/**
* @var int
*/
private $toVersionId;

/**
* @var int
*/
private $batchSize;

/**
* @param ResourceConnection $resourceConnection
* @param ChangelogInterface $changelog
* @param int $fromVersionId
* @param int $toVersionId
* @param int $batchSize
* @param array $entityTypeCodes
*/
public function __construct(
ResourceConnection $resourceConnection,
ChangelogInterface $changelog,
int $fromVersionId,
int $toVersionId,
int $batchSize,
array $entityTypeCodes = []
) {
$this->resourceConnection = $resourceConnection;
$this->entityTypeCodes = $entityTypeCodes;
$this->changelog = $changelog;
$this->fromVersionId = $fromVersionId;
$this->toVersionId = $toVersionId;
$this->batchSize = $batchSize;
}

/**
* @return \Generator|\Traversable
* @throws \Exception
*/
public function getIterator(): \Generator
{
while ($this->fromVersionId < $this->toVersionId) {
$ids = $this->walk();

if (empty($ids)) {
break;
}
$this->fromVersionId += $this->batchSize;
yield $ids;
}
}

/**
* Calculate EAV attributes size
*
* @param ChangelogInterface $changelog
* @return int
* @throws LocalizedException
*/
private function calculateEavAttributeSize(): int
{
$connection = $this->resourceConnection->getConnection();

if (!isset($this->entityTypeCodes[$this->changelog->getViewId()])) {
throw new LocalizedException(__('Entity type for view was not defined'));
}

$select = $connection->select();
$select->from(
$this->resourceConnection->getTableName('eav_attribute'),
new Expression('COUNT(*)')
)->joinInner(
['type' => $connection->getTableName('eav_entity_type')],
'type.entity_type_id=eav_attribute.entity_type_id'
)->where('type.entity_type_code = ?', $this->entityTypeCodes[$this->changelog->getViewId()]);

return (int) $connection->fetchOne($select);
}

/**
* Prepare group max concat
*
* @param int $numberOfAttributes
* @return void
* @throws \Exception
*/
private function setGroupConcatMax(int $numberOfAttributes): void
{
$connection = $this->resourceConnection->getConnection();
$connection->query(sprintf(
'SET SESSION %s=%s',
self::GROUP_CONCAT_MAX_VARIABLE,
$numberOfAttributes * (self::DEFAULT_ID_SIZE + 1)
));
}

/**
* @inheritdoc
* @throws \Exception
*/
private function walk()
{
$connection = $this->resourceConnection->getConnection();
$numberOfAttributes = $this->calculateEavAttributeSize();
$this->setGroupConcatMax($numberOfAttributes);
$select = $connection->select()->distinct(true)
->where(
'version_id > ?',
(int) $this->fromVersionId
)
->where(
'version_id <= ?',
$this->toVersionId
)
->group([$this->changelog->getColumnName(), 'store_id'])
->limit($this->batchSize);

$columns = [
$this->changelog->getColumnName(),
'attribute_ids' => new Expression('GROUP_CONCAT(attribute_id)'),
'store_id'
];
$select->from($this->changelog->getName(), $columns);
return $connection->fetchAll($select);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Magento_TestModuleMview" active="true">
</module>
</config>
24 changes: 24 additions & 0 deletions dev/tests/integration/_files/Magento/TestModuleMview/etc/mview.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Mview/etc/mview.xsd">
<view id="test_view"
class="\Some\Class"
group="indexer"
walker="Magento\Eav\Model\Mview\ChangeLogBatchWalker"
>
<subscriptions>
<table name="catalog_product_entity" entity_column="entity_id" />
<table name="catalog_product_entity_varchar" entity_column="entity_id">
<additionalColumns>
<column name="store_id" cl_name="store_id" />
<column name="attribute_id" cl_name="attribute_id" />
</additionalColumns>
</table>
</subscriptions>
</view>
</config>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/

use Magento\Framework\Component\ComponentRegistrar;

$registrar = new ComponentRegistrar();
if ($registrar->getPath(ComponentRegistrar::MODULE, 'Magento_TestModuleMview') === null) {
ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_TestModuleMview', __DIR__);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/

namespace Magento\Eav\Model;

use Magento\Catalog\Model\ProductRepository;
use Magento\Framework\App\ResourceConnection;

/**
* Test Class for \Magento\Framework\Mview\View\Changelog
*
* @magentoDbIsolation disabled
*/
class EnhancedMviewTest extends \PHPUnit\Framework\TestCase
{
/**
* @var \Magento\Framework\ObjectManagerInterface
*/
private $objectManager;

/**
* @var ResourceConnection
*/
private $resourceConnection;

protected function setUp(): void
{
$this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
$this->resourceConnection = $this->objectManager->get(ResourceConnection::class);
parent::setUp();
}

/**
* @dataProvider attributesDataProvider
* @magentoDataFixture Magento/Eav/_files/enable_mview_for_test_view.php
* @magentoDataFixture Magento/Catalog/_files/product_simple.php
* @param array $expectedAttributes
* @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function testCreateProduct(array $expectedAttributes)
{
$changelogData = $this->getChangelogData();
$attributesMapping = $this->getAttributeCodes();
$attributes = [];
foreach ($changelogData as $row) {
$this->assertArrayHasKey('store_id', $row);
$this->assertArrayHasKey('attribute_id', $row);

if ($row['store_id'] == 0 && $row['attribute_id'] !== null) {
$attributes[$attributesMapping[$row['attribute_id']]] = $attributesMapping[$row['attribute_id']];
}
}
sort($expectedAttributes);
sort($attributes);
$this->assertEquals($attributes, $expectedAttributes);

$this->assertTrue(true);
}

/**
* @return array
*/
private function getAttributeCodes(): array
{
$connection = $this->resourceConnection->getConnection();
$select = $connection->select()
->from($connection->getTableName('eav_attribute'), ['attribute_id', 'attribute_code']);
return $connection->fetchPairs($select);
}

/**
* @return array|\string[][]
*/
public function attributesDataProvider(): array
{
return [
[
'default' => [
'name',
'meta_title',
'meta_description',
'is_returnable',
'options_container'
]
]
];
}

/**
* @return array
*/
private function getChangelogData()
{
$connection = $this->resourceConnection->getConnection();
$select = $connection->select()
->from($connection->getTableName('test_view_cl'));
return $connection->fetchAll($select);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

use Magento\TestFramework\Helper\Bootstrap;

$objectManager = Bootstrap::getObjectManager();
/** @var \Magento\Framework\Mview\View $view */
$view = $objectManager->create(\Magento\Framework\Mview\View::class);
$view->load('test_view');
$view->subscribe();
Loading