Translated using Weblate (Portuguese)
[phpmyadmin.git] / src / Operations.php
blobba886db762e96a56bab43b79677dd83c3f8897d2
1 <?php
3 declare(strict_types=1);
5 namespace PhpMyAdmin;
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\Table;
15 use PhpMyAdmin\Triggers\Triggers;
17 use function __;
18 use function array_merge;
19 use function count;
20 use function explode;
21 use function in_array;
22 use function is_scalar;
23 use function is_string;
24 use function mb_strtolower;
25 use function str_replace;
26 use function strtolower;
27 use function urldecode;
29 /**
30 * Set of functions with the operations section in phpMyAdmin
32 class Operations
34 public function __construct(private DatabaseInterface $dbi, private Relation $relation)
38 /**
39 * Run the Procedure definitions and function definitions
41 * to avoid selecting alternatively the current and new db
42 * we would need to modify the CREATE definitions to qualify
43 * the db name
45 * @param string $db database name
47 public function runProcedureAndFunctionDefinitions(string $db, DatabaseName $newDatabaseName): void
49 foreach (Routines::getProcedureNames($this->dbi, $db) as $procedureName) {
50 $this->dbi->selectDb($db);
51 $query = Routines::getProcedureDefinition($this->dbi, $db, $procedureName);
52 if ($query === null) {
53 continue;
56 // collect for later display
57 $GLOBALS['sql_query'] .= "\n" . $query;
58 $this->dbi->selectDb($newDatabaseName);
59 $this->dbi->query($query);
62 foreach (Routines::getFunctionNames($this->dbi, $db) as $functionName) {
63 $this->dbi->selectDb($db);
64 $query = Routines::getFunctionDefinition($this->dbi, $db, $functionName);
65 if ($query === null) {
66 continue;
69 // collect for later display
70 $GLOBALS['sql_query'] .= "\n" . $query;
71 $this->dbi->selectDb($newDatabaseName);
72 $this->dbi->query($query);
76 /**
77 * Create database before copy
79 public function createDbBeforeCopy(UserPrivileges $userPrivileges, DatabaseName $newDatabaseName): void
81 $localQuery = 'CREATE DATABASE IF NOT EXISTS '
82 . Util::backquote($newDatabaseName);
83 if (isset($_POST['db_collation'])) {
84 $localQuery .= ' DEFAULT'
85 . Util::getCharsetQueryPart($_POST['db_collation']);
88 $localQuery .= ';';
89 $GLOBALS['sql_query'] .= $localQuery;
91 // save the original db name because Tracker.php which
92 // may be called under $this->dbi->query() changes \PhpMyAdmin\Current::$database
93 // for some statements, one of which being CREATE DATABASE
94 $originalDb = Current::$database;
95 $this->dbi->query($localQuery);
96 Current::$database = $originalDb;
98 // Set the SQL mode to NO_AUTO_VALUE_ON_ZERO to prevent MySQL from creating
99 // export statements it cannot import
100 $sqlSetMode = "SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO'";
101 $this->dbi->query($sqlSetMode);
103 // rebuild the database list because Table::moveCopy
104 // checks in this list if the target db exists
105 $this->dbi->getDatabaseList()->build($userPrivileges);
109 * Get views as an array and create SQL view stand-in
111 * @param string[] $tables array of all tables in given db or dbs
112 * @param ExportSql $exportSqlPlugin export plugin instance
113 * @param string $db database name
115 * @return string[]
117 public function getViewsAndCreateSqlViewStandIn(
118 array $tables,
119 ExportSql $exportSqlPlugin,
120 string $db,
121 DatabaseName $newDatabaseName,
122 ): array {
123 $views = [];
124 foreach ($tables as $table) {
125 // to be able to rename a db containing views,
126 // first all the views are collected and a stand-in is created
127 // the real views are created after the tables
128 if (! $this->dbi->getTable($db, $table)->isView()) {
129 continue;
132 // If view exists, and 'add drop view' is selected: Drop it!
133 if ($_POST['what'] !== 'nocopy' && isset($_POST['drop_if_exists']) && $_POST['drop_if_exists'] === 'true') {
134 $dropQuery = 'DROP VIEW IF EXISTS '
135 . Util::backquote($newDatabaseName) . '.'
136 . Util::backquote($table);
137 $this->dbi->query($dropQuery);
139 $GLOBALS['sql_query'] .= "\n" . $dropQuery . ';';
142 $views[] = $table;
143 // Create stand-in definition to resolve view dependencies
144 $sqlViewStandin = $exportSqlPlugin->getTableDefStandIn($db, $table);
145 $this->dbi->selectDb($newDatabaseName);
146 $this->dbi->query($sqlViewStandin);
147 $GLOBALS['sql_query'] .= "\n" . $sqlViewStandin;
150 return $views;
154 * Get sql query for copy/rename table and boolean for whether copy/rename or not
156 * @param string[] $tables array of all tables in given db or dbs
157 * @param bool $move whether database name is empty or not
158 * @param string $db database name
160 * @return mixed[] SQL queries for the constraints
162 public function copyTables(array $tables, bool $move, string $db, DatabaseName $newDatabaseName): array
164 $sqlContraints = [];
165 foreach ($tables as $table) {
166 // skip the views; we have created stand-in definitions
167 if ($this->dbi->getTable($db, $table)->isView()) {
168 continue;
171 // value of $what for this table only
172 $copyMode = $_POST['what'];
174 // do not copy the data from a Merge table
175 // note: on the calling FORM, 'data' means 'structure and data'
176 if ($this->dbi->getTable($db, $table)->isMerge()) {
177 if ($copyMode === 'data') {
178 $copyMode = 'structure';
181 if ($copyMode === 'dataonly') {
182 $copyMode = 'nocopy';
186 if ($copyMode === 'nocopy') {
187 continue;
190 // keep the triggers from the original db+table
191 // (third param is empty because delimiters are only intended
192 // for importing via the mysql client or our Import feature)
193 $triggers = Triggers::getDetails($this->dbi, $db, $table);
195 if (
196 ! Table::moveCopy(
197 $db,
198 $table,
199 $newDatabaseName->getName(),
200 $table,
201 $copyMode ?? 'data',
202 $move,
203 'db_copy',
204 isset($_POST['drop_if_exists']) && $_POST['drop_if_exists'] === 'true',
207 $GLOBALS['_error'] = true;
208 break;
211 // apply the triggers to the destination db+table
212 if ($triggers !== []) {
213 $this->dbi->selectDb($newDatabaseName);
214 foreach ($triggers as $trigger) {
215 $createSqlQuery = $trigger->getCreateSql('');
216 $this->dbi->query($createSqlQuery);
217 $GLOBALS['sql_query'] .= "\n" . $createSqlQuery . ';';
221 // this does not apply to a rename operation
222 if (! isset($_POST['add_constraints']) || empty($GLOBALS['sql_constraints_query'])) {
223 continue;
226 $sqlContraints[] = $GLOBALS['sql_constraints_query'];
227 unset($GLOBALS['sql_constraints_query']);
230 return $sqlContraints;
234 * Run the EVENT definition for selected database
236 * to avoid selecting alternatively the current and new db
237 * we would need to modify the CREATE definitions to qualify
238 * the db name
240 * @param string $db database name
242 public function runEventDefinitionsForDb(string $db, DatabaseName $newDatabaseName): void
244 /** @var string[] $eventNames */
245 $eventNames = $this->dbi->fetchResult(
246 'SELECT EVENT_NAME FROM information_schema.EVENTS WHERE EVENT_SCHEMA= '
247 . $this->dbi->quoteString($db) . ';',
250 foreach ($eventNames as $eventName) {
251 $this->dbi->selectDb($db);
252 $query = Events::getDefinition($this->dbi, $db, $eventName);
253 // collect for later display
254 $GLOBALS['sql_query'] .= "\n" . $query;
255 $this->dbi->selectDb($newDatabaseName);
256 $this->dbi->query($query);
261 * Handle the views, return the boolean value whether table rename/copy or not
263 * @param string[] $views views as an array
264 * @param bool $move whether database name is empty or not
265 * @param string $db database name
267 public function handleTheViews(array $views, bool $move, string $db, DatabaseName $newDatabaseName): void
269 // Add DROP IF EXIST to CREATE VIEW query, to remove stand-in VIEW that was created earlier.
270 foreach ($views as $view) {
271 $copyingSucceeded = Table::moveCopy(
272 $db,
273 $view,
274 $newDatabaseName->getName(),
275 $view,
276 'structure',
277 $move,
278 'db_copy',
279 true,
281 if (! $copyingSucceeded) {
282 $GLOBALS['_error'] = true;
283 break;
289 * Adjust the privileges after Renaming the db
291 * @param string $oldDb Database name before renaming
293 public function adjustPrivilegesMoveDb(
294 UserPrivileges $userPrivileges,
295 string $oldDb,
296 DatabaseName $newDatabaseName,
297 ): void {
298 if (
299 ! $userPrivileges->database || ! $userPrivileges->table
300 || ! $userPrivileges->column || ! $userPrivileges->routines
301 || ! $userPrivileges->isReload
303 return;
306 $this->dbi->selectDb('mysql');
307 $newName = str_replace('_', '\_', $newDatabaseName->getName());
308 $oldDb = str_replace('_', '\_', $oldDb);
310 // For Db specific privileges
311 $this->dbi->query('UPDATE ' . Util::backquote('db')
312 . 'SET Db = ' . $this->dbi->quoteString($newName)
313 . ' where Db = ' . $this->dbi->quoteString($oldDb) . ';');
315 // For table specific privileges
316 $this->dbi->query('UPDATE ' . Util::backquote('tables_priv')
317 . 'SET Db = ' . $this->dbi->quoteString($newName)
318 . ' where Db = ' . $this->dbi->quoteString($oldDb) . ';');
320 // For column specific privileges
321 $this->dbi->query('UPDATE ' . Util::backquote('columns_priv')
322 . 'SET Db = ' . $this->dbi->quoteString($newName)
323 . ' where Db = ' . $this->dbi->quoteString($oldDb) . ';');
325 // For procedures specific privileges
326 $this->dbi->query('UPDATE ' . Util::backquote('procs_priv')
327 . 'SET Db = ' . $this->dbi->quoteString($newName)
328 . ' where Db = ' . $this->dbi->quoteString($oldDb) . ';');
330 // Finally FLUSH the new privileges
331 $this->dbi->query('FLUSH PRIVILEGES;');
335 * Adjust the privileges after Copying the db
337 * @param string $oldDb Database name before copying
339 public function adjustPrivilegesCopyDb(
340 UserPrivileges $userPrivileges,
341 string $oldDb,
342 DatabaseName $newDatabaseName,
343 ): void {
344 if (
345 ! $userPrivileges->database || ! $userPrivileges->table
346 || ! $userPrivileges->column || ! $userPrivileges->routines
347 || ! $userPrivileges->isReload
349 return;
352 $this->dbi->selectDb('mysql');
353 $newName = str_replace('_', '\_', $newDatabaseName->getName());
354 $oldDb = str_replace('_', '\_', $oldDb);
356 $queryDbSpecificOld = 'SELECT * FROM '
357 . Util::backquote('db') . ' WHERE '
358 . 'Db = "' . $oldDb . '";';
360 $oldPrivsDb = $this->dbi->fetchResult($queryDbSpecificOld, 0);
362 foreach ($oldPrivsDb as $oldPriv) {
363 $newDbDbPrivsQuery = 'INSERT INTO ' . Util::backquote('db')
364 . ' VALUES("' . $oldPriv[0] . '", "' . $newName . '"';
365 $privCount = count($oldPriv);
366 for ($i = 2; $i < $privCount; $i++) {
367 $newDbDbPrivsQuery .= ', "' . $oldPriv[$i] . '"';
370 $newDbDbPrivsQuery .= ')';
372 $this->dbi->query($newDbDbPrivsQuery);
375 // For Table Specific privileges
376 $queryTableSpecificOld = 'SELECT * FROM '
377 . Util::backquote('tables_priv') . ' WHERE '
378 . 'Db = "' . $oldDb . '";';
380 $oldPrivsTable = $this->dbi->fetchResult($queryTableSpecificOld, 0);
382 foreach ($oldPrivsTable as $oldPriv) {
383 $newDbTablePrivsQuery = 'INSERT INTO ' . Util::backquote(
384 'tables_priv',
385 ) . ' VALUES("' . $oldPriv[0] . '", "' . $newName . '", "'
386 . $oldPriv[2] . '", "' . $oldPriv[3] . '", "' . $oldPriv[4]
387 . '", "' . $oldPriv[5] . '", "' . $oldPriv[6] . '", "'
388 . $oldPriv[7] . '");';
390 $this->dbi->query($newDbTablePrivsQuery);
393 // For Column Specific privileges
394 $queryColSpecificOld = 'SELECT * FROM '
395 . Util::backquote('columns_priv') . ' WHERE '
396 . 'Db = "' . $oldDb . '";';
398 $oldPrivsCol = $this->dbi->fetchResult($queryColSpecificOld, 0);
400 foreach ($oldPrivsCol as $oldPriv) {
401 $newDbColPrivsQuery = 'INSERT INTO ' . Util::backquote(
402 'columns_priv',
403 ) . ' VALUES("' . $oldPriv[0] . '", "' . $newName . '", "'
404 . $oldPriv[2] . '", "' . $oldPriv[3] . '", "' . $oldPriv[4]
405 . '", "' . $oldPriv[5] . '", "' . $oldPriv[6] . '");';
407 $this->dbi->query($newDbColPrivsQuery);
410 // For Procedure Specific privileges
411 $queryProcSpecificOld = 'SELECT * FROM '
412 . Util::backquote('procs_priv') . ' WHERE '
413 . 'Db = "' . $oldDb . '";';
415 $oldPrivsProc = $this->dbi->fetchResult($queryProcSpecificOld, 0);
417 foreach ($oldPrivsProc as $oldPriv) {
418 $newDbProcPrivsQuery = 'INSERT INTO ' . Util::backquote(
419 'procs_priv',
420 ) . ' VALUES("' . $oldPriv[0] . '", "' . $newName . '", "'
421 . $oldPriv[2] . '", "' . $oldPriv[3] . '", "' . $oldPriv[4]
422 . '", "' . $oldPriv[5] . '", "' . $oldPriv[6] . '", "'
423 . $oldPriv[7] . '");';
425 $this->dbi->query($newDbProcPrivsQuery);
428 // Finally FLUSH the new privileges
429 $this->dbi->query('FLUSH PRIVILEGES;');
433 * Create all accumulated constraints
435 * @param mixed[] $sqlConstraints array of sql constraints for the database
437 public function createAllAccumulatedConstraints(array $sqlConstraints, DatabaseName $newDatabaseName): void
439 $this->dbi->selectDb($newDatabaseName);
440 foreach ($sqlConstraints as $query) {
441 $this->dbi->query($query);
442 // and prepare to display them
443 $GLOBALS['sql_query'] .= "\n" . $query;
448 * Duplicate the bookmarks for the db (done once for each db)
450 * @param bool $error whether table rename/copy or not
451 * @param string $db database name
453 public function duplicateBookmarks(bool $error, string $db, DatabaseName $newDatabaseName): void
455 if ($error || $db === $newDatabaseName->getName()) {
456 return;
459 $getFields = ['user', 'label', 'query'];
460 $whereFields = ['dbase' => $db];
461 $newFields = ['dbase' => $newDatabaseName->getName()];
462 Table::duplicateInfo('bookmarkwork', 'bookmark', $getFields, $whereFields, $newFields);
466 * Get array of possible row formats
468 * @return string[][]
470 public function getPossibleRowFormat(): array
472 // the outer array is for engines, the inner array contains the dropdown
473 // option values as keys then the dropdown option labels
475 $possibleRowFormats = [
476 'ARCHIVE' => ['COMPRESSED' => 'COMPRESSED'],
477 'ARIA' => ['FIXED' => 'FIXED', 'DYNAMIC' => 'DYNAMIC', 'PAGE' => 'PAGE'],
478 'MARIA' => ['FIXED' => 'FIXED', 'DYNAMIC' => 'DYNAMIC', 'PAGE' => 'PAGE'],
479 'MYISAM' => ['FIXED' => 'FIXED', 'DYNAMIC' => 'DYNAMIC'],
480 'PBXT' => ['FIXED' => 'FIXED', 'DYNAMIC' => 'DYNAMIC'],
481 'INNODB' => ['COMPACT' => 'COMPACT', 'REDUNDANT' => 'REDUNDANT'],
484 /** @var Innodb $innodbEnginePlugin */
485 $innodbEnginePlugin = StorageEngine::getEngine('Innodb');
486 $innodbPluginVersion = $innodbEnginePlugin->getInnodbPluginVersion();
487 $innodbFileFormat = '';
488 if ($innodbPluginVersion !== '') {
489 $innodbFileFormat = $innodbEnginePlugin->getInnodbFileFormat() ?? '';
493 * Newer MySQL/MariaDB always return empty a.k.a '' on $innodbFileFormat otherwise
494 * old versions of MySQL/MariaDB must be returning something or not empty.
495 * This patch is to support newer MySQL/MariaDB while also for backward compatibilities.
497 if (
498 (strtolower($innodbFileFormat) === 'barracuda') || ($innodbFileFormat == '')
499 && $innodbEnginePlugin->supportsFilePerTable()
501 $possibleRowFormats['INNODB']['DYNAMIC'] = 'DYNAMIC';
502 $possibleRowFormats['INNODB']['COMPRESSED'] = 'COMPRESSED';
505 return $possibleRowFormats;
508 /** @return array<string, string> */
509 public function getPartitionMaintenanceChoices(): array
511 $choices = [
512 'ANALYZE' => __('Analyze'),
513 'CHECK' => __('Check'),
514 'OPTIMIZE' => __('Optimize'),
515 'REBUILD' => __('Rebuild'),
516 'REPAIR' => __('Repair'),
517 'TRUNCATE' => __('Truncate'),
520 $partitionMethod = Partition::getPartitionMethod(Current::$database, Current::$table);
522 // add COALESCE or DROP option to choices array depending on Partition method
523 if (
524 $partitionMethod === 'RANGE'
525 || $partitionMethod === 'RANGE COLUMNS'
526 || $partitionMethod === 'LIST'
527 || $partitionMethod === 'LIST COLUMNS'
529 $choices['DROP'] = __('Drop');
530 } else {
531 $choices['COALESCE'] = __('Coalesce');
534 return $choices;
538 * @param mixed[] $urlParams Array of url parameters.
539 * @param bool $hasRelationFeature If relation feature is enabled.
541 * @return mixed[]
543 public function getForeignersForReferentialIntegrityCheck(
544 array $urlParams,
545 bool $hasRelationFeature,
546 ): array {
547 if (! $hasRelationFeature) {
548 return [];
551 $foreigners = [];
552 $this->dbi->selectDb(Current::$database);
553 $foreign = $this->relation->getForeigners(Current::$database, Current::$table, '', 'internal');
555 foreach ($foreign as $master => $arr) {
556 $joinQuery = 'SELECT '
557 . Util::backquote(Current::$table) . '.*'
558 . ' FROM ' . Util::backquote(Current::$table)
559 . ' LEFT JOIN '
560 . Util::backquote($arr['foreign_db'])
561 . '.'
562 . Util::backquote($arr['foreign_table']);
564 if ($arr['foreign_table'] == Current::$table) {
565 $foreignTable = Current::$table . '1';
566 $joinQuery .= ' AS ' . Util::backquote($foreignTable);
567 } else {
568 $foreignTable = $arr['foreign_table'];
571 $joinQuery .= ' ON '
572 . Util::backquote(Current::$table) . '.'
573 . Util::backquote($master)
574 . ' = '
575 . Util::backquote($arr['foreign_db'])
576 . '.'
577 . Util::backquote($foreignTable) . '.'
578 . Util::backquote($arr['foreign_field'])
579 . ' WHERE '
580 . Util::backquote($arr['foreign_db'])
581 . '.'
582 . Util::backquote($foreignTable) . '.'
583 . Util::backquote($arr['foreign_field'])
584 . ' IS NULL AND '
585 . Util::backquote(Current::$table) . '.'
586 . Util::backquote($master)
587 . ' IS NOT NULL';
588 $thisUrlParams = array_merge(
589 $urlParams,
590 ['sql_query' => $joinQuery, 'sql_signature' => Core::signSqlQuery($joinQuery)],
593 $foreigners[] = [
594 'params' => $thisUrlParams,
595 'master' => $master,
596 'db' => $arr['foreign_db'],
597 'table' => $arr['foreign_table'],
598 'field' => $arr['foreign_field'],
602 return $foreigners;
606 * Get table alters array
608 * @param Table $pmaTable The Table object
609 * @param string $packKeys pack keys
610 * @param string $checksum value of checksum
611 * @param string $pageChecksum value of page checksum
612 * @param string $delayKeyWrite delay key write
613 * @param string $rowFormat row format
614 * @param string $newTblStorageEngine table storage engine
615 * @param string $transactional value of transactional
616 * @param string $tableCollation collation of the table
618 * @return string[]
620 public function getTableAltersArray(
621 Table $pmaTable,
622 string $packKeys,
623 string $checksum,
624 string $pageChecksum,
625 string $delayKeyWrite,
626 string $rowFormat,
627 string $newTblStorageEngine,
628 string $transactional,
629 string $tableCollation,
630 string $tableStorageEngine,
631 ): array {
632 $GLOBALS['auto_increment'] ??= null;
634 $tableAlters = [];
636 if (isset($_POST['comment']) && urldecode($_POST['prev_comment']) !== $_POST['comment']) {
637 $tableAlters[] = 'COMMENT = ' . $this->dbi->quoteString($_POST['comment']);
640 if (
641 $newTblStorageEngine !== ''
642 && mb_strtolower($newTblStorageEngine) !== mb_strtolower($tableStorageEngine)
644 $tableAlters[] = 'ENGINE = ' . $newTblStorageEngine;
647 if (! empty($_POST['tbl_collation']) && $_POST['tbl_collation'] !== $tableCollation) {
648 $tableAlters[] = 'DEFAULT '
649 . Util::getCharsetQueryPart($_POST['tbl_collation']);
652 if (
653 $pmaTable->isEngine(['MYISAM', 'ARIA', 'ISAM'])
654 && isset($_POST['new_pack_keys'])
655 && $_POST['new_pack_keys'] != $packKeys
657 $tableAlters[] = 'pack_keys = ' . $_POST['new_pack_keys'];
660 $newChecksum = empty($_POST['new_checksum']) ? '0' : '1';
661 if ($pmaTable->isEngine(['MYISAM', 'ARIA']) && $newChecksum !== $checksum) {
662 $tableAlters[] = 'checksum = ' . $newChecksum;
665 $newTransactional = empty($_POST['new_transactional']) ? '0' : '1';
666 if ($pmaTable->isEngine('ARIA') && $newTransactional !== $transactional) {
667 $tableAlters[] = 'TRANSACTIONAL = ' . $newTransactional;
670 $newPageChecksum = empty($_POST['new_page_checksum']) ? '0' : '1';
671 if ($pmaTable->isEngine('ARIA') && $newPageChecksum !== $pageChecksum) {
672 $tableAlters[] = 'PAGE_CHECKSUM = ' . $newPageChecksum;
675 $newDelayKeyWrite = empty($_POST['new_delay_key_write']) ? '0' : '1';
676 if ($pmaTable->isEngine(['MYISAM', 'ARIA']) && $newDelayKeyWrite !== $delayKeyWrite) {
677 $tableAlters[] = 'delay_key_write = ' . $newDelayKeyWrite;
680 if (
681 $pmaTable->isEngine(['MYISAM', 'ARIA', 'INNODB', 'PBXT', 'ROCKSDB'])
682 && ! empty($_POST['new_auto_increment'])
683 && (! isset($GLOBALS['auto_increment'])
684 || $_POST['new_auto_increment'] !== $GLOBALS['auto_increment'])
685 && $_POST['new_auto_increment'] !== $_POST['hidden_auto_increment']
687 $tableAlters[] = 'auto_increment = ' . (int) $_POST['new_auto_increment'];
690 if (! empty($_POST['new_row_format'])) {
691 $newRowFormat = $_POST['new_row_format'];
692 $newRowFormatLower = mb_strtolower($newRowFormat);
693 if (
694 $pmaTable->isEngine(['MYISAM', 'ARIA', 'INNODB', 'PBXT'])
695 && ($rowFormat === '' || $newRowFormatLower !== mb_strtolower($rowFormat))
696 && in_array($newRowFormat, ['DEFAULT', 'DYNAMIC', 'FIXED', 'COMPRESSED', 'REDUNDANT', 'COMPACT'], true)
698 $tableAlters[] = 'ROW_FORMAT = ' . $newRowFormat;
702 return $tableAlters;
706 * Get warning messages array
708 * @return string[]
710 public function getWarningMessagesArray(mixed $newTableStorageEngine): array
712 $warningMessages = [];
713 foreach ($this->dbi->getWarnings() as $warning) {
714 // In MariaDB 5.1.44, when altering a table from Maria to MyISAM
715 // and if TRANSACTIONAL was set, the system reports an error;
716 // I discussed with a Maria developer and he agrees that this
717 // should not be reported with a Level of Error, so here
718 // I just ignore it. But there are other 1478 messages
719 // that it's better to show.
720 if (
721 $newTableStorageEngine === 'MyISAM'
722 && $warning->code === 1478
723 && $warning->level === 'Error'
725 continue;
728 $warningMessages[] = (string) $warning;
731 return $warningMessages;
735 * Adjust the privileges after renaming/moving a table
737 * @param string $oldDb Database name before table renaming/moving table
738 * @param string $oldTable Table name before table renaming/moving table
739 * @param string $newDb Database name after table renaming/ moving table
740 * @param string $newTable Table name after table renaming/moving table
742 public function adjustPrivilegesRenameOrMoveTable(
743 UserPrivileges $userPrivileges,
744 string $oldDb,
745 string $oldTable,
746 string $newDb,
747 string $newTable,
748 ): void {
749 if (! $userPrivileges->table || ! $userPrivileges->column || ! $userPrivileges->isReload) {
750 return;
753 $this->dbi->selectDb('mysql');
755 // For table specific privileges
756 $this->dbi->query('UPDATE ' . Util::backquote('tables_priv')
757 . 'SET Db = ' . $this->dbi->quoteString($newDb)
758 . ', Table_name = ' . $this->dbi->quoteString($newTable)
759 . ' where Db = ' . $this->dbi->quoteString($oldDb)
760 . ' AND Table_name = ' . $this->dbi->quoteString($oldTable)
761 . ';');
763 // For column specific privileges
764 $this->dbi->query('UPDATE ' . Util::backquote('columns_priv')
765 . 'SET Db = ' . $this->dbi->quoteString($newDb)
766 . ', Table_name = ' . $this->dbi->quoteString($newTable)
767 . ' where Db = ' . $this->dbi->quoteString($oldDb)
768 . ' AND Table_name = ' . $this->dbi->quoteString($oldTable)
769 . ';');
771 // Finally FLUSH the new privileges
772 $this->dbi->query('FLUSH PRIVILEGES;');
776 * Adjust the privileges after copying a table
778 * @param string $oldDb Database name before table copying
779 * @param string $oldTable Table name before table copying
780 * @param string $newDb Database name after table copying
781 * @param string $newTable Table name after table copying
783 public function adjustPrivilegesCopyTable(
784 UserPrivileges $userPrivileges,
785 string $oldDb,
786 string $oldTable,
787 string $newDb,
788 string $newTable,
789 ): void {
790 if (! $userPrivileges->table || ! $userPrivileges->column || ! $userPrivileges->isReload) {
791 return;
794 $this->dbi->selectDb('mysql');
796 // For Table Specific privileges
797 $queryTableSpecificOld = 'SELECT * FROM '
798 . Util::backquote('tables_priv') . ' where '
799 . 'Db = "' . $oldDb . '" AND Table_name = "' . $oldTable . '";';
801 $oldPrivsTable = $this->dbi->fetchResult($queryTableSpecificOld, 0);
803 foreach ($oldPrivsTable as $oldPriv) {
804 $newDbTablePrivsQuery = 'INSERT INTO '
805 . Util::backquote('tables_priv') . ' VALUES("'
806 . $oldPriv[0] . '", "' . $newDb . '", "' . $oldPriv[2] . '", "'
807 . $newTable . '", "' . $oldPriv[4] . '", "' . $oldPriv[5]
808 . '", "' . $oldPriv[6] . '", "' . $oldPriv[7] . '");';
810 $this->dbi->query($newDbTablePrivsQuery);
813 // For Column Specific privileges
814 $queryColSpecificOld = 'SELECT * FROM '
815 . Util::backquote('columns_priv') . ' WHERE '
816 . 'Db = "' . $oldDb . '" AND Table_name = "' . $oldTable . '";';
818 $oldPrivsCol = $this->dbi->fetchResult($queryColSpecificOld, 0);
820 foreach ($oldPrivsCol as $oldPriv) {
821 $newDbColPrivsQuery = 'INSERT INTO '
822 . Util::backquote('columns_priv') . ' VALUES("'
823 . $oldPriv[0] . '", "' . $newDb . '", "' . $oldPriv[2] . '", "'
824 . $newTable . '", "' . $oldPriv[4] . '", "' . $oldPriv[5]
825 . '", "' . $oldPriv[6] . '");';
827 $this->dbi->query($newDbColPrivsQuery);
830 // Finally FLUSH the new privileges
831 $this->dbi->query('FLUSH PRIVILEGES;');
835 * Change all collations and character sets of all columns in table
837 * @param string $db Database name
838 * @param string $table Table name
839 * @param string $tableCollation Collation Name
841 public function changeAllColumnsCollation(string $db, string $table, string $tableCollation): void
843 $this->dbi->selectDb($db);
845 $changeAllCollationsQuery = 'ALTER TABLE '
846 . Util::backquote($table)
847 . ' CONVERT TO';
849 [$charset] = explode('_', $tableCollation);
851 $changeAllCollationsQuery .= ' CHARACTER SET ' . $charset
852 . ($charset === $tableCollation ? '' : ' COLLATE ' . $tableCollation);
854 $this->dbi->query($changeAllCollationsQuery);
858 * Move or copy a table
860 * @param string $db current database name
861 * @param string $table current table name
863 public function moveOrCopyTable(UserPrivileges $userPrivileges, string $db, string $table): Message
866 * Selects the database to work with
868 $this->dbi->selectDb($db);
871 * $_POST['target_db'] could be empty in case we came from an input field
872 * (when there are many databases, no drop-down)
874 $targetDb = $db;
875 if (isset($_POST['target_db']) && is_string($_POST['target_db']) && $_POST['target_db'] !== '') {
876 $targetDb = $_POST['target_db'];
880 * A target table name has been sent to this script -> do the work
882 if (isset($_POST['new_name']) && is_scalar($_POST['new_name']) && (string) $_POST['new_name'] !== '') {
883 if ($db === $targetDb && $table == $_POST['new_name']) {
884 if (isset($_POST['submit_move'])) {
885 $message = Message::error(__('Can\'t move table to same one!'));
886 } else {
887 $message = Message::error(__('Can\'t copy table to same one!'));
889 } else {
890 Table::moveCopy(
891 $db,
892 $table,
893 $targetDb,
894 (string) $_POST['new_name'],
895 $_POST['what'],
896 isset($_POST['submit_move']),
897 'one_table',
898 isset($_POST['drop_if_exists']) && $_POST['drop_if_exists'] === 'true',
901 if (! empty($_POST['adjust_privileges'])) {
902 if (isset($_POST['submit_move'])) {
903 $this->adjustPrivilegesRenameOrMoveTable(
904 $userPrivileges,
905 $db,
906 $table,
907 $targetDb,
908 (string) $_POST['new_name'],
910 } else {
911 $this->adjustPrivilegesCopyTable(
912 $userPrivileges,
913 $db,
914 $table,
915 $targetDb,
916 (string) $_POST['new_name'],
920 if (isset($_POST['submit_move'])) {
921 $message = Message::success(
923 'Table %s has been moved to %s. Privileges have been adjusted.',
926 } else {
927 $message = Message::success(
929 'Table %s has been copied to %s. Privileges have been adjusted.',
933 } elseif (isset($_POST['submit_move'])) {
934 $message = Message::success(
935 __('Table %s has been moved to %s.'),
937 } else {
938 $message = Message::success(
939 __('Table %s has been copied to %s.'),
943 $old = Util::backquote($db) . '.'
944 . Util::backquote($table);
945 $message->addParam($old);
947 $newName = (string) $_POST['new_name'];
948 if ($this->dbi->getLowerCaseNames() === 1) {
949 $newName = strtolower($newName);
952 Current::$table = $newName;
954 $new = Util::backquote($targetDb) . '.'
955 . Util::backquote($newName);
956 $message->addParam($new);
958 } else {
960 * No new name for the table!
962 $message = Message::error(__('The table name is empty!'));
965 return $message;