Create TableMover class

Signed-off-by: Kamil Tekiela <tekiela246@gmail.com>
This commit is contained in:
Kamil Tekiela 2024-03-22 14:02:48 +01:00
parent accafad56b
commit 81853e877b
8 changed files with 643 additions and 596 deletions

View File

@ -1906,7 +1906,7 @@ parameters:
path: src/Controllers/Database/Structure/CentralColumns/RemoveController.php
-
message: "#^Parameter \\#5 \\$what of static method PhpMyAdmin\\\\Table\\\\Table\\:\\:moveCopy\\(\\) expects string, mixed given\\.$#"
message: "#^Parameter \\#5 \\$what of static method PhpMyAdmin\\\\Table\\\\TableMover\\:\\:moveCopy\\(\\) expects string, mixed given\\.$#"
count: 1
path: src/Controllers/Database/Structure/CopyTableController.php
@ -9681,7 +9681,7 @@ parameters:
path: src/Operations.php
-
message: "#^Parameter \\#5 \\$what of static method PhpMyAdmin\\\\Table\\\\Table\\:\\:moveCopy\\(\\) expects string, mixed given\\.$#"
message: "#^Parameter \\#5 \\$what of static method PhpMyAdmin\\\\Table\\\\TableMover\\:\\:moveCopy\\(\\) expects string, mixed given\\.$#"
count: 2
path: src/Operations.php
@ -13930,11 +13930,6 @@ parameters:
count: 1
path: src/Table/Search.php
-
message: "#^Argument of an invalid type array\\<PhpMyAdmin\\\\SqlParser\\\\Components\\\\AlterOperation\\>\\|null supplied for foreach, only iterables are supported\\.$#"
count: 2
path: src/Table/Table.php
-
message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#"
count: 1
@ -13942,7 +13937,7 @@ parameters:
-
message: "#^Call to function in_array\\(\\) requires parameter \\#3 to be set\\.$#"
count: 3
count: 2
path: src/Table/Table.php
-
@ -14040,19 +14035,9 @@ parameters:
count: 6
path: src/Table/Table.php
-
message: "#^Cannot call method has\\(\\) on PhpMyAdmin\\\\SqlParser\\\\Components\\\\OptionsArray\\|null\\.$#"
count: 2
path: src/Table/Table.php
-
message: "#^Cannot cast mixed to string\\.$#"
count: 2
path: src/Table/Table.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 17
count: 13
path: src/Table/Table.php
-
@ -14110,11 +14095,6 @@ parameters:
count: 2
path: src/Table/Table.php
-
message: "#^Only booleans are allowed in a negated boolean, bool\\|string given\\.$#"
count: 1
path: src/Table/Table.php
-
message: "#^Only booleans are allowed in a negated boolean, int\\|false given\\.$#"
count: 1
@ -14125,11 +14105,6 @@ parameters:
count: 1
path: src/Table/Table.php
-
message: "#^Only booleans are allowed in a negated boolean, mixed given\\.$#"
count: 2
path: src/Table/Table.php
-
message: "#^Only booleans are allowed in an elseif condition, int\\|false given\\.$#"
count: 1
@ -14160,21 +14135,11 @@ parameters:
count: 6
path: src/Table/Table.php
-
message: "#^Parameter \\#1 \\$list of class PhpMyAdmin\\\\SqlParser\\\\Parser constructor expects PhpMyAdmin\\\\SqlParser\\\\TokensList\\|PhpMyAdmin\\\\SqlParser\\\\UtfString\\|string\\|null, mixed given\\.$#"
count: 3
path: src/Table/Table.php
-
message: "#^Parameter \\#1 \\$str of method PhpMyAdmin\\\\DatabaseInterface\\:\\:quoteString\\(\\) expects string, mixed given\\.$#"
count: 9
path: src/Table/Table.php
-
message: "#^Parameter \\#1 \\$str of method PhpMyAdmin\\\\DatabaseInterface\\:\\:quoteString\\(\\) expects string, string\\|null given\\.$#"
count: 6
path: src/Table/Table.php
-
message: "#^Parameter \\#1 \\$string of function substr expects string, mixed given\\.$#"
count: 2
@ -14240,6 +14205,51 @@ parameters:
count: 1
path: src/Table/Table.php
-
message: "#^Argument of an invalid type array\\<PhpMyAdmin\\\\SqlParser\\\\Components\\\\AlterOperation\\>\\|null supplied for foreach, only iterables are supported\\.$#"
count: 2
path: src/Table/TableMover.php
-
message: "#^Call to function in_array\\(\\) requires parameter \\#3 to be set\\.$#"
count: 1
path: src/Table/TableMover.php
-
message: "#^Cannot call method has\\(\\) on PhpMyAdmin\\\\SqlParser\\\\Components\\\\OptionsArray\\|null\\.$#"
count: 2
path: src/Table/TableMover.php
-
message: "#^Cannot cast mixed to string\\.$#"
count: 2
path: src/Table/TableMover.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 4
path: src/Table/TableMover.php
-
message: "#^Only booleans are allowed in a negated boolean, bool\\|string given\\.$#"
count: 1
path: src/Table/TableMover.php
-
message: "#^Only booleans are allowed in a negated boolean, mixed given\\.$#"
count: 2
path: src/Table/TableMover.php
-
message: "#^Parameter \\#1 \\$list of class PhpMyAdmin\\\\SqlParser\\\\Parser constructor expects PhpMyAdmin\\\\SqlParser\\\\TokensList\\|PhpMyAdmin\\\\SqlParser\\\\UtfString\\|string\\|null, mixed given\\.$#"
count: 3
path: src/Table/TableMover.php
-
message: "#^Parameter \\#1 \\$str of method PhpMyAdmin\\\\DatabaseInterface\\:\\:quoteString\\(\\) expects string, string\\|null given\\.$#"
count: 6
path: src/Table/TableMover.php
-
message: "#^Call to function in_array\\(\\) requires parameter \\#3 to be set\\.$#"
count: 1

View File

@ -11228,15 +11228,7 @@
<code><![CDATA[Config::getInstance()]]></code>
<code><![CDATA[Config::getInstance()]]></code>
<code><![CDATA[DatabaseInterface::getInstance()]]></code>
<code><![CDATA[DatabaseInterface::getInstance()]]></code>
<code><![CDATA[DatabaseInterface::getInstance()]]></code>
</DeprecatedMethod>
<InvalidArrayOffset>
<code><![CDATA[$GLOBALS['add_constraints']]]></code>
<code><![CDATA[$GLOBALS['errorUrl']]]></code>
<code><![CDATA[$GLOBALS['sql_auto_increments']]]></code>
<code><![CDATA[$GLOBALS['sql_indexes']]]></code>
</InvalidArrayOffset>
<InvalidReturnStatement>
<code><![CDATA[$tableAutoIncrement ?? '']]></code>
<code><![CDATA[$this->getStatusInfo('TABLE_COLLATION') ?? '']]></code>
@ -11248,8 +11240,6 @@
<code><![CDATA[string]]></code>
</InvalidReturnType>
<MixedArgument>
<code><![CDATA[$GLOBALS['sql_auto_increments']]]></code>
<code><![CDATA[$GLOBALS['sql_indexes']]]></code>
<code><![CDATA[$_POST['constraint_name'][$masterFieldMd5]]]></code>
<code><![CDATA[$column]]></code>
<code><![CDATA[$column['Extra']]]></code>
@ -11345,9 +11335,6 @@
<code><![CDATA[$optionsArray[$existrelForeign[$masterFieldMd5]['on_update'] ?? '']]]></code>
</MixedArrayTypeCoercion>
<MixedAssignment>
<code><![CDATA[$GLOBALS['errorUrl']]]></code>
<code><![CDATA[$altered]]></code>
<code><![CDATA[$altered]]></code>
<code><![CDATA[$column]]></code>
<code><![CDATA[$column]]></code>
<code><![CDATA[$columns[$row['Field']]]]></code>
@ -11371,30 +11358,16 @@
<code><![CDATA[$ret[]]]></code>
<code><![CDATA[$row]]></code>
<code><![CDATA[$value]]></code>
<code><![CDATA[$value]]></code>
<code><![CDATA[$value]]></code>
</MixedAssignment>
<MixedInferredReturnType>
<code><![CDATA[mixed[]]]></code>
<code><![CDATA[string]]></code>
<code><![CDATA[string]]></code>
</MixedInferredReturnType>
<MixedMethodCall>
<code><![CDATA[has]]></code>
<code><![CDATA[has]]></code>
</MixedMethodCall>
<MixedOperand>
<code><![CDATA[$backquoted ? Util::backquote($column) : $column]]></code>
<code><![CDATA[$index[0]]]></code>
</MixedOperand>
<MixedPropertyAssignment>
<code><![CDATA[$altered]]></code>
<code><![CDATA[$altered]]></code>
</MixedPropertyAssignment>
<MixedPropertyFetch>
<code><![CDATA[$altered->options]]></code>
<code><![CDATA[$altered->options]]></code>
</MixedPropertyFetch>
<MixedReturnStatement>
<code><![CDATA[end($this->errors)]]></code>
<code><![CDATA[end($this->messages)]]></code>
@ -11418,15 +11391,9 @@
<code><![CDATA[$_POST['on_update'][$masterFieldMd5]]]></code>
</PossiblyInvalidArrayOffset>
<PossiblyNullArgument>
<code><![CDATA[$commentsCopyRow['column_name']]]></code>
<code><![CDATA[$commentsCopyRow['comment']]]></code>
<code><![CDATA[$commentsCopyRow['mimetype']]]></code>
<code><![CDATA[$commentsCopyRow['transformation']]]></code>
<code><![CDATA[$commentsCopyRow['transformation_options']]]></code>
<code><![CDATA[$existrelForeign[$masterFieldMd5]['ref_db_name']]]></code>
<code><![CDATA[$existrelForeign[$masterFieldMd5]['ref_index_list']]]></code>
<code><![CDATA[$existrelForeign[$masterFieldMd5]['ref_table_name']]]></code>
<code><![CDATA[$val]]></code>
</PossiblyNullArgument>
<PossiblyNullArrayAccess>
<code><![CDATA[$existrelForeign[$masterFieldMd5]['constraint']]]></code>
@ -11441,10 +11408,6 @@
<code><![CDATA[$optionsArray]]></code>
<code><![CDATA[$optionsArray]]></code>
</PossiblyNullArrayOffset>
<PossiblyNullIterator>
<code><![CDATA[$statement->altered]]></code>
<code><![CDATA[$statement->altered]]></code>
</PossiblyNullIterator>
<PossiblyNullOperand>
<code><![CDATA[Generator::mysqlDie($tmpErrorCreate, $createQuery, false, '', false)]]></code>
<code><![CDATA[Generator::mysqlDie($tmpErrorDrop, $dropQuery, false, '', false)]]></code>
@ -11456,19 +11419,68 @@
<PossiblyUnusedReturnValue>
<code><![CDATA[true|Message]]></code>
</PossiblyUnusedReturnValue>
<RiskyTruthyFalsyComparison>
<code><![CDATA[! Context::isKeyword($columnName, true)]]></code>
</RiskyTruthyFalsyComparison>
<UnsupportedPropertyReferenceUsage>
<code><![CDATA[$this->uiprefs =& $_SESSION['tmpval']['table_uiprefs'][$serverId][$this->dbName][$this->name]]]></code>
</UnsupportedPropertyReferenceUsage>
</file>
<file src="src/Table/TableMover.php">
<DeprecatedMethod>
<code><![CDATA[DatabaseInterface::getInstance()]]></code>
<code><![CDATA[DatabaseInterface::getInstance()]]></code>
</DeprecatedMethod>
<InvalidArrayOffset>
<code><![CDATA[$GLOBALS['add_constraints']]]></code>
<code><![CDATA[$GLOBALS['errorUrl']]]></code>
<code><![CDATA[$GLOBALS['sql_auto_increments']]]></code>
<code><![CDATA[$GLOBALS['sql_indexes']]]></code>
</InvalidArrayOffset>
<MixedArgument>
<code><![CDATA[$GLOBALS['sql_auto_increments']]]></code>
<code><![CDATA[$GLOBALS['sql_indexes']]]></code>
</MixedArgument>
<MixedAssignment>
<code><![CDATA[$GLOBALS['errorUrl']]]></code>
<code><![CDATA[$altered]]></code>
<code><![CDATA[$altered]]></code>
<code><![CDATA[$value]]></code>
<code><![CDATA[$value]]></code>
</MixedAssignment>
<MixedMethodCall>
<code><![CDATA[has]]></code>
<code><![CDATA[has]]></code>
</MixedMethodCall>
<MixedPropertyAssignment>
<code><![CDATA[$altered]]></code>
<code><![CDATA[$altered]]></code>
</MixedPropertyAssignment>
<MixedPropertyFetch>
<code><![CDATA[$altered->options]]></code>
<code><![CDATA[$altered->options]]></code>
</MixedPropertyFetch>
<PossiblyNullArgument>
<code><![CDATA[$commentsCopyRow['column_name']]]></code>
<code><![CDATA[$commentsCopyRow['comment']]]></code>
<code><![CDATA[$commentsCopyRow['mimetype']]]></code>
<code><![CDATA[$commentsCopyRow['transformation']]]></code>
<code><![CDATA[$commentsCopyRow['transformation_options']]]></code>
<code><![CDATA[$val]]></code>
</PossiblyNullArgument>
<PossiblyNullIterator>
<code><![CDATA[$statement->altered]]></code>
<code><![CDATA[$statement->altered]]></code>
</PossiblyNullIterator>
<RedundantConditionGivenDocblockType>
<code><![CDATA[! empty($parser->statements[0])]]></code>
</RedundantConditionGivenDocblockType>
<RiskyTruthyFalsyComparison>
<code><![CDATA[! $relationParams[$work]]]></code>
<code><![CDATA[! Context::isKeyword($columnName, true)]]></code>
</RiskyTruthyFalsyComparison>
<TypeDoesNotContainType>
<code><![CDATA[! empty($GLOBALS['sql_constraints_query'])]]></code>
</TypeDoesNotContainType>
<UnsupportedPropertyReferenceUsage>
<code><![CDATA[$this->uiprefs =& $_SESSION['tmpval']['table_uiprefs'][$serverId][$this->dbName][$this->name]]]></code>
</UnsupportedPropertyReferenceUsage>
<UnusedVariable>
<code><![CDATA[$maintainRelations]]></code>
</UnusedVariable>

View File

@ -11,7 +11,7 @@ use PhpMyAdmin\Http\Response;
use PhpMyAdmin\Http\ServerRequest;
use PhpMyAdmin\Message;
use PhpMyAdmin\Operations;
use PhpMyAdmin\Table\Table;
use PhpMyAdmin\Table\TableMover;
use PhpMyAdmin\UserPrivilegesFactory;
final class CopyTableController implements InvocableController
@ -33,7 +33,7 @@ final class CopyTableController implements InvocableController
$userPrivileges = $this->userPrivilegesFactory->getPrivileges();
foreach ($selected as $selectedValue) {
Table::moveCopy(
TableMover::moveCopy(
Current::$database,
$selectedValue,
$targetDb,

View File

@ -10,7 +10,7 @@ use PhpMyAdmin\Current;
use PhpMyAdmin\Http\Response;
use PhpMyAdmin\Http\ServerRequest;
use PhpMyAdmin\Message;
use PhpMyAdmin\Table\Table;
use PhpMyAdmin\Table\TableMover;
use function mb_strlen;
use function mb_substr;
@ -33,7 +33,7 @@ final class CopyTableWithPrefixController implements InvocableController
foreach ($selected as $selectedValue) {
$newTableName = $toPrefix . mb_substr($selectedValue, mb_strlen((string) $fromPrefix));
Table::moveCopy(
TableMover::moveCopy(
Current::$database,
$selectedValue,
Current::$database,

View File

@ -12,6 +12,7 @@ use PhpMyAdmin\Identifiers\DatabaseName;
use PhpMyAdmin\Partitioning\Partition;
use PhpMyAdmin\Plugins\Export\ExportSql;
use PhpMyAdmin\Table\Table;
use PhpMyAdmin\Table\TableMover;
use PhpMyAdmin\Triggers\Triggers;
use function __;
@ -193,7 +194,7 @@ class Operations
$triggers = Triggers::getDetails($this->dbi, $db, $table);
if (
! Table::moveCopy(
! TableMover::moveCopy(
$db,
$table,
$newDatabaseName->getName(),
@ -268,7 +269,7 @@ class Operations
{
// Add DROP IF EXIST to CREATE VIEW query, to remove stand-in VIEW that was created earlier.
foreach ($views as $view) {
$copyingSucceeded = Table::moveCopy(
$copyingSucceeded = TableMover::moveCopy(
$db,
$view,
$newDatabaseName->getName(),
@ -459,7 +460,7 @@ class Operations
$getFields = ['user', 'label', 'query'];
$whereFields = ['dbase' => $db];
$newFields = ['dbase' => $newDatabaseName->getName()];
Table::duplicateInfo('bookmarkwork', 'bookmark', $getFields, $whereFields, $newFields);
TableMover::duplicateInfo('bookmarkwork', 'bookmark', $getFields, $whereFields, $newFields);
}
/**
@ -887,7 +888,7 @@ class Operations
$message = Message::error(__('Can\'t copy table to same one!'));
}
} else {
Table::moveCopy(
TableMover::moveCopy(
$db,
$table,
$targetDb,

View File

@ -17,17 +17,11 @@ use PhpMyAdmin\Html\Generator;
use PhpMyAdmin\Html\MySQLDocumentation;
use PhpMyAdmin\Index;
use PhpMyAdmin\Message;
use PhpMyAdmin\Plugins;
use PhpMyAdmin\Plugins\Export\ExportSql;
use PhpMyAdmin\Query\Compatibility;
use PhpMyAdmin\Query\Generator as QueryGenerator;
use PhpMyAdmin\SqlParser\Components\Expression;
use PhpMyAdmin\SqlParser\Components\OptionsArray;
use PhpMyAdmin\SqlParser\Context;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Statements\AlterStatement;
use PhpMyAdmin\SqlParser\Statements\CreateStatement;
use PhpMyAdmin\SqlParser\Statements\DropStatement;
use PhpMyAdmin\SqlParser\Utils\Table as TableUtils;
use PhpMyAdmin\Triggers\Triggers;
use PhpMyAdmin\Util;
@ -766,504 +760,6 @@ class Table implements Stringable
);
}
/**
* Inserts existing entries in a PMA_* table by reading a value from an old
* entry
*
* @param string $work The array index, which Relation feature to check ('relwork', 'commwork', ...)
* @param string $table The array index, which PMA-table to update ('bookmark', 'relation', ...)
* @param string[] $getFields Which fields will be SELECT'ed from the old entry
* @param mixed[] $whereFields Which fields will be used for the WHERE query (array('FIELDNAME' => 'FIELDVALUE'))
* @param mixed[] $newFields Which fields will be used as new VALUES. These are the important keys which differ
* from the old entry (array('FIELDNAME' => 'NEW FIELDVALUE'))
*/
public static function duplicateInfo(
string $work,
string $table,
array $getFields,
array $whereFields,
array $newFields,
): int|bool {
$dbi = DatabaseInterface::getInstance();
$relation = new Relation($dbi);
$relationParameters = $relation->getRelationParameters();
$relationParams = $relationParameters->toArray();
$lastId = -1;
if (! isset($relationParams[$work], $relationParams[$table]) || ! $relationParams[$work]) {
return true;
}
$selectParts = [];
$rowFields = [];
foreach ($getFields as $getField) {
$selectParts[] = Util::backquote($getField);
$rowFields[] = $getField;
}
$whereParts = [];
foreach ($whereFields as $where => $value) {
$whereParts[] = Util::backquote((string) $where) . ' = '
. $dbi->quoteString((string) $value, ConnectionType::ControlUser);
}
$newParts = [];
$newValueParts = [];
foreach ($newFields as $where => $value) {
$newParts[] = Util::backquote((string) $where);
$newValueParts[] = $dbi->quoteString((string) $value, ConnectionType::ControlUser);
}
$tableCopyQuery = '
SELECT ' . implode(', ', $selectParts) . '
FROM ' . Util::backquote($relationParameters->db) . '.'
. Util::backquote((string) $relationParams[$table]) . '
WHERE ' . implode(' AND ', $whereParts);
// must use DatabaseInterface::QUERY_BUFFERED here, since we execute
// another query inside the loop
$tableCopyRs = $dbi->queryAsControlUser($tableCopyQuery);
foreach ($tableCopyRs as $tableCopyRow) {
$valueParts = [];
foreach ($tableCopyRow as $key => $val) {
if (! in_array($key, $rowFields)) {
continue;
}
$valueParts[] = $dbi->quoteString($val, ConnectionType::ControlUser);
}
$newTableQuery = 'INSERT IGNORE INTO '
. Util::backquote($relationParameters->db)
. '.' . Util::backquote((string) $relationParams[$table])
. ' (' . implode(', ', $selectParts) . ', '
. implode(', ', $newParts) . ') VALUES ('
. implode(', ', $valueParts) . ', '
. implode(', ', $newValueParts) . ')';
$dbi->queryAsControlUser($newTableQuery);
$lastId = $dbi->insertId();
}
return $lastId;
}
/**
* Copies or renames table
*
* @param string $sourceDb source database
* @param string $sourceTable source table
* @param string $targetDb target database
* @param string $targetTable target table
* @param string $what what to be moved or copied (data, dataonly)
* @param bool $move whether to move
* @param string $mode mode
*/
public static function moveCopy(
string $sourceDb,
string $sourceTable,
string $targetDb,
string $targetTable,
string $what,
bool $move,
string $mode,
bool $addDropIfExists,
): bool {
$GLOBALS['errorUrl'] ??= null;
$dbi = DatabaseInterface::getInstance();
$relation = new Relation($dbi);
// Try moving the tables directly, using native `RENAME` statement.
if ($move && $what === 'data') {
$tbl = new Table($sourceTable, $sourceDb, $dbi);
if ($tbl->rename($targetTable, $targetDb)) {
$GLOBALS['message'] = $tbl->getLastMessage();
return true;
}
}
// Setting required export settings.
$GLOBALS['asfile'] = 1;
// Ensuring the target database is valid.
$databaseList = $dbi->getDatabaseList();
if (! $databaseList->exists($sourceDb, $targetDb)) {
if (! $databaseList->exists($sourceDb)) {
$GLOBALS['message'] = Message::rawError(
sprintf(
__('Source database `%s` was not found!'),
htmlspecialchars($sourceDb),
),
);
}
if (! $databaseList->exists($targetDb)) {
$GLOBALS['message'] = Message::rawError(
sprintf(
__('Target database `%s` was not found!'),
htmlspecialchars($targetDb),
),
);
}
return false;
}
/**
* The full name of source table, quoted.
*/
$source = Util::backquote($sourceDb) . '.' . Util::backquote($sourceTable);
// If the target database is not specified, the operation is taking
// place in the same database.
if ($targetDb === '') {
$targetDb = $sourceDb;
}
// Selecting the database could avoid some problems with replicated
// databases, when moving table from replicated one to not replicated one.
$dbi->selectDb($targetDb);
/**
* The full name of target table, quoted.
*/
$target = Util::backquote($targetDb) . '.' . Util::backquote($targetTable);
// No table is created when this is a data-only operation.
if ($what !== 'dataonly') {
/**
* Instance used for exporting the current structure of the table.
*
* @var ExportSql $exportSqlPlugin
*/
$exportSqlPlugin = Plugins::getPlugin('export', 'sql', [
'export_type' => 'table',
'single_table' => false,
]);
// It is better that all identifiers are quoted
$exportSqlPlugin->useSqlBackquotes(true);
$noConstraintsComments = true;
$GLOBALS['sql_constraints_query'] = '';
// set the value of global sql_auto_increment variable
if (isset($_POST['sql_auto_increment'])) {
$GLOBALS['sql_auto_increment'] = $_POST['sql_auto_increment'];
}
$isView = (new Table($sourceTable, $sourceDb, $dbi))->isView();
/**
* The old structure of the table.
*/
$sqlStructure = $exportSqlPlugin->getTableDef($sourceDb, $sourceTable, false, false, $isView);
unset($noConstraintsComments);
// -----------------------------------------------------------------
// Phase 0: Preparing structures used.
/**
* The destination where the table is moved or copied to.
*/
$destination = new Expression($targetDb, $targetTable, '');
// Find server's SQL mode so the builder can generate correct
// queries.
// One of the options that alters the behaviour is `ANSI_QUOTES`.
Context::setMode((string) $dbi->fetchValue('SELECT @@sql_mode'));
// -----------------------------------------------------------------
// Phase 1: Dropping existent element of the same name (if exists
// and required).
if ($addDropIfExists) {
/**
* Drop statement used for building the query.
*/
$statement = new DropStatement();
$tbl = new Table($targetTable, $targetDb, $dbi);
$statement->options = new OptionsArray(
[$tbl->isView() ? 'VIEW' : 'TABLE', 'IF EXISTS'],
);
$statement->fields = [$destination];
// Building the query.
$dropQuery = $statement->build() . ';';
// Executing it.
$dbi->query($dropQuery);
$GLOBALS['sql_query'] .= "\n" . $dropQuery;
// If an existing table gets deleted, maintain any entries for
// the PMA_* tables.
$maintainRelations = true;
}
// -----------------------------------------------------------------
// Phase 2: Generating the new query of this structure.
/**
* The parser responsible for parsing the old queries.
*/
$parser = new Parser($sqlStructure);
if (! empty($parser->statements[0])) {
/**
* The CREATE statement of this structure.
*
* @var CreateStatement $statement
*/
$statement = $parser->statements[0];
// Changing the destination.
$statement->name = $destination;
// Building back the query.
$sqlStructure = $statement->build() . ';';
// This is to avoid some issues when renaming databases with views
// See: https://github.com/phpmyadmin/phpmyadmin/issues/16422
if ($move) {
$dbi->selectDb($targetDb);
}
// Executing it
$dbi->query($sqlStructure);
$GLOBALS['sql_query'] .= "\n" . $sqlStructure;
}
// -----------------------------------------------------------------
// Phase 3: Adding constraints.
// All constraint names are removed because they must be unique.
if (($move || isset($GLOBALS['add_constraints'])) && ! empty($GLOBALS['sql_constraints_query'])) {
$parser = new Parser($GLOBALS['sql_constraints_query']);
/**
* The ALTER statement that generates the constraints.
*
* @var AlterStatement $statement
*/
$statement = $parser->statements[0];
// Changing the altered table to the destination.
$statement->table = $destination;
// Removing the name of the constraints.
foreach ($statement->altered as $altered) {
// All constraint names are removed because they must be unique.
if (! $altered->options->has('CONSTRAINT')) {
continue;
}
$altered->field = null;
}
// Building back the query.
$GLOBALS['sql_constraints_query'] = $statement->build() . ';';
// Executing it.
if ($mode === 'one_table') {
$dbi->query($GLOBALS['sql_constraints_query']);
}
$GLOBALS['sql_query'] .= "\n" . $GLOBALS['sql_constraints_query'];
if ($mode === 'one_table') {
unset($GLOBALS['sql_constraints_query']);
}
}
// -----------------------------------------------------------------
// Phase 4: Adding indexes.
// View phase 3.
if (! empty($GLOBALS['sql_indexes'])) {
$parser = new Parser($GLOBALS['sql_indexes']);
$GLOBALS['sql_indexes'] = '';
/**
* The ALTER statement that generates the indexes.
*
* @var AlterStatement $statement
*/
foreach ($parser->statements as $statement) {
// Changing the altered table to the destination.
$statement->table = $destination;
// Removing the name of the constraints.
foreach ($statement->altered as $altered) {
// All constraint names are removed because they must be unique.
if (! $altered->options->has('CONSTRAINT')) {
continue;
}
$altered->field = null;
}
// Building back the query.
$sqlIndex = $statement->build() . ';';
// Executing it.
if ($mode === 'one_table' || $mode === 'db_copy') {
$dbi->query($sqlIndex);
}
$GLOBALS['sql_indexes'] .= $sqlIndex;
}
$GLOBALS['sql_query'] .= "\n" . $GLOBALS['sql_indexes'];
if ($mode === 'one_table' || $mode === 'db_copy') {
unset($GLOBALS['sql_indexes']);
}
}
// -----------------------------------------------------------------
// Phase 5: Adding AUTO_INCREMENT.
if (! empty($GLOBALS['sql_auto_increments']) && ($mode === 'one_table' || $mode === 'db_copy')) {
$parser = new Parser($GLOBALS['sql_auto_increments']);
/**
* The ALTER statement that alters the AUTO_INCREMENT value.
*/
$statement = $parser->statements[0];
if ($statement instanceof AlterStatement) {
// Changing the altered table to the destination.
$statement->table = $destination;
// Building back the query.
$GLOBALS['sql_auto_increments'] = $statement->build() . ';';
// Executing it.
$dbi->query($GLOBALS['sql_auto_increments']);
$GLOBALS['sql_query'] .= "\n" . $GLOBALS['sql_auto_increments'];
}
unset($GLOBALS['sql_auto_increments']);
}
} else {
$GLOBALS['sql_query'] = '';
}
$table = new Table($targetTable, $targetDb, $dbi);
// Copy the data unless this is a VIEW
if (($what === 'data' || $what === 'dataonly') && ! $table->isView()) {
$sqlSetMode = "SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO'";
$dbi->query($sqlSetMode);
$GLOBALS['sql_query'] .= "\n\n" . $sqlSetMode . ';';
$oldTable = new Table($sourceTable, $sourceDb, $dbi);
$nonGeneratedCols = $oldTable->getNonGeneratedColumns();
if ($nonGeneratedCols !== []) {
$sqlInsertData = 'INSERT INTO ' . $target . '('
. implode(', ', $nonGeneratedCols)
. ') SELECT ' . implode(', ', $nonGeneratedCols)
. ' FROM ' . $source;
$dbi->query($sqlInsertData);
$GLOBALS['sql_query'] .= "\n\n" . $sqlInsertData . ';';
}
}
$relationParameters = $relation->getRelationParameters();
// Drops old table if the user has requested to move it
if ($move) {
// This could avoid some problems with replicated databases, when
// moving table from replicated one to not replicated one
$dbi->selectDb($sourceDb);
$sourceTableObj = new Table($sourceTable, $sourceDb, $dbi);
$sqlDropQuery = $sourceTableObj->isView() ? 'DROP VIEW' : 'DROP TABLE';
$sqlDropQuery .= ' ' . $source;
$dbi->query($sqlDropQuery);
// Rename table in configuration storage
$relation->renameTable($sourceDb, $targetDb, $sourceTable, $targetTable);
$GLOBALS['sql_query'] .= "\n\n" . $sqlDropQuery . ';';
return true;
}
// we are copying
// Create new entries as duplicates from old PMA DBs
if ($what === 'dataonly' || isset($maintainRelations)) {
return true;
}
if ($relationParameters->columnCommentsFeature !== null) {
// Get all comments and MIME-Types for current table
$commentsCopyRs = $dbi->queryAsControlUser(
'SELECT column_name, comment'
. ($relationParameters->browserTransformationFeature !== null
? ', mimetype, transformation, transformation_options'
: '')
. ' FROM '
. Util::backquote($relationParameters->columnCommentsFeature->database)
. '.'
. Util::backquote($relationParameters->columnCommentsFeature->columnInfo)
. ' WHERE '
. ' db_name = ' . $dbi->quoteString($sourceDb, ConnectionType::ControlUser)
. ' AND '
. ' table_name = ' . $dbi->quoteString($sourceTable, ConnectionType::ControlUser),
);
// Write every comment as new copied entry. [MIME]
foreach ($commentsCopyRs as $commentsCopyRow) {
$newCommentQuery = 'REPLACE INTO '
. Util::backquote($relationParameters->columnCommentsFeature->database)
. '.' . Util::backquote($relationParameters->columnCommentsFeature->columnInfo)
. ' (db_name, table_name, column_name, comment'
. ($relationParameters->browserTransformationFeature !== null
? ', mimetype, transformation, transformation_options'
: '')
. ') VALUES(' . $dbi->quoteString($targetDb, ConnectionType::ControlUser)
. ',' . $dbi->quoteString($targetTable, ConnectionType::ControlUser) . ','
. $dbi->quoteString($commentsCopyRow['column_name'], ConnectionType::ControlUser)
. ','
. $dbi->quoteString($commentsCopyRow['comment'], ConnectionType::ControlUser)
. ($relationParameters->browserTransformationFeature !== null
? ',' . $dbi->quoteString($commentsCopyRow['mimetype'], ConnectionType::ControlUser)
. ',' . $dbi->quoteString($commentsCopyRow['transformation'], ConnectionType::ControlUser)
. ','
. $dbi->quoteString($commentsCopyRow['transformation_options'], ConnectionType::ControlUser)
: '')
. ')';
$dbi->queryAsControlUser($newCommentQuery);
}
unset($commentsCopyRs);
}
// duplicating the bookmarks must not be done here, but
// just once per db
$getFields = ['display_field'];
$whereFields = ['db_name' => $sourceDb, 'table_name' => $sourceTable];
$newFields = ['db_name' => $targetDb, 'table_name' => $targetTable];
self::duplicateInfo('displaywork', 'table_info', $getFields, $whereFields, $newFields);
/** @todo revise this code when we support cross-db relations */
$getFields = ['master_field', 'foreign_table', 'foreign_field'];
$whereFields = ['master_db' => $sourceDb, 'master_table' => $sourceTable];
$newFields = ['master_db' => $targetDb, 'foreign_db' => $targetDb, 'master_table' => $targetTable];
self::duplicateInfo('relwork', 'relation', $getFields, $whereFields, $newFields);
$getFields = ['foreign_field', 'master_table', 'master_field'];
$whereFields = ['foreign_db' => $sourceDb, 'foreign_table' => $sourceTable];
$newFields = ['master_db' => $targetDb, 'foreign_db' => $targetDb, 'foreign_table' => $targetTable];
self::duplicateInfo('relwork', 'relation', $getFields, $whereFields, $newFields);
return true;
}
/**
* checks if given name is a valid table name,
* currently if not empty, trailing spaces, '.', '/' and '\'

527
src/Table/TableMover.php Normal file
View File

@ -0,0 +1,527 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Table;
use PhpMyAdmin\ConfigStorage\Relation;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\Dbal\ConnectionType;
use PhpMyAdmin\Message;
use PhpMyAdmin\Plugins;
use PhpMyAdmin\Plugins\Export\ExportSql;
use PhpMyAdmin\SqlParser\Components\Expression;
use PhpMyAdmin\SqlParser\Components\OptionsArray;
use PhpMyAdmin\SqlParser\Context;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Statements\AlterStatement;
use PhpMyAdmin\SqlParser\Statements\CreateStatement;
use PhpMyAdmin\SqlParser\Statements\DropStatement;
use PhpMyAdmin\Util;
use function __;
use function htmlspecialchars;
use function implode;
use function in_array;
use function sprintf;
class TableMover
{
/**
* Copies or renames table
*
* @param string $sourceDb source database
* @param string $sourceTable source table
* @param string $targetDb target database
* @param string $targetTable target table
* @param string $what what to be moved or copied (data, dataonly)
* @param bool $move whether to move
* @param string $mode mode
*/
public static function moveCopy(
string $sourceDb,
string $sourceTable,
string $targetDb,
string $targetTable,
string $what,
bool $move,
string $mode,
bool $addDropIfExists,
): bool {
$GLOBALS['errorUrl'] ??= null;
$dbi = DatabaseInterface::getInstance();
$relation = new Relation($dbi);
// Try moving the tables directly, using native `RENAME` statement.
if ($move && $what === 'data') {
$tbl = new Table($sourceTable, $sourceDb, $dbi);
if ($tbl->rename($targetTable, $targetDb)) {
$GLOBALS['message'] = $tbl->getLastMessage();
return true;
}
}
// Setting required export settings.
$GLOBALS['asfile'] = 1;
// Ensuring the target database is valid.
$databaseList = $dbi->getDatabaseList();
if (! $databaseList->exists($sourceDb, $targetDb)) {
if (! $databaseList->exists($sourceDb)) {
$GLOBALS['message'] = Message::rawError(
sprintf(
__('Source database `%s` was not found!'),
htmlspecialchars($sourceDb),
),
);
}
if (! $databaseList->exists($targetDb)) {
$GLOBALS['message'] = Message::rawError(
sprintf(
__('Target database `%s` was not found!'),
htmlspecialchars($targetDb),
),
);
}
return false;
}
/**
* The full name of source table, quoted.
*/
$source = Util::backquote($sourceDb) . '.' . Util::backquote($sourceTable);
// If the target database is not specified, the operation is taking
// place in the same database.
if ($targetDb === '') {
$targetDb = $sourceDb;
}
// Selecting the database could avoid some problems with replicated
// databases, when moving table from replicated one to not replicated one.
$dbi->selectDb($targetDb);
/**
* The full name of target table, quoted.
*/
$target = Util::backquote($targetDb) . '.' . Util::backquote($targetTable);
// No table is created when this is a data-only operation.
if ($what !== 'dataonly') {
/**
* Instance used for exporting the current structure of the table.
*
* @var ExportSql $exportSqlPlugin
*/
$exportSqlPlugin = Plugins::getPlugin('export', 'sql', [
'export_type' => 'table',
'single_table' => false,
]);
// It is better that all identifiers are quoted
$exportSqlPlugin->useSqlBackquotes(true);
$noConstraintsComments = true;
$GLOBALS['sql_constraints_query'] = '';
// set the value of global sql_auto_increment variable
if (isset($_POST['sql_auto_increment'])) {
$GLOBALS['sql_auto_increment'] = $_POST['sql_auto_increment'];
}
$isView = (new Table($sourceTable, $sourceDb, $dbi))->isView();
/**
* The old structure of the table.
*/
$sqlStructure = $exportSqlPlugin->getTableDef($sourceDb, $sourceTable, false, false, $isView);
unset($noConstraintsComments);
// -----------------------------------------------------------------
// Phase 0: Preparing structures used.
/**
* The destination where the table is moved or copied to.
*/
$destination = new Expression($targetDb, $targetTable, '');
// Find server's SQL mode so the builder can generate correct
// queries.
// One of the options that alters the behaviour is `ANSI_QUOTES`.
Context::setMode((string) $dbi->fetchValue('SELECT @@sql_mode'));
// -----------------------------------------------------------------
// Phase 1: Dropping existent element of the same name (if exists
// and required).
if ($addDropIfExists) {
/**
* Drop statement used for building the query.
*/
$statement = new DropStatement();
$tbl = new Table($targetTable, $targetDb, $dbi);
$statement->options = new OptionsArray(
[$tbl->isView() ? 'VIEW' : 'TABLE', 'IF EXISTS'],
);
$statement->fields = [$destination];
// Building the query.
$dropQuery = $statement->build() . ';';
// Executing it.
$dbi->query($dropQuery);
$GLOBALS['sql_query'] .= "\n" . $dropQuery;
// If an existing table gets deleted, maintain any entries for
// the PMA_* tables.
$maintainRelations = true;
}
// -----------------------------------------------------------------
// Phase 2: Generating the new query of this structure.
/**
* The parser responsible for parsing the old queries.
*/
$parser = new Parser($sqlStructure);
if (! empty($parser->statements[0])) {
/**
* The CREATE statement of this structure.
*
* @var CreateStatement $statement
*/
$statement = $parser->statements[0];
// Changing the destination.
$statement->name = $destination;
// Building back the query.
$sqlStructure = $statement->build() . ';';
// This is to avoid some issues when renaming databases with views
// See: https://github.com/phpmyadmin/phpmyadmin/issues/16422
if ($move) {
$dbi->selectDb($targetDb);
}
// Executing it
$dbi->query($sqlStructure);
$GLOBALS['sql_query'] .= "\n" . $sqlStructure;
}
// -----------------------------------------------------------------
// Phase 3: Adding constraints.
// All constraint names are removed because they must be unique.
if (($move || isset($GLOBALS['add_constraints'])) && ! empty($GLOBALS['sql_constraints_query'])) {
$parser = new Parser($GLOBALS['sql_constraints_query']);
/**
* The ALTER statement that generates the constraints.
*
* @var AlterStatement $statement
*/
$statement = $parser->statements[0];
// Changing the altered table to the destination.
$statement->table = $destination;
// Removing the name of the constraints.
foreach ($statement->altered as $altered) {
// All constraint names are removed because they must be unique.
if (! $altered->options->has('CONSTRAINT')) {
continue;
}
$altered->field = null;
}
// Building back the query.
$GLOBALS['sql_constraints_query'] = $statement->build() . ';';
// Executing it.
if ($mode === 'one_table') {
$dbi->query($GLOBALS['sql_constraints_query']);
}
$GLOBALS['sql_query'] .= "\n" . $GLOBALS['sql_constraints_query'];
if ($mode === 'one_table') {
unset($GLOBALS['sql_constraints_query']);
}
}
// -----------------------------------------------------------------
// Phase 4: Adding indexes.
// View phase 3.
if (! empty($GLOBALS['sql_indexes'])) {
$parser = new Parser($GLOBALS['sql_indexes']);
$GLOBALS['sql_indexes'] = '';
/**
* The ALTER statement that generates the indexes.
*
* @var AlterStatement $statement
*/
foreach ($parser->statements as $statement) {
// Changing the altered table to the destination.
$statement->table = $destination;
// Removing the name of the constraints.
foreach ($statement->altered as $altered) {
// All constraint names are removed because they must be unique.
if (! $altered->options->has('CONSTRAINT')) {
continue;
}
$altered->field = null;
}
// Building back the query.
$sqlIndex = $statement->build() . ';';
// Executing it.
if ($mode === 'one_table' || $mode === 'db_copy') {
$dbi->query($sqlIndex);
}
$GLOBALS['sql_indexes'] .= $sqlIndex;
}
$GLOBALS['sql_query'] .= "\n" . $GLOBALS['sql_indexes'];
if ($mode === 'one_table' || $mode === 'db_copy') {
unset($GLOBALS['sql_indexes']);
}
}
// -----------------------------------------------------------------
// Phase 5: Adding AUTO_INCREMENT.
if (! empty($GLOBALS['sql_auto_increments']) && ($mode === 'one_table' || $mode === 'db_copy')) {
$parser = new Parser($GLOBALS['sql_auto_increments']);
/**
* The ALTER statement that alters the AUTO_INCREMENT value.
*/
$statement = $parser->statements[0];
if ($statement instanceof AlterStatement) {
// Changing the altered table to the destination.
$statement->table = $destination;
// Building back the query.
$GLOBALS['sql_auto_increments'] = $statement->build() . ';';
// Executing it.
$dbi->query($GLOBALS['sql_auto_increments']);
$GLOBALS['sql_query'] .= "\n" . $GLOBALS['sql_auto_increments'];
}
unset($GLOBALS['sql_auto_increments']);
}
} else {
$GLOBALS['sql_query'] = '';
}
$table = new Table($targetTable, $targetDb, $dbi);
// Copy the data unless this is a VIEW
if (($what === 'data' || $what === 'dataonly') && ! $table->isView()) {
$sqlSetMode = "SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO'";
$dbi->query($sqlSetMode);
$GLOBALS['sql_query'] .= "\n\n" . $sqlSetMode . ';';
$oldTable = new Table($sourceTable, $sourceDb, $dbi);
$nonGeneratedCols = $oldTable->getNonGeneratedColumns();
if ($nonGeneratedCols !== []) {
$sqlInsertData = 'INSERT INTO ' . $target . '('
. implode(', ', $nonGeneratedCols)
. ') SELECT ' . implode(', ', $nonGeneratedCols)
. ' FROM ' . $source;
$dbi->query($sqlInsertData);
$GLOBALS['sql_query'] .= "\n\n" . $sqlInsertData . ';';
}
}
$relationParameters = $relation->getRelationParameters();
// Drops old table if the user has requested to move it
if ($move) {
// This could avoid some problems with replicated databases, when
// moving table from replicated one to not replicated one
$dbi->selectDb($sourceDb);
$sourceTableObj = new Table($sourceTable, $sourceDb, $dbi);
$sqlDropQuery = $sourceTableObj->isView() ? 'DROP VIEW' : 'DROP TABLE';
$sqlDropQuery .= ' ' . $source;
$dbi->query($sqlDropQuery);
// Rename table in configuration storage
$relation->renameTable($sourceDb, $targetDb, $sourceTable, $targetTable);
$GLOBALS['sql_query'] .= "\n\n" . $sqlDropQuery . ';';
return true;
}
// we are copying
// Create new entries as duplicates from old PMA DBs
if ($what === 'dataonly' || isset($maintainRelations)) {
return true;
}
if ($relationParameters->columnCommentsFeature !== null) {
// Get all comments and MIME-Types for current table
$commentsCopyRs = $dbi->queryAsControlUser(
'SELECT column_name, comment'
. ($relationParameters->browserTransformationFeature !== null
? ', mimetype, transformation, transformation_options'
: '')
. ' FROM '
. Util::backquote($relationParameters->columnCommentsFeature->database)
. '.'
. Util::backquote($relationParameters->columnCommentsFeature->columnInfo)
. ' WHERE '
. ' db_name = ' . $dbi->quoteString($sourceDb, ConnectionType::ControlUser)
. ' AND '
. ' table_name = ' . $dbi->quoteString($sourceTable, ConnectionType::ControlUser),
);
// Write every comment as new copied entry. [MIME]
foreach ($commentsCopyRs as $commentsCopyRow) {
$newCommentQuery = 'REPLACE INTO '
. Util::backquote($relationParameters->columnCommentsFeature->database)
. '.' . Util::backquote($relationParameters->columnCommentsFeature->columnInfo)
. ' (db_name, table_name, column_name, comment'
. ($relationParameters->browserTransformationFeature !== null
? ', mimetype, transformation, transformation_options'
: '')
. ') VALUES(' . $dbi->quoteString($targetDb, ConnectionType::ControlUser)
. ',' . $dbi->quoteString($targetTable, ConnectionType::ControlUser) . ','
. $dbi->quoteString($commentsCopyRow['column_name'], ConnectionType::ControlUser)
. ','
. $dbi->quoteString($commentsCopyRow['comment'], ConnectionType::ControlUser)
. ($relationParameters->browserTransformationFeature !== null
? ',' . $dbi->quoteString($commentsCopyRow['mimetype'], ConnectionType::ControlUser)
. ',' . $dbi->quoteString($commentsCopyRow['transformation'], ConnectionType::ControlUser)
. ','
. $dbi->quoteString($commentsCopyRow['transformation_options'], ConnectionType::ControlUser)
: '')
. ')';
$dbi->queryAsControlUser($newCommentQuery);
}
unset($commentsCopyRs);
}
// duplicating the bookmarks must not be done here, but
// just once per db
$getFields = ['display_field'];
$whereFields = ['db_name' => $sourceDb, 'table_name' => $sourceTable];
$newFields = ['db_name' => $targetDb, 'table_name' => $targetTable];
self::duplicateInfo('displaywork', 'table_info', $getFields, $whereFields, $newFields);
/** @todo revise this code when we support cross-db relations */
$getFields = ['master_field', 'foreign_table', 'foreign_field'];
$whereFields = ['master_db' => $sourceDb, 'master_table' => $sourceTable];
$newFields = ['master_db' => $targetDb, 'foreign_db' => $targetDb, 'master_table' => $targetTable];
self::duplicateInfo('relwork', 'relation', $getFields, $whereFields, $newFields);
$getFields = ['foreign_field', 'master_table', 'master_field'];
$whereFields = ['foreign_db' => $sourceDb, 'foreign_table' => $sourceTable];
$newFields = ['master_db' => $targetDb, 'foreign_db' => $targetDb, 'foreign_table' => $targetTable];
self::duplicateInfo('relwork', 'relation', $getFields, $whereFields, $newFields);
return true;
}
/**
* Inserts existing entries in a PMA_* table by reading a value from an old
* entry
*
* @param string $work The array index, which Relation feature to check ('relwork', 'commwork', ...)
* @param string $table The array index, which PMA-table to update ('bookmark', 'relation', ...)
* @param string[] $getFields Which fields will be SELECT'ed from the old entry
* @param mixed[] $whereFields Which fields will be used for the WHERE query (array('FIELDNAME' => 'FIELDVALUE'))
* @param mixed[] $newFields Which fields will be used as new VALUES. These are the important keys which differ
* from the old entry (array('FIELDNAME' => 'NEW FIELDVALUE'))
*/
public static function duplicateInfo(
string $work,
string $table,
array $getFields,
array $whereFields,
array $newFields,
): int|bool {
$dbi = DatabaseInterface::getInstance();
$relation = new Relation($dbi);
$relationParameters = $relation->getRelationParameters();
$relationParams = $relationParameters->toArray();
$lastId = -1;
if (! isset($relationParams[$work], $relationParams[$table]) || ! $relationParams[$work]) {
return true;
}
$selectParts = [];
$rowFields = [];
foreach ($getFields as $getField) {
$selectParts[] = Util::backquote($getField);
$rowFields[] = $getField;
}
$whereParts = [];
foreach ($whereFields as $where => $value) {
$whereParts[] = Util::backquote((string) $where) . ' = '
. $dbi->quoteString((string) $value, ConnectionType::ControlUser);
}
$newParts = [];
$newValueParts = [];
foreach ($newFields as $where => $value) {
$newParts[] = Util::backquote((string) $where);
$newValueParts[] = $dbi->quoteString((string) $value, ConnectionType::ControlUser);
}
$tableCopyQuery = '
SELECT ' . implode(', ', $selectParts) . '
FROM ' . Util::backquote($relationParameters->db) . '.'
. Util::backquote((string) $relationParams[$table]) . '
WHERE ' . implode(' AND ', $whereParts);
// must use DatabaseInterface::QUERY_BUFFERED here, since we execute
// another query inside the loop
$tableCopyRs = $dbi->queryAsControlUser($tableCopyQuery);
foreach ($tableCopyRs as $tableCopyRow) {
$valueParts = [];
foreach ($tableCopyRow as $key => $val) {
if (! in_array($key, $rowFields)) {
continue;
}
$valueParts[] = $dbi->quoteString($val, ConnectionType::ControlUser);
}
$newTableQuery = 'INSERT IGNORE INTO '
. Util::backquote($relationParameters->db)
. '.' . Util::backquote((string) $relationParams[$table])
. ' (' . implode(', ', $selectParts) . ', '
. implode(', ', $newParts) . ') VALUES ('
. implode(', ', $valueParts) . ', '
. implode(', ', $newValueParts) . ')';
$dbi->queryAsControlUser($newTableQuery);
$lastId = $dbi->insertId();
}
return $lastId;
}
}

View File

@ -13,6 +13,7 @@ use PhpMyAdmin\ListDatabase;
use PhpMyAdmin\Query\Cache;
use PhpMyAdmin\SqlParser\Context;
use PhpMyAdmin\Table\Table;
use PhpMyAdmin\Table\TableMover;
use PhpMyAdmin\Tests\AbstractTestCase;
use PhpMyAdmin\Tests\FieldHelper;
use PhpMyAdmin\Tests\Stubs\DbiDummy;
@ -898,7 +899,7 @@ class TableTest extends AbstractTestCase
]);
(new ReflectionProperty(Relation::class, 'cache'))->setValue(null, $relationParameters);
$ret = Table::duplicateInfo('relwork', 'relation', $getFields, $whereFields, $newFields);
$ret = TableMover::duplicateInfo('relwork', 'relation', $getFields, $whereFields, $newFields);
self::assertSame(-1, $ret);
}
@ -1322,7 +1323,7 @@ class TableTest extends AbstractTestCase
$this->mockedDbi->expects(self::any())->method('getTable')
->willReturnMap($getTableMap);
$return = Table::moveCopy($sourceDb, $sourceTable, $targetDb, $targetTable, $what, $move, $mode, true);
$return = TableMover::moveCopy($sourceDb, $sourceTable, $targetDb, $targetTable, $what, $move, $mode, true);
//successfully
self::assertTrue($return);
@ -1333,7 +1334,7 @@ class TableTest extends AbstractTestCase
$sqlQuery = 'DROP VIEW `PMA`.`PMA_BookMark`';
self::assertStringContainsString($sqlQuery, $GLOBALS['sql_query']);
$return = Table::moveCopy($sourceDb, $sourceTable, $targetDb, $targetTable, $what, false, $mode, true);
$return = TableMover::moveCopy($sourceDb, $sourceTable, $targetDb, $targetTable, $what, false, $mode, true);
//successfully
self::assertTrue($return);
@ -1375,7 +1376,7 @@ class TableTest extends AbstractTestCase
]);
$GLOBALS['sql_query'] = '';
$return = Table::moveCopy('aa', 'ad', 'bb', 'ad', 'structure', true, 'db_copy', true);
$return = TableMover::moveCopy('aa', 'ad', 'bb', 'ad', 'structure', true, 'db_copy', true);
self::assertTrue($return);
self::assertStringContainsString('DROP TABLE IF EXISTS `bb`.`ad`;', $GLOBALS['sql_query']);
self::assertStringContainsString(