3 declare(strict_types
=1);
7 use PhpMyAdmin\ConfigStorage\Relation
;
8 use PhpMyAdmin\Database\Events
;
9 use PhpMyAdmin\Database\Routines
;
10 use PhpMyAdmin\Engines\Innodb
;
11 use PhpMyAdmin\Identifiers\DatabaseName
;
12 use PhpMyAdmin\Partitioning\Partition
;
13 use PhpMyAdmin\Plugins\Export\ExportSql
;
14 use PhpMyAdmin\Table\MoveMode
;
15 use PhpMyAdmin\Table\MoveScope
;
16 use PhpMyAdmin\Table\Table
;
17 use PhpMyAdmin\Table\TableMover
;
18 use PhpMyAdmin\Triggers\Triggers
;
21 use function array_merge
;
24 use function in_array
;
25 use function is_scalar
;
26 use function is_string
;
27 use function mb_strtolower
;
28 use function str_replace
;
29 use function strtolower
;
30 use function urldecode
;
33 * Set of functions with the operations section in phpMyAdmin
37 public function __construct(
38 private readonly DatabaseInterface
$dbi,
39 private readonly Relation
$relation,
40 private readonly TableMover
$tableMover,
45 * Run the Procedure definitions and function definitions
47 * to avoid selecting alternatively the current and new db
48 * we would need to modify the CREATE definitions to qualify
51 * @param string $db database name
53 public function runProcedureAndFunctionDefinitions(string $db, DatabaseName
$newDatabaseName): void
55 foreach (Routines
::getProcedureNames($this->dbi
, $db) as $procedureName) {
56 $this->dbi
->selectDb($db);
57 $query = Routines
::getProcedureDefinition($this->dbi
, $db, $procedureName);
58 if ($query === null) {
62 // collect for later display
63 $GLOBALS['sql_query'] .= "\n" . $query;
64 $this->dbi
->selectDb($newDatabaseName);
65 $this->dbi
->query($query);
68 foreach (Routines
::getFunctionNames($this->dbi
, $db) as $functionName) {
69 $this->dbi
->selectDb($db);
70 $query = Routines
::getFunctionDefinition($this->dbi
, $db, $functionName);
71 if ($query === null) {
75 // collect for later display
76 $GLOBALS['sql_query'] .= "\n" . $query;
77 $this->dbi
->selectDb($newDatabaseName);
78 $this->dbi
->query($query);
83 * Create database before copy
85 public function createDbBeforeCopy(UserPrivileges
$userPrivileges, DatabaseName
$newDatabaseName): void
87 $localQuery = 'CREATE DATABASE IF NOT EXISTS '
88 . Util
::backquote($newDatabaseName);
89 if (isset($_POST['db_collation'])) {
90 $localQuery .= ' DEFAULT'
91 . Util
::getCharsetQueryPart($_POST['db_collation']);
95 $GLOBALS['sql_query'] .= $localQuery;
97 // save the original db name because Tracker.php which
98 // may be called under $this->dbi->query() changes \PhpMyAdmin\Current::$database
99 // for some statements, one of which being CREATE DATABASE
100 $originalDb = Current
::$database;
101 $this->dbi
->query($localQuery);
102 Current
::$database = $originalDb;
104 // Set the SQL mode to NO_AUTO_VALUE_ON_ZERO to prevent MySQL from creating
105 // export statements it cannot import
106 $sqlSetMode = "SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO'";
107 $this->dbi
->query($sqlSetMode);
109 // rebuild the database list because Table::moveCopy
110 // checks in this list if the target db exists
111 $this->dbi
->getDatabaseList()->build($userPrivileges);
115 * Get views as an array and create SQL view stand-in
117 * @param string[] $tables array of all tables in given db or dbs
118 * @param ExportSql $exportSqlPlugin export plugin instance
119 * @param string $db database name
123 public function getViewsAndCreateSqlViewStandIn(
125 ExportSql
$exportSqlPlugin,
127 DatabaseName
$newDatabaseName,
130 foreach ($tables as $table) {
131 // to be able to rename a db containing views,
132 // first all the views are collected and a stand-in is created
133 // the real views are created after the tables
134 if (! $this->dbi
->getTable($db, $table)->isView()) {
138 // If view exists, and 'add drop view' is selected: Drop it!
139 if ($_POST['what'] !== 'nocopy' && isset($_POST['drop_if_exists']) && $_POST['drop_if_exists'] === 'true') {
140 $dropQuery = 'DROP VIEW IF EXISTS '
141 . Util
::backquote($newDatabaseName) . '.'
142 . Util
::backquote($table);
143 $this->dbi
->query($dropQuery);
145 $GLOBALS['sql_query'] .= "\n" . $dropQuery . ';';
149 // Create stand-in definition to resolve view dependencies
150 $sqlViewStandin = $exportSqlPlugin->getTableDefStandIn($db, $table);
151 $this->dbi
->selectDb($newDatabaseName);
152 $this->dbi
->query($sqlViewStandin);
153 $GLOBALS['sql_query'] .= "\n" . $sqlViewStandin;
160 * Get sql query for copy/rename table and boolean for whether copy/rename or not
162 * @param string[] $tables array of all tables in given db or dbs
163 * @param bool $move whether database name is empty or not
164 * @param string $db database name
166 * @return mixed[] SQL queries for the constraints
168 public function copyTables(array $tables, bool $move, string $db, DatabaseName
$newDatabaseName): array
171 foreach ($tables as $table) {
172 // skip the views; we have created stand-in definitions
173 if ($this->dbi
->getTable($db, $table)->isView()) {
177 // value of $what for this table only
178 $copyMode = $_POST['what'];
180 // do not copy the data from a Merge table
181 // note: on the calling FORM, 'data' means 'structure and data'
182 if ($this->dbi
->getTable($db, $table)->isMerge()) {
183 if ($copyMode === 'data') {
184 $copyMode = 'structure';
187 if ($copyMode === 'dataonly') {
188 $copyMode = 'nocopy';
192 if ($copyMode === 'nocopy') {
196 // keep the triggers from the original db+table
197 // (third param is empty because delimiters are only intended
198 // for importing via the mysql client or our Import feature)
199 $triggers = Triggers
::getDetails($this->dbi
, $db, $table);
201 $moveScope = MoveScope
::tryFrom($copyMode) ?? MoveScope
::StructureAndData
;
203 ! $this->tableMover
->moveCopy(
206 $newDatabaseName->getName(),
208 $move ? MoveScope
::Move
: $moveScope,
209 MoveMode
::WholeDatabase
,
210 isset($_POST['drop_if_exists']) && $_POST['drop_if_exists'] === 'true',
213 $GLOBALS['_error'] = true;
217 // apply the triggers to the destination db+table
218 if ($triggers !== []) {
219 $this->dbi
->selectDb($newDatabaseName);
220 foreach ($triggers as $trigger) {
221 $createSqlQuery = $trigger->getCreateSql('');
222 $this->dbi
->query($createSqlQuery);
223 $GLOBALS['sql_query'] .= "\n" . $createSqlQuery . ';';
227 // this does not apply to a rename operation
228 if (! isset($_POST['add_constraints']) ||
$this->tableMover
->sqlConstraintsQuery
=== '') {
232 $sqlContraints[] = $this->tableMover
->sqlConstraintsQuery
;
233 $this->tableMover
->sqlConstraintsQuery
= '';
236 return $sqlContraints;
240 * Run the EVENT definition for selected database
242 * to avoid selecting alternatively the current and new db
243 * we would need to modify the CREATE definitions to qualify
246 * @param string $db database name
248 public function runEventDefinitionsForDb(string $db, DatabaseName
$newDatabaseName): void
250 /** @var string[] $eventNames */
251 $eventNames = $this->dbi
->fetchResult(
252 'SELECT EVENT_NAME FROM information_schema.EVENTS WHERE EVENT_SCHEMA= '
253 . $this->dbi
->quoteString($db) . ';',
256 foreach ($eventNames as $eventName) {
257 $this->dbi
->selectDb($db);
258 $query = Events
::getDefinition($this->dbi
, $db, $eventName);
259 // collect for later display
260 $GLOBALS['sql_query'] .= "\n" . $query;
261 $this->dbi
->selectDb($newDatabaseName);
262 $this->dbi
->query($query);
267 * Handle the views, return the boolean value whether table rename/copy or not
269 * @param string[] $views views as an array
270 * @param bool $move whether database name is empty or not
271 * @param string $db database name
273 public function handleTheViews(array $views, bool $move, string $db, DatabaseName
$newDatabaseName): void
275 // Add DROP IF EXIST to CREATE VIEW query, to remove stand-in VIEW that was created earlier.
276 foreach ($views as $view) {
277 $copyingSucceeded = $this->tableMover
->moveCopy(
280 $newDatabaseName->getName(),
282 $move ? MoveScope
::Move
: MoveScope
::StructureOnly
,
283 MoveMode
::WholeDatabase
,
286 if (! $copyingSucceeded) {
287 $GLOBALS['_error'] = true;
294 * Adjust the privileges after Renaming the db
296 * @param string $oldDb Database name before renaming
298 public function adjustPrivilegesMoveDb(
299 UserPrivileges
$userPrivileges,
301 DatabaseName
$newDatabaseName,
304 ! $userPrivileges->database ||
! $userPrivileges->table
305 ||
! $userPrivileges->column ||
! $userPrivileges->routines
306 ||
! $userPrivileges->isReload
311 $this->dbi
->selectDb('mysql');
312 $newName = str_replace('_', '\_', $newDatabaseName->getName());
313 $oldDb = str_replace('_', '\_', $oldDb);
315 // For Db specific privileges
316 $this->dbi
->query('UPDATE ' . Util
::backquote('db')
317 . 'SET Db = ' . $this->dbi
->quoteString($newName)
318 . ' where Db = ' . $this->dbi
->quoteString($oldDb) . ';');
320 // For table specific privileges
321 $this->dbi
->query('UPDATE ' . Util
::backquote('tables_priv')
322 . 'SET Db = ' . $this->dbi
->quoteString($newName)
323 . ' where Db = ' . $this->dbi
->quoteString($oldDb) . ';');
325 // For column specific privileges
326 $this->dbi
->query('UPDATE ' . Util
::backquote('columns_priv')
327 . 'SET Db = ' . $this->dbi
->quoteString($newName)
328 . ' where Db = ' . $this->dbi
->quoteString($oldDb) . ';');
330 // For procedures specific privileges
331 $this->dbi
->query('UPDATE ' . Util
::backquote('procs_priv')
332 . 'SET Db = ' . $this->dbi
->quoteString($newName)
333 . ' where Db = ' . $this->dbi
->quoteString($oldDb) . ';');
335 // Finally FLUSH the new privileges
336 $this->dbi
->tryQuery('FLUSH PRIVILEGES;');
340 * Adjust the privileges after Copying the db
342 * @param string $oldDb Database name before copying
344 public function adjustPrivilegesCopyDb(
345 UserPrivileges
$userPrivileges,
347 DatabaseName
$newDatabaseName,
350 ! $userPrivileges->database ||
! $userPrivileges->table
351 ||
! $userPrivileges->column ||
! $userPrivileges->routines
352 ||
! $userPrivileges->isReload
357 $this->dbi
->selectDb('mysql');
358 $newName = str_replace('_', '\_', $newDatabaseName->getName());
359 $oldDb = str_replace('_', '\_', $oldDb);
361 $queryDbSpecificOld = 'SELECT * FROM '
362 . Util
::backquote('db') . ' WHERE '
363 . 'Db = "' . $oldDb . '";';
365 $oldPrivsDb = $this->dbi
->fetchResult($queryDbSpecificOld, 0);
367 foreach ($oldPrivsDb as $oldPriv) {
368 $newDbDbPrivsQuery = 'INSERT INTO ' . Util
::backquote('db')
369 . ' VALUES("' . $oldPriv[0] . '", "' . $newName . '"';
370 $privCount = count($oldPriv);
371 for ($i = 2; $i < $privCount; $i++
) {
372 $newDbDbPrivsQuery .= ', "' . $oldPriv[$i] . '"';
375 $newDbDbPrivsQuery .= ')';
377 $this->dbi
->query($newDbDbPrivsQuery);
380 // For Table Specific privileges
381 $queryTableSpecificOld = 'SELECT * FROM '
382 . Util
::backquote('tables_priv') . ' WHERE '
383 . 'Db = "' . $oldDb . '";';
385 $oldPrivsTable = $this->dbi
->fetchResult($queryTableSpecificOld, 0);
387 foreach ($oldPrivsTable as $oldPriv) {
388 $newDbTablePrivsQuery = 'INSERT INTO ' . Util
::backquote(
390 ) . ' VALUES("' . $oldPriv[0] . '", "' . $newName . '", "'
391 . $oldPriv[2] . '", "' . $oldPriv[3] . '", "' . $oldPriv[4]
392 . '", "' . $oldPriv[5] . '", "' . $oldPriv[6] . '", "'
393 . $oldPriv[7] . '");';
395 $this->dbi
->query($newDbTablePrivsQuery);
398 // For Column Specific privileges
399 $queryColSpecificOld = 'SELECT * FROM '
400 . Util
::backquote('columns_priv') . ' WHERE '
401 . 'Db = "' . $oldDb . '";';
403 $oldPrivsCol = $this->dbi
->fetchResult($queryColSpecificOld, 0);
405 foreach ($oldPrivsCol as $oldPriv) {
406 $newDbColPrivsQuery = 'INSERT INTO ' . Util
::backquote(
408 ) . ' VALUES("' . $oldPriv[0] . '", "' . $newName . '", "'
409 . $oldPriv[2] . '", "' . $oldPriv[3] . '", "' . $oldPriv[4]
410 . '", "' . $oldPriv[5] . '", "' . $oldPriv[6] . '");';
412 $this->dbi
->query($newDbColPrivsQuery);
415 // For Procedure Specific privileges
416 $queryProcSpecificOld = 'SELECT * FROM '
417 . Util
::backquote('procs_priv') . ' WHERE '
418 . 'Db = "' . $oldDb . '";';
420 $oldPrivsProc = $this->dbi
->fetchResult($queryProcSpecificOld, 0);
422 foreach ($oldPrivsProc as $oldPriv) {
423 $newDbProcPrivsQuery = 'INSERT INTO ' . Util
::backquote(
425 ) . ' VALUES("' . $oldPriv[0] . '", "' . $newName . '", "'
426 . $oldPriv[2] . '", "' . $oldPriv[3] . '", "' . $oldPriv[4]
427 . '", "' . $oldPriv[5] . '", "' . $oldPriv[6] . '", "'
428 . $oldPriv[7] . '");';
430 $this->dbi
->query($newDbProcPrivsQuery);
433 // Finally FLUSH the new privileges
434 $this->dbi
->tryQuery('FLUSH PRIVILEGES;');
438 * Create all accumulated constraints
440 * @param mixed[] $sqlConstraints array of sql constraints for the database
442 public function createAllAccumulatedConstraints(array $sqlConstraints, DatabaseName
$newDatabaseName): void
444 $this->dbi
->selectDb($newDatabaseName);
445 foreach ($sqlConstraints as $query) {
446 $this->dbi
->query($query);
447 // and prepare to display them
448 $GLOBALS['sql_query'] .= "\n" . $query;
453 * Duplicate the bookmarks for the db (done once for each db)
455 * @param bool $error whether table rename/copy or not
456 * @param string $db database name
458 public function duplicateBookmarks(bool $error, string $db, DatabaseName
$newDatabaseName): void
460 if ($error ||
$db === $newDatabaseName->getName()) {
464 $getFields = ['user', 'label', 'query'];
465 $whereFields = ['dbase' => $db];
466 $newFields = ['dbase' => $newDatabaseName->getName()];
467 $this->tableMover
->duplicateInfo('bookmarkwork', 'bookmark', $getFields, $whereFields, $newFields);
471 * Get array of possible row formats
475 public function getPossibleRowFormat(): array
477 // the outer array is for engines, the inner array contains the dropdown
478 // option values as keys then the dropdown option labels
480 $possibleRowFormats = [
481 'ARCHIVE' => ['COMPRESSED' => 'COMPRESSED'],
482 'ARIA' => ['FIXED' => 'FIXED', 'DYNAMIC' => 'DYNAMIC', 'PAGE' => 'PAGE'],
483 'MARIA' => ['FIXED' => 'FIXED', 'DYNAMIC' => 'DYNAMIC', 'PAGE' => 'PAGE'],
484 'MYISAM' => ['FIXED' => 'FIXED', 'DYNAMIC' => 'DYNAMIC'],
485 'PBXT' => ['FIXED' => 'FIXED', 'DYNAMIC' => 'DYNAMIC'],
486 'INNODB' => ['COMPACT' => 'COMPACT', 'REDUNDANT' => 'REDUNDANT'],
489 /** @var Innodb $innodbEnginePlugin */
490 $innodbEnginePlugin = StorageEngine
::getEngine('Innodb');
491 $innodbPluginVersion = $innodbEnginePlugin->getInnodbPluginVersion();
492 $innodbFileFormat = '';
493 if ($innodbPluginVersion !== '') {
494 $innodbFileFormat = $innodbEnginePlugin->getInnodbFileFormat() ??
'';
498 * Newer MySQL/MariaDB always return empty a.k.a '' on $innodbFileFormat otherwise
499 * old versions of MySQL/MariaDB must be returning something or not empty.
500 * This patch is to support newer MySQL/MariaDB while also for backward compatibilities.
503 (strtolower($innodbFileFormat) === 'barracuda') ||
($innodbFileFormat == '')
504 && $innodbEnginePlugin->supportsFilePerTable()
506 $possibleRowFormats['INNODB']['DYNAMIC'] = 'DYNAMIC';
507 $possibleRowFormats['INNODB']['COMPRESSED'] = 'COMPRESSED';
510 return $possibleRowFormats;
513 /** @return array<string, string> */
514 public function getPartitionMaintenanceChoices(): array
517 'ANALYZE' => __('Analyze'),
518 'CHECK' => __('Check'),
519 'OPTIMIZE' => __('Optimize'),
520 'REBUILD' => __('Rebuild'),
521 'REPAIR' => __('Repair'),
522 'TRUNCATE' => __('Truncate'),
525 $partitionMethod = Partition
::getPartitionMethod(Current
::$database, Current
::$table);
527 // add COALESCE or DROP option to choices array depending on Partition method
529 $partitionMethod === 'RANGE'
530 ||
$partitionMethod === 'RANGE COLUMNS'
531 ||
$partitionMethod === 'LIST'
532 ||
$partitionMethod === 'LIST COLUMNS'
534 $choices['DROP'] = __('Drop');
536 $choices['COALESCE'] = __('Coalesce');
543 * @param mixed[] $urlParams Array of url parameters.
544 * @param bool $hasRelationFeature If relation feature is enabled.
548 public function getForeignersForReferentialIntegrityCheck(
550 bool $hasRelationFeature,
552 if (! $hasRelationFeature) {
557 $this->dbi
->selectDb(Current
::$database);
558 $foreign = $this->relation
->getForeignersInternal(Current
::$database, Current
::$table);
560 foreach ($foreign as $master => $arr) {
561 $joinQuery = 'SELECT '
562 . Util
::backquote(Current
::$table) . '.*'
563 . ' FROM ' . Util
::backquote(Current
::$table)
565 . Util
::backquote($arr['foreign_db'])
567 . Util
::backquote($arr['foreign_table']);
569 if ($arr['foreign_table'] == Current
::$table) {
570 $foreignTable = Current
::$table . '1';
571 $joinQuery .= ' AS ' . Util
::backquote($foreignTable);
573 $foreignTable = $arr['foreign_table'];
577 . Util
::backquote(Current
::$table) . '.'
578 . Util
::backquote($master)
580 . Util
::backquote($arr['foreign_db'])
582 . Util
::backquote($foreignTable) . '.'
583 . Util
::backquote($arr['foreign_field'])
585 . Util
::backquote($arr['foreign_db'])
587 . Util
::backquote($foreignTable) . '.'
588 . Util
::backquote($arr['foreign_field'])
590 . Util
::backquote(Current
::$table) . '.'
591 . Util
::backquote($master)
593 $thisUrlParams = array_merge(
595 ['sql_query' => $joinQuery, 'sql_signature' => Core
::signSqlQuery($joinQuery)],
599 'params' => $thisUrlParams,
601 'db' => $arr['foreign_db'],
602 'table' => $arr['foreign_table'],
603 'field' => $arr['foreign_field'],
611 * Get table alters array
613 * @param Table $pmaTable The Table object
614 * @param string $packKeys pack keys
615 * @param string $checksum value of checksum
616 * @param string $pageChecksum value of page checksum
617 * @param string $delayKeyWrite delay key write
618 * @param string $rowFormat row format
619 * @param string $newTblStorageEngine table storage engine
620 * @param string $transactional value of transactional
621 * @param string $tableCollation collation of the table
625 public function getTableAltersArray(
629 string $pageChecksum,
630 string $delayKeyWrite,
632 string $newTblStorageEngine,
633 string $transactional,
634 string $tableCollation,
635 string $tableStorageEngine,
637 $GLOBALS['auto_increment'] ??
= null;
641 if (isset($_POST['comment']) && urldecode($_POST['prev_comment']) !== $_POST['comment']) {
642 $tableAlters[] = 'COMMENT = ' . $this->dbi
->quoteString($_POST['comment']);
646 $newTblStorageEngine !== ''
647 && mb_strtolower($newTblStorageEngine) !== mb_strtolower($tableStorageEngine)
649 $tableAlters[] = 'ENGINE = ' . $newTblStorageEngine;
652 if (! empty($_POST['tbl_collation']) && $_POST['tbl_collation'] !== $tableCollation) {
653 $tableAlters[] = 'DEFAULT '
654 . Util
::getCharsetQueryPart($_POST['tbl_collation']);
658 $pmaTable->isEngine(['MYISAM', 'ARIA', 'ISAM'])
659 && isset($_POST['new_pack_keys'])
660 && $_POST['new_pack_keys'] != $packKeys
662 $tableAlters[] = 'pack_keys = ' . $_POST['new_pack_keys'];
665 $newChecksum = empty($_POST['new_checksum']) ?
'0' : '1';
666 if ($pmaTable->isEngine(['MYISAM', 'ARIA']) && $newChecksum !== $checksum) {
667 $tableAlters[] = 'checksum = ' . $newChecksum;
670 $newTransactional = empty($_POST['new_transactional']) ?
'0' : '1';
671 if ($pmaTable->isEngine('ARIA') && $newTransactional !== $transactional) {
672 $tableAlters[] = 'TRANSACTIONAL = ' . $newTransactional;
675 $newPageChecksum = empty($_POST['new_page_checksum']) ?
'0' : '1';
676 if ($pmaTable->isEngine('ARIA') && $newPageChecksum !== $pageChecksum) {
677 $tableAlters[] = 'PAGE_CHECKSUM = ' . $newPageChecksum;
680 $newDelayKeyWrite = empty($_POST['new_delay_key_write']) ?
'0' : '1';
681 if ($pmaTable->isEngine(['MYISAM', 'ARIA']) && $newDelayKeyWrite !== $delayKeyWrite) {
682 $tableAlters[] = 'delay_key_write = ' . $newDelayKeyWrite;
686 $pmaTable->isEngine(['MYISAM', 'ARIA', 'INNODB', 'PBXT', 'ROCKSDB'])
687 && ! empty($_POST['new_auto_increment'])
688 && (! isset($GLOBALS['auto_increment'])
689 ||
$_POST['new_auto_increment'] !== $GLOBALS['auto_increment'])
690 && $_POST['new_auto_increment'] !== $_POST['hidden_auto_increment']
692 $tableAlters[] = 'auto_increment = ' . (int) $_POST['new_auto_increment'];
695 if (! empty($_POST['new_row_format'])) {
696 $newRowFormat = $_POST['new_row_format'];
697 $newRowFormatLower = mb_strtolower($newRowFormat);
699 $pmaTable->isEngine(['MYISAM', 'ARIA', 'INNODB', 'PBXT'])
700 && ($rowFormat === '' ||
$newRowFormatLower !== mb_strtolower($rowFormat))
701 && in_array($newRowFormat, ['DEFAULT', 'DYNAMIC', 'FIXED', 'COMPRESSED', 'REDUNDANT', 'COMPACT'], true)
703 $tableAlters[] = 'ROW_FORMAT = ' . $newRowFormat;
711 * Get warning messages array
715 public function getWarningMessagesArray(mixed $newTableStorageEngine): array
717 $warningMessages = [];
718 foreach ($this->dbi
->getWarnings() as $warning) {
719 // In MariaDB 5.1.44, when altering a table from Maria to MyISAM
720 // and if TRANSACTIONAL was set, the system reports an error;
721 // I discussed with a Maria developer and he agrees that this
722 // should not be reported with a Level of Error, so here
723 // I just ignore it. But there are other 1478 messages
724 // that it's better to show.
726 $newTableStorageEngine === 'MyISAM'
727 && $warning->code
=== 1478
728 && $warning->level
=== 'Error'
733 $warningMessages[] = (string) $warning;
736 return $warningMessages;
740 * Adjust the privileges after renaming/moving a table
742 * @param string $oldDb Database name before table renaming/moving table
743 * @param string $oldTable Table name before table renaming/moving table
744 * @param string $newDb Database name after table renaming/ moving table
745 * @param string $newTable Table name after table renaming/moving table
747 public function adjustPrivilegesRenameOrMoveTable(
748 UserPrivileges
$userPrivileges,
754 if (! $userPrivileges->table ||
! $userPrivileges->column ||
! $userPrivileges->isReload
) {
758 $this->dbi
->selectDb('mysql');
760 // For table specific privileges
761 $this->dbi
->query('UPDATE ' . Util
::backquote('tables_priv')
762 . 'SET Db = ' . $this->dbi
->quoteString($newDb)
763 . ', Table_name = ' . $this->dbi
->quoteString($newTable)
764 . ' where Db = ' . $this->dbi
->quoteString($oldDb)
765 . ' AND Table_name = ' . $this->dbi
->quoteString($oldTable)
768 // For column specific privileges
769 $this->dbi
->query('UPDATE ' . Util
::backquote('columns_priv')
770 . 'SET Db = ' . $this->dbi
->quoteString($newDb)
771 . ', Table_name = ' . $this->dbi
->quoteString($newTable)
772 . ' where Db = ' . $this->dbi
->quoteString($oldDb)
773 . ' AND Table_name = ' . $this->dbi
->quoteString($oldTable)
776 // Finally FLUSH the new privileges
777 $this->dbi
->tryQuery('FLUSH PRIVILEGES;');
781 * Adjust the privileges after copying a table
783 * @param string $oldDb Database name before table copying
784 * @param string $oldTable Table name before table copying
785 * @param string $newDb Database name after table copying
786 * @param string $newTable Table name after table copying
788 public function adjustPrivilegesCopyTable(
789 UserPrivileges
$userPrivileges,
795 if (! $userPrivileges->table ||
! $userPrivileges->column ||
! $userPrivileges->isReload
) {
799 $this->dbi
->selectDb('mysql');
801 // For Table Specific privileges
802 $queryTableSpecificOld = 'SELECT * FROM '
803 . Util
::backquote('tables_priv') . ' where '
804 . 'Db = "' . $oldDb . '" AND Table_name = "' . $oldTable . '";';
806 $oldPrivsTable = $this->dbi
->fetchResult($queryTableSpecificOld, 0);
808 foreach ($oldPrivsTable as $oldPriv) {
809 $newDbTablePrivsQuery = 'INSERT INTO '
810 . Util
::backquote('tables_priv') . ' VALUES("'
811 . $oldPriv[0] . '", "' . $newDb . '", "' . $oldPriv[2] . '", "'
812 . $newTable . '", "' . $oldPriv[4] . '", "' . $oldPriv[5]
813 . '", "' . $oldPriv[6] . '", "' . $oldPriv[7] . '");';
815 $this->dbi
->query($newDbTablePrivsQuery);
818 // For Column Specific privileges
819 $queryColSpecificOld = 'SELECT * FROM '
820 . Util
::backquote('columns_priv') . ' WHERE '
821 . 'Db = "' . $oldDb . '" AND Table_name = "' . $oldTable . '";';
823 $oldPrivsCol = $this->dbi
->fetchResult($queryColSpecificOld, 0);
825 foreach ($oldPrivsCol as $oldPriv) {
826 $newDbColPrivsQuery = 'INSERT INTO '
827 . Util
::backquote('columns_priv') . ' VALUES("'
828 . $oldPriv[0] . '", "' . $newDb . '", "' . $oldPriv[2] . '", "'
829 . $newTable . '", "' . $oldPriv[4] . '", "' . $oldPriv[5]
830 . '", "' . $oldPriv[6] . '");';
832 $this->dbi
->query($newDbColPrivsQuery);
835 // Finally FLUSH the new privileges
836 $this->dbi
->tryQuery('FLUSH PRIVILEGES;');
840 * Change all collations and character sets of all columns in table
842 * @param string $db Database name
843 * @param string $table Table name
844 * @param string $tableCollation Collation Name
846 public function changeAllColumnsCollation(string $db, string $table, string $tableCollation): void
848 $this->dbi
->selectDb($db);
850 $changeAllCollationsQuery = 'ALTER TABLE '
851 . Util
::backquote($table)
854 [$charset] = explode('_', $tableCollation);
856 $changeAllCollationsQuery .= ' CHARACTER SET ' . $charset
857 . ($charset === $tableCollation ?
'' : ' COLLATE ' . $tableCollation);
859 $this->dbi
->query($changeAllCollationsQuery);
863 * Move or copy a table
865 * @param string $db current database name
866 * @param string $table current table name
868 public function moveOrCopyTable(UserPrivileges
$userPrivileges, string $db, string $table): Message
871 * Selects the database to work with
873 $this->dbi
->selectDb($db);
876 * $_POST['target_db'] could be empty in case we came from an input field
877 * (when there are many databases, no drop-down)
880 if (isset($_POST['target_db']) && is_string($_POST['target_db']) && $_POST['target_db'] !== '') {
881 $targetDb = $_POST['target_db'];
885 * A target table name has been sent to this script -> do the work
887 if (isset($_POST['new_name']) && is_scalar($_POST['new_name']) && (string) $_POST['new_name'] !== '') {
888 if ($db === $targetDb && $table == $_POST['new_name']) {
889 if (isset($_POST['submit_move'])) {
890 $message = Message
::error(__('Can\'t move table to same one!'));
892 $message = Message
::error(__('Can\'t copy table to same one!'));
895 $move = isset($_POST['submit_move']);
896 $this->tableMover
->moveCopy(
900 (string) $_POST['new_name'],
901 $move ? MoveScope
::Move
: MoveScope
::from($_POST['what']),
902 MoveMode
::SingleTable
,
903 isset($_POST['drop_if_exists']) && $_POST['drop_if_exists'] === 'true',
906 if (! empty($_POST['adjust_privileges'])) {
907 if (isset($_POST['submit_move'])) {
908 $this->adjustPrivilegesRenameOrMoveTable(
913 (string) $_POST['new_name'],
916 $this->adjustPrivilegesCopyTable(
921 (string) $_POST['new_name'],
925 if (isset($_POST['submit_move'])) {
926 $message = Message
::success(
928 'Table %s has been moved to %s. Privileges have been adjusted.',
932 $message = Message
::success(
934 'Table %s has been copied to %s. Privileges have been adjusted.',
938 } elseif (isset($_POST['submit_move'])) {
939 $message = Message
::success(
940 __('Table %s has been moved to %s.'),
943 $message = Message
::success(
944 __('Table %s has been copied to %s.'),
948 $old = Util
::backquote($db) . '.'
949 . Util
::backquote($table);
950 $message->addParam($old);
952 $newName = (string) $_POST['new_name'];
953 if ($this->dbi
->getLowerCaseNames() === 1) {
954 $newName = strtolower($newName);
957 Current
::$table = $newName;
959 $new = Util
::backquote($targetDb) . '.'
960 . Util
::backquote($newName);
961 $message->addParam($new);
965 * No new name for the table!
967 $message = Message
::error(__('The table name is empty!'));