Skip to content

Commit 7dbcb26

Browse files
committed
Merge branch 'ACP2E-3230' of https://github.com/adobe-commerce-tier-4/magento2ce into PR-10-01-2024
2 parents 5ebdfb5 + 03cffa0 commit 7dbcb26

File tree

2 files changed

+296
-16
lines changed

2 files changed

+296
-16
lines changed

lib/internal/Magento/Framework/Setup/Declaration/Schema/Db/MySQL/DbSchemaWriter.php

Lines changed: 143 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
namespace Magento\Framework\Setup\Declaration\Schema\Db\MySQL;
88

99
use Magento\Framework\App\ResourceConnection;
10+
use Magento\Framework\DB\Adapter\ConnectionException;
11+
use Magento\Framework\DB\Adapter\SqlVersionProvider;
12+
use Magento\Framework\DB\Adapter\AdapterInterface;
1013
use Magento\Framework\Setup\Declaration\Schema\Db\DbSchemaWriterInterface;
1114
use Magento\Framework\Setup\Declaration\Schema\Db\Statement;
1215
use Magento\Framework\Setup\Declaration\Schema\Db\StatementAggregator;
@@ -59,22 +62,30 @@ class DbSchemaWriter implements DbSchemaWriterInterface
5962
*/
6063
private $dryRunLogger;
6164

65+
/**
66+
* @var SqlVersionProvider
67+
*/
68+
private $sqlVersionProvider;
69+
6270
/**
6371
* @param ResourceConnection $resourceConnection
64-
* @param StatementFactory $statementFactory
65-
* @param DryRunLogger $dryRunLogger
66-
* @param array $tableOptions
72+
* @param StatementFactory $statementFactory
73+
* @param DryRunLogger $dryRunLogger
74+
* @param SqlVersionProvider $sqlVersionProvider
75+
* @param array $tableOptions
6776
*/
6877
public function __construct(
6978
ResourceConnection $resourceConnection,
7079
StatementFactory $statementFactory,
7180
DryRunLogger $dryRunLogger,
81+
SqlVersionProvider $sqlVersionProvider,
7282
array $tableOptions = []
7383
) {
7484
$this->resourceConnection = $resourceConnection;
7585
$this->statementFactory = $statementFactory;
7686
$this->dryRunLogger = $dryRunLogger;
7787
$this->tableOptions = array_replace($this->tableOptions, $tableOptions);
88+
$this->sqlVersionProvider = $sqlVersionProvider;
7889
}
7990

8091
/**
@@ -271,15 +282,14 @@ public function compile(StatementAggregator $statementAggregator, $dryRun)
271282
$statementsSql = [];
272283
$statement = null;
273284

274-
/**
275-
* @var Statement $statement
276-
*/
277-
foreach ($statementBank as $statement) {
278-
$statementsSql[] = $statement->getStatement();
279-
}
280-
$adapter = $this->resourceConnection->getConnection($statement->getResource());
281-
282285
if ($dryRun) {
286+
/**
287+
* @var Statement $statement
288+
*/
289+
foreach ($statementBank as $statement) {
290+
$statementsSql[] = $statement->getStatement();
291+
}
292+
$adapter = $this->resourceConnection->getConnection($statement->getResource());
283293
$this->dryRunLogger->log(
284294
sprintf(
285295
$this->statementDirectives[$statement->getType()],
@@ -288,18 +298,81 @@ public function compile(StatementAggregator $statementAggregator, $dryRun)
288298
)
289299
);
290300
} else {
301+
$this->doQuery($statementBank);
302+
$statement = end($statementBank);
303+
//Do post update, like SQL DML operations or etc...
304+
foreach ($statement->getTriggers() as $trigger) {
305+
call_user_func($trigger);
306+
}
307+
}
308+
}
309+
}
310+
311+
/**
312+
* Check if we can concatenate sql into one statement
313+
*
314+
* Due to issues with some versions of MariaBD such statements
315+
* may produce errors, e.g. with foreign key definition with column modification
316+
*
317+
* @return bool
318+
* @throws ConnectionException
319+
*/
320+
private function isNeedToSplitSql() : bool
321+
{
322+
return str_contains($this->sqlVersionProvider->getSqlVersion(), SqlVersionProvider::MARIA_DB_10_4_VERSION) ||
323+
str_contains($this->sqlVersionProvider->getSqlVersion(), SqlVersionProvider::MARIA_DB_10_6_VERSION);
324+
}
325+
326+
/**
327+
* Perform queries based on statements
328+
*
329+
* @param Statement[] $statementBank
330+
* @return void
331+
* @throws ConnectionException
332+
*/
333+
private function doQuery(
334+
array $statementBank
335+
) : void {
336+
if (empty($statementBank)) {
337+
return;
338+
}
339+
340+
$statement = null;
341+
$statementsSql = [];
342+
foreach ($statementBank as $statement) {
343+
$statementsSql[] = $statement->getStatement();
344+
}
345+
$adapter = $this->resourceConnection->getConnection($statement->getResource());
346+
347+
if ($this->isNeedToSplitSql()) {
348+
$preparedStatements = $this->getPreparedStatements($statementBank);
349+
350+
if (!empty($preparedStatements['canBeCombinedStatements'])) {
291351
$adapter->query(
292352
sprintf(
293353
$this->statementDirectives[$statement->getType()],
294354
$adapter->quoteIdentifier($statement->getTableName()),
295-
implode(", ", $statementsSql)
355+
implode(", ", $preparedStatements['canBeCombinedStatements'])
356+
)
357+
);
358+
}
359+
foreach ($preparedStatements['separatedStatements'] as $separatedStatement) {
360+
$adapter->query(
361+
sprintf(
362+
$this->statementDirectives[$statement->getType()],
363+
$adapter->quoteIdentifier($statement->getTableName()),
364+
$separatedStatement
296365
)
297366
);
298-
//Do post update, like SQL DML operations or etc...
299-
foreach ($statement->getTriggers() as $trigger) {
300-
call_user_func($trigger);
301-
}
302367
}
368+
} else {
369+
$adapter->query(
370+
sprintf(
371+
$this->statementDirectives[$statement->getType()],
372+
$adapter->quoteIdentifier($statement->getTableName()),
373+
implode(", ", $statementsSql)
374+
)
375+
);
303376
}
304377
}
305378

@@ -309,6 +382,7 @@ public function compile(StatementAggregator $statementAggregator, $dryRun)
309382
* @param string $tableName
310383
* @param string $resource
311384
* @return int
385+
* @throws \Zend_Db_Statement_Exception
312386
*/
313387
private function getNextAutoIncrementValue(string $tableName, string $resource): int
314388
{
@@ -323,6 +397,59 @@ private function getNextAutoIncrementValue(string $tableName, string $resource):
323397
} else {
324398
return 1;
325399
}
400+
}
326401

402+
/**
403+
* Prepare list of modified columns from statement
404+
*
405+
* @param array $statementBank
406+
* @return array
407+
*/
408+
private function getModifiedColumns(array $statementBank) : array
409+
{
410+
$columns = [];
411+
foreach ($statementBank as $statement) {
412+
if ($statement->getType() === 'alter'
413+
&& str_contains($statement->getStatement(), 'MODIFY COLUMN')) {
414+
$columns[] = $statement->getName();
415+
}
416+
}
417+
return $columns;
418+
}
419+
420+
/**
421+
* Separate statements that can't be executed as one statement
422+
*
423+
* @param array $statementBank
424+
* @return array
425+
*/
426+
private function getPreparedStatements(array $statementBank) : array
427+
{
428+
$statementsSql = [];
429+
foreach ($statementBank as $statement) {
430+
$statementsSql[] = $statement->getStatement();
431+
}
432+
$result = ['separatedStatements' => [], 'canBeCombinedStatements' => []];
433+
$modifiedColumns = $this->getModifiedColumns($statementBank);
434+
435+
foreach ($statementsSql as $statementSql) {
436+
if (str_contains($statementSql, 'FOREIGN KEY')) {
437+
$isThisColumnModified = false;
438+
foreach ($modifiedColumns as $modifiedColumn) {
439+
if (str_contains($statementSql, '`' . $modifiedColumn . '`')) {
440+
$isThisColumnModified = true;
441+
break;
442+
}
443+
}
444+
if ($isThisColumnModified) {
445+
$result['separatedStatements'][] = $statementSql;
446+
} else {
447+
$result['canBeCombinedStatements'][] = $statementSql;
448+
}
449+
} else {
450+
$result['canBeCombinedStatements'][] = $statementSql;
451+
}
452+
}
453+
return $result;
327454
}
328455
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
<?php
2+
/**
3+
* Copyright 2024 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Framework\Setup\Test\Unit\Declaration\Schema\Db;
9+
10+
use Magento\Framework\App\ResourceConnection;
11+
use Magento\Framework\DB\Adapter\SqlVersionProvider;
12+
use Magento\Framework\DB\Adapter\AdapterInterface;
13+
use Magento\Framework\Setup\Declaration\Schema\Db\Statement;
14+
use Magento\Framework\Setup\Declaration\Schema\Db\StatementAggregator;
15+
use Magento\Framework\Setup\Declaration\Schema\Db\StatementFactory;
16+
use Magento\Framework\Setup\Declaration\Schema\DryRunLogger;
17+
use Magento\Framework\Setup\Declaration\Schema\Db\MySQL\DbSchemaWriter;
18+
use PHPUnit\Framework\MockObject\MockObject;
19+
use PHPUnit\Framework\TestCase;
20+
21+
class DbSchemaWriterTest extends TestCase
22+
{
23+
/**
24+
* @var ResourceConnection|MockObject
25+
*/
26+
private $resourceConnection;
27+
28+
/**
29+
* @var StatementFactory|MockObject
30+
*/
31+
private $statementFactory;
32+
33+
/**
34+
* @var DryRunLogger|MockObject
35+
*/
36+
private $dryRunLogger;
37+
38+
/**
39+
* @var SqlVersionProvider|MockObject
40+
*/
41+
private $sqlVersionProvider;
42+
43+
/**
44+
* @var AdapterInterface|MockObject
45+
*/
46+
private $adapter;
47+
48+
/**
49+
* @var DbSchemaWriter
50+
*/
51+
private $model;
52+
53+
protected function setUp(): void
54+
{
55+
$this->resourceConnection = $this->getMockBuilder(ResourceConnection::class)
56+
->disableOriginalConstructor()
57+
->getMock();
58+
$this->statementFactory = $this->getMockBuilder(StatementFactory::class)
59+
->disableOriginalConstructor()
60+
->getMock();
61+
$this->dryRunLogger = $this->getMockBuilder(DryRunLogger::class)
62+
->disableOriginalConstructor()
63+
->getMock();
64+
$this->sqlVersionProvider = $this->getMockBuilder(SqlVersionProvider::class)
65+
->disableOriginalConstructor()
66+
->getMock();
67+
68+
$this->adapter = $this->getMockBuilder(AdapterInterface::class)
69+
->getMockForAbstractClass();
70+
$this->resourceConnection->expects($this->any())
71+
->method('getConnection')
72+
->willReturn($this->adapter);
73+
74+
$this->model = new DbSchemaWriter(
75+
$this->resourceConnection,
76+
$this->statementFactory,
77+
$this->dryRunLogger,
78+
$this->sqlVersionProvider
79+
);
80+
}
81+
82+
/**
83+
* Test to check that column modification and adding fk are run as separate queries with MariaDb
84+
*
85+
* @param string $dbVersion
86+
* @param int $numberOfQueries
87+
* @return void
88+
*
89+
* @dataProvider compileDataProvider
90+
*/
91+
public function testCompileWithColumnModificationAndFK(string $dbVersion, int $numberOfQueries) : void
92+
{
93+
$dryRun = false;
94+
$statementAggregator = $this->getMockBuilder(StatementAggregator::class)
95+
->disableOriginalConstructor()
96+
->getMock();
97+
$statement1 = $this->getMockBuilder(Statement::class)
98+
->disableOriginalConstructor()
99+
->getMock();
100+
$statement1->expects($this->any())
101+
->method('getResource')
102+
->willReturn('resource');
103+
$statement1->expects($this->any())
104+
->method('getType')
105+
->willReturn('alter');
106+
$statement1->expects($this->any())
107+
->method('getName')
108+
->willReturn('column');
109+
$statement1->expects($this->any())
110+
->method('getStatement')
111+
->willReturn('MODIFY COLUMN `column1` varchar(64) NOT NULL');
112+
113+
$statement2 = $this->getMockBuilder(Statement::class)
114+
->disableOriginalConstructor()
115+
->getMock();
116+
$statement2->expects($this->any())
117+
->method('getResource')
118+
->willReturn('resource');
119+
$statement2->expects($this->any())
120+
->method('getType')
121+
->willReturn('alter');
122+
$statement2->expects($this->any())
123+
->method('getName')
124+
->willReturn('FK_COLUMN');
125+
$statement2->expects($this->any())
126+
->method('getStatement')
127+
->willReturn('ADD CONSTRAINT `FK_COLUMN` FOREIGN KEY (`column`)');
128+
129+
$statementBank = [$statement1, $statement2];
130+
$statementAggregator->expects($this->any())
131+
->method('getStatementsBank')
132+
->willReturn([$statementBank]);
133+
$this->sqlVersionProvider->expects($this->any())
134+
->method('getSqlVersion')
135+
->willReturn($dbVersion);
136+
$this->adapter->expects($this->exactly($numberOfQueries))
137+
->method('query');
138+
139+
$this->model->compile($statementAggregator, $dryRun);
140+
}
141+
142+
/**
143+
* @return array
144+
*/
145+
public static function compileDataProvider() : array
146+
{
147+
return [
148+
[SqlVersionProvider::MARIA_DB_10_4_VERSION, 2],
149+
[SqlVersionProvider::MARIA_DB_10_6_VERSION, 2],
150+
[SqlVersionProvider::MYSQL_8_0_VERSION, 1],
151+
];
152+
}
153+
}

0 commit comments

Comments
 (0)