Skip to content

Commit fb1537d

Browse files
committed
ACP2E-1675: [Cloud] Deployment issue due to exceeded memory and large tables
1 parent 7e251ca commit fb1537d

File tree

2 files changed

+94
-153
lines changed

2 files changed

+94
-153
lines changed

app/code/Magento/Catalog/Setup/Patch/Data/UpdateMultiselectAttributesBackendTypes.php

Lines changed: 61 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -10,54 +10,37 @@
1010
use Magento\Eav\Setup\EavSetup;
1111
use Magento\Eav\Setup\EavSetupFactory;
1212
use Magento\Framework\DB\Adapter\AdapterInterface;
13-
use Magento\Framework\DB\Query\Generator;
14-
use Magento\Framework\DB\Select;
15-
use Magento\Framework\DB\Sql\Expression;
1613
use Magento\Framework\Setup\ModuleDataSetupInterface;
1714
use Magento\Framework\Setup\Patch\DataPatchInterface;
1815
use Magento\Framework\Setup\Patch\NonTransactionableInterface;
1916

2017
class UpdateMultiselectAttributesBackendTypes implements DataPatchInterface, NonTransactionableInterface
2118
{
22-
private const BATCH_SIZE = 10000;
23-
2419
/**
2520
* @var ModuleDataSetupInterface
2621
*/
2722
private $dataSetup;
28-
2923
/**
3024
* @var EavSetupFactory
3125
*/
3226
private $eavSetupFactory;
3327

3428
/**
35-
* @var Generator
36-
*/
37-
private Generator $batchQueryGenerator;
38-
39-
/**
40-
* @var int
29+
* @var array
4130
*/
42-
private int $batchSize;
31+
private $triggersRestoreQueries = [];
4332

4433
/**
4534
* MigrateMultiselectAttributesData constructor.
4635
* @param ModuleDataSetupInterface $dataSetup
4736
* @param EavSetupFactory $eavSetupFactory
48-
* @param Generator $batchQueryGenerator
49-
* @param int $batchSize
5037
*/
5138
public function __construct(
5239
ModuleDataSetupInterface $dataSetup,
53-
EavSetupFactory $eavSetupFactory,
54-
Generator $batchQueryGenerator,
55-
int $batchSize = self::BATCH_SIZE
40+
EavSetupFactory $eavSetupFactory
5641
) {
5742
$this->dataSetup = $dataSetup;
5843
$this->eavSetupFactory = $eavSetupFactory;
59-
$this->batchQueryGenerator = $batchQueryGenerator;
60-
$this->batchSize = $batchSize;
6144
}
6245

6346
/**
@@ -84,6 +67,7 @@ public function apply()
8467
$this->dataSetup->startSetup();
8568
$setup = $this->dataSetup;
8669
$connection = $setup->getConnection();
70+
$this->triggersRestoreQueries = [];
8771

8872
$attributeTable = $setup->getTable('eav_attribute');
8973
/** @var EavSetup $eavSetup */
@@ -97,43 +81,30 @@ public function apply()
9781
->where('backend_type = ?', 'varchar')
9882
->where('frontend_input = ?', 'multiselect')
9983
);
84+
$attributesToMigrate = array_map('intval', $attributesToMigrate);
10085

10186
$varcharTable = $setup->getTable('catalog_product_entity_varchar');
10287
$textTable = $setup->getTable('catalog_product_entity_text');
10388

10489
$columns = $connection->describeTable($varcharTable);
105-
$primaryKey = 'value_id';
106-
unset($columns[$primaryKey]);
107-
$columnNames = array_keys($columns);
108-
$batchIterator = $this->batchQueryGenerator->generate(
109-
$primaryKey,
110-
$connection->select()
111-
->from($varcharTable)
112-
->where('attribute_id in (?)', $attributesToMigrate),
113-
$this->batchSize
114-
);
115-
foreach ($batchIterator as $select) {
116-
$selectForInsert = clone $select;
117-
$selectForInsert->reset(Select::COLUMNS);
118-
$selectForInsert->columns($columnNames);
90+
unset($columns['value_id']);
91+
$this->dropTriggers($textTable);
92+
$this->dropTriggers($varcharTable);
93+
try {
11994
$connection->query(
12095
$connection->insertFromSelect(
121-
$selectForInsert,
96+
$connection->select()
97+
->from($varcharTable, array_keys($columns))
98+
->where('attribute_id in (?)', $attributesToMigrate),
12299
$textTable,
123-
$columnNames,
100+
array_keys($columns),
124101
AdapterInterface::INSERT_ON_DUPLICATE
125102
)
126103
);
127-
$selectForDelete = clone $select;
128-
$selectForDelete->reset(Select::COLUMNS);
129-
$selectForDelete->columns($primaryKey);
130-
$selectForDelete = $connection->select()
131-
->from($selectForDelete, $primaryKey);
132-
133-
$connection->delete(
134-
$varcharTable,
135-
new Expression($primaryKey . ' IN (' . $selectForDelete->assemble() . ')')
136-
);
104+
$connection->delete($varcharTable, ['attribute_id IN (?)' => $attributesToMigrate]);
105+
} finally {
106+
$this->restoreTriggers($textTable);
107+
$this->restoreTriggers($varcharTable);
137108
}
138109

139110
foreach ($attributesToMigrate as $attributeId) {
@@ -144,4 +115,48 @@ public function apply()
144115

145116
return $this;
146117
}
118+
119+
/**
120+
* Drop table triggers
121+
*
122+
* @param string $tableName
123+
* @return void
124+
* @throws \Zend_Db_Statement_Exception
125+
*/
126+
private function dropTriggers(string $tableName): void
127+
{
128+
$triggers = $this->dataSetup->getConnection()
129+
->query('SHOW TRIGGERS LIKE \''. $tableName . '\'')
130+
->fetchAll();
131+
132+
if (!$triggers) {
133+
return;
134+
}
135+
136+
foreach ($triggers as $trigger) {
137+
$triggerData = $this->dataSetup->getConnection()
138+
->query('SHOW CREATE TRIGGER '. $trigger['Trigger'])
139+
->fetch();
140+
$this->triggersRestoreQueries[$tableName][] =
141+
preg_replace('/DEFINER=[^\s]*/', '', $triggerData['SQL Original Statement']);
142+
// phpcs:ignore Magento2.SQL.RawQuery.FoundRawSql
143+
$this->dataSetup->getConnection()->query('DROP TRIGGER IF EXISTS ' . $trigger['Trigger']);
144+
}
145+
}
146+
147+
/**
148+
* Restore table triggers.
149+
*
150+
* @param string $tableName
151+
* @return void
152+
* @throws \Zend_Db_Statement_Exception
153+
*/
154+
private function restoreTriggers(string $tableName): void
155+
{
156+
if (array_key_exists($tableName, $this->triggersRestoreQueries)) {
157+
foreach ($this->triggersRestoreQueries[$tableName] as $query) {
158+
$this->dataSetup->getConnection()->multiQuery($query);
159+
}
160+
}
161+
}
147162
}

app/code/Magento/Catalog/Test/Unit/Setup/Patch/Data/UpdateMultiselectAttributesBackendTypesTest.php

Lines changed: 33 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -11,30 +11,22 @@
1111
use Magento\Eav\Setup\EavSetup;
1212
use Magento\Eav\Setup\EavSetupFactory;
1313
use Magento\Framework\DB\Adapter\AdapterInterface;
14-
use Magento\Framework\DB\Query\BatchIteratorInterface;
15-
use Magento\Framework\DB\Query\Generator;
1614
use Magento\Framework\DB\Select;
1715
use Magento\Framework\Setup\ModuleDataSetupInterface;
18-
use PHPUnit\Framework\MockObject\MockObject;
1916
use PHPUnit\Framework\TestCase;
2017

2118
class UpdateMultiselectAttributesBackendTypesTest extends TestCase
2219
{
2320
/**
24-
* @var ModuleDataSetupInterface|MockObject
21+
* @var ModuleDataSetupInterface|\PHPUnit\Framework\MockObject\MockObject
2522
*/
2623
private $dataSetup;
2724

2825
/**
29-
* @var EavSetupFactory|MockObject
26+
* @var EavSetupFactory|\PHPUnit\Framework\MockObject\MockObject
3027
*/
3128
private $eavSetupFactory;
3229

33-
/**
34-
* @var Generator|MockObject
35-
*/
36-
private $batchQueryGenerator;
37-
3830
/**
3931
* @var UpdateMultiselectAttributesBackendTypes
4032
*/
@@ -48,64 +40,37 @@ protected function setUp(): void
4840
parent::setUp();
4941
$this->dataSetup = $this->createMock(ModuleDataSetupInterface::class);
5042
$this->eavSetupFactory = $this->createMock(EavSetupFactory::class);
51-
$this->batchQueryGenerator = $this->createMock(Generator::class);
52-
$this->model = new UpdateMultiselectAttributesBackendTypes(
53-
$this->dataSetup,
54-
$this->eavSetupFactory,
55-
$this->batchQueryGenerator
56-
);
43+
$this->model = new UpdateMultiselectAttributesBackendTypes($this->dataSetup, $this->eavSetupFactory);
5744
}
5845

59-
/**
60-
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
61-
* @return void
62-
*/
6346
public function testApply(): void
6447
{
6548
$attributeIds = [3, 7];
6649
$entityTypeId = 4;
6750
$eavSetup = $this->createMock(EavSetup::class);
6851
$connection = $this->createMock(AdapterInterface::class);
69-
$selectAttributes = $this->createMock(Select::class);
70-
$selectAttributesValues = $this->createMock(Select::class);
71-
$selectForInsert1 = $this->createMock(Select::class);
72-
$selectForInsert2 = $this->createMock(Select::class);
73-
$selectForDelete1 = $this->createMock(Select::class);
74-
$selectForDelete2 = $this->createMock(Select::class);
75-
$batchIterator = $this->getMockForAbstractClass(BatchIteratorInterface::class);
76-
$batchIterator->method('current')
77-
->willReturnOnConsecutiveCalls($selectForInsert1, $selectForInsert2);
78-
$batchIterator->method('valid')
79-
->willReturnOnConsecutiveCalls(true, true, false);
80-
$this->eavSetupFactory->expects($this->once())
81-
->method('create')
52+
$select1 = $this->createMock(Select::class);
53+
$select2 = $this->createMock(Select::class);
54+
$select3 = $this->createMock(Select::class);
55+
$statement = $this->createMock(\Zend_Db_Statement_Interface::class);
56+
57+
$this->eavSetupFactory->method('create')
8258
->willReturn($eavSetup);
83-
$this->dataSetup->expects($this->once())
84-
->method('getConnection')
59+
$this->dataSetup->method('getConnection')
8560
->willReturn($connection);
8661
$this->dataSetup->method('getTable')
8762
->willReturnArgument(0);
88-
$this->batchQueryGenerator->expects($this->once())
89-
->method('generate')
90-
->willReturn($batchIterator);
9163
$eavSetup->method('getEntityTypeId')
9264
->willReturn(4);
93-
$eavSetup->expects($this->exactly(2))
94-
->method('updateAttribute')
65+
$eavSetup->method('updateAttribute')
9566
->withConsecutive(
9667
[$entityTypeId, 3, 'backend_type', 'text'],
9768
[$entityTypeId, 7, 'backend_type', 'text']
9869
);
99-
$connection->expects($this->exactly(4))
70+
$connection->expects($this->exactly(2))
10071
->method('select')
101-
->willReturnOnConsecutiveCalls(
102-
$selectAttributes,
103-
$selectAttributesValues,
104-
$selectForDelete1,
105-
$selectForDelete2
106-
);
107-
$connection->expects($this->once())
108-
->method('describeTable')
72+
->willReturnOnConsecutiveCalls($select1, $select2, $select3);
73+
$connection->method('describeTable')
10974
->willReturn(
11075
[
11176
'value_id' => [],
@@ -115,78 +80,39 @@ public function testApply(): void
11580
'row_id' => [],
11681
]
11782
);
118-
$connection->expects($this->once())
119-
->method('fetchCol')
120-
->with($selectAttributes)
83+
$connection->method('query')
84+
->willReturn($statement);
85+
$connection->method('fetchAll')
86+
->willReturn([]);
87+
$connection->method('fetchCol')
88+
->with($select1)
12189
->willReturn($attributeIds);
122-
$connection->expects($this->exactly(2))
123-
->method('insertFromSelect')
124-
->withConsecutive(
125-
[$selectForInsert1, 'catalog_product_entity_text', ['attribute_id', 'store_id', 'value', 'row_id']],
126-
[$selectForInsert2, 'catalog_product_entity_text', ['attribute_id', 'store_id', 'value', 'row_id']],
127-
)
90+
$connection->method('insertFromSelect')
91+
->with($select3, 'catalog_product_entity_text', ['attribute_id', 'store_id', 'value', 'row_id'])
12892
->willReturn('');
129-
$connection->expects($this->exactly(2))
130-
->method('delete')
131-
->withConsecutive(
132-
['catalog_product_entity_varchar', 'value_id IN (select1)'],
133-
['catalog_product_entity_varchar', 'value_id IN (select2)'],
134-
)
93+
$connection->method('deleteFromSelect')
94+
->with($select2, 'catalog_product_entity_varchar')
13595
->willReturn('');
136-
$selectAttributes->expects($this->once())
137-
->method('from')
96+
$select1->method('from')
13897
->with('eav_attribute', ['attribute_id'])
13998
->willReturnSelf();
140-
$selectAttributes->expects($this->exactly(3))
141-
->method('where')
99+
$select1->method('where')
142100
->withConsecutive(
143101
['entity_type_id = ?', $entityTypeId],
144102
['backend_type = ?', 'varchar'],
145103
['frontend_input = ?', 'multiselect']
146104
)
147105
->willReturnSelf();
148-
$selectForInsert1->expects($this->exactly(2))
149-
->method('reset')
150-
->with(Select::COLUMNS)
151-
->willReturnSelf();
152-
$selectForInsert1->expects($this->exactly(2))
153-
->method('columns')
154-
->withConsecutive(
155-
[['attribute_id', 'store_id', 'value', 'row_id']],
156-
['value_id']
157-
)
158-
->willReturnSelf();
159-
$selectForInsert2->expects($this->exactly(2))
160-
->method('reset')
161-
->with(Select::COLUMNS)
106+
$select2->method('from')
107+
->with('catalog_product_entity_varchar')
162108
->willReturnSelf();
163-
$selectForInsert2->expects($this->exactly(2))
164-
->method('columns')
165-
->withConsecutive(
166-
[['attribute_id', 'store_id', 'value', 'row_id']],
167-
['value_id']
168-
)
169-
->willReturnSelf();
170-
$selectForDelete1->expects($this->once())
171-
->method('from')
172-
->with($selectForInsert1, 'value_id')
173-
->willReturnSelf();
174-
$selectForDelete1->expects($this->once())
175-
->method('assemble')
176-
->willReturn('select1');
177-
$selectForDelete2->expects($this->once())
178-
->method('from')
179-
->with($selectForInsert2, 'value_id')
109+
$select2->method('where')
110+
->with('attribute_id in (?)', $attributeIds)
180111
->willReturnSelf();
181-
$selectForDelete2->expects($this->once())
182-
->method('assemble')
183-
->willReturn('select2');
184-
$selectAttributesValues->expects($this->once())
185-
->method('from')
186-
->with('catalog_product_entity_varchar', '*')
112+
$select3->method('from')
113+
->with('catalog_product_entity_varchar', ['attribute_id', 'store_id', 'value', 'row_id'])
187114
->willReturnSelf();
188-
$selectAttributesValues->expects($this->once())
189-
->method('where')
115+
$select3->method('where')
190116
->with('attribute_id in (?)', $attributeIds)
191117
->willReturnSelf();
192118
$this->model->apply();

0 commit comments

Comments
 (0)