Skip to content

[Setup]Add table prefix to read table on schema builder #33322

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
5 changes: 5 additions & 0 deletions app/etc/di.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2004,4 +2004,9 @@
</type>
<preference for="Magento\Framework\Filter\Input\PurifierInterface" type="Magento\Framework\Filter\Input\Purifier"/>
<preference for="Magento\Framework\App\PageCache\IdentifierInterface" type="Magento\Framework\App\PageCache\Identifier"/>
<type name="Magento\Framework\Setup\Declaration\Schema\Db\MySQL\DbSchemaReader">
<arguments>
<argument name="dbHelper" xsi:type="object">Magento\Framework\DB\Helper\Proxy</argument>
</arguments>
</type>
</config>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0"?>
<!--
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Magento\Framework\DB\Helper">
<arguments>
<argument name="modulePrefix" xsi:type="string">setup</argument>
</arguments>
</type>
</config>
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

namespace Magento\Setup;

use Magento\Framework\DB\Helper;
use Magento\Framework\Setup\Declaration\Schema\Diff\DiffFactory;
use Magento\Framework\Setup\Declaration\Schema\Diff\SchemaDiff;
use Magento\Framework\Setup\Declaration\Schema\SchemaConfigInterface;
Expand Down Expand Up @@ -52,6 +53,15 @@ protected function setUp(): void
$this->schemaConfig = $objectManager->get(SchemaConfigInterface::class);
$this->schemaDiff = $objectManager->get(SchemaDiff::class);
$this->changeRegistryFactory = $objectManager->get(DiffFactory::class);

/* Reinitialize shared db helper with required module prefix */
$dbHelper = $objectManager->create(
Helper::class, [
'modulePrefix' => 'setup'
]
);
$objectManager->removeSharedInstance(Helper::class);
$objectManager->addSharedInstance($dbHelper, Helper::class);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,18 @@

namespace Magento\Framework\Setup\Declaration\Schema\Db\MySQL;

use Magento\Framework\App\ObjectManager;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\DB\Helper;
use Magento\Framework\DB\Sql\Expression;
use Magento\Framework\Setup\Declaration\Schema\Db\DbSchemaReaderInterface;
use Magento\Framework\Setup\Declaration\Schema\Db\DefinitionAggregator;
use Magento\Framework\Setup\Declaration\Schema\Dto\Constraint;
use Zend_Db;

/**
* @inheritdoc
* This class is responsible for read different schema
* structural elements: indexes, constraints, table names and columns.
*/
class DbSchemaReader implements DbSchemaReaderInterface
{
Expand All @@ -33,18 +37,26 @@ class DbSchemaReader implements DbSchemaReaderInterface
*/
private $definitionAggregator;

/**
* @var Helper
*/
private $dbHelper;

/**
* Constructor.
*
* @param ResourceConnection $resourceConnection
* @param DefinitionAggregator $definitionAggregator
* @param Helper|null $dbHelper
*/
public function __construct(
ResourceConnection $resourceConnection,
DefinitionAggregator $definitionAggregator
DefinitionAggregator $definitionAggregator,
?Helper $dbHelper = null
) {
$this->resourceConnection = $resourceConnection;
$this->definitionAggregator = $definitionAggregator;
$this->dbHelper = $dbHelper ?: ObjectManager::getInstance()->get(Helper::class);
}

/**
Expand Down Expand Up @@ -79,8 +91,8 @@ public function getTableOptions($tableName, $resource)
/**
* Prepare and fetch query: Describe {table_name}.
*
* @param string $tableName
* @param string $resource
* @param string $tableName
* @param string $resource
* @return array
*/
public function readColumns($tableName, $resource)
Expand Down Expand Up @@ -118,8 +130,8 @@ public function readColumns($tableName, $resource)
/**
* Fetch all indexes from table.
*
* @param string $tableName
* @param string $resource
* @param string $tableName
* @param string $resource
* @return array
*/
public function readIndexes($tableName, $resource)
Expand All @@ -131,7 +143,7 @@ public function readIndexes($tableName, $resource)
$stmt = $adapter->query($sql);

// Use FETCH_NUM so we are not dependent on the CASE attribute of the PDO connection
$indexesDefinition = $stmt->fetchAll(\Zend_Db::FETCH_ASSOC);
$indexesDefinition = $stmt->fetchAll(Zend_Db::FETCH_ASSOC);

foreach ($indexesDefinition as $indexDefinition) {
$indexDefinition['type'] = 'index';
Expand Down Expand Up @@ -165,16 +177,16 @@ public function readReferences($tableName, $resource)
/**
* Retrieve Create table SQL, from SHOW CREATE TABLE query.
*
* @param string $tableName
* @param string $resource
* @param string $tableName
* @param string $resource
* @return array
*/
public function getCreateTableSql($tableName, $resource)
{
$adapter = $this->resourceConnection->getConnection($resource);
$sql = sprintf('SHOW CREATE TABLE `%s`', $tableName);
$stmt = $adapter->query($sql);
return $stmt->fetch(\Zend_Db::FETCH_ASSOC);
return $stmt->fetch(Zend_Db::FETCH_ASSOC);
}

/**
Expand All @@ -193,7 +205,7 @@ public function readConstraints($tableName, $resource)
$stmt = $adapter->query($sql);

// Use FETCH_NUM so we are not dependent on the CASE attribute of the PDO connection
$constraintsDefinition = $stmt->fetchAll(\Zend_Db::FETCH_ASSOC);
$constraintsDefinition = $stmt->fetchAll(Zend_Db::FETCH_ASSOC);

foreach ($constraintsDefinition as $constraintDefinition) {
$constraintDefinition['type'] = Constraint::TYPE;
Expand All @@ -212,7 +224,7 @@ public function readConstraints($tableName, $resource)
/**
* Return names of all tables from shard.
*
* @param string $resource Shard name.
* @param string $resource Shard name.
* @return array
*/
public function readTables($resource)
Expand All @@ -226,6 +238,14 @@ public function readTables($resource)
)
->where('TABLE_SCHEMA = ?', $dbName)
->where('TABLE_TYPE = ?', self::MYSQL_TABLE_TYPE);

$tablePrefix = $this->resourceConnection->getTablePrefix();
if ($tablePrefix) {
$stmt->where('TABLE_NAME LIKE ?', $this->dbHelper->addLikeEscape(
$tablePrefix,
['position' => 'start']
));
}
return $adapter->fetchCol($stmt);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Magento\Framework\Setup\Test\Unit\Declaration\Schema\Db\MySQL;

use Magento\Framework\App\ResourceConnection;
use Magento\Framework\DB\Helper;
use Magento\Framework\DB\Select;
use Magento\Framework\Setup\Declaration\Schema\Db\MySQL\DbSchemaReader;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Magento\Framework\DB\Adapter\Pdo\Mysql;
use Magento\Framework\Setup\Declaration\Schema\Db\DefinitionAggregator;

class DbSchemaReaderTest extends TestCase
{
private const TEST_DB_NAME = 'test';

/**
* @var ResourceConnection|MockObject
*/
private $resourceConnectionMock;

/**
* @var DefinitionAggregator|MockObject
*/
private $definitionAggregatorMock;

/**
* @var Select|MockObject
*/
private $selectMock;

/**
* @var Mysql|MockObject
*/
private $connectionMock;

/**
* @var Helper|MockObject
*/
private $dbHelperMock;

/**
* Set up main mocks
*/
protected function setUp(): void
{
$this->connectionMock = $this->createMock(Mysql::class);
$this->resourceConnectionMock = $this->getMockBuilder(ResourceConnection::class)
->disableOriginalConstructor()
->getMock();
$this->resourceConnectionMock->expects($this->once())
->method('getConnection')
->willReturn($this->connectionMock);
$this->resourceConnectionMock->expects($this->once())
->method('getSchemaName')
->willReturn(self::TEST_DB_NAME);

$this->selectMock = $this->createMock(Select::class);
$this->connectionMock->expects($this->once())
->method('select')
->willReturn($this->selectMock);

$this->definitionAggregatorMock = $this->createMock(DefinitionAggregator::class);
$this->dbHelperMock = $this->createMock(Helper::class);
}

/**
* Test for read tables for cases with one or multiple installations into the same database
*
* @param array $nonPrefixedTables
* @param array $prefixedTables
* @param string $prefix
* @param string $escapedPrefix
* @param array $expectedTables
* @dataProvider readTablesDataProvider
*/
public function testReadTables(
array $nonPrefixedTables,
array $prefixedTables,
string $prefix,
string $escapedPrefix,
array $expectedTables
) {
$this->prepareSchemaMocks($nonPrefixedTables, $prefixedTables, $prefix, $escapedPrefix);
$dbSchemaReader = new DbSchemaReader(
$this->resourceConnectionMock,
$this->definitionAggregatorMock,
$this->dbHelperMock
);
$actualTables = $dbSchemaReader->readTables('default');

$this->assertEquals($expectedTables, $actualTables);
}

/**
* Prepare schema mocks
*
* @param array $nonPrefixedTables
* @param array $prefixedTables
* @param string $prefix
* @param string $escapedPrefix
*/
private function prepareSchemaMocks(
array $nonPrefixedTables,
array $prefixedTables,
string $prefix,
string $escapedPrefix
) {
$this->resourceConnectionMock->expects($this->once())
->method('getTablePrefix')
->willReturn($prefix);

$this->selectMock->expects($this->once())
->method('from')
->with(
['information_schema.TABLES'],
['TABLE_NAME']
)->willReturnSelf();

$whereExecuteCount = 2;
$whereRules = [
['TABLE_SCHEMA = ?', self::TEST_DB_NAME],
['TABLE_TYPE = ?', DbSchemaReader::MYSQL_TABLE_TYPE]
];
if ($prefix) {
$this->dbHelperMock->expects($this->once())
->method('addLikeEscape')
->with(
$prefix,
['position' => 'start']
)
->willReturn($escapedPrefix);

$whereExecuteCount++;
$whereRules[] = ['TABLE_NAME LIKE ?', $escapedPrefix];
}
$this->selectMock->expects($this->exactly($whereExecuteCount))
->method('where')
->withConsecutive(...$whereRules)
->willReturnSelf();

$this->connectionMock->expects($this->once())
->method('fetchCol')
->with($this->selectMock)
->willReturn(empty($prefix) ? array_merge($nonPrefixedTables, $prefixedTables) : $prefixedTables);
}

/**
* Read tables tests data provider
*
* @return array[]
*/
public function readTablesDataProvider(): array
{
return [
[
['first_table', 'second_table'],
[],
'',
'',
['first_table', 'second_table']
],
[
['first_table', 'second_table'],
['prefix_first_table', 'prefix_second_table'],
'',
'',
['first_table', 'second_table', 'prefix_first_table', 'prefix_second_table']
],
[
['first_table', 'second_table'],
['prefix_first_table', 'prefix_second_table'],
'prefix_',
'prefix\\_%',
['prefix_first_table', 'prefix_second_table']
]
];
}
}