| Current Path : /home/rtorresani/www/vendor/magento/module-catalog/Model/Indexer/Product/Flat/ |
| Current File : //home/rtorresani/www/vendor/magento/module-catalog/Model/Indexer/Product/Flat/TableBuilder.php |
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\Catalog\Model\Indexer\Product\Flat;
use Magento\Catalog\Model\Indexer\Product\Flat\Table\BuilderInterfaceFactory;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Store\Model\Store;
/**
* Prepare temporary tables structure for product flat indexer
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class TableBuilder
{
/**
* @var \Magento\Catalog\Helper\Product\Flat\Indexer
*/
protected $_productIndexerHelper;
/**
* @var \Magento\Framework\DB\Adapter\AdapterInterface
*/
protected $_connection;
/**
* @var MetadataPool
*/
protected $metadataPool;
/**
* @var \Magento\Framework\App\ResourceConnection
*/
protected $resource;
/**
* @var BuilderInterfaceFactory
*/
private $tableBuilderFactory;
/**
* Constructor
*
* @param \Magento\Catalog\Helper\Product\Flat\Indexer $productIndexerHelper
* @param \Magento\Framework\App\ResourceConnection $resource
* @param BuilderInterfaceFactory|null $tableBuilderFactory
* @param MetadataPool|null $metadataPool
*/
public function __construct(
\Magento\Catalog\Helper\Product\Flat\Indexer $productIndexerHelper,
\Magento\Framework\App\ResourceConnection $resource,
BuilderInterfaceFactory $tableBuilderFactory = null,
MetadataPool $metadataPool = null
) {
$this->_productIndexerHelper = $productIndexerHelper;
$this->resource = $resource;
$this->_connection = $resource->getConnection();
$this->tableBuilderFactory = $tableBuilderFactory ?? ObjectManager::getInstance()
->get(BuilderInterfaceFactory::class);
$this->metadataPool = $metadataPool ?? ObjectManager::getInstance()->get(MetadataPool::class);
}
/**
* Prepare temporary tables only for first call of reindex all
*
* @param int $storeId
* @param array $changedIds
* @param string $valueFieldSuffix
* @return void
*/
public function build($storeId, $changedIds, $valueFieldSuffix)
{
$entityTableName = $this->_productIndexerHelper->getTable('catalog_product_entity');
$attributes = $this->_productIndexerHelper->getAttributes();
$eavAttributes = $this->_productIndexerHelper->getTablesStructure($attributes);
$entityTableColumns = $eavAttributes[$entityTableName];
$linkField = $this->metadataPool
->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class)
->getLinkField();
$temporaryEavAttributes = $eavAttributes;
//add status global value to the base table
/* @var $status \Magento\Eav\Model\Entity\Attribute */
$status = $this->_productIndexerHelper->getAttribute('status');
$temporaryEavAttributes[$status->getBackendTable()]['status'] = $status;
//Create list of temporary tables based on available attributes attributes
$valueTables = [];
foreach ($temporaryEavAttributes as $tableName => $columns) {
$valueTables[] = $this->_createTemporaryTable(
$this->_getTemporaryTableName($tableName),
$columns,
$valueFieldSuffix
);
}
$valueTables = array_merge([], ...$valueTables);
//Fill "base" table which contains all available products
$this->_fillTemporaryEntityTable($entityTableName, $entityTableColumns, $changedIds);
//Add primary key to "base" temporary table for increase speed of joins in future
$this->_addPrimaryKeyToTable($this->_getTemporaryTableName($entityTableName));
unset($temporaryEavAttributes[$entityTableName]);
foreach ($temporaryEavAttributes as $tableName => $columns) {
$temporaryTableName = $this->_getTemporaryTableName($tableName);
//Add primary key to temporary table for increase speed of joins in future
$this->_addPrimaryKeyToTable($temporaryTableName, $linkField);
//Create temporary table for composite attributes
if (isset($valueTables[$temporaryTableName . $valueFieldSuffix])) {
$this->_addPrimaryKeyToTable($temporaryTableName . $valueFieldSuffix, $linkField);
}
//Fill temporary tables with attributes grouped by it type
$this->_fillTemporaryTable($tableName, $columns, $changedIds, $valueFieldSuffix, $storeId);
}
}
/**
* Create empty temporary table with given columns list
*
* @param string $tableName Table name
* @param array $columns array('columnName' => \Magento\Catalog\Model\ResourceModel\Eav\Attribute, ...)
* @param string $valueFieldSuffix
*
* @return array
*/
protected function _createTemporaryTable($tableName, array $columns, $valueFieldSuffix)
{
$valueTables = [];
if (!empty($columns)) {
$valueTableName = $tableName . $valueFieldSuffix;
$temporaryTableBuilder = $this->tableBuilderFactory->create(
[
'connection' => $this->_connection,
'tableName' => $tableName
]
);
$valueTemporaryTableBuilder = $this->tableBuilderFactory->create(
[
'connection' => $this->_connection,
'tableName' => $valueTableName
]
);
$flatColumns = $this->_productIndexerHelper->getFlatColumns();
$temporaryTableBuilder->addColumn(
'entity_id',
\Magento\Framework\DB\Ddl\Table::TYPE_INTEGER,
null,
['unsigned'=>true]
);
$temporaryTableBuilder->addColumn('type_id', \Magento\Framework\DB\Ddl\Table::TYPE_TEXT);
$temporaryTableBuilder->addColumn('attribute_set_id', \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER);
$valueTemporaryTableBuilder->addColumn(
'entity_id',
\Magento\Framework\DB\Ddl\Table::TYPE_INTEGER,
null,
['unsigned'=>true]
);
/** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */
foreach ($columns as $columnName => $attribute) {
$attributeCode = $attribute->getAttributeCode();
if (isset($flatColumns[$attributeCode])) {
$column = $flatColumns[$attributeCode];
} else {
$column = $attribute->_getFlatColumnsDdlDefinition();
$column = $column[$attributeCode];
}
$temporaryTableBuilder->addColumn(
$columnName,
$column['type'],
isset($column['length']) ? $column['length'] : null
);
$columnValueName = $attributeCode . $valueFieldSuffix;
if (isset($flatColumns[$columnValueName])) {
$columnValue = $flatColumns[$columnValueName];
$valueTemporaryTableBuilder->addColumn(
$columnValueName,
$columnValue['type'],
isset($columnValue['length']) ? $columnValue['length'] : null
);
}
}
$this->_connection->dropTemporaryTable($tableName);
$this->_connection->createTemporaryTable($temporaryTableBuilder->getTable());
if (count($valueTemporaryTableBuilder->getTable()->getColumns()) > 1) {
$this->_connection->dropTemporaryTable($valueTableName);
$this->_connection->createTemporaryTable($valueTemporaryTableBuilder->getTable());
$valueTables[$valueTableName] = $valueTableName;
}
}
return $valueTables;
}
/**
* Retrieve temporary table name by regular table name
*
* @param string $tableName
* @return string
*/
protected function _getTemporaryTableName($tableName)
{
return sprintf('%s_tmp_indexer', $tableName);
}
/**
* Fill temporary entity table
*
* @param string $tableName
* @param array $columns
* @param array $changedIds
* @return void
* @throws \Exception
*/
protected function _fillTemporaryEntityTable($tableName, array $columns, array $changedIds = [])
{
if (!empty($columns)) {
$select = $this->_connection->select();
$temporaryEntityTable = $this->_getTemporaryTableName($tableName);
$metadata = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class);
$idsColumns = array_unique([$metadata->getLinkField(), 'entity_id', 'type_id', 'attribute_set_id']);
$columns = array_merge($idsColumns, array_keys($columns));
$select->from(['e' => $tableName], $columns);
$onDuplicate = false;
if (!empty($changedIds)) {
$select->where($this->_connection->quoteInto('e.entity_id IN (?)', $changedIds));
$onDuplicate = true;
}
$sql = $select->insertFromSelect($temporaryEntityTable, $columns, $onDuplicate);
$this->_connection->query($sql);
}
}
/**
* Add primary key to table by it name
*
* @param string $tableName
* @param string $columnName
* @return void
*/
protected function _addPrimaryKeyToTable($tableName, $columnName = 'entity_id')
{
$this->_connection->addIndex(
$tableName,
'entity_id',
[$columnName],
\Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_PRIMARY
);
}
/**
* Fill temporary table by data from products EAV attributes by type
*
* @param string $tableName
* @param array $tableColumns
* @param array $changedIds
* @param string $valueFieldSuffix
* @param int $storeId
* @return void
* @throws \Exception
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
protected function _fillTemporaryTable(
$tableName,
array $tableColumns,
array $changedIds,
$valueFieldSuffix,
$storeId
) {
if (!empty($tableColumns)) {
$columnsChunks = array_chunk(
$tableColumns,
intdiv(Action\Indexer::ATTRIBUTES_CHUNK_SIZE, 2),
true
);
$entityTableName = $this->_productIndexerHelper->getTable('catalog_product_entity');
$entityTemporaryTableName = $this->_getTemporaryTableName($entityTableName);
$temporaryTableName = $this->_getTemporaryTableName($tableName);
$temporaryValueTableName = $temporaryTableName . $valueFieldSuffix;
$attributeOptionValueTableName = $this->_productIndexerHelper->getTable('eav_attribute_option_value');
$flatColumns = $this->_productIndexerHelper->getFlatColumns();
$defaultStoreId = Store::DEFAULT_STORE_ID;
$linkField = $this->metadataPool
->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class)
->getLinkField();
foreach ($columnsChunks as $columnsList) {
$select = $this->_connection->select();
$selectValue = $this->_connection->select();
$keyColumn = array_unique([$linkField, 'entity_id']);
// phpcs:ignore Magento2.Performance.ForeachArrayMerge
$columns = array_merge($keyColumn, array_keys($columnsList));
$valueColumns = $keyColumn;
$iterationNum = 1;
$select->from(['et' => $entityTemporaryTableName], $keyColumn)
->join(['e' => $entityTableName], 'e.entity_id = et.entity_id', []);
$selectValue->from(['e' => $temporaryTableName], $keyColumn);
/** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */
foreach ($columnsList as $columnName => $attribute) {
$countTableName = 't' . ($iterationNum++);
$joinCondition = 'e.%3$s = %1$s.%3$s AND %1$s.attribute_id = %2$d AND %1$s.store_id = %4$d';
$select->joinLeft(
[$countTableName => $tableName],
sprintf($joinCondition, $countTableName, $attribute->getId(), $linkField, $defaultStoreId),
[]
)->joinLeft(
['s' . $countTableName => $tableName],
sprintf($joinCondition, 's' . $countTableName, $attribute->getId(), $linkField, $storeId),
[]
);
$columnValue = $this->_connection->getIfNullSql(
's' . $countTableName . '.value',
$countTableName . '.value'
);
$select->columns([$columnName => $columnValue]);
if ($attribute->getFlatUpdateSelect($storeId) instanceof \Magento\Framework\DB\Select) {
$attributeCode = $attribute->getAttributeCode();
$columnValueName = $attributeCode . $valueFieldSuffix;
if (isset($flatColumns[$columnValueName])) {
$valueJoinCondition = 'e.%1$s = %2$s.option_id AND %2$s.store_id = %3$d';
$selectValue->joinLeft(
[$countTableName => $attributeOptionValueTableName],
sprintf($valueJoinCondition, $attributeCode, $countTableName, $defaultStoreId),
[]
)->joinLeft(
['s' . $countTableName => $attributeOptionValueTableName],
sprintf($valueJoinCondition, $attributeCode, 's' . $countTableName, $storeId),
[]
);
$selectValue->columns([$columnValueName => $columnValue]);
$valueColumns[] = $columnValueName;
}
}
}
if (!empty($changedIds)) {
$select->where(
$this->_connection->quoteInto('e.entity_id IN (?)', $changedIds, \Zend_Db::INT_TYPE)
);
}
$sql = $select->insertFromSelect($temporaryTableName, $columns, true);
$this->_connection->query($sql);
if (count($valueColumns) > 1) {
if (!empty($changedIds)) {
$selectValue->where(
$this->_connection->quoteInto('e.entity_id IN (?)', $changedIds, \Zend_Db::INT_TYPE)
);
}
$sql = $selectValue->insertFromSelect($temporaryValueTableName, $valueColumns, true);
$this->_connection->query($sql);
}
}
}
}
}