diff --git a/vendor/magento/module-catalog/Model/Product/Gallery/CreateHandler.php b/vendor/magento/module-catalog/Model/Product/Gallery/CreateHandler.php
index 03d418f3ba0..bd28b65bb79 100644
--- a/vendor/magento/module-catalog/Model/Product/Gallery/CreateHandler.php
+++ b/vendor/magento/module-catalog/Model/Product/Gallery/CreateHandler.php
@@ -245,12 +245,13 @@ class CreateHandler implements ExtensionInterface
if (empty($image['removed'])) {
$data = $this->processNewImage($product, $image);
- $this->resourceModel->deleteGalleryValueInStore(
- $image['value_id'],
- $product->getData($this->metadata->getLinkField()),
- $product->getStoreId()
- );
-
+ if (!$product->isObjectNew()) {
+ $this->resourceModel->deleteGalleryValueInStore(
+ $image['value_id'],
+ $product->getData($this->metadata->getLinkField()),
+ $product->getStoreId()
+ );
+ }
// Add per store labels, position, disabled
$data['value_id'] = $image['value_id'];
$data['label'] = isset($image['label']) ? $image['label'] : '';
diff --git a/vendor/magento/module-catalog/Model/ResourceModel/AbstractResource.php b/vendor/magento/module-catalog/Model/ResourceModel/AbstractResource.php
index 1296ca62be7..de9ad8393c5 100644
--- a/vendor/magento/module-catalog/Model/ResourceModel/AbstractResource.php
+++ b/vendor/magento/module-catalog/Model/ResourceModel/AbstractResource.php
@@ -177,7 +177,10 @@ abstract class AbstractResource extends \Magento\Eav\Model\Entity\AbstractEntity
protected function _saveAttributeValue($object, $attribute, $value)
{
$connection = $this->getConnection();
- $storeId = (int) $this->_storeManager->getStore($object->getStoreId())->getId();
+ $hasSingleStore = $this->_storeManager->hasSingleStore();
+ $storeId = $hasSingleStore
+ ? $this->getDefaultStoreId()
+ : (int) $this->_storeManager->getStore($object->getStoreId())->getId();
$table = $attribute->getBackend()->getTable();
/**
@@ -186,15 +189,18 @@ abstract class AbstractResource extends \Magento\Eav\Model\Entity\AbstractEntity
* In this case we clear all not default values
*/
$entityIdField = $this->getLinkField();
- if ($this->_storeManager->hasSingleStore()) {
- $storeId = $this->getDefaultStoreId();
+ $conditions = [
+ 'attribute_id = ?' => $attribute->getAttributeId(),
+ "{$entityIdField} = ?" => $object->getData($entityIdField),
+ 'store_id <> ?' => $storeId
+ ];
+ if ($hasSingleStore
+ && !$object->isObjectNew()
+ && $this->isAttributePresentForNonDefaultStore($attribute, $conditions)
+ ) {
$connection->delete(
$table,
- [
- 'attribute_id = ?' => $attribute->getAttributeId(),
- "{$entityIdField} = ?" => $object->getData($entityIdField),
- 'store_id <> ?' => $storeId
- ]
+ $conditions
);
}
@@ -233,6 +239,27 @@ abstract class AbstractResource extends \Magento\Eav\Model\Entity\AbstractEntity
return $this;
}
+ /**
+ * Check if attribute present for non default Store View.
+ * Prevent "delete" query locking in a case when nothing to delete
+ *
+ * @param AbstractAttribute $attribute
+ * @param array $conditions
+ *
+ * @return boolean
+ */
+ private function isAttributePresentForNonDefaultStore($attribute, $conditions)
+ {
+ $connection = $this->getConnection();
+ $select = $connection->select()->from($attribute->getBackend()->getTable());
+ foreach ($conditions as $condition => $conditionValue) {
+ $select->where($condition, $conditionValue);
+ }
+ $select->limit(1);
+
+ return !empty($connection->fetchRow($select));
+ }
+
/**
* Insert entity attribute value
*
diff --git a/vendor/magento/module-catalog/Model/ResourceModel/Product/CategoryLink.php b/vendor/magento/module-catalog/Model/ResourceModel/Product/CategoryLink.php
index 6e2642d0991..b54c19a1115 100644
--- a/vendor/magento/module-catalog/Model/ResourceModel/Product/CategoryLink.php
+++ b/vendor/magento/module-catalog/Model/ResourceModel/Product/CategoryLink.php
@@ -83,11 +83,12 @@ class CategoryLink
$insertUpdate = $this->processCategoryLinks($categoryLinks, $oldCategoryLinks);
$deleteUpdate = $this->processCategoryLinks($oldCategoryLinks, $categoryLinks);
- list($delete, $insert) = $this->analyseUpdatedLinks($deleteUpdate, $insertUpdate);
+ list($delete, $insert, $update) = $this->analyseUpdatedLinks($deleteUpdate, $insertUpdate);
return array_merge(
- $this->updateCategoryLinks($product, $insert),
- $this->deleteCategoryLinks($product, $delete)
+ $this->deleteCategoryLinks($product, $delete),
+ $this->updateCategoryLinks($product, $insert, true),
+ $this->updateCategoryLinks($product, $update)
);
}
@@ -133,16 +134,15 @@ class CategoryLink
/**
* @param ProductInterface $product
* @param array $insertLinks
+ * @param bool $insert
* @return array
*/
- private function updateCategoryLinks(ProductInterface $product, array $insertLinks)
+ private function updateCategoryLinks(ProductInterface $product, array $insertLinks, $insert = false)
{
if (empty($insertLinks)) {
return [];
}
- $connection = $this->resourceConnection->getConnection();
-
$data = [];
foreach ($insertLinks as $categoryLink) {
$data[] = [
@@ -153,11 +153,22 @@ class CategoryLink
}
if ($data) {
- $connection->insertOnDuplicate(
- $this->getCategoryLinkMetadata()->getEntityTable(),
- $data,
- ['position']
- );
+ $connection = $this->resourceConnection->getConnection();
+ if ($insert) {
+ $connection->insertArray(
+ $this->getCategoryLinkMetadata()->getEntityTable(),
+ array_keys($data[0]),
+ $data,
+ \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_IGNORE
+ );
+ } else {
+ // for mass update category links with constraint by unique key use insert on duplicate statement
+ $connection->insertOnDuplicate(
+ $this->getCategoryLinkMetadata()->getEntityTable(),
+ $data,
+ ['position']
+ );
+ }
}
return array_column($insertLinks, 'category_id');
@@ -215,7 +226,7 @@ class CategoryLink
}
/**
- * Analyse category links for update or/and delete
+ * Analyse category links for update or/and delete. Return array of links for delete, insert and update
*
* @param array $deleteUpdate
* @param array $insertUpdate
@@ -226,8 +237,7 @@ class CategoryLink
$delete = $deleteUpdate['changed'] ?: [];
$insert = $insertUpdate['changed'] ?: [];
$insert = array_merge_recursive($insert, $deleteUpdate['updated']);
- $insert = array_merge_recursive($insert, $insertUpdate['updated']);
- return [$delete, $insert];
+ return [$delete, $insert, $insertUpdate['updated']];
}
}
diff --git a/vendor/magento/module-catalog/Model/ResourceModel/Product/Link.php b/vendor/magento/module-catalog/Model/ResourceModel/Product/Link.php
index 4b420329eef..e52943d0f77 100644
--- a/vendor/magento/module-catalog/Model/ResourceModel/Product/Link.php
+++ b/vendor/magento/module-catalog/Model/ResourceModel/Product/Link.php
@@ -5,6 +5,8 @@
*/
namespace Magento\Catalog\Model\ResourceModel\Product;
+use Magento\Framework\DB\Adapter\AdapterInterface;
+
/**
* Catalog product link resource model
*
@@ -21,7 +23,7 @@ class Link extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
/**
* Catalog product relation
- *
+ *vendor/magento/module-catalog/Observer/UnsetSpecialPrice.php
* @var Relation
*/
protected $_catalogProductRelation;
@@ -136,7 +138,6 @@ class Link extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
$data = [];
}
- $attributes = $this->getAttributesByType($typeId);
$connection = $this->getConnection();
$bind = [':product_id' => (int)$parentId, ':link_type_id' => (int)$typeId];
@@ -151,42 +152,38 @@ class Link extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
$links = $connection->fetchPairs($select, $bind);
- foreach ($data as $linkedProductId => $linkInfo) {
- $linkId = null;
- if (isset($links[$linkedProductId])) {
- $linkId = $links[$linkedProductId];
- unset($links[$linkedProductId]);
- } else {
- $bind = [
- 'product_id' => $parentId,
- 'linked_product_id' => $linkedProductId,
- 'link_type_id' => $typeId,
- ];
- $connection->insert($this->getMainTable(), $bind);
- $linkId = $connection->lastInsertId($this->getMainTable());
- }
+ list($insertData, $updateData, $deleteConditions) = $this->prepareProductLinksData(
+ $parentId,
+ $data,
+ $typeId,
+ $links
+ );
- foreach ($attributes as $attributeInfo) {
- $attributeTable = $this->getAttributeTypeTable($attributeInfo['type']);
- if ($attributeTable) {
- if (isset($linkInfo[$attributeInfo['code']])) {
- $value = $this->_prepareAttributeValue(
- $attributeInfo['type'],
- $linkInfo[$attributeInfo['code']]
- );
- $bind = [
- 'product_link_attribute_id' => $attributeInfo['id'],
- 'link_id' => $linkId,
- 'value' => $value,
- ];
- $connection->insertOnDuplicate($attributeTable, $bind, ['value']);
- } else {
- $connection->delete(
- $attributeTable,
- ['link_id = ?' => $linkId, 'product_link_attribute_id = ?' => $attributeInfo['id']]
- );
- }
- }
+ if ($insertData) {
+ $insertColumns = [
+ 'product_link_attribute_id',
+ 'link_id',
+ 'value',
+ ];
+ foreach ($insertData as $table => $values) {
+ $connection->insertArray($table, $insertColumns, $values, AdapterInterface::INSERT_IGNORE);
+ }
+ }
+ if ($updateData) {
+ // for mass update product links with constraint by unique key use insert on duplicate statement
+ foreach ($updateData as $table => $values) {
+ $connection->insertOnDuplicate($table, $values, ['value']);
+ }
+ }
+ if ($deleteConditions) {
+ foreach ($deleteConditions as $table => $deleteCondition) {
+ $connection->delete(
+ $table,
+ [
+ 'link_id = ?' => $deleteCondition['link_id'],
+ 'product_link_attribute_id = ?' => $deleteCondition['product_link_attribute_id']
+ ]
+ );
}
}
@@ -302,4 +299,69 @@ class Link extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
return $parentIds;
}
+
+ /**
+ * Prepare data for insert, update or delete product link attributes
+ *
+ * @param int $parentId
+ * @param array $data
+ * @param int $typeId
+ * @param array $links
+ * @return array
+ */
+ private function prepareProductLinksData($parentId, $data, $typeId, $links)
+ {
+ $connection = $this->getConnection();
+ $attributes = $this->getAttributesByType($typeId);
+
+ $insertData = [];
+ $updateData = [];
+ $deleteConditions = [];
+
+ foreach ($data as $linkedProductId => $linkInfo) {
+ $linkId = null;
+ if (isset($links[$linkedProductId])) {
+ $linkId = $links[$linkedProductId];
+ } else {
+ $bind = [
+ 'product_id' => $parentId,
+ 'linked_product_id' => $linkedProductId,
+ 'link_type_id' => $typeId,
+ ];
+ $connection->insert($this->getMainTable(), $bind);
+ $linkId = $connection->lastInsertId($this->getMainTable());
+ }
+
+ foreach ($attributes as $attributeInfo) {
+ $attributeTable = $this->getAttributeTypeTable($attributeInfo['type']);
+ if (!$attributeTable) {
+ continue;
+ }
+ if (isset($linkInfo[$attributeInfo['code']])) {
+ $value = $this->_prepareAttributeValue(
+ $attributeInfo['type'],
+ $linkInfo[$attributeInfo['code']]
+ );
+ if (isset($links[$linkedProductId])) {
+ $updateData[$attributeTable][] = [
+ 'product_link_attribute_id' => $attributeInfo['id'],
+ 'link_id' => $linkId,
+ 'value' => $value,
+ ];
+ } else {
+ $insertData[$attributeTable][] = [
+ 'product_link_attribute_id' => $attributeInfo['id'],
+ 'link_id' => $linkId,
+ 'value' => $value,
+ ];
+ }
+ } else {
+ $deleteConditions[$attributeTable]['link_id'][] = $linkId;
+ $deleteConditions[$attributeTable]['product_link_attribute_id'][] = $attributeInfo['id'];
+ }
+ }
+ }
+
+ return [$insertData, $updateData, $deleteConditions];
+ }
}
diff --git a/vendor/magento/module-catalog/Observer/UnsetSpecialPrice.php b/vendor/magento/module-catalog/Observer/UnsetSpecialPrice.php
deleted file mode 100644
index 0ba1251edc7..00000000000
--- a/vendor/magento/module-catalog/Observer/UnsetSpecialPrice.php
+++ /dev/null
@@ -1,31 +0,0 @@
-getEvent()->getProduct();
- if ($product->getSpecialPrice() === null) {
- $product->setData('special_price', '');
- }
-
- return $this;
- }
-}
diff --git a/vendor/magento/module-catalog/Plugin/Model/ResourceModel/ReadSnapshotPlugin.php b/vendor/magento/module-catalog/Plugin/Model/ResourceModel/ReadSnapshotPlugin.php
new file mode 100644
index 00000000000..ff4d2f93c91
--- /dev/null
+++ b/vendor/magento/module-catalog/Plugin/Model/ResourceModel/ReadSnapshotPlugin.php
@@ -0,0 +1,97 @@
+metadataPool = $metadataPool;
+ $this->config = $config;
+ }
+
+ /**
+ * @param ReadSnapshot $subject
+ * @param array $entityData
+ * @param string $entityType
+ * @return array
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function afterExecute(ReadSnapshot $subject, array $entityData, $entityType)
+ {
+ if (!in_array($entityType, [ProductInterface::class, CategoryInterface::class], true)) {
+ return $entityData;
+ }
+
+ $metadata = $this->metadataPool->getMetadata($entityType);
+ $connection = $metadata->getEntityConnection();
+ $globalAttributes = [];
+ $attributesMap = [];
+ $eavEntityType = $metadata->getEavEntityType();
+ $attributes = null === $eavEntityType
+ ? []
+ : $this->config->getEntityAttributes($eavEntityType, new \Magento\Framework\DataObject($entityData));
+
+ /** @var \Magento\Eav\Model\Entity\Attribute\AbstractAttribute $attribute */
+ foreach ($attributes as $attribute) {
+ if (!$attribute->isStatic() && $attribute->isScopeGlobal()) {
+ $globalAttributes[$attribute->getBackend()->getTable()][] = $attribute->getAttributeId();
+ $attributesMap[$attribute->getAttributeId()] = $attribute->getAttributeCode();
+ }
+ }
+
+ if ($globalAttributes) {
+ $selects = [];
+ foreach ($globalAttributes as $table => $attributeIds) {
+ $select = $connection->select()
+ ->from(
+ ['t' => $table],
+ ['value' => 't.value', 'attribute_id' => 't.attribute_id']
+ )
+ ->where($metadata->getLinkField() . ' = ?', $entityData[$metadata->getLinkField()])
+ ->where('attribute_id' . ' in (?)', $attributeIds)
+ ->where('store_id = ?', \Magento\Store\Model\Store::DEFAULT_STORE_ID);
+ $selects[] = $select;
+ }
+ $unionSelect = new \Magento\Framework\DB\Sql\UnionExpression(
+ $selects,
+ \Magento\Framework\DB\Select::SQL_UNION_ALL
+ );
+ foreach ($connection->fetchAll($unionSelect) as $attributeValue) {
+ $entityData[$attributesMap[$attributeValue['attribute_id']]] = $attributeValue['value'];
+ }
+ }
+
+ return $entityData;
+ }
+}
diff --git a/vendor/magento/module-catalog/etc/di.xml b/vendor/magento/module-catalog/etc/di.xml
index 114d46f63fd..5686a544249 100644
--- a/vendor/magento/module-catalog/etc/di.xml
+++ b/vendor/magento/module-catalog/etc/di.xml
@@ -809,6 +809,9 @@
Magento\Catalog\Model\ResourceModel\AttributePersistor
+
+
+
diff --git a/vendor/magento/module-catalog/etc/events.xml b/vendor/magento/module-catalog/etc/events.xml
index 3fdb554e65b..63bd5748943 100644
--- a/vendor/magento/module-catalog/etc/events.xml
+++ b/vendor/magento/module-catalog/etc/events.xml
@@ -56,7 +56,6 @@
-
diff --git a/vendor/magento/module-catalog/etc/mview.xml b/vendor/magento/module-catalog/etc/mview.xml
index 7ae38a7f2d0..a10c29ed842 100644
--- a/vendor/magento/module-catalog/etc/mview.xml
+++ b/vendor/magento/module-catalog/etc/mview.xml
@@ -36,7 +36,6 @@
-
diff --git a/vendor/magento/module-catalog-url-rewrite/Observer/ProductProcessUrlRewriteSavingObserver.php b/vendor/magento/module-catalog-url-rewrite/Observer/ProductProcessUrlRewriteSavingObserver.php
index e4ccd0b869d..c4d67f447e2 100644
--- a/vendor/magento/module-catalog-url-rewrite/Observer/ProductProcessUrlRewriteSavingObserver.php
+++ b/vendor/magento/module-catalog-url-rewrite/Observer/ProductProcessUrlRewriteSavingObserver.php
@@ -50,13 +50,6 @@ class ProductProcessUrlRewriteSavingObserver implements ObserverInterface
|| $product->getIsChangedWebsites()
|| $product->dataHasChangedFor('visibility')
) {
- $this->urlPersist->deleteByData([
- UrlRewrite::ENTITY_ID => $product->getId(),
- UrlRewrite::ENTITY_TYPE => ProductUrlRewriteGenerator::ENTITY_TYPE,
- UrlRewrite::REDIRECT_TYPE => 0,
- UrlRewrite::STORE_ID => $product->getStoreId()
- ]);
-
if ($product->isVisibleInSiteVisibility()) {
$this->urlPersist->replace($this->productUrlRewriteGenerator->generate($product));
}
diff --git a/vendor/magento/module-configurable-product/Model/ResourceModel/Product/Type/Configurable.php b/vendor/magento/module-configurable-product/Model/ResourceModel/Product/Type/Configurable.php
index 3c9689a1c4e..95afba984d5 100644
--- a/vendor/magento/module-configurable-product/Model/ResourceModel/Product/Type/Configurable.php
+++ b/vendor/magento/module-configurable-product/Model/ResourceModel/Product/Type/Configurable.php
@@ -17,6 +17,7 @@ use Magento\ConfigurableProduct\Model\AttributeOptionProviderInterface;
use Magento\ConfigurableProduct\Model\ResourceModel\Attribute\OptionProvider;
use Magento\Framework\App\ScopeResolverInterface;
use Magento\Framework\App\ObjectManager;
+use Magento\Framework\DB\Adapter\AdapterInterface;
class Configurable extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
{
@@ -109,27 +110,34 @@ class Configurable extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
}
$productId = $mainProduct->getData($this->optionProvider->getProductEntityLinkField());
+ $select = $this->getConnection()->select()->from(
+ ['t' => $this->getMainTable()],
+ ['product_id']
+ )->where(
+ 't.parent_id = ?',
+ $productId
+ );
- $data = [];
- foreach ($productIds as $id) {
- $data[] = ['product_id' => (int) $id, 'parent_id' => (int) $productId];
- }
+ $existingProductIds = $this->getConnection()->fetchCol($select);
+ $insertProductIds = array_diff($productIds, $existingProductIds);
+ $deleteProductIds = array_diff($existingProductIds, $productIds);
- if (!empty($data)) {
- $this->getConnection()->insertOnDuplicate(
+ if (!empty($insertProductIds)) {
+ $insertData = [];
+ foreach ($insertProductIds as $id) {
+ $insertData[] = ['product_id' => (int) $id, 'parent_id' => (int) $productId];
+ }
+ $this->getConnection()->insertMultiple(
$this->getMainTable(),
- $data,
- ['product_id', 'parent_id']
+ $insertData
);
}
- $where = ['parent_id = ?' => $productId];
- if (!empty($productIds)) {
- $where['product_id NOT IN(?)'] = $productIds;
+ if (!empty($deleteProductIds)) {
+ $where = ['parent_id = ?' => $productId, 'product_id IN (?)' => $deleteProductIds];
+ $this->getConnection()->delete($this->getMainTable(), $where);
}
- $this->getConnection()->delete($this->getMainTable(), $where);
-
// configurable product relations should be added to relation table
$this->catalogProductRelation->processRelations($productId, $productIds);
diff --git a/vendor/magento/module-configurable-product/Model/ResourceModel/Product/Type/Configurable/Attribute.php b/vendor/magento/module-configurable-product/Model/ResourceModel/Product/Type/Configurable/Attribute.php
index 7ea83099f25..9b551c462f4 100644
--- a/vendor/magento/module-configurable-product/Model/ResourceModel/Product/Type/Configurable/Attribute.php
+++ b/vendor/magento/module-configurable-product/Model/ResourceModel/Product/Type/Configurable/Attribute.php
@@ -8,8 +8,9 @@
namespace Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable;
use Magento\ConfigurableProduct\Model\Product\Type\Configurable\Attribute as ConfigurableAttribute;
+use Magento\Framework\DB\Adapter\AdapterInterface;
use Magento\Store\Model\Store;
-use Magento\Store\Model\StoreManagerInterface;
+
class Attribute extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
{
@@ -85,22 +86,30 @@ class Attribute extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
'store_id' => \Magento\Store\Model\Store::DEFAULT_STORE_ID,
];
$valueId = $connection->fetchOne($select, $bind);
+
if ($valueId) {
- $storeId = (int)$attribute->getStoreId() ?: $this->_storeManager->getStore()->getId();
+ $connection->insertOnDuplicate(
+ $this->_labelTable,
+ [
+ 'product_super_attribute_id' => (int)$attribute->getId(),
+ 'store_id' => (int)$attribute->getStoreId() ?: $this->_storeManager->getStore()->getId(),
+ 'use_default' => (int)$attribute->getUseDefault(),
+ 'value' => $attribute->getLabel(),
+ ],
+ ['value', 'use_default']
+ );
} else {
// if attribute label not exists, always store on default store (0)
- $storeId = Store::DEFAULT_STORE_ID;
+ $connection->insert(
+ $this->_labelTable,
+ [
+ 'product_super_attribute_id' => (int)$attribute->getId(),
+ 'store_id' => Store::DEFAULT_STORE_ID,
+ 'use_default' => (int)$attribute->getUseDefault(),
+ 'value' => $attribute->getLabel(),
+ ]
+ );
}
- $connection->insertOnDuplicate(
- $this->_labelTable,
- [
- 'product_super_attribute_id' => (int)$attribute->getId(),
- 'use_default' => (int)$attribute->getUseDefault(),
- 'store_id' => $storeId,
- 'value' => $attribute->getLabel(),
- ],
- ['value', 'use_default']
- );
return $this;
}
diff --git a/vendor/magento/module-eav/Model/ResourceModel/AttributePersistor.php b/vendor/magento/module-eav/Model/ResourceModel/AttributePersistor.php
index f3b2e0ba3e6..9c6adc0354f 100644
--- a/vendor/magento/module-eav/Model/ResourceModel/AttributePersistor.php
+++ b/vendor/magento/module-eav/Model/ResourceModel/AttributePersistor.php
@@ -144,6 +144,31 @@ class AttributePersistor
return;
}
$metadata = $this->metadataPool->getMetadata($entityType);
+ $insertData = $this->prepareInsertDataForMultipleSave($entityType, $context);
+
+ foreach ($insertData as $table => $tableData) {
+ foreach ($tableData as $data) {
+ $metadata->getEntityConnection()->insertArray(
+ $table,
+ $data['columns'],
+ $data['data'],
+ \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_IGNORE
+ );
+ }
+ }
+ }
+
+ /**
+ * Prepare data for insert multiple rows
+ *
+ * @param string $entityType
+ * @param \Magento\Framework\Model\Entity\ScopeInterface[] $context
+ * @return array
+ */
+ private function prepareInsertDataForMultipleSave($entityType, $context)
+ {
+ $metadata = $this->metadataPool->getMetadata($entityType);
+ $insertData = [];
foreach ($this->insert[$entityType] as $link => $data) {
foreach ($data as $attributeCode => $attributeValue) {
/** @var AbstractAttribute $attribute */
@@ -151,19 +176,21 @@ class AttributePersistor
$metadata->getEavEntityType(),
$attributeCode
);
-
+ $attributeTable = $attribute->getBackend()->getTable();
$conditions = $this->buildInsertConditions($attribute, $metadata, $context, $link);
$value = $this->prepareValue($entityType, $attributeValue, $attribute);
foreach ($conditions as $condition) {
$condition['value'] = $value;
- $metadata->getEntityConnection()->insertOnDuplicate(
- $attribute->getBackend()->getTable(),
- $condition
- );
+ $columns = array_keys($condition);
+ $columnsHash = implode('', $columns);
+ $insertData[$attributeTable][$columnsHash]['columns'] = $columns;
+ $insertData[$attributeTable][$columnsHash]['data'][] = array_values($condition);
}
}
}
+
+ return $insertData;
}
/**
diff --git a/vendor/magento/framework/DB/Adapter/Pdo/Mysql.php b/vendor/magento/framework/DB/Adapter/Pdo/Mysql.php
index 2e5d38f2c62..9f662c4202d 100644
--- a/vendor/magento/framework/DB/Adapter/Pdo/Mysql.php
+++ b/vendor/magento/framework/DB/Adapter/Pdo/Mysql.php
@@ -1994,11 +1994,11 @@ class Mysql extends \Zend_Db_Adapter_Pdo_Mysql implements AdapterInterface
}
switch ($strategy) {
- case self::INSERT_ON_DUPLICATE:
+ case self::REPLACE:
$query = $this->_getReplaceSqlQuery($table, $columns, $values);
break;
default:
- $query = $this->_getInsertSqlQuery($table, $columns, $values);
+ $query = $this->_getInsertSqlQuery($table, $columns, $values, $strategy);
}
// execute the statement and return the number of affected rows
@@ -3641,16 +3641,18 @@ class Mysql extends \Zend_Db_Adapter_Pdo_Mysql implements AdapterInterface
* @param string $tableName
* @param array $columns
* @param array $values
+ * @param null|int $strategy
* @return string
*/
- protected function _getInsertSqlQuery($tableName, array $columns, array $values)
+ protected function _getInsertSqlQuery($tableName, array $columns, array $values, $strategy = null)
{
$tableName = $this->quoteIdentifier($tableName, true);
$columns = array_map([$this, 'quoteIdentifier'], $columns);
$columns = implode(',', $columns);
$values = implode(', ', $values);
+ $strategy = $strategy === self::INSERT_IGNORE ? 'IGNORE' : '';
- $insertSql = sprintf('INSERT INTO %s (%s) VALUES %s', $tableName, $columns, $values);
+ $insertSql = sprintf('INSERT %s INTO %s (%s) VALUES %s', $strategy, $tableName, $columns, $values);
return $insertSql;
}
diff --git a/setup/src/Magento/Setup/Model/FixtureGenerator/SqlCollector.php b/setup/src/Magento/Setup/Model/FixtureGenerator/SqlCollector.php
index 6101e475cd5..5e34db4dfdf 100644
--- a/setup/src/Magento/Setup/Model/FixtureGenerator/SqlCollector.php
+++ b/setup/src/Magento/Setup/Model/FixtureGenerator/SqlCollector.php
@@ -44,7 +44,7 @@ class SqlCollector
*/
private function addSql($sql, $bind)
{
- preg_match('~INSERT INTO `(.*)` \((.*)\) VALUES (\(.*\))+~', $sql, $queryMatches);
+ preg_match('~(?:INSERT|REPLACE)\s+(?:IGNORE)?\s*INTO `(.*)` \((.*)\) VALUES (\(.*\))+~', $sql, $queryMatches);
if ($queryMatches) {
$table = $queryMatches[1];
$fields = preg_replace('~[\s+`]+~', '', $queryMatches[2]);
@@ -121,12 +121,25 @@ class SqlCollector
$this->getProfiler()->setEnabled(false);
$queries = $this->getProfiler()->getQueryProfiles() ?: [];
foreach ($queries as $query) {
- if ($query->getQueryType() === Profiler::INSERT) {
+ if ($query->getQueryType() === Profiler::INSERT || $this->isReplaceQuery($query)) {
+ // For generator we do not care about REPLACE query and can use INSERT instead
+ // due to it's not support parallel execution
$this->addSql($query->getQuery(), $query->getQueryParams());
}
}
}
+ /**
+ * Detect "REPLACE INTO ..." query.
+ *
+ * @param Profiler $query
+ * @return bool
+ */
+ private function isReplaceQuery($query)
+ {
+ return $query->getQueryType() === Profiler::QUERY && 0 === stripos(ltrim($query->getQuery()), 'replace');
+ }
+
/**
* @return \Zend_Db_Profiler
*/
diff --git a/vendor/magento/module-catalog-permissions/Model/Indexer/AbstractAction.php b/vendor/magento/module-catalog-permissions/Model/Indexer/AbstractAction.php
index b7047fbadd6..fd867224064 100644
--- a/vendor/magento/module-catalog-permissions/Model/Indexer/AbstractAction.php
+++ b/vendor/magento/module-catalog-permissions/Model/Indexer/AbstractAction.php
@@ -584,7 +584,7 @@ abstract class AbstractAction
'grant_checkout_items',
],
$data,
- AdapterInterface::INSERT_ON_DUPLICATE
+ AdapterInterface::REPLACE
);
}
}
diff --git a/vendor/magento/module-catalog-staging/Controller/Adminhtml/Category/Update/Delete.php b/vendor/magento/module-catalog-staging/Controller/Adminhtml/Category/Update/Delete.php
index 4c09461da3e..c0479eb6427 100644
--- a/vendor/magento/module-catalog-staging/Controller/Adminhtml/Category/Update/Delete.php
+++ b/vendor/magento/module-catalog-staging/Controller/Adminhtml/Category/Update/Delete.php
@@ -8,6 +8,7 @@ namespace Magento\CatalogStaging\Controller\Adminhtml\Category\Update;
use Magento\Backend\App\Action;
use Magento\Staging\Model\Entity\Update\Delete as StagingUpdateDelete;
+use Magento\Staging\Model\VersionManager;
class Delete extends Action
{
@@ -52,6 +53,11 @@ class Delete extends Action
*/
public function execute()
{
+ $mode = $this->getRequest()->getParam('staging')['mode'];
+ if ($mode === 'remove') {
+ $this->addVersionToRequest();
+ }
+
return $this->stagingUpdateDelete->execute(
[
'entityId' => $this->getRequest()->getParam(static::ENTITY_IDENTIFIER),
@@ -60,4 +66,20 @@ class Delete extends Action
]
);
}
+
+ /**
+ * Add update version to request params to prevent deleting all catalog category sequence when
+ * a scheduled update is currently active.
+ *
+ * @return void
+ */
+ private function addVersionToRequest()
+ {
+ $requestParams = $this->getRequest()->getParams();
+
+ $requestParams[VersionManager::PARAM_NAME]
+ = $this->getRequest()->getParam('update_id');
+
+ $this->getRequest()->setParams($requestParams);
+ }
}
diff --git a/vendor/magento/module-catalog-staging/Model/Mview/View/Attribute/Subscription.php b/vendor/magento/module-catalog-staging/Model/Mview/View/Attribute/Subscription.php
index 086d0abc721..ff2562c84f3 100644
--- a/vendor/magento/module-catalog-staging/Model/Mview/View/Attribute/Subscription.php
+++ b/vendor/magento/module-catalog-staging/Model/Mview/View/Attribute/Subscription.php
@@ -22,6 +22,13 @@ class Subscription extends \Magento\Framework\Mview\View\Subscription
*/
protected $entityMetadata;
+ /**
+ * Save state of Subscription for build statement for retrieving entity id value
+ *
+ * @var array
+ */
+ private $statementState = [];
+
/**
* @param ResourceConnection $resource
* @param \Magento\Framework\DB\Ddl\TriggerFactory $triggerFactory
@@ -65,35 +72,72 @@ class Subscription extends \Magento\Framework\Mview\View\Subscription
*/
protected function buildStatement($event, $changelog)
{
- $triggerBody = null;
+ $triggerBody = '';
+
switch ($event) {
case Trigger::EVENT_INSERT:
case Trigger::EVENT_UPDATE:
- $triggerBody = "INSERT IGNORE INTO %1\$s (%2\$s) SELECT %3\$s FROM %4\$s WHERE %5\$s = NEW.%5\$s;";
+ $eventType = 'NEW';
break;
case Trigger::EVENT_DELETE:
- $triggerBody = "INSERT IGNORE INTO %1\$s (%2\$s) SELECT %3\$s FROM %4\$s WHERE %5\$s = OLD.%5\$s;";
+ $eventType = 'OLD';
break;
default:
- break;
+ return $triggerBody;
}
- $params = [
- $this->connection->quoteIdentifier(
- $this->resource->getTableName($changelog->getName())
- ),
- $this->connection->quoteIdentifier(
- $changelog->getColumnName()
- ),
- $this->connection->quoteIdentifier(
- $this->entityMetadata->getIdentifierField()
- ),
- $this->connection->quoteIdentifier(
- $this->resource->getTableName($this->entityMetadata->getEntityTable())
- ),
- $this->connection->quoteIdentifier(
- $this->entityMetadata->getLinkField()
- )
- ];
- return vsprintf($triggerBody, $params);
+ $entityIdHash = $this->entityMetadata->getIdentifierField()
+ . $this->entityMetadata->getEntityTable()
+ . $this->entityMetadata->getLinkField()
+ . $event;
+ if (!isset($this->statementState[$entityIdHash])) {
+ $triggerBody = $this->buildEntityIdStatementByEventType($eventType);
+ $this->statementState[$entityIdHash] = true;
+ }
+
+ $triggerBody .= $this->buildStatementByEventType($changelog);
+
+ return $triggerBody;
+ }
+
+ /**
+ * @param string $eventType
+ * @return string
+ */
+ private function buildEntityIdStatementByEventType($eventType): string
+ {
+ return vsprintf(
+ 'SET @entity_id = (SELECT %1$s FROM %2$s WHERE %3$s = %4$s.%3$s);',
+ [
+ $this->connection->quoteIdentifier(
+ $this->entityMetadata->getIdentifierField()
+ ),
+ $this->connection->quoteIdentifier(
+ $this->resource->getTableName($this->entityMetadata->getEntityTable())
+ ),
+ $this->connection->quoteIdentifier(
+ $this->entityMetadata->getLinkField()
+ ),
+ $eventType
+ ]
+ ) . PHP_EOL;
+ }
+
+ /**
+ * @param \Magento\Framework\Mview\View\ChangelogInterface $changelog
+ * @return string
+ */
+ private function buildStatementByEventType($changelog): string
+ {
+ return vsprintf(
+ 'INSERT IGNORE INTO %1$s (%2$s) values(@entity_id);',
+ [
+ $this->connection->quoteIdentifier(
+ $this->resource->getTableName($changelog->getName())
+ ),
+ $this->connection->quoteIdentifier(
+ $changelog->getColumnName()
+ ),
+ ]
+ );
}
}
diff --git a/vendor/magento/module-staging/Model/Operation/Delete.php b/vendor/magento/module-staging/Model/Operation/Delete.php
index 0fad691bcbe..bcbf5d4492f 100644
--- a/vendor/magento/module-staging/Model/Operation/Delete.php
+++ b/vendor/magento/module-staging/Model/Operation/Delete.php
@@ -141,7 +141,7 @@ class Delete implements DeleteInterface
$this->deleteMain->execute($entity, $arguments);
$this->updateIntersectedUpdates->execute($entity);
- if (!$this->versionManager->isPreviewVersion()) {
+ if (!$this->versionManager->isPreviewVersion() && !$this->isUnscheduleOperation($arguments)) {
$this->sequenceManager->delete($entityType, $entityData[$metadata->getIdentifierField()]);
}
$this->eventManager->dispatchEntityEvent($entityType, 'delete_after', ['entity' => $entity]);
@@ -159,4 +159,18 @@ class Delete implements DeleteInterface
}
return true;
}
+
+ /**
+ * Checks whether we try to unschedule scheduled update or delete whole entity.
+ *
+ * Needs to prevent deleting original entity when the scheduled update
+ * is currently active and should be removed.
+ *
+ * @param array $arguments
+ * @return bool
+ */
+ private function isUnscheduleOperation(array $arguments): bool
+ {
+ return array_key_exists('created_in', $arguments);
+ }
}
diff --git a/vendor/magento/module-staging/Model/VersionManager.php b/vendor/magento/module-staging/Model/VersionManager.php
index aa61d325f4d..fe20e06bfa6 100644
--- a/vendor/magento/module-staging/Model/VersionManager.php
+++ b/vendor/magento/module-staging/Model/VersionManager.php
@@ -129,14 +129,8 @@ class VersionManager
*/
public function getRequestedTimestamp()
{
- if ($this->requestedTimestamp) {
- return $this->requestedTimestamp;
- }
-
- $requestedTimestamp = $this->request->getParam(self::PARAM_NAME);
-
- if ($requestedTimestamp) {
- $this->requestedTimestamp = $requestedTimestamp;
+ if (!$this->requestedTimestamp) {
+ $this->requestedTimestamp = (int)$this->request->getParam(self::PARAM_NAME);
}
return $this->requestedTimestamp;