Operations.php 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997
  1. <?php
  2. declare(strict_types=1);
  3. namespace PhpMyAdmin;
  4. use PhpMyAdmin\ConfigStorage\Relation;
  5. use PhpMyAdmin\Engines\Innodb;
  6. use PhpMyAdmin\Partitioning\Partition;
  7. use PhpMyAdmin\Plugins\Export\ExportSql;
  8. use function __;
  9. use function array_merge;
  10. use function count;
  11. use function explode;
  12. use function is_scalar;
  13. use function is_string;
  14. use function mb_strtolower;
  15. use function str_replace;
  16. use function strlen;
  17. use function strtolower;
  18. use function urldecode;
  19. /**
  20. * Set of functions with the operations section in phpMyAdmin
  21. */
  22. class Operations
  23. {
  24. /** @var Relation */
  25. private $relation;
  26. /** @var DatabaseInterface */
  27. private $dbi;
  28. /**
  29. * @param DatabaseInterface $dbi DatabaseInterface object
  30. * @param Relation $relation Relation object
  31. */
  32. public function __construct(DatabaseInterface $dbi, Relation $relation)
  33. {
  34. $this->dbi = $dbi;
  35. $this->relation = $relation;
  36. }
  37. /**
  38. * Run the Procedure definitions and function definitions
  39. *
  40. * to avoid selecting alternatively the current and new db
  41. * we would need to modify the CREATE definitions to qualify
  42. * the db name
  43. *
  44. * @param string $db database name
  45. */
  46. public function runProcedureAndFunctionDefinitions($db): void
  47. {
  48. $procedure_names = $this->dbi->getProceduresOrFunctions($db, 'PROCEDURE');
  49. if ($procedure_names) {
  50. foreach ($procedure_names as $procedure_name) {
  51. $this->dbi->selectDb($db);
  52. $tmp_query = $this->dbi->getDefinition($db, 'PROCEDURE', $procedure_name);
  53. if ($tmp_query === null) {
  54. continue;
  55. }
  56. // collect for later display
  57. $GLOBALS['sql_query'] .= "\n" . $tmp_query;
  58. $this->dbi->selectDb($_POST['newname']);
  59. $this->dbi->query($tmp_query);
  60. }
  61. }
  62. $function_names = $this->dbi->getProceduresOrFunctions($db, 'FUNCTION');
  63. if (! $function_names) {
  64. return;
  65. }
  66. foreach ($function_names as $function_name) {
  67. $this->dbi->selectDb($db);
  68. $tmp_query = $this->dbi->getDefinition($db, 'FUNCTION', $function_name);
  69. if ($tmp_query === null) {
  70. continue;
  71. }
  72. // collect for later display
  73. $GLOBALS['sql_query'] .= "\n" . $tmp_query;
  74. $this->dbi->selectDb($_POST['newname']);
  75. $this->dbi->query($tmp_query);
  76. }
  77. }
  78. /**
  79. * Create database before copy
  80. */
  81. public function createDbBeforeCopy(): void
  82. {
  83. $local_query = 'CREATE DATABASE IF NOT EXISTS '
  84. . Util::backquote($_POST['newname']);
  85. if (isset($_POST['db_collation'])) {
  86. $local_query .= ' DEFAULT'
  87. . Util::getCharsetQueryPart($_POST['db_collation'] ?? '');
  88. }
  89. $local_query .= ';';
  90. $GLOBALS['sql_query'] .= $local_query;
  91. // save the original db name because Tracker.php which
  92. // may be called under $this->dbi->query() changes $GLOBALS['db']
  93. // for some statements, one of which being CREATE DATABASE
  94. $original_db = $GLOBALS['db'];
  95. $this->dbi->query($local_query);
  96. $GLOBALS['db'] = $original_db;
  97. // Set the SQL mode to NO_AUTO_VALUE_ON_ZERO to prevent MySQL from creating
  98. // export statements it cannot import
  99. $sql_set_mode = "SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO'";
  100. $this->dbi->query($sql_set_mode);
  101. // rebuild the database list because Table::moveCopy
  102. // checks in this list if the target db exists
  103. $GLOBALS['dblist']->databases->build();
  104. }
  105. /**
  106. * Get views as an array and create SQL view stand-in
  107. *
  108. * @param string[] $tables array of all tables in given db or dbs
  109. * @param ExportSql $export_sql_plugin export plugin instance
  110. * @param string $db database name
  111. *
  112. * @return array
  113. */
  114. public function getViewsAndCreateSqlViewStandIn(
  115. array $tables,
  116. $export_sql_plugin,
  117. $db
  118. ) {
  119. $views = [];
  120. foreach ($tables as $table) {
  121. // to be able to rename a db containing views,
  122. // first all the views are collected and a stand-in is created
  123. // the real views are created after the tables
  124. if (! $this->dbi->getTable($db, $table)->isView()) {
  125. continue;
  126. }
  127. // If view exists, and 'add drop view' is selected: Drop it!
  128. if ($_POST['what'] !== 'nocopy' && isset($_POST['drop_if_exists']) && $_POST['drop_if_exists'] === 'true') {
  129. $drop_query = 'DROP VIEW IF EXISTS '
  130. . Util::backquote($_POST['newname']) . '.'
  131. . Util::backquote($table);
  132. $this->dbi->query($drop_query);
  133. $GLOBALS['sql_query'] .= "\n" . $drop_query . ';';
  134. }
  135. $views[] = $table;
  136. // Create stand-in definition to resolve view dependencies
  137. $sql_view_standin = $export_sql_plugin->getTableDefStandIn($db, $table, "\n");
  138. $this->dbi->selectDb($_POST['newname']);
  139. $this->dbi->query($sql_view_standin);
  140. $GLOBALS['sql_query'] .= "\n" . $sql_view_standin;
  141. }
  142. return $views;
  143. }
  144. /**
  145. * Get sql query for copy/rename table and boolean for whether copy/rename or not
  146. *
  147. * @param string[] $tables array of all tables in given db or dbs
  148. * @param bool $move whether database name is empty or not
  149. * @param string $db database name
  150. *
  151. * @return array SQL queries for the constraints
  152. */
  153. public function copyTables(array $tables, $move, $db)
  154. {
  155. $sqlContraints = [];
  156. foreach ($tables as $table) {
  157. // skip the views; we have created stand-in definitions
  158. if ($this->dbi->getTable($db, $table)->isView()) {
  159. continue;
  160. }
  161. // value of $what for this table only
  162. $this_what = $_POST['what'];
  163. // do not copy the data from a Merge table
  164. // note: on the calling FORM, 'data' means 'structure and data'
  165. if ($this->dbi->getTable($db, $table)->isMerge()) {
  166. if ($this_what === 'data') {
  167. $this_what = 'structure';
  168. }
  169. if ($this_what === 'dataonly') {
  170. $this_what = 'nocopy';
  171. }
  172. }
  173. if ($this_what === 'nocopy') {
  174. continue;
  175. }
  176. // keep the triggers from the original db+table
  177. // (third param is empty because delimiters are only intended
  178. // for importing via the mysql client or our Import feature)
  179. $triggers = $this->dbi->getTriggers($db, $table, '');
  180. if (
  181. ! Table::moveCopy(
  182. $db,
  183. $table,
  184. $_POST['newname'],
  185. $table,
  186. ($this_what ?? 'data'),
  187. $move,
  188. 'db_copy',
  189. isset($_POST['drop_if_exists']) && $_POST['drop_if_exists'] === 'true'
  190. )
  191. ) {
  192. $GLOBALS['_error'] = true;
  193. break;
  194. }
  195. // apply the triggers to the destination db+table
  196. if ($triggers) {
  197. $this->dbi->selectDb($_POST['newname']);
  198. foreach ($triggers as $trigger) {
  199. $this->dbi->query($trigger['create']);
  200. $GLOBALS['sql_query'] .= "\n" . $trigger['create'] . ';';
  201. }
  202. }
  203. // this does not apply to a rename operation
  204. if (! isset($_POST['add_constraints']) || empty($GLOBALS['sql_constraints_query'])) {
  205. continue;
  206. }
  207. $sqlContraints[] = $GLOBALS['sql_constraints_query'];
  208. unset($GLOBALS['sql_constraints_query']);
  209. }
  210. return $sqlContraints;
  211. }
  212. /**
  213. * Run the EVENT definition for selected database
  214. *
  215. * to avoid selecting alternatively the current and new db
  216. * we would need to modify the CREATE definitions to qualify
  217. * the db name
  218. *
  219. * @param string $db database name
  220. */
  221. public function runEventDefinitionsForDb($db): void
  222. {
  223. $event_names = $this->dbi->fetchResult(
  224. 'SELECT EVENT_NAME FROM information_schema.EVENTS WHERE EVENT_SCHEMA= \''
  225. . $this->dbi->escapeString($db) . '\';'
  226. );
  227. if (! $event_names) {
  228. return;
  229. }
  230. foreach ($event_names as $event_name) {
  231. $this->dbi->selectDb($db);
  232. $tmp_query = $this->dbi->getDefinition($db, 'EVENT', $event_name);
  233. // collect for later display
  234. $GLOBALS['sql_query'] .= "\n" . $tmp_query;
  235. $this->dbi->selectDb($_POST['newname']);
  236. $this->dbi->query($tmp_query);
  237. }
  238. }
  239. /**
  240. * Handle the views, return the boolean value whether table rename/copy or not
  241. *
  242. * @param array $views views as an array
  243. * @param bool $move whether database name is empty or not
  244. * @param string $db database name
  245. */
  246. public function handleTheViews(array $views, $move, $db): void
  247. {
  248. // Add DROP IF EXIST to CREATE VIEW query, to remove stand-in VIEW that was created earlier.
  249. foreach ($views as $view) {
  250. $copying_succeeded = Table::moveCopy(
  251. $db,
  252. $view,
  253. $_POST['newname'],
  254. $view,
  255. 'structure',
  256. $move,
  257. 'db_copy',
  258. true
  259. );
  260. if (! $copying_succeeded) {
  261. $GLOBALS['_error'] = true;
  262. break;
  263. }
  264. }
  265. }
  266. /**
  267. * Adjust the privileges after Renaming the db
  268. *
  269. * @param string $oldDb Database name before renaming
  270. * @param string $newname New Database name requested
  271. */
  272. public function adjustPrivilegesMoveDb($oldDb, $newname): void
  273. {
  274. if (
  275. ! $GLOBALS['db_priv'] || ! $GLOBALS['table_priv']
  276. || ! $GLOBALS['col_priv'] || ! $GLOBALS['proc_priv']
  277. || ! $GLOBALS['is_reload_priv']
  278. ) {
  279. return;
  280. }
  281. $this->dbi->selectDb('mysql');
  282. $newname = str_replace('_', '\_', $newname);
  283. $oldDb = str_replace('_', '\_', $oldDb);
  284. // For Db specific privileges
  285. $query_db_specific = 'UPDATE ' . Util::backquote('db')
  286. . 'SET Db = \'' . $this->dbi->escapeString($newname)
  287. . '\' where Db = \'' . $this->dbi->escapeString($oldDb) . '\';';
  288. $this->dbi->query($query_db_specific);
  289. // For table specific privileges
  290. $query_table_specific = 'UPDATE ' . Util::backquote('tables_priv')
  291. . 'SET Db = \'' . $this->dbi->escapeString($newname)
  292. . '\' where Db = \'' . $this->dbi->escapeString($oldDb) . '\';';
  293. $this->dbi->query($query_table_specific);
  294. // For column specific privileges
  295. $query_col_specific = 'UPDATE ' . Util::backquote('columns_priv')
  296. . 'SET Db = \'' . $this->dbi->escapeString($newname)
  297. . '\' where Db = \'' . $this->dbi->escapeString($oldDb) . '\';';
  298. $this->dbi->query($query_col_specific);
  299. // For procedures specific privileges
  300. $query_proc_specific = 'UPDATE ' . Util::backquote('procs_priv')
  301. . 'SET Db = \'' . $this->dbi->escapeString($newname)
  302. . '\' where Db = \'' . $this->dbi->escapeString($oldDb) . '\';';
  303. $this->dbi->query($query_proc_specific);
  304. // Finally FLUSH the new privileges
  305. $this->dbi->tryQuery('FLUSH PRIVILEGES;');
  306. }
  307. /**
  308. * Adjust the privileges after Copying the db
  309. *
  310. * @param string $oldDb Database name before copying
  311. * @param string $newname New Database name requested
  312. */
  313. public function adjustPrivilegesCopyDb($oldDb, $newname): void
  314. {
  315. if (
  316. ! $GLOBALS['db_priv'] || ! $GLOBALS['table_priv']
  317. || ! $GLOBALS['col_priv'] || ! $GLOBALS['proc_priv']
  318. || ! $GLOBALS['is_reload_priv']
  319. ) {
  320. return;
  321. }
  322. $this->dbi->selectDb('mysql');
  323. $newname = str_replace('_', '\_', $newname);
  324. $oldDb = str_replace('_', '\_', $oldDb);
  325. $query_db_specific_old = 'SELECT * FROM '
  326. . Util::backquote('db') . ' WHERE '
  327. . 'Db = "' . $oldDb . '";';
  328. $old_privs_db = $this->dbi->fetchResult($query_db_specific_old, 0);
  329. foreach ($old_privs_db as $old_priv) {
  330. $newDb_db_privs_query = 'INSERT INTO ' . Util::backquote('db')
  331. . ' VALUES("' . $old_priv[0] . '", "' . $newname . '"';
  332. $privCount = count($old_priv);
  333. for ($i = 2; $i < $privCount; $i++) {
  334. $newDb_db_privs_query .= ', "' . $old_priv[$i] . '"';
  335. }
  336. $newDb_db_privs_query .= ')';
  337. $this->dbi->query($newDb_db_privs_query);
  338. }
  339. // For Table Specific privileges
  340. $query_table_specific_old = 'SELECT * FROM '
  341. . Util::backquote('tables_priv') . ' WHERE '
  342. . 'Db = "' . $oldDb . '";';
  343. $old_privs_table = $this->dbi->fetchResult($query_table_specific_old, 0);
  344. foreach ($old_privs_table as $old_priv) {
  345. $newDb_table_privs_query = 'INSERT INTO ' . Util::backquote(
  346. 'tables_priv'
  347. ) . ' VALUES("' . $old_priv[0] . '", "' . $newname . '", "'
  348. . $old_priv[2] . '", "' . $old_priv[3] . '", "' . $old_priv[4]
  349. . '", "' . $old_priv[5] . '", "' . $old_priv[6] . '", "'
  350. . $old_priv[7] . '");';
  351. $this->dbi->query($newDb_table_privs_query);
  352. }
  353. // For Column Specific privileges
  354. $query_col_specific_old = 'SELECT * FROM '
  355. . Util::backquote('columns_priv') . ' WHERE '
  356. . 'Db = "' . $oldDb . '";';
  357. $old_privs_col = $this->dbi->fetchResult($query_col_specific_old, 0);
  358. foreach ($old_privs_col as $old_priv) {
  359. $newDb_col_privs_query = 'INSERT INTO ' . Util::backquote(
  360. 'columns_priv'
  361. ) . ' VALUES("' . $old_priv[0] . '", "' . $newname . '", "'
  362. . $old_priv[2] . '", "' . $old_priv[3] . '", "' . $old_priv[4]
  363. . '", "' . $old_priv[5] . '", "' . $old_priv[6] . '");';
  364. $this->dbi->query($newDb_col_privs_query);
  365. }
  366. // For Procedure Specific privileges
  367. $query_proc_specific_old = 'SELECT * FROM '
  368. . Util::backquote('procs_priv') . ' WHERE '
  369. . 'Db = "' . $oldDb . '";';
  370. $old_privs_proc = $this->dbi->fetchResult($query_proc_specific_old, 0);
  371. foreach ($old_privs_proc as $old_priv) {
  372. $newDb_proc_privs_query = 'INSERT INTO ' . Util::backquote(
  373. 'procs_priv'
  374. ) . ' VALUES("' . $old_priv[0] . '", "' . $newname . '", "'
  375. . $old_priv[2] . '", "' . $old_priv[3] . '", "' . $old_priv[4]
  376. . '", "' . $old_priv[5] . '", "' . $old_priv[6] . '", "'
  377. . $old_priv[7] . '");';
  378. $this->dbi->query($newDb_proc_privs_query);
  379. }
  380. // Finally FLUSH the new privileges
  381. $this->dbi->tryQuery('FLUSH PRIVILEGES;');
  382. }
  383. /**
  384. * Create all accumulated constraints
  385. *
  386. * @param array $sqlConstratints array of sql constraints for the database
  387. */
  388. public function createAllAccumulatedConstraints(array $sqlConstratints): void
  389. {
  390. $this->dbi->selectDb($_POST['newname']);
  391. foreach ($sqlConstratints as $one_query) {
  392. $this->dbi->query($one_query);
  393. // and prepare to display them
  394. $GLOBALS['sql_query'] .= "\n" . $one_query;
  395. }
  396. }
  397. /**
  398. * Duplicate the bookmarks for the db (done once for each db)
  399. *
  400. * @param bool $_error whether table rename/copy or not
  401. * @param string $db database name
  402. */
  403. public function duplicateBookmarks($_error, $db): void
  404. {
  405. if ($_error || $db == $_POST['newname']) {
  406. return;
  407. }
  408. $get_fields = [
  409. 'user',
  410. 'label',
  411. 'query',
  412. ];
  413. $where_fields = ['dbase' => $db];
  414. $new_fields = ['dbase' => $_POST['newname']];
  415. Table::duplicateInfo('bookmarkwork', 'bookmark', $get_fields, $where_fields, $new_fields);
  416. }
  417. /**
  418. * Get array of possible row formats
  419. *
  420. * @return array
  421. */
  422. public function getPossibleRowFormat()
  423. {
  424. // the outer array is for engines, the inner array contains the dropdown
  425. // option values as keys then the dropdown option labels
  426. $possible_row_formats = [
  427. 'ARCHIVE' => ['COMPRESSED' => 'COMPRESSED'],
  428. 'ARIA' => [
  429. 'FIXED' => 'FIXED',
  430. 'DYNAMIC' => 'DYNAMIC',
  431. 'PAGE' => 'PAGE',
  432. ],
  433. 'MARIA' => [
  434. 'FIXED' => 'FIXED',
  435. 'DYNAMIC' => 'DYNAMIC',
  436. 'PAGE' => 'PAGE',
  437. ],
  438. 'MYISAM' => [
  439. 'FIXED' => 'FIXED',
  440. 'DYNAMIC' => 'DYNAMIC',
  441. ],
  442. 'PBXT' => [
  443. 'FIXED' => 'FIXED',
  444. 'DYNAMIC' => 'DYNAMIC',
  445. ],
  446. 'INNODB' => [
  447. 'COMPACT' => 'COMPACT',
  448. 'REDUNDANT' => 'REDUNDANT',
  449. ],
  450. ];
  451. /** @var Innodb $innodbEnginePlugin */
  452. $innodbEnginePlugin = StorageEngine::getEngine('Innodb');
  453. $innodbPluginVersion = $innodbEnginePlugin->getInnodbPluginVersion();
  454. $innodb_file_format = '';
  455. if (! empty($innodbPluginVersion)) {
  456. $innodb_file_format = $innodbEnginePlugin->getInnodbFileFormat() ?? '';
  457. }
  458. /**
  459. * Newer MySQL/MariaDB always return empty a.k.a '' on $innodb_file_format otherwise
  460. * old versions of MySQL/MariaDB must be returning something or not empty.
  461. * This patch is to support newer MySQL/MariaDB while also for backward compatibilities.
  462. */
  463. if (
  464. (strtolower($innodb_file_format) === 'barracuda') || ($innodb_file_format == '')
  465. && $innodbEnginePlugin->supportsFilePerTable()
  466. ) {
  467. $possible_row_formats['INNODB']['DYNAMIC'] = 'DYNAMIC';
  468. $possible_row_formats['INNODB']['COMPRESSED'] = 'COMPRESSED';
  469. }
  470. return $possible_row_formats;
  471. }
  472. /**
  473. * @return array<string, string>
  474. */
  475. public function getPartitionMaintenanceChoices(): array
  476. {
  477. global $db, $table;
  478. $choices = [
  479. 'ANALYZE' => __('Analyze'),
  480. 'CHECK' => __('Check'),
  481. 'OPTIMIZE' => __('Optimize'),
  482. 'REBUILD' => __('Rebuild'),
  483. 'REPAIR' => __('Repair'),
  484. 'TRUNCATE' => __('Truncate'),
  485. ];
  486. $partitionMethod = Partition::getPartitionMethod($db, $table);
  487. // add COALESCE or DROP option to choices array depending on Partition method
  488. if (
  489. $partitionMethod === 'RANGE'
  490. || $partitionMethod === 'RANGE COLUMNS'
  491. || $partitionMethod === 'LIST'
  492. || $partitionMethod === 'LIST COLUMNS'
  493. ) {
  494. $choices['DROP'] = __('Drop');
  495. } else {
  496. $choices['COALESCE'] = __('Coalesce');
  497. }
  498. return $choices;
  499. }
  500. /**
  501. * @param array $urlParams Array of url parameters.
  502. * @param bool $hasRelationFeature If relation feature is enabled.
  503. *
  504. * @return array
  505. */
  506. public function getForeignersForReferentialIntegrityCheck(
  507. array $urlParams,
  508. $hasRelationFeature
  509. ): array {
  510. global $db, $table;
  511. if (! $hasRelationFeature) {
  512. return [];
  513. }
  514. $foreigners = [];
  515. $this->dbi->selectDb($db);
  516. $foreign = $this->relation->getForeigners($db, $table, '', 'internal');
  517. foreach ($foreign as $master => $arr) {
  518. $joinQuery = 'SELECT '
  519. . Util::backquote($table) . '.*'
  520. . ' FROM ' . Util::backquote($table)
  521. . ' LEFT JOIN '
  522. . Util::backquote($arr['foreign_db'])
  523. . '.'
  524. . Util::backquote($arr['foreign_table']);
  525. if ($arr['foreign_table'] == $table) {
  526. $foreignTable = $table . '1';
  527. $joinQuery .= ' AS ' . Util::backquote($foreignTable);
  528. } else {
  529. $foreignTable = $arr['foreign_table'];
  530. }
  531. $joinQuery .= ' ON '
  532. . Util::backquote($table) . '.'
  533. . Util::backquote($master)
  534. . ' = '
  535. . Util::backquote($arr['foreign_db'])
  536. . '.'
  537. . Util::backquote($foreignTable) . '.'
  538. . Util::backquote($arr['foreign_field'])
  539. . ' WHERE '
  540. . Util::backquote($arr['foreign_db'])
  541. . '.'
  542. . Util::backquote($foreignTable) . '.'
  543. . Util::backquote($arr['foreign_field'])
  544. . ' IS NULL AND '
  545. . Util::backquote($table) . '.'
  546. . Util::backquote($master)
  547. . ' IS NOT NULL';
  548. $thisUrlParams = array_merge(
  549. $urlParams,
  550. [
  551. 'sql_query' => $joinQuery,
  552. 'sql_signature' => Core::signSqlQuery($joinQuery),
  553. ]
  554. );
  555. $foreigners[] = [
  556. 'params' => $thisUrlParams,
  557. 'master' => $master,
  558. 'db' => $arr['foreign_db'],
  559. 'table' => $arr['foreign_table'],
  560. 'field' => $arr['foreign_field'],
  561. ];
  562. }
  563. return $foreigners;
  564. }
  565. /**
  566. * Get table alters array
  567. *
  568. * @param Table $pma_table The Table object
  569. * @param string $pack_keys pack keys
  570. * @param string $checksum value of checksum
  571. * @param string $page_checksum value of page checksum
  572. * @param string $delay_key_write delay key write
  573. * @param string $row_format row format
  574. * @param string $newTblStorageEngine table storage engine
  575. * @param string $transactional value of transactional
  576. * @param string $tbl_collation collation of the table
  577. *
  578. * @return array
  579. */
  580. public function getTableAltersArray(
  581. $pma_table,
  582. $pack_keys,
  583. $checksum,
  584. $page_checksum,
  585. $delay_key_write,
  586. $row_format,
  587. $newTblStorageEngine,
  588. $transactional,
  589. $tbl_collation
  590. ) {
  591. global $auto_increment;
  592. $table_alters = [];
  593. if (isset($_POST['comment']) && urldecode($_POST['prev_comment']) !== $_POST['comment']) {
  594. $table_alters[] = 'COMMENT = \''
  595. . $this->dbi->escapeString($_POST['comment']) . '\'';
  596. }
  597. if (
  598. ! empty($newTblStorageEngine)
  599. && mb_strtolower($newTblStorageEngine) !== mb_strtolower($GLOBALS['tbl_storage_engine'])
  600. ) {
  601. $table_alters[] = 'ENGINE = ' . $newTblStorageEngine;
  602. }
  603. if (! empty($_POST['tbl_collation']) && $_POST['tbl_collation'] !== $tbl_collation) {
  604. $table_alters[] = 'DEFAULT '
  605. . Util::getCharsetQueryPart($_POST['tbl_collation'] ?? '');
  606. }
  607. if (
  608. $pma_table->isEngine(['MYISAM', 'ARIA', 'ISAM'])
  609. && isset($_POST['new_pack_keys'])
  610. && $_POST['new_pack_keys'] != (string) $pack_keys
  611. ) {
  612. $table_alters[] = 'pack_keys = ' . $_POST['new_pack_keys'];
  613. }
  614. $newChecksum = empty($_POST['new_checksum']) ? '0' : '1';
  615. if ($pma_table->isEngine(['MYISAM', 'ARIA']) && $newChecksum !== $checksum) {
  616. $table_alters[] = 'checksum = ' . $newChecksum;
  617. }
  618. $newTransactional = empty($_POST['new_transactional']) ? '0' : '1';
  619. if ($pma_table->isEngine('ARIA') && $newTransactional !== $transactional) {
  620. $table_alters[] = 'TRANSACTIONAL = ' . $newTransactional;
  621. }
  622. $newPageChecksum = empty($_POST['new_page_checksum']) ? '0' : '1';
  623. if ($pma_table->isEngine('ARIA') && $newPageChecksum !== $page_checksum) {
  624. $table_alters[] = 'PAGE_CHECKSUM = ' . $newPageChecksum;
  625. }
  626. $newDelayKeyWrite = empty($_POST['new_delay_key_write']) ? '0' : '1';
  627. if ($pma_table->isEngine(['MYISAM', 'ARIA']) && $newDelayKeyWrite !== $delay_key_write) {
  628. $table_alters[] = 'delay_key_write = ' . $newDelayKeyWrite;
  629. }
  630. if (
  631. $pma_table->isEngine(['MYISAM', 'ARIA', 'INNODB', 'PBXT', 'ROCKSDB'])
  632. && ! empty($_POST['new_auto_increment'])
  633. && (! isset($auto_increment)
  634. || $_POST['new_auto_increment'] !== $auto_increment)
  635. && $_POST['new_auto_increment'] !== $_POST['hidden_auto_increment']
  636. ) {
  637. $table_alters[] = 'auto_increment = '
  638. . $this->dbi->escapeString($_POST['new_auto_increment']);
  639. }
  640. if (! empty($_POST['new_row_format'])) {
  641. $newRowFormat = $_POST['new_row_format'];
  642. $newRowFormatLower = mb_strtolower($newRowFormat);
  643. if (
  644. $pma_table->isEngine(['MYISAM', 'ARIA', 'INNODB', 'PBXT'])
  645. && (strlen($row_format) === 0
  646. || $newRowFormatLower !== mb_strtolower($row_format))
  647. ) {
  648. $table_alters[] = 'ROW_FORMAT = '
  649. . $this->dbi->escapeString($newRowFormat);
  650. }
  651. }
  652. return $table_alters;
  653. }
  654. /**
  655. * Get warning messages array
  656. *
  657. * @return string[]
  658. */
  659. public function getWarningMessagesArray(): array
  660. {
  661. $warning_messages = [];
  662. foreach ($this->dbi->getWarnings() as $warning) {
  663. // In MariaDB 5.1.44, when altering a table from Maria to MyISAM
  664. // and if TRANSACTIONAL was set, the system reports an error;
  665. // I discussed with a Maria developer and he agrees that this
  666. // should not be reported with a Level of Error, so here
  667. // I just ignore it. But there are other 1478 messages
  668. // that it's better to show.
  669. if (
  670. isset($_POST['new_tbl_storage_engine'])
  671. && $_POST['new_tbl_storage_engine'] === 'MyISAM'
  672. && $warning->code === 1478
  673. && $warning->level === 'Error'
  674. ) {
  675. continue;
  676. }
  677. $warning_messages[] = (string) $warning;
  678. }
  679. return $warning_messages;
  680. }
  681. /**
  682. * Adjust the privileges after renaming/moving a table
  683. *
  684. * @param string $oldDb Database name before table renaming/moving table
  685. * @param string $oldTable Table name before table renaming/moving table
  686. * @param string $newDb Database name after table renaming/ moving table
  687. * @param string $newTable Table name after table renaming/moving table
  688. */
  689. public function adjustPrivilegesRenameOrMoveTable($oldDb, $oldTable, $newDb, $newTable): void
  690. {
  691. if (! $GLOBALS['table_priv'] || ! $GLOBALS['col_priv'] || ! $GLOBALS['is_reload_priv']) {
  692. return;
  693. }
  694. $this->dbi->selectDb('mysql');
  695. // For table specific privileges
  696. $query_table_specific = 'UPDATE ' . Util::backquote('tables_priv')
  697. . 'SET Db = \'' . $this->dbi->escapeString($newDb)
  698. . '\', Table_name = \'' . $this->dbi->escapeString($newTable)
  699. . '\' where Db = \'' . $this->dbi->escapeString($oldDb)
  700. . '\' AND Table_name = \'' . $this->dbi->escapeString($oldTable)
  701. . '\';';
  702. $this->dbi->query($query_table_specific);
  703. // For column specific privileges
  704. $query_col_specific = 'UPDATE ' . Util::backquote('columns_priv')
  705. . 'SET Db = \'' . $this->dbi->escapeString($newDb)
  706. . '\', Table_name = \'' . $this->dbi->escapeString($newTable)
  707. . '\' where Db = \'' . $this->dbi->escapeString($oldDb)
  708. . '\' AND Table_name = \'' . $this->dbi->escapeString($oldTable)
  709. . '\';';
  710. $this->dbi->query($query_col_specific);
  711. // Finally FLUSH the new privileges
  712. $this->dbi->tryQuery('FLUSH PRIVILEGES;');
  713. }
  714. /**
  715. * Adjust the privileges after copying a table
  716. *
  717. * @param string $oldDb Database name before table copying
  718. * @param string $oldTable Table name before table copying
  719. * @param string $newDb Database name after table copying
  720. * @param string $newTable Table name after table copying
  721. */
  722. public function adjustPrivilegesCopyTable($oldDb, $oldTable, $newDb, $newTable): void
  723. {
  724. if (! $GLOBALS['table_priv'] || ! $GLOBALS['col_priv'] || ! $GLOBALS['is_reload_priv']) {
  725. return;
  726. }
  727. $this->dbi->selectDb('mysql');
  728. // For Table Specific privileges
  729. $query_table_specific_old = 'SELECT * FROM '
  730. . Util::backquote('tables_priv') . ' where '
  731. . 'Db = "' . $oldDb . '" AND Table_name = "' . $oldTable . '";';
  732. $old_privs_table = $this->dbi->fetchResult($query_table_specific_old, 0);
  733. foreach ($old_privs_table as $old_priv) {
  734. $newDb_table_privs_query = 'INSERT INTO '
  735. . Util::backquote('tables_priv') . ' VALUES("'
  736. . $old_priv[0] . '", "' . $newDb . '", "' . $old_priv[2] . '", "'
  737. . $newTable . '", "' . $old_priv[4] . '", "' . $old_priv[5]
  738. . '", "' . $old_priv[6] . '", "' . $old_priv[7] . '");';
  739. $this->dbi->query($newDb_table_privs_query);
  740. }
  741. // For Column Specific privileges
  742. $query_col_specific_old = 'SELECT * FROM '
  743. . Util::backquote('columns_priv') . ' WHERE '
  744. . 'Db = "' . $oldDb . '" AND Table_name = "' . $oldTable . '";';
  745. $old_privs_col = $this->dbi->fetchResult($query_col_specific_old, 0);
  746. foreach ($old_privs_col as $old_priv) {
  747. $newDb_col_privs_query = 'INSERT INTO '
  748. . Util::backquote('columns_priv') . ' VALUES("'
  749. . $old_priv[0] . '", "' . $newDb . '", "' . $old_priv[2] . '", "'
  750. . $newTable . '", "' . $old_priv[4] . '", "' . $old_priv[5]
  751. . '", "' . $old_priv[6] . '");';
  752. $this->dbi->query($newDb_col_privs_query);
  753. }
  754. // Finally FLUSH the new privileges
  755. $this->dbi->tryQuery('FLUSH PRIVILEGES;');
  756. }
  757. /**
  758. * Change all collations and character sets of all columns in table
  759. *
  760. * @param string $db Database name
  761. * @param string $table Table name
  762. * @param string $tbl_collation Collation Name
  763. */
  764. public function changeAllColumnsCollation($db, $table, $tbl_collation): void
  765. {
  766. $this->dbi->selectDb($db);
  767. $change_all_collations_query = 'ALTER TABLE '
  768. . Util::backquote($table)
  769. . ' CONVERT TO';
  770. [$charset] = explode('_', $tbl_collation);
  771. $change_all_collations_query .= ' CHARACTER SET ' . $charset
  772. . ($charset == $tbl_collation ? '' : ' COLLATE ' . $tbl_collation);
  773. $this->dbi->query($change_all_collations_query);
  774. }
  775. /**
  776. * Move or copy a table
  777. *
  778. * @param string $db current database name
  779. * @param string $table current table name
  780. */
  781. public function moveOrCopyTable($db, $table): Message
  782. {
  783. /**
  784. * Selects the database to work with
  785. */
  786. $this->dbi->selectDb($db);
  787. /**
  788. * $_POST['target_db'] could be empty in case we came from an input field
  789. * (when there are many databases, no drop-down)
  790. */
  791. $targetDb = $db;
  792. if (isset($_POST['target_db']) && is_string($_POST['target_db']) && strlen($_POST['target_db']) > 0) {
  793. $targetDb = $_POST['target_db'];
  794. }
  795. /**
  796. * A target table name has been sent to this script -> do the work
  797. */
  798. if (isset($_POST['new_name']) && is_scalar($_POST['new_name']) && strlen((string) $_POST['new_name']) > 0) {
  799. if ($db == $targetDb && $table == $_POST['new_name']) {
  800. if (isset($_POST['submit_move'])) {
  801. $message = Message::error(__('Can\'t move table to same one!'));
  802. } else {
  803. $message = Message::error(__('Can\'t copy table to same one!'));
  804. }
  805. } else {
  806. Table::moveCopy(
  807. $db,
  808. $table,
  809. $targetDb,
  810. (string) $_POST['new_name'],
  811. $_POST['what'],
  812. isset($_POST['submit_move']),
  813. 'one_table',
  814. isset($_POST['drop_if_exists']) && $_POST['drop_if_exists'] === 'true'
  815. );
  816. if (isset($_POST['adjust_privileges']) && ! empty($_POST['adjust_privileges'])) {
  817. if (isset($_POST['submit_move'])) {
  818. $this->adjustPrivilegesRenameOrMoveTable($db, $table, $targetDb, (string) $_POST['new_name']);
  819. } else {
  820. $this->adjustPrivilegesCopyTable($db, $table, $targetDb, (string) $_POST['new_name']);
  821. }
  822. if (isset($_POST['submit_move'])) {
  823. $message = Message::success(
  824. __(
  825. 'Table %s has been moved to %s. Privileges have been adjusted.'
  826. )
  827. );
  828. } else {
  829. $message = Message::success(
  830. __(
  831. 'Table %s has been copied to %s. Privileges have been adjusted.'
  832. )
  833. );
  834. }
  835. } else {
  836. if (isset($_POST['submit_move'])) {
  837. $message = Message::success(
  838. __('Table %s has been moved to %s.')
  839. );
  840. } else {
  841. $message = Message::success(
  842. __('Table %s has been copied to %s.')
  843. );
  844. }
  845. }
  846. $old = Util::backquote($db) . '.'
  847. . Util::backquote($table);
  848. $message->addParam($old);
  849. $new_name = (string) $_POST['new_name'];
  850. if ($this->dbi->getLowerCaseNames() === '1') {
  851. $new_name = strtolower($new_name);
  852. }
  853. $GLOBALS['table'] = $new_name;
  854. $new = Util::backquote($targetDb) . '.'
  855. . Util::backquote($new_name);
  856. $message->addParam($new);
  857. }
  858. } else {
  859. /**
  860. * No new name for the table!
  861. */
  862. $message = Message::error(__('The table name is empty!'));
  863. }
  864. return $message;
  865. }
  866. }