dbi = $dbi; $this->relation = $relation; } /** * Run the Procedure definitions and function definitions * * to avoid selecting alternatively the current and new db * we would need to modify the CREATE definitions to qualify * the db name * * @param string $db database name * * @return void */ public function runProcedureAndFunctionDefinitions($db) { $procedure_names = $this->dbi->getProceduresOrFunctions($db, 'PROCEDURE'); if ($procedure_names) { foreach ($procedure_names as $procedure_name) { $this->dbi->selectDb($db); $tmp_query = $this->dbi->getDefinition( $db, 'PROCEDURE', $procedure_name ); if ($tmp_query !== null) { // collect for later display $GLOBALS['sql_query'] .= "\n" . $tmp_query; $this->dbi->selectDb($_POST['newname']); $this->dbi->query($tmp_query); } } } $function_names = $this->dbi->getProceduresOrFunctions($db, 'FUNCTION'); if ($function_names) { foreach ($function_names as $function_name) { $this->dbi->selectDb($db); $tmp_query = $this->dbi->getDefinition( $db, 'FUNCTION', $function_name ); if ($tmp_query !== null) { // collect for later display $GLOBALS['sql_query'] .= "\n" . $tmp_query; $this->dbi->selectDb($_POST['newname']); $this->dbi->query($tmp_query); } } } } /** * Create database before copy * * @return void */ public function createDbBeforeCopy() { $local_query = 'CREATE DATABASE IF NOT EXISTS ' . Util::backquote($_POST['newname']); if (isset($_POST['db_collation'])) { $local_query .= ' DEFAULT' . Util::getCharsetQueryPart($_POST['db_collation']); } $local_query .= ';'; $GLOBALS['sql_query'] .= $local_query; // save the original db name because Tracker.php which // may be called under $this->dbi->query() changes $GLOBALS['db'] // for some statements, one of which being CREATE DATABASE $original_db = $GLOBALS['db']; $this->dbi->query($local_query); $GLOBALS['db'] = $original_db; // Set the SQL mode to NO_AUTO_VALUE_ON_ZERO to prevent MySQL from creating // export statements it cannot import $sql_set_mode = "SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO'"; $this->dbi->query($sql_set_mode); // rebuild the database list because Table::moveCopy // checks in this list if the target db exists $GLOBALS['dblist']->databases->build(); } /** * Get views as an array and create SQL view stand-in * * @param array $tables_full array of all tables in given db or dbs * @param ExportSql $export_sql_plugin export plugin instance * @param string $db database name * * @return array */ public function getViewsAndCreateSqlViewStandIn( array $tables_full, $export_sql_plugin, $db ) { $views = []; foreach ($tables_full as $each_table => $tmp) { // to be able to rename a db containing views, // first all the views are collected and a stand-in is created // the real views are created after the tables if ($this->dbi->getTable($db, (string) $each_table)->isView()) { // If view exists, and 'add drop view' is selected: Drop it! if ($_POST['what'] != 'nocopy' && isset($_POST['drop_if_exists']) && $_POST['drop_if_exists'] == 'true' ) { $drop_query = 'DROP VIEW IF EXISTS ' . Util::backquote($_POST['newname']) . '.' . Util::backquote($each_table); $this->dbi->query($drop_query); $GLOBALS['sql_query'] .= "\n" . $drop_query . ';'; } $views[] = $each_table; // Create stand-in definition to resolve view dependencies $sql_view_standin = $export_sql_plugin->getTableDefStandIn( $db, $each_table, "\n" ); $this->dbi->selectDb($_POST['newname']); $this->dbi->query($sql_view_standin); $GLOBALS['sql_query'] .= "\n" . $sql_view_standin; } } return $views; } /** * Get sql query for copy/rename table and boolean for whether copy/rename or not * * @param array $tables_full array of all tables in given db or dbs * @param bool $move whether database name is empty or not * @param string $db database name * * @return array SQL queries for the constraints */ public function copyTables(array $tables_full, $move, $db) { $sqlContraints = []; foreach ($tables_full as $each_table => $tmp) { // skip the views; we have created stand-in definitions if ($this->dbi->getTable($db, (string) $each_table)->isView()) { continue; } // value of $what for this table only $this_what = $_POST['what']; // do not copy the data from a Merge table // note: on the calling FORM, 'data' means 'structure and data' if ($this->dbi->getTable($db, (string) $each_table)->isMerge()) { if ($this_what == 'data') { $this_what = 'structure'; } if ($this_what == 'dataonly') { $this_what = 'nocopy'; } } if ($this_what != 'nocopy') { // keep the triggers from the original db+table // (third param is empty because delimiters are only intended // for importing via the mysql client or our Import feature) $triggers = $this->dbi->getTriggers($db, (string) $each_table, ''); if (! Table::moveCopy( $db, $each_table, $_POST['newname'], $each_table, ($this_what ?? 'data'), $move, 'db_copy' )) { $GLOBALS['_error'] = true; break; } // apply the triggers to the destination db+table if ($triggers) { $this->dbi->selectDb($_POST['newname']); foreach ($triggers as $trigger) { $this->dbi->query($trigger['create']); $GLOBALS['sql_query'] .= "\n" . $trigger['create'] . ';'; } } // this does not apply to a rename operation if (isset($_POST['add_constraints']) && ! empty($GLOBALS['sql_constraints_query']) ) { $sqlContraints[] = $GLOBALS['sql_constraints_query']; unset($GLOBALS['sql_constraints_query']); } } } return $sqlContraints; } /** * Run the EVENT definition for selected database * * to avoid selecting alternatively the current and new db * we would need to modify the CREATE definitions to qualify * the db name * * @param string $db database name * * @return void */ public function runEventDefinitionsForDb($db) { $event_names = $this->dbi->fetchResult( 'SELECT EVENT_NAME FROM information_schema.EVENTS WHERE EVENT_SCHEMA= \'' . $this->dbi->escapeString($db) . '\';' ); if ($event_names) { foreach ($event_names as $event_name) { $this->dbi->selectDb($db); $tmp_query = $this->dbi->getDefinition($db, 'EVENT', $event_name); // collect for later display $GLOBALS['sql_query'] .= "\n" . $tmp_query; $this->dbi->selectDb($_POST['newname']); $this->dbi->query($tmp_query); } } } /** * Handle the views, return the boolean value whether table rename/copy or not * * @param array $views views as an array * @param bool $move whether database name is empty or not * @param string $db database name * * @return void */ public function handleTheViews(array $views, $move, $db) { // temporarily force to add DROP IF EXIST to CREATE VIEW query, // to remove stand-in VIEW that was created earlier // ( $_POST['drop_if_exists'] is used in moveCopy() ) if (isset($_POST['drop_if_exists'])) { $temp_drop_if_exists = $_POST['drop_if_exists']; } $_POST['drop_if_exists'] = 'true'; foreach ($views as $view) { $copying_succeeded = Table::moveCopy( $db, $view, $_POST['newname'], $view, 'structure', $move, 'db_copy' ); if (! $copying_succeeded) { $GLOBALS['_error'] = true; break; } } unset($_POST['drop_if_exists']); if (isset($temp_drop_if_exists)) { // restore previous value $_POST['drop_if_exists'] = $temp_drop_if_exists; } } /** * Adjust the privileges after Renaming the db * * @param string $oldDb Database name before renaming * @param string $newname New Database name requested * * @return void */ public function adjustPrivilegesMoveDb($oldDb, $newname) { if ($GLOBALS['db_priv'] && $GLOBALS['table_priv'] && $GLOBALS['col_priv'] && $GLOBALS['proc_priv'] && $GLOBALS['is_reload_priv'] ) { $this->dbi->selectDb('mysql'); $newname = str_replace('_', '\_', $newname); $oldDb = str_replace('_', '\_', $oldDb); // For Db specific privileges $query_db_specific = 'UPDATE ' . Util::backquote('db') . 'SET Db = \'' . $this->dbi->escapeString($newname) . '\' where Db = \'' . $this->dbi->escapeString($oldDb) . '\';'; $this->dbi->query($query_db_specific); // For table specific privileges $query_table_specific = 'UPDATE ' . Util::backquote('tables_priv') . 'SET Db = \'' . $this->dbi->escapeString($newname) . '\' where Db = \'' . $this->dbi->escapeString($oldDb) . '\';'; $this->dbi->query($query_table_specific); // For column specific privileges $query_col_specific = 'UPDATE ' . Util::backquote('columns_priv') . 'SET Db = \'' . $this->dbi->escapeString($newname) . '\' where Db = \'' . $this->dbi->escapeString($oldDb) . '\';'; $this->dbi->query($query_col_specific); // For procedures specific privileges $query_proc_specific = 'UPDATE ' . Util::backquote('procs_priv') . 'SET Db = \'' . $this->dbi->escapeString($newname) . '\' where Db = \'' . $this->dbi->escapeString($oldDb) . '\';'; $this->dbi->query($query_proc_specific); // Finally FLUSH the new privileges $flush_query = 'FLUSH PRIVILEGES;'; $this->dbi->query($flush_query); } } /** * Adjust the privileges after Copying the db * * @param string $oldDb Database name before copying * @param string $newname New Database name requested * * @return void */ public function adjustPrivilegesCopyDb($oldDb, $newname) { if ($GLOBALS['db_priv'] && $GLOBALS['table_priv'] && $GLOBALS['col_priv'] && $GLOBALS['proc_priv'] && $GLOBALS['is_reload_priv'] ) { $this->dbi->selectDb('mysql'); $newname = str_replace('_', '\_', $newname); $oldDb = str_replace('_', '\_', $oldDb); $query_db_specific_old = 'SELECT * FROM ' . Util::backquote('db') . ' WHERE ' . 'Db = "' . $oldDb . '";'; $old_privs_db = $this->dbi->fetchResult($query_db_specific_old, 0); foreach ($old_privs_db as $old_priv) { $newDb_db_privs_query = 'INSERT INTO ' . Util::backquote('db') . ' VALUES("' . $old_priv[0] . '", "' . $newname . '"'; $privCount = count($old_priv); for ($i = 2; $i < $privCount; $i++) { $newDb_db_privs_query .= ', "' . $old_priv[$i] . '"'; } $newDb_db_privs_query .= ')'; $this->dbi->query($newDb_db_privs_query); } // For Table Specific privileges $query_table_specific_old = 'SELECT * FROM ' . Util::backquote('tables_priv') . ' WHERE ' . 'Db = "' . $oldDb . '";'; $old_privs_table = $this->dbi->fetchResult( $query_table_specific_old, 0 ); foreach ($old_privs_table as $old_priv) { $newDb_table_privs_query = 'INSERT INTO ' . Util::backquote( 'tables_priv' ) . ' VALUES("' . $old_priv[0] . '", "' . $newname . '", "' . $old_priv[2] . '", "' . $old_priv[3] . '", "' . $old_priv[4] . '", "' . $old_priv[5] . '", "' . $old_priv[6] . '", "' . $old_priv[7] . '");'; $this->dbi->query($newDb_table_privs_query); } // For Column Specific privileges $query_col_specific_old = 'SELECT * FROM ' . Util::backquote('columns_priv') . ' WHERE ' . 'Db = "' . $oldDb . '";'; $old_privs_col = $this->dbi->fetchResult( $query_col_specific_old, 0 ); foreach ($old_privs_col as $old_priv) { $newDb_col_privs_query = 'INSERT INTO ' . Util::backquote( 'columns_priv' ) . ' VALUES("' . $old_priv[0] . '", "' . $newname . '", "' . $old_priv[2] . '", "' . $old_priv[3] . '", "' . $old_priv[4] . '", "' . $old_priv[5] . '", "' . $old_priv[6] . '");'; $this->dbi->query($newDb_col_privs_query); } // For Procedure Specific privileges $query_proc_specific_old = 'SELECT * FROM ' . Util::backquote('procs_priv') . ' WHERE ' . 'Db = "' . $oldDb . '";'; $old_privs_proc = $this->dbi->fetchResult( $query_proc_specific_old, 0 ); foreach ($old_privs_proc as $old_priv) { $newDb_proc_privs_query = 'INSERT INTO ' . Util::backquote( 'procs_priv' ) . ' VALUES("' . $old_priv[0] . '", "' . $newname . '", "' . $old_priv[2] . '", "' . $old_priv[3] . '", "' . $old_priv[4] . '", "' . $old_priv[5] . '", "' . $old_priv[6] . '", "' . $old_priv[7] . '");'; $this->dbi->query($newDb_proc_privs_query); } // Finally FLUSH the new privileges $flush_query = 'FLUSH PRIVILEGES;'; $this->dbi->query($flush_query); } } /** * Create all accumulated constraints * * @param array $sqlConstratints array of sql constraints for the database * * @return void */ public function createAllAccumulatedConstraints(array $sqlConstratints) { $this->dbi->selectDb($_POST['newname']); foreach ($sqlConstratints as $one_query) { $this->dbi->query($one_query); // and prepare to display them $GLOBALS['sql_query'] .= "\n" . $one_query; } } /** * Duplicate the bookmarks for the db (done once for each db) * * @param bool $_error whether table rename/copy or not * @param string $db database name * * @return void */ public function duplicateBookmarks($_error, $db) { if (! $_error && $db != $_POST['newname']) { $get_fields = [ 'user', 'label', 'query', ]; $where_fields = ['dbase' => $db]; $new_fields = ['dbase' => $_POST['newname']]; Table::duplicateInfo( 'bookmarkwork', 'bookmark', $get_fields, $where_fields, $new_fields ); } } /** * Get array of possible row formats * * @return array */ public function getPossibleRowFormat() { // the outer array is for engines, the inner array contains the dropdown // option values as keys then the dropdown option labels $possible_row_formats = [ 'ARCHIVE' => [ 'COMPRESSED' => 'COMPRESSED', ], 'ARIA' => [ 'FIXED' => 'FIXED', 'DYNAMIC' => 'DYNAMIC', 'PAGE' => 'PAGE', ], 'MARIA' => [ 'FIXED' => 'FIXED', 'DYNAMIC' => 'DYNAMIC', 'PAGE' => 'PAGE', ], 'MYISAM' => [ 'FIXED' => 'FIXED', 'DYNAMIC' => 'DYNAMIC', ], 'PBXT' => [ 'FIXED' => 'FIXED', 'DYNAMIC' => 'DYNAMIC', ], 'INNODB' => [ 'COMPACT' => 'COMPACT', 'REDUNDANT' => 'REDUNDANT', ], ]; /** @var Innodb $innodbEnginePlugin */ $innodbEnginePlugin = StorageEngine::getEngine('Innodb'); $innodbPluginVersion = $innodbEnginePlugin->getInnodbPluginVersion(); if (! empty($innodbPluginVersion)) { $innodb_file_format = $innodbEnginePlugin->getInnodbFileFormat(); } else { $innodb_file_format = ''; } /** * Newer MySQL/MariaDB always return empty a.k.a '' on $innodb_file_format otherwise * old versions of MySQL/MariaDB must be returning something or not empty. * This patch is to support newer MySQL/MariaDB while also for backward compatibilities. */ if (($innodb_file_format == 'Barracuda') || ($innodb_file_format == '') && $innodbEnginePlugin->supportsFilePerTable() ) { $possible_row_formats['INNODB']['DYNAMIC'] = 'DYNAMIC'; $possible_row_formats['INNODB']['COMPRESSED'] = 'COMPRESSED'; } return $possible_row_formats; } /** * @param Table $tableObject Table object * * @return array */ public function getMaintenanceActions(Table $tableObject): array { global $table; $actions = []; if ($tableObject->isEngine(['MYISAM', 'ARIA', 'INNODB', 'BERKELEYDB', 'TOKUDB'])) { $actions[] = [ 'params' => [ 'sql_query' => 'ANALYZE TABLE ' . Util::backquote($table), 'table_maintenance' => 'Go', ], 'message' => __('Analyze table'), 'link' => 'ANALYZE_TABLE', ]; } if ($tableObject->isEngine(['MYISAM', 'ARIA', 'INNODB', 'TOKUDB'])) { $actions[] = [ 'params' => [ 'sql_query' => 'CHECK TABLE ' . Util::backquote($table), 'table_maintenance' => 'Go', ], 'message' => __('Check table'), 'link' => 'CHECK_TABLE', ]; } $actions[] = [ 'params' => [ 'sql_query' => 'CHECKSUM TABLE ' . Util::backquote($table), 'table_maintenance' => 'Go', ], 'message' => __('Checksum table'), 'link' => 'CHECKSUM_TABLE', ]; if ($tableObject->isEngine(['INNODB'])) { $actions[] = [ 'params' => [ 'sql_query' => 'ALTER TABLE ' . Util::backquote($table) . ' ENGINE = InnoDB;', ], 'message' => __('Defragment table'), 'link' => 'InnoDB_File_Defragmenting', ]; } $actions[] = [ 'params' => [ 'sql_query' => 'FLUSH TABLE ' . Util::backquote($table), 'message_to_show' => sprintf( __('Table %s has been flushed.'), htmlspecialchars($table) ), 'reload' => true, ], 'message' => __('Flush the table (FLUSH)'), 'link' => 'FLUSH', ]; if ($tableObject->isEngine(['MYISAM', 'ARIA', 'INNODB', 'BERKELEYDB', 'TOKUDB'])) { $actions[] = [ 'params' => [ 'sql_query' => 'OPTIMIZE TABLE ' . Util::backquote($table), 'table_maintenance' => 'Go', ], 'message' => __('Optimize table'), 'link' => 'OPTIMIZE_TABLE', ]; } if ($tableObject->isEngine(['MYISAM', 'ARIA'])) { $actions[] = [ 'params' => [ 'sql_query' => 'REPAIR TABLE ' . Util::backquote($table), 'table_maintenance' => 'Go', ], 'message' => __('Repair table'), 'link' => 'REPAIR_TABLE', ]; } return $actions; } /** * Get the HTML link for Truncate table, Drop table and Drop db * * @param array $url_params url parameter array for delete data or table * @param string $syntax TRUNCATE_TABLE or DROP_TABLE or DROP_DATABASE * @param string $link link to be shown * @param string $htmlId id of the link * * @return string html output */ public function getDeleteDataOrTablelink(array $url_params, $syntax, $link, $htmlId) { return '