diff --git a/src/index/Model/Changelog/InsertRecords.php b/src/index/Model/Changelog/InsertRecords.php index f250287..ac8c1aa 100644 --- a/src/index/Model/Changelog/InsertRecords.php +++ b/src/index/Model/Changelog/InsertRecords.php @@ -6,57 +6,53 @@ use Aligent\FredhopperIndexer\Model\DataHandler; use Aligent\FredhopperIndexer\Model\ResourceModel\Changelog as ChangelogResource; use Magento\Framework\App\ResourceConnection; -use Magento\Framework\Exception\LocalizedException; class InsertRecords { /** * @param ResourceConnection $resourceConnection * @param ChangelogResource $changelogResource - * @param TempTable $tempTable */ public function __construct( private readonly ResourceConnection $resourceConnection, private readonly ChangelogResource $changelogResource, - private readonly TempTable $tempTable ) { } /** * Insert add, update and delete records into the changelog table * + * @param string $replicaId * @return void * @throws \Zend_Db_Select_Exception - * @throws LocalizedException */ - public function execute(): void + public function execute(string $replicaId): void { - $tempTableName = $this->tempTable->getTempTableName(); $addedProductIds = $this->getAddedOrDeletedProductsByType( true, DataHandler::TYPE_PRODUCT, - $tempTableName + $replicaId ); $addedVariantIds = $this->getAddedOrDeletedProductsByType( true, DataHandler::TYPE_VARIANT, - $tempTableName + $replicaId ); $this->changelogResource->insertAdditionOperations($addedProductIds, $addedVariantIds); - $updatedProductIds = $this->getUpdatedProductsByType(DataHandler::TYPE_PRODUCT, $tempTableName); - $updatedVariantIds = $this->getUpdatedProductsByType(DataHandler::TYPE_VARIANT, $tempTableName); + $updatedProductIds = $this->getUpdatedProductsByType(DataHandler::TYPE_PRODUCT, $replicaId); + $updatedVariantIds = $this->getUpdatedProductsByType(DataHandler::TYPE_VARIANT, $replicaId); $this->changelogResource->insertUpdateOperations($updatedProductIds, $updatedVariantIds); $deletedProductIds = $this->getAddedOrDeletedProductsByType( false, DataHandler::TYPE_PRODUCT, - $tempTableName + $replicaId ); $deletedVariantIds = $this->getAddedOrDeletedProductsByType( false, DataHandler::TYPE_VARIANT, - $tempTableName + $replicaId ); $this->changelogResource->insertDeleteOperations($deletedProductIds, $deletedVariantIds); } @@ -66,27 +62,41 @@ public function execute(): void * * @param bool $isAddition * @param string $productType - * @param string $tempTableName + * @param string $replicaId * @return array */ private function getAddedOrDeletedProductsByType( bool $isAddition, string $productType, - string $tempTableName + string $replicaId ): array { $connection = $this->resourceConnection->getConnection(); $select = $connection->select(); - $select->from( - ['main_table' => ($isAddition ? DataHandler::INDEX_TABLE_NAME : $tempTableName)], - ['product_id'] - ); - $select->joinLeft( - ['temp_table' => ($isAddition ? $tempTableName : DataHandler::INDEX_TABLE_NAME)], - 'temp_table.product_id = main_table.product_id AND '. - 'temp_table.product_type = main_table.product_type', - [] - ); + if ($isAddition) { + $select->from( + ['main_table' => DataHandler::INDEX_TABLE_NAME], + ['product_id'] + ); + $select->joinLeft( + ['temp_table' => ReplicaTableMaintainer::REPLICA_TABLE_NAME], + 'temp_table.product_id = main_table.product_id AND '. + 'temp_table.product_type = main_table.product_type AND '. + 'temp_table.replica_id = ' . "'" . $replicaId . "'" + ); + } else { + $select->from( + ['main_table' => ReplicaTableMaintainer::REPLICA_TABLE_NAME], + ['product_id'] + ); + $select->joinLeft( + ['temp_table' => DataHandler::INDEX_TABLE_NAME], + 'temp_table.product_id = main_table.product_id AND '. + 'temp_table.product_type = main_table.product_type' + ); + $select->where('main_table.replica_id = ?', $replicaId); + } + $select->where('temp_table.product_id is null'); $select->where('main_table.product_type = ?', $productType); $select->group('main_table.product_id'); @@ -95,20 +105,20 @@ private function getAddedOrDeletedProductsByType( } /** - * Determine which products have been updated between the main and temporary table + * Determine which products have been updated between the main and replica table * * @param string $productType - * @param string $tempTableName + * @param string $replicaId * @return array * @throws \Zend_Db_Select_Exception */ - private function getUpdatedProductsByType(string $productType, string $tempTableName): array + private function getUpdatedProductsByType(string $productType, string $replicaId): array { // get all product ids and variant ids that exist in both tables // we do not want to consider products that are being added or deleted completely $connection = $this->resourceConnection->getConnection(); $existingProductsSelect = $connection->select(); - $existingProductsSelect->from(['temp_table' => $tempTableName], ['product_id']); + $existingProductsSelect->from(['temp_table' => ReplicaTableMaintainer::REPLICA_TABLE_NAME], ['product_id']); $existingProductsSelect->joinInner( ['main_table' => DataHandler::INDEX_TABLE_NAME], 'main_table.product_id = temp_table.product_id AND ' . @@ -117,6 +127,7 @@ private function getUpdatedProductsByType(string $productType, string $tempTable ); $existingProductsSelect->distinct(); $existingProductsSelect->where('temp_table.product_type = ?', $productType); + $existingProductsSelect->where('temp_table.replica_id = ?', $replicaId); $existingProductIds = $connection->fetchCol($existingProductsSelect); // records that are in the new table, but not in the old table @@ -126,10 +137,11 @@ private function getUpdatedProductsByType(string $productType, string $tempTable ['product_id'] ); $existingProductsTempMissingSelect->joinLeft( - ['temp_table' => $tempTableName], + ['temp_table' => ReplicaTableMaintainer::REPLICA_TABLE_NAME], 'main_table.product_id = temp_table.product_id AND ' . 'main_table.product_type = temp_table.product_type AND ' . - 'main_table.store_id = temp_table.store_id', + 'main_table.store_id = temp_table.store_id AND ' . + 'temp_table.replica_id = ' . "'" . $replicaId . "'", [] ); $existingProductsTempMissingSelect->where('temp_table.product_id IS NULL'); @@ -139,7 +151,7 @@ private function getUpdatedProductsByType(string $productType, string $tempTable // records that are in the old table, but not in the new table $existingProductsMainMissingSelect = $connection->select(); $existingProductsMainMissingSelect->from( - ['temp_table' => $tempTableName], + ['temp_table' => ReplicaTableMaintainer::REPLICA_TABLE_NAME], ['product_id'] ); $existingProductsMainMissingSelect->joinLeft( @@ -152,6 +164,7 @@ private function getUpdatedProductsByType(string $productType, string $tempTable $existingProductsMainMissingSelect->where('main_table.product_id IS NULL'); $existingProductsMainMissingSelect->where('temp_table.product_type = ?', $productType); $existingProductsMainMissingSelect->where('temp_table.product_id in (?)', $existingProductIds); + $existingProductsMainMissingSelect->where('temp_table.replica_id = ?', $replicaId); // records that differ by parent_id or attribute_data $existingProductsDifferenceSelect = $connection->select(); @@ -160,10 +173,11 @@ private function getUpdatedProductsByType(string $productType, string $tempTable ['product_id'] ); $existingProductsDifferenceSelect->joinInner( - ['temp_table' => $tempTableName], + ['temp_table' => ReplicaTableMaintainer::REPLICA_TABLE_NAME], 'main_table.product_id = temp_table.product_id AND ' . 'main_table.product_type = temp_table.product_type AND ' . 'main_table.store_id = temp_table.store_id AND '. + 'temp_table.replica_id = ' . "'" . $replicaId . "'" . ' AND ' . 'NOT (main_table.parent_id <=> temp_table.parent_id AND ' . 'main_table.attribute_data <=> temp_table.attribute_data)', [] diff --git a/src/index/Model/Changelog/ReplicaTableMaintainer.php b/src/index/Model/Changelog/ReplicaTableMaintainer.php new file mode 100644 index 0000000..ae5b9e6 --- /dev/null +++ b/src/index/Model/Changelog/ReplicaTableMaintainer.php @@ -0,0 +1,83 @@ +resourceConnection->getConnection(); + $copySelect = $connection->select(); + $selectColumns = [ + 'replica_id' => new \Zend_Db_Expr("'$id'"), + 'store_id' => 'store_id', + 'product_type' => 'product_type', + 'product_id' => 'product_id', + 'parent_id' => 'parent_id', + 'attribute_data' => 'attribute_data', + + ]; + $copySelect->from( + DataHandler::INDEX_TABLE_NAME, + $selectColumns + ); + + $copyInsert = $connection->insertFromSelect( + $copySelect, + self::REPLICA_TABLE_NAME + ); + $connection->query($copyInsert); + } + + /** + * Delete records from the replica table with the given ID + * + * @param string $id + * @return void + */ + public function deleteRecords(string $id): void + { + $connection = $this->resourceConnection->getConnection(); + $connection->delete( + self::REPLICA_TABLE_NAME, + ['replica_id = ?' => $id] + ); + } +} diff --git a/src/index/Model/Changelog/TempTable.php b/src/index/Model/Changelog/TempTable.php deleted file mode 100644 index b4de087..0000000 --- a/src/index/Model/Changelog/TempTable.php +++ /dev/null @@ -1,102 +0,0 @@ -tempTableName)) { - throw new LocalizedException(__(__METHOD__ . ': temp table name not set')); - } - return $this->tempTableName; - } - - /** - * Sets the current temporary table to a unique value - * - * @return void - * @throws LocalizedException - */ - public function generateTempTableName(): void - { - if (isset($this->tempTableName)) { - throw new LocalizedException(__(__METHOD__ . ': temp table name already set')); - } - try { - $tempTableName = self::TEMP_TABLE_PREFIX . bin2hex(random_bytes(4)); - } catch (RandomException) { - $tempTableName = uniqid(self::TEMP_TABLE_PREFIX, true); - } - $this->tempTableName = $tempTableName; - } - - /** - * Creates a temporary copy of the index table for use in generating changelog records - * - * @return void - * @throws LocalizedException - */ - public function create(): void - { - if (!isset($this->tempTableName)) { - throw new LocalizedException(__(__METHOD__ . ': temp table name not set')); - } - $connection = $this->resourceConnection->getConnection(); - $table = $connection->createTableByDdl(DataHandler::INDEX_TABLE_NAME, $this->tempTableName); - if ($connection->isTableExists($this->tempTableName)) { - throw new LocalizedException(__(__METHOD__ . ': temp table already exists')); - } - try { - $connection->createTable($table); - } catch (\Exception $e) { - throw new LocalizedException(__(__METHOD__ . ': ' . $e->getMessage()), $e); - } - $copySelect = $connection->select(); - $copySelect->from(DataHandler::INDEX_TABLE_NAME); - $copyInsert = $connection->insertFromSelect( - $copySelect, - $this->tempTableName - ); - $connection->query($copyInsert); - } - - /** - * Drops the temporary table if it exists - * - * @return void - * @throws LocalizedException - */ - public function drop(): void - { - if (!isset($this->tempTableName)) { - throw new LocalizedException(__(__METHOD__ . ': temp table name not set')); - } - $this->resourceConnection->getConnection()->dropTable($this->tempTableName); - unset($this->tempTableName); - } -} diff --git a/src/index/Model/ProductIndexer.php b/src/index/Model/ProductIndexer.php index e4181db..afb88c4 100644 --- a/src/index/Model/ProductIndexer.php +++ b/src/index/Model/ProductIndexer.php @@ -4,7 +4,7 @@ namespace Aligent\FredhopperIndexer\Model; use Aligent\FredhopperIndexer\Model\Changelog\InsertRecords; -use Aligent\FredhopperIndexer\Model\Changelog\TempTable; +use Aligent\FredhopperIndexer\Model\Changelog\ReplicaTableMaintainer; use Aligent\FredhopperIndexer\Model\Data\FredhopperDataProvider; use Magento\CatalogSearch\Model\ResourceModel\Fulltext as FulltextResource; use Magento\Framework\App\DeploymentConfig; @@ -29,7 +29,7 @@ class ProductIndexer implements DimensionalIndexerInterface, IndexerActionInterf * @param DataHandler $dataHandler * @param DeploymentConfig $deploymentConfig * @param FulltextResource $fulltextResource - * @param TempTable $tempTable + * @param ReplicaTableMaintainer $replicaTableMaintainer * @param InsertRecords $insertChangelogRecords * @param LoggerInterface $logger * @param int $batchSize @@ -40,7 +40,7 @@ public function __construct( private readonly DataHandler $dataHandler, private readonly DeploymentConfig $deploymentConfig, private readonly FulltextResource $fulltextResource, - private readonly TempTable $tempTable, + private readonly ReplicaTableMaintainer $replicaTableMaintainer, private readonly InsertRecords $insertChangelogRecords, private readonly LoggerInterface $logger, private readonly int $batchSize = 1000 @@ -49,8 +49,6 @@ public function __construct( /** * @inheritDoc - * - * @throws LocalizedException */ public function execute($ids): void { @@ -59,8 +57,6 @@ public function execute($ids): void /** * @inheritDoc - * - * @throws LocalizedException */ public function executeFull(): void { @@ -69,15 +65,14 @@ public function executeFull(): void /** * @inheritDoc - * */ public function executeList(array $ids): void { try { - // create temporary table to handle changelogs - $this->tempTable->generateTempTableName(); - $this->tempTable->create(); - // try block here is nested to ensure that if the table was created, it gets dropped at the end + // create ID for temporary records to handle changelogs + $replicaId = $this->replicaTableMaintainer->generateUniqueId(); + $this->replicaTableMaintainer->insertRecords($replicaId); + // try block here is nested to ensure that if the records were inserted, they get removed at the end try { foreach ($this->dimensionProvider->getIterator() as $dimension) { try { @@ -86,10 +81,10 @@ public function executeList(array $ids): void continue; } } - $this->insertChangelogRecords->execute(); + $this->insertChangelogRecords->execute($replicaId); } finally { - // we want to ensure that the "temporary" table is always dropped - $this->tempTable->drop(); + // we want to ensure that the temporary records are always deleted + $this->replicaTableMaintainer->deleteRecords($replicaId); } } catch (\Exception $e) { $this->logger->critical($e->getMessage(), ['exception' => $e]); diff --git a/src/index/etc/db_schema.xml b/src/index/etc/db_schema.xml index bed1488..f0accbe 100644 --- a/src/index/etc/db_schema.xml +++ b/src/index/etc/db_schema.xml @@ -18,6 +18,26 @@ + + + + + + + + + + + + + +