StructureController.php 60 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * Holds the PhpMyAdmin\Controllers\Table\StructureController
  5. *
  6. * @package PhpMyAdmin\Controllers
  7. */
  8. declare(strict_types=1);
  9. namespace PhpMyAdmin\Controllers\Table;
  10. use PhpMyAdmin\CentralColumns;
  11. use PhpMyAdmin\Charsets;
  12. use PhpMyAdmin\CheckUserPrivileges;
  13. use PhpMyAdmin\Config\PageSettings;
  14. use PhpMyAdmin\Core;
  15. use PhpMyAdmin\CreateAddField;
  16. use PhpMyAdmin\DatabaseInterface;
  17. use PhpMyAdmin\Engines\Innodb;
  18. use PhpMyAdmin\Index;
  19. use PhpMyAdmin\Message;
  20. use PhpMyAdmin\ParseAnalyze;
  21. use PhpMyAdmin\Partition;
  22. use PhpMyAdmin\Relation;
  23. use PhpMyAdmin\Response;
  24. use PhpMyAdmin\Sql;
  25. use PhpMyAdmin\SqlParser\Context;
  26. use PhpMyAdmin\SqlParser\Parser;
  27. use PhpMyAdmin\SqlParser\Statements\CreateStatement;
  28. use PhpMyAdmin\StorageEngine;
  29. use PhpMyAdmin\Table;
  30. use PhpMyAdmin\TablePartitionDefinition;
  31. use PhpMyAdmin\Template;
  32. use PhpMyAdmin\Tracker;
  33. use PhpMyAdmin\Transformations;
  34. use PhpMyAdmin\Url;
  35. use PhpMyAdmin\Util;
  36. use stdClass;
  37. use Symfony\Component\DependencyInjection\ContainerBuilder;
  38. /**
  39. * Handles table structure logic
  40. *
  41. * @package PhpMyAdmin\Controllers
  42. */
  43. class StructureController extends AbstractController
  44. {
  45. /**
  46. * @var Table The table object
  47. */
  48. protected $table_obj;
  49. /**
  50. * @var string The URL query string
  51. */
  52. protected $_url_query;
  53. /**
  54. * @var bool DB is information_schema
  55. */
  56. protected $_db_is_system_schema;
  57. /**
  58. * @var bool Table is a view
  59. */
  60. protected $_tbl_is_view;
  61. /**
  62. * @var string Table storage engine
  63. */
  64. protected $_tbl_storage_engine;
  65. /**
  66. * @var int Number of rows
  67. */
  68. protected $_table_info_num_rows;
  69. /**
  70. * @var string Table collation
  71. */
  72. protected $_tbl_collation;
  73. /**
  74. * @var array Show table info
  75. */
  76. protected $_showtable;
  77. /**
  78. * @var CreateAddField
  79. */
  80. private $createAddField;
  81. /**
  82. * @var Relation
  83. */
  84. private $relation;
  85. /**
  86. * @var Transformations
  87. */
  88. private $transformations;
  89. /**
  90. * StructureController constructor
  91. *
  92. * @param Response $response Response object
  93. * @param DatabaseInterface $dbi DatabaseInterface object
  94. * @param Template $template Template object
  95. * @param string $db Database name
  96. * @param string $table Table name
  97. * @param bool $db_is_system_schema DB is information_schema
  98. * @param bool $tbl_is_view Table is a view
  99. * @param string $tbl_storage_engine Table storage engine
  100. * @param int $table_info_num_rows Number of rows
  101. * @param string $tbl_collation Table collation
  102. * @param array $showtable Show table info
  103. * @param Relation $relation Relation instance
  104. * @param Transformations $transformations Transformations instance
  105. * @param CreateAddField $createAddField CreateAddField instance
  106. */
  107. public function __construct(
  108. $response,
  109. $dbi,
  110. Template $template,
  111. $db,
  112. $table,
  113. $db_is_system_schema,
  114. $tbl_is_view,
  115. $tbl_storage_engine,
  116. $table_info_num_rows,
  117. $tbl_collation,
  118. $showtable,
  119. Relation $relation,
  120. Transformations $transformations,
  121. CreateAddField $createAddField
  122. ) {
  123. parent::__construct($response, $dbi, $template, $db, $table);
  124. $this->_db_is_system_schema = $db_is_system_schema;
  125. $this->_url_query = Url::getCommonRaw(['db' => $db, 'table' => $table]);
  126. $this->_tbl_is_view = $tbl_is_view;
  127. $this->_tbl_storage_engine = $tbl_storage_engine;
  128. $this->_table_info_num_rows = $table_info_num_rows;
  129. $this->_tbl_collation = $tbl_collation;
  130. $this->_showtable = $showtable;
  131. $this->table_obj = $this->dbi->getTable($this->db, $this->table);
  132. $this->createAddField = $createAddField;
  133. $this->relation = $relation;
  134. $this->transformations = $transformations;
  135. }
  136. /**
  137. * Index action
  138. *
  139. * @param ContainerBuilder $containerBuilder ContainerBuilder instance
  140. *
  141. * @return void
  142. */
  143. public function indexAction(ContainerBuilder $containerBuilder): void
  144. {
  145. global $sql_query;
  146. PageSettings::showGroup('TableStructure');
  147. $checkUserPrivileges = new CheckUserPrivileges($this->dbi);
  148. $checkUserPrivileges->getPrivileges();
  149. $this->response->getHeader()->getScripts()->addFiles(
  150. [
  151. 'table/structure.js',
  152. 'indexes.js',
  153. ]
  154. );
  155. /**
  156. * Handle column moving
  157. */
  158. if (isset($_POST['move_columns'])
  159. && is_array($_POST['move_columns'])
  160. && $this->response->isAjax()
  161. ) {
  162. $this->moveColumns();
  163. return;
  164. }
  165. /**
  166. * handle MySQL reserved words columns check
  167. */
  168. if (isset($_POST['reserved_word_check'])) {
  169. if ($GLOBALS['cfg']['ReservedWordDisableWarning'] === false) {
  170. $columns_names = $_POST['field_name'];
  171. $reserved_keywords_names = [];
  172. foreach ($columns_names as $column) {
  173. if (Context::isKeyword(trim($column), true)) {
  174. $reserved_keywords_names[] = trim($column);
  175. }
  176. }
  177. if (Context::isKeyword(trim($this->table), true)) {
  178. $reserved_keywords_names[] = trim($this->table);
  179. }
  180. if (count($reserved_keywords_names) === 0) {
  181. $this->response->setRequestStatus(false);
  182. }
  183. $this->response->addJSON(
  184. 'message',
  185. sprintf(
  186. _ngettext(
  187. 'The name \'%s\' is a MySQL reserved keyword.',
  188. 'The names \'%s\' are MySQL reserved keywords.',
  189. count($reserved_keywords_names)
  190. ),
  191. implode(',', $reserved_keywords_names)
  192. )
  193. );
  194. } else {
  195. $this->response->setRequestStatus(false);
  196. }
  197. return;
  198. }
  199. /**
  200. * A click on Change has been made for one column
  201. */
  202. if (isset($_GET['change_column'])) {
  203. $this->displayHtmlForColumnChange(null, 'tbl_structure.php', $containerBuilder);
  204. return;
  205. }
  206. /**
  207. * Adding or editing partitioning of the table
  208. */
  209. if (isset($_POST['edit_partitioning'])
  210. && ! isset($_POST['save_partitioning'])
  211. ) {
  212. $this->displayHtmlForPartitionChange();
  213. return;
  214. }
  215. /**
  216. * handle multiple field commands if required
  217. *
  218. * submit_mult_*_x comes from IE if <input type="img" ...> is used
  219. */
  220. $submit_mult = $this->getMultipleFieldCommandType();
  221. if (! empty($submit_mult)) {
  222. if (isset($_POST['selected_fld'])) {
  223. if ($submit_mult == 'browse') {
  224. // browsing the table displaying only selected columns
  225. $this->displayTableBrowseForSelectedColumns(
  226. $GLOBALS['goto'],
  227. $GLOBALS['pmaThemeImage']
  228. );
  229. } else {
  230. // handle multiple field commands
  231. // handle confirmation of deleting multiple columns
  232. $action = 'tbl_structure.php';
  233. $GLOBALS['selected'] = $_POST['selected_fld'];
  234. list(
  235. $what_ret, $query_type_ret, $is_unset_submit_mult,
  236. $mult_btn_ret, $centralColsError
  237. )
  238. = $this->getDataForSubmitMult(
  239. $submit_mult,
  240. $_POST['selected_fld'],
  241. $action,
  242. $containerBuilder
  243. );
  244. //update the existing variables
  245. // todo: refactor mult_submits.inc.php such as
  246. // below globals are not needed anymore
  247. if (isset($what_ret)) {
  248. $GLOBALS['what'] = $what_ret;
  249. global $what;
  250. }
  251. if (isset($query_type_ret)) {
  252. $GLOBALS['query_type'] = $query_type_ret;
  253. global $query_type;
  254. }
  255. if ($is_unset_submit_mult) {
  256. unset($submit_mult);
  257. }
  258. if (isset($mult_btn_ret)) {
  259. $GLOBALS['mult_btn'] = $mult_btn_ret;
  260. global $mult_btn;
  261. }
  262. include ROOT_PATH . 'libraries/mult_submits.inc.php';
  263. /**
  264. * if $submit_mult == 'change', execution will have stopped
  265. * at this point
  266. */
  267. if (empty($message)) {
  268. $message = Message::success();
  269. }
  270. $this->response->addHTML(
  271. Util::getMessage($message, $sql_query)
  272. );
  273. }
  274. } else {
  275. $this->response->setRequestStatus(false);
  276. $this->response->addJSON('message', __('No column selected.'));
  277. }
  278. }
  279. /**
  280. * Modifications have been submitted -> updates the table
  281. */
  282. if (isset($_POST['do_save_data'])) {
  283. $regenerate = $this->updateColumns();
  284. if (! $regenerate) {
  285. // continue to show the table's structure
  286. unset($_POST['selected']);
  287. }
  288. }
  289. /**
  290. * Modifications to the partitioning have been submitted -> updates the table
  291. */
  292. if (isset($_POST['save_partitioning'])) {
  293. $this->updatePartitioning();
  294. }
  295. /**
  296. * Adding indexes
  297. */
  298. if (isset($_POST['add_key'])
  299. || isset($_POST['partition_maintenance'])
  300. ) {
  301. //todo: set some variables for sql.php include, to be eliminated
  302. //after refactoring sql.php
  303. $db = $this->db;
  304. $table = $this->table;
  305. $sql_query = $GLOBALS['sql_query'];
  306. $cfg = $GLOBALS['cfg'];
  307. $pmaThemeImage = $GLOBALS['pmaThemeImage'];
  308. include ROOT_PATH . 'sql.php';
  309. $GLOBALS['reload'] = true;
  310. }
  311. /**
  312. * Gets the relation settings
  313. */
  314. $cfgRelation = $this->relation->getRelationsParam();
  315. /**
  316. * Runs common work
  317. */
  318. // set db, table references, for require_once that follows
  319. // got to be eliminated in long run
  320. $db = &$this->db;
  321. $table = &$this->table;
  322. $url_params = [];
  323. include_once ROOT_PATH . 'libraries/tbl_common.inc.php';
  324. $this->_db_is_system_schema = $db_is_system_schema;
  325. $this->_url_query = Url::getCommonRaw([
  326. 'db' => $db,
  327. 'table' => $table,
  328. 'goto' => 'tbl_structure.php',
  329. 'back' => 'tbl_structure.php',
  330. ]);
  331. /* The url_params array is initialized in above include */
  332. $url_params['goto'] = 'tbl_structure.php';
  333. $url_params['back'] = 'tbl_structure.php';
  334. // 2. Gets table keys and retains them
  335. // @todo should be: $server->db($db)->table($table)->primary()
  336. $primary = Index::getPrimary($this->table, $this->db);
  337. $columns_with_index = $this->dbi
  338. ->getTable($this->db, $this->table)
  339. ->getColumnsWithIndex(
  340. Index::UNIQUE | Index::INDEX | Index::SPATIAL
  341. | Index::FULLTEXT
  342. );
  343. $columns_with_unique_index = $this->dbi
  344. ->getTable($this->db, $this->table)
  345. ->getColumnsWithIndex(Index::UNIQUE);
  346. // 3. Get fields
  347. $fields = (array) $this->dbi->getColumns(
  348. $this->db,
  349. $this->table,
  350. null,
  351. true
  352. );
  353. //display table structure
  354. $this->response->addHTML(
  355. $this->displayStructure(
  356. $cfgRelation,
  357. $columns_with_unique_index,
  358. $url_params,
  359. $primary,
  360. $fields,
  361. $columns_with_index
  362. )
  363. );
  364. }
  365. /**
  366. * Moves columns in the table's structure based on $_REQUEST
  367. *
  368. * @return void
  369. */
  370. protected function moveColumns()
  371. {
  372. $this->dbi->selectDb($this->db);
  373. /*
  374. * load the definitions for all columns
  375. */
  376. $columns = $this->dbi->getColumnsFull($this->db, $this->table);
  377. $column_names = array_keys($columns);
  378. $changes = [];
  379. // @see https://mariadb.com/kb/en/library/changes-improvements-in-mariadb-102/#information-schema
  380. $usesLiteralNull = $this->dbi->isMariaDB() && $this->dbi->getVersion() >= 100200;
  381. $defaultNullValue = $usesLiteralNull ? 'NULL' : null;
  382. // move columns from first to last
  383. for ($i = 0, $l = count($_POST['move_columns']); $i < $l; $i++) {
  384. $column = $_POST['move_columns'][$i];
  385. // is this column already correctly placed?
  386. if ($column_names[$i] == $column) {
  387. continue;
  388. }
  389. // it is not, let's move it to index $i
  390. $data = $columns[$column];
  391. $extracted_columnspec = Util::extractColumnSpec($data['Type']);
  392. if (isset($data['Extra'])
  393. && $data['Extra'] == 'on update CURRENT_TIMESTAMP'
  394. ) {
  395. $extracted_columnspec['attribute'] = $data['Extra'];
  396. unset($data['Extra']);
  397. }
  398. $current_timestamp = ($data['Type'] == 'timestamp'
  399. || $data['Type'] == 'datetime')
  400. && ($data['Default'] == 'CURRENT_TIMESTAMP'
  401. || $data['Default'] == 'current_timestamp()');
  402. // @see https://mariadb.com/kb/en/library/information-schema-columns-table/#examples
  403. if ($data['Null'] === 'YES' && in_array($data['Default'], [$defaultNullValue, null])) {
  404. $default_type = 'NULL';
  405. } elseif ($current_timestamp) {
  406. $default_type = 'CURRENT_TIMESTAMP';
  407. } elseif ($data['Default'] === null) {
  408. $default_type = 'NONE';
  409. } else {
  410. $default_type = 'USER_DEFINED';
  411. }
  412. $virtual = [
  413. 'VIRTUAL',
  414. 'PERSISTENT',
  415. 'VIRTUAL GENERATED',
  416. 'STORED GENERATED',
  417. ];
  418. $data['Virtuality'] = '';
  419. $data['Expression'] = '';
  420. if (isset($data['Extra']) && in_array($data['Extra'], $virtual)) {
  421. $data['Virtuality'] = str_replace(' GENERATED', '', $data['Extra']);
  422. $expressions = $this->table_obj->getColumnGenerationExpression($column);
  423. $data['Expression'] = $expressions[$column];
  424. }
  425. $changes[] = 'CHANGE ' . Table::generateAlter(
  426. $column,
  427. $column,
  428. mb_strtoupper($extracted_columnspec['type']),
  429. $extracted_columnspec['spec_in_brackets'],
  430. $extracted_columnspec['attribute'],
  431. isset($data['Collation']) ? $data['Collation'] : '',
  432. $data['Null'] === 'YES' ? 'YES' : 'NO',
  433. $default_type,
  434. $current_timestamp ? '' : $data['Default'],
  435. isset($data['Extra']) && $data['Extra'] !== '' ? $data['Extra']
  436. : false,
  437. isset($data['COLUMN_COMMENT']) && $data['COLUMN_COMMENT'] !== ''
  438. ? $data['COLUMN_COMMENT'] : false,
  439. $data['Virtuality'],
  440. $data['Expression'],
  441. $i === 0 ? '-first' : $column_names[$i - 1]
  442. );
  443. // update current column_names array, first delete old position
  444. for ($j = 0, $ll = count($column_names); $j < $ll; $j++) {
  445. if ($column_names[$j] == $column) {
  446. unset($column_names[$j]);
  447. }
  448. }
  449. // insert moved column
  450. array_splice($column_names, $i, 0, $column);
  451. }
  452. if (empty($changes) && ! isset($_REQUEST['preview_sql'])) { // should never happen
  453. $this->response->setRequestStatus(false);
  454. return;
  455. }
  456. // query for moving the columns
  457. $sql_query = sprintf(
  458. 'ALTER TABLE %s %s',
  459. Util::backquote($this->table),
  460. implode(', ', $changes)
  461. );
  462. if (isset($_REQUEST['preview_sql'])) { // preview sql
  463. $this->response->addJSON(
  464. 'sql_data',
  465. $this->template->render('preview_sql', [
  466. 'query_data' => $sql_query,
  467. ])
  468. );
  469. } else { // move column
  470. $this->dbi->tryQuery($sql_query);
  471. $tmp_error = $this->dbi->getError();
  472. if ($tmp_error) {
  473. $this->response->setRequestStatus(false);
  474. $this->response->addJSON('message', Message::error($tmp_error));
  475. } else {
  476. $message = Message::success(
  477. __('The columns have been moved successfully.')
  478. );
  479. $this->response->addJSON('message', $message);
  480. $this->response->addJSON('columns', $column_names);
  481. }
  482. }
  483. }
  484. /**
  485. * Displays HTML for changing one or more columns
  486. *
  487. * @param array $selected the selected columns
  488. * @param string $action target script to call
  489. * @param ContainerBuilder $containerBuilder Container builder instance (Used in tbl_columns_definition_form.inc.php)
  490. *
  491. * @return void
  492. */
  493. protected function displayHtmlForColumnChange($selected, $action, ContainerBuilder $containerBuilder)
  494. {
  495. // $selected comes from mult_submits.inc.php
  496. if (empty($selected)) {
  497. $selected[] = $_REQUEST['field'];
  498. $selected_cnt = 1;
  499. } else { // from a multiple submit
  500. $selected_cnt = count($selected);
  501. }
  502. /**
  503. * @todo optimize in case of multiple fields to modify
  504. */
  505. $fields_meta = [];
  506. for ($i = 0; $i < $selected_cnt; $i++) {
  507. $value = $this->dbi->getColumns(
  508. $this->db,
  509. $this->table,
  510. $this->dbi->escapeString($selected[$i]),
  511. true
  512. );
  513. if (count($value) === 0) {
  514. $message = Message::error(
  515. __('Failed to get description of column %s!')
  516. );
  517. $message->addParam($selected[$i]);
  518. $this->response->addHTML($message);
  519. } else {
  520. $fields_meta[] = $value;
  521. }
  522. }
  523. $num_fields = count($fields_meta);
  524. // set these globals because tbl_columns_definition_form.inc.php
  525. // verifies them
  526. // @todo: refactor tbl_columns_definition_form.inc.php so that it uses
  527. // protected function params
  528. $GLOBALS['action'] = $action;
  529. $GLOBALS['num_fields'] = $num_fields;
  530. /**
  531. * Form for changing properties.
  532. */
  533. $checkUserPrivileges = new CheckUserPrivileges($this->dbi);
  534. $checkUserPrivileges->getPrivileges();
  535. include ROOT_PATH . 'libraries/tbl_columns_definition_form.inc.php';
  536. }
  537. /**
  538. * Displays HTML for partition change
  539. *
  540. * @return void
  541. */
  542. protected function displayHtmlForPartitionChange()
  543. {
  544. $partitionDetails = null;
  545. if (! isset($_POST['partition_by'])) {
  546. $partitionDetails = $this->_extractPartitionDetails();
  547. }
  548. $partitionDetails = TablePartitionDefinition::getDetails($partitionDetails);
  549. $this->response->addHTML(
  550. $this->template->render('table/structure/partition_definition_form', [
  551. 'db' => $this->db,
  552. 'table' => $this->table,
  553. 'partition_details' => $partitionDetails,
  554. ])
  555. );
  556. }
  557. /**
  558. * Extracts partition details from CREATE TABLE statement
  559. *
  560. * @return array[]|null array of partition details
  561. */
  562. private function _extractPartitionDetails()
  563. {
  564. $createTable = (new Table($this->table, $this->db))->showCreate();
  565. if (! $createTable) {
  566. return null;
  567. }
  568. $parser = new Parser($createTable);
  569. /**
  570. * @var CreateStatement $stmt
  571. */
  572. $stmt = $parser->statements[0];
  573. $partitionDetails = [];
  574. $partitionDetails['partition_by'] = '';
  575. $partitionDetails['partition_expr'] = '';
  576. $partitionDetails['partition_count'] = '';
  577. if (! empty($stmt->partitionBy)) {
  578. $openPos = strpos($stmt->partitionBy, "(");
  579. $closePos = strrpos($stmt->partitionBy, ")");
  580. $partitionDetails['partition_by']
  581. = trim(substr($stmt->partitionBy, 0, $openPos));
  582. $partitionDetails['partition_expr']
  583. = trim(substr($stmt->partitionBy, $openPos + 1, $closePos - ($openPos + 1)));
  584. if (isset($stmt->partitionsNum)) {
  585. $count = $stmt->partitionsNum;
  586. } else {
  587. $count = count($stmt->partitions);
  588. }
  589. $partitionDetails['partition_count'] = $count;
  590. }
  591. $partitionDetails['subpartition_by'] = '';
  592. $partitionDetails['subpartition_expr'] = '';
  593. $partitionDetails['subpartition_count'] = '';
  594. if (! empty($stmt->subpartitionBy)) {
  595. $openPos = strpos($stmt->subpartitionBy, "(");
  596. $closePos = strrpos($stmt->subpartitionBy, ")");
  597. $partitionDetails['subpartition_by']
  598. = trim(substr($stmt->subpartitionBy, 0, $openPos));
  599. $partitionDetails['subpartition_expr']
  600. = trim(substr($stmt->subpartitionBy, $openPos + 1, $closePos - ($openPos + 1)));
  601. if (isset($stmt->subpartitionsNum)) {
  602. $count = $stmt->subpartitionsNum;
  603. } else {
  604. $count = count($stmt->partitions[0]->subpartitions);
  605. }
  606. $partitionDetails['subpartition_count'] = $count;
  607. }
  608. // Only LIST and RANGE type parameters allow subpartitioning
  609. $partitionDetails['can_have_subpartitions']
  610. = $partitionDetails['partition_count'] > 1
  611. && ($partitionDetails['partition_by'] == 'RANGE'
  612. || $partitionDetails['partition_by'] == 'RANGE COLUMNS'
  613. || $partitionDetails['partition_by'] == 'LIST'
  614. || $partitionDetails['partition_by'] == 'LIST COLUMNS');
  615. // Values are specified only for LIST and RANGE type partitions
  616. $partitionDetails['value_enabled'] = isset($partitionDetails['partition_by'])
  617. && ($partitionDetails['partition_by'] == 'RANGE'
  618. || $partitionDetails['partition_by'] == 'RANGE COLUMNS'
  619. || $partitionDetails['partition_by'] == 'LIST'
  620. || $partitionDetails['partition_by'] == 'LIST COLUMNS');
  621. $partitionDetails['partitions'] = [];
  622. for ($i = 0, $iMax = (int) $partitionDetails['partition_count']; $i < $iMax; $i++) {
  623. if (! isset($stmt->partitions[$i])) {
  624. $partitionDetails['partitions'][$i] = [
  625. 'name' => 'p' . $i,
  626. 'value_type' => '',
  627. 'value' => '',
  628. 'engine' => '',
  629. 'comment' => '',
  630. 'data_directory' => '',
  631. 'index_directory' => '',
  632. 'max_rows' => '',
  633. 'min_rows' => '',
  634. 'tablespace' => '',
  635. 'node_group' => '',
  636. ];
  637. } else {
  638. $p = $stmt->partitions[$i];
  639. $type = $p->type;
  640. $expr = trim((string) $p->expr, '()');
  641. if ($expr == 'MAXVALUE') {
  642. $type .= ' MAXVALUE';
  643. $expr = '';
  644. }
  645. $partitionDetails['partitions'][$i] = [
  646. 'name' => $p->name,
  647. 'value_type' => $type,
  648. 'value' => $expr,
  649. 'engine' => $p->options->has('ENGINE', true),
  650. 'comment' => trim((string) $p->options->has('COMMENT', true), "'"),
  651. 'data_directory' => trim((string) $p->options->has('DATA DIRECTORY', true), "'"),
  652. 'index_directory' => trim((string) $p->options->has('INDEX_DIRECTORY', true), "'"),
  653. 'max_rows' => $p->options->has('MAX_ROWS', true),
  654. 'min_rows' => $p->options->has('MIN_ROWS', true),
  655. 'tablespace' => $p->options->has('TABLESPACE', true),
  656. 'node_group' => $p->options->has('NODEGROUP', true),
  657. ];
  658. }
  659. $partition =& $partitionDetails['partitions'][$i];
  660. $partition['prefix'] = 'partitions[' . $i . ']';
  661. if ($partitionDetails['subpartition_count'] > 1) {
  662. $partition['subpartition_count'] = $partitionDetails['subpartition_count'];
  663. $partition['subpartitions'] = [];
  664. for ($j = 0, $jMax = (int) $partitionDetails['subpartition_count']; $j < $jMax; $j++) {
  665. if (! isset($stmt->partitions[$i]->subpartitions[$j])) {
  666. $partition['subpartitions'][$j] = [
  667. 'name' => $partition['name'] . '_s' . $j,
  668. 'engine' => '',
  669. 'comment' => '',
  670. 'data_directory' => '',
  671. 'index_directory' => '',
  672. 'max_rows' => '',
  673. 'min_rows' => '',
  674. 'tablespace' => '',
  675. 'node_group' => '',
  676. ];
  677. } else {
  678. $sp = $stmt->partitions[$i]->subpartitions[$j];
  679. $partition['subpartitions'][$j] = [
  680. 'name' => $sp->name,
  681. 'engine' => $sp->options->has('ENGINE', true),
  682. 'comment' => trim($sp->options->has('COMMENT', true), "'"),
  683. 'data_directory' => trim($sp->options->has('DATA DIRECTORY', true), "'"),
  684. 'index_directory' => trim($sp->options->has('INDEX_DIRECTORY', true), "'"),
  685. 'max_rows' => $sp->options->has('MAX_ROWS', true),
  686. 'min_rows' => $sp->options->has('MIN_ROWS', true),
  687. 'tablespace' => $sp->options->has('TABLESPACE', true),
  688. 'node_group' => $sp->options->has('NODEGROUP', true),
  689. ];
  690. }
  691. $subpartition =& $partition['subpartitions'][$j];
  692. $subpartition['prefix'] = 'partitions[' . $i . ']'
  693. . '[subpartitions][' . $j . ']';
  694. }
  695. }
  696. }
  697. return $partitionDetails;
  698. }
  699. /**
  700. * Update the table's partitioning based on $_REQUEST
  701. *
  702. * @return void
  703. */
  704. protected function updatePartitioning()
  705. {
  706. $sql_query = "ALTER TABLE " . Util::backquote($this->table) . " "
  707. . $this->createAddField->getPartitionsDefinition();
  708. // Execute alter query
  709. $result = $this->dbi->tryQuery($sql_query);
  710. if ($result !== false) {
  711. $message = Message::success(
  712. __('Table %1$s has been altered successfully.')
  713. );
  714. $message->addParam($this->table);
  715. $this->response->addHTML(
  716. Util::getMessage($message, $sql_query, 'success')
  717. );
  718. } else {
  719. $this->response->setRequestStatus(false);
  720. $this->response->addJSON(
  721. 'message',
  722. Message::rawError(
  723. __('Query error') . ':<br>' . $this->dbi->getError()
  724. )
  725. );
  726. }
  727. }
  728. /**
  729. * Function to get the type of command for multiple field handling
  730. *
  731. * @return string|null
  732. */
  733. protected function getMultipleFieldCommandType()
  734. {
  735. $types = [
  736. 'change',
  737. 'drop',
  738. 'primary',
  739. 'index',
  740. 'unique',
  741. 'spatial',
  742. 'fulltext',
  743. 'browse',
  744. ];
  745. foreach ($types as $type) {
  746. if (isset($_POST['submit_mult_' . $type . '_x'])) {
  747. return $type;
  748. }
  749. }
  750. if (isset($_POST['submit_mult'])) {
  751. return $_POST['submit_mult'];
  752. } elseif (isset($_POST['mult_btn'])
  753. && $_POST['mult_btn'] == __('Yes')
  754. ) {
  755. if (isset($_POST['selected'])) {
  756. $_POST['selected_fld'] = $_POST['selected'];
  757. }
  758. return 'row_delete';
  759. }
  760. return null;
  761. }
  762. /**
  763. * Function to display table browse for selected columns
  764. *
  765. * @param string $goto goto page url
  766. * @param string $pmaThemeImage URI of the pma theme image
  767. *
  768. * @return void
  769. */
  770. protected function displayTableBrowseForSelectedColumns($goto, $pmaThemeImage)
  771. {
  772. $GLOBALS['active_page'] = 'sql.php';
  773. $fields = [];
  774. foreach ($_POST['selected_fld'] as $sval) {
  775. $fields[] = Util::backquote($sval);
  776. }
  777. $sql_query = sprintf(
  778. 'SELECT %s FROM %s.%s',
  779. implode(', ', $fields),
  780. Util::backquote($this->db),
  781. Util::backquote($this->table)
  782. );
  783. // Parse and analyze the query
  784. $db = &$this->db;
  785. list(
  786. $analyzed_sql_results,
  787. $db,
  788. ) = ParseAnalyze::sqlQuery($sql_query, $db);
  789. // @todo: possibly refactor
  790. extract($analyzed_sql_results);
  791. $sql = new Sql();
  792. $this->response->addHTML(
  793. $sql->executeQueryAndGetQueryResponse(
  794. isset($analyzed_sql_results) ? $analyzed_sql_results : '',
  795. false, // is_gotofile
  796. $this->db, // db
  797. $this->table, // table
  798. null, // find_real_end
  799. null, // sql_query_for_bookmark
  800. null, // extra_data
  801. null, // message_to_show
  802. null, // message
  803. null, // sql_data
  804. $goto, // goto
  805. $pmaThemeImage, // pmaThemeImage
  806. null, // disp_query
  807. null, // disp_message
  808. null, // query_type
  809. $sql_query, // sql_query
  810. null, // selectedTables
  811. null // complete_query
  812. )
  813. );
  814. }
  815. /**
  816. * Update the table's structure based on $_REQUEST
  817. *
  818. * @return boolean true if error occurred
  819. *
  820. */
  821. protected function updateColumns()
  822. {
  823. $err_url = 'tbl_structure.php' . Url::getCommon(
  824. [
  825. 'db' => $this->db,
  826. 'table' => $this->table,
  827. ]
  828. );
  829. $regenerate = false;
  830. $field_cnt = count($_POST['field_name']);
  831. $changes = [];
  832. $adjust_privileges = [];
  833. $columns_with_index = $this->dbi
  834. ->getTable($this->db, $this->table)
  835. ->getColumnsWithIndex(
  836. Index::PRIMARY | Index::UNIQUE
  837. );
  838. for ($i = 0; $i < $field_cnt; $i++) {
  839. if (! $this->columnNeedsAlterTable($i)) {
  840. continue;
  841. }
  842. $changes[] = 'CHANGE ' . Table::generateAlter(
  843. Util::getValueByKey($_POST, "field_orig.${i}", ''),
  844. $_POST['field_name'][$i],
  845. $_POST['field_type'][$i],
  846. $_POST['field_length'][$i],
  847. $_POST['field_attribute'][$i],
  848. Util::getValueByKey($_POST, "field_collation.${i}", ''),
  849. Util::getValueByKey($_POST, "field_null.${i}", 'NO'),
  850. $_POST['field_default_type'][$i],
  851. $_POST['field_default_value'][$i],
  852. Util::getValueByKey($_POST, "field_extra.${i}", false),
  853. Util::getValueByKey($_POST, "field_comments.${i}", ''),
  854. Util::getValueByKey($_POST, "field_virtuality.${i}", ''),
  855. Util::getValueByKey($_POST, "field_expression.${i}", ''),
  856. Util::getValueByKey($_POST, "field_move_to.${i}", ''),
  857. $columns_with_index
  858. );
  859. // find the remembered sort expression
  860. $sorted_col = $this->table_obj->getUiProp(
  861. Table::PROP_SORTED_COLUMN
  862. );
  863. // if the old column name is part of the remembered sort expression
  864. if (mb_strpos(
  865. (string) $sorted_col,
  866. Util::backquote($_POST['field_orig'][$i])
  867. ) !== false) {
  868. // delete the whole remembered sort expression
  869. $this->table_obj->removeUiProp(Table::PROP_SORTED_COLUMN);
  870. }
  871. if (isset($_POST['field_adjust_privileges'][$i])
  872. && ! empty($_POST['field_adjust_privileges'][$i])
  873. && $_POST['field_orig'][$i] != $_POST['field_name'][$i]
  874. ) {
  875. $adjust_privileges[$_POST['field_orig'][$i]]
  876. = $_POST['field_name'][$i];
  877. }
  878. } // end for
  879. if (count($changes) > 0 || isset($_POST['preview_sql'])) {
  880. // Builds the primary keys statements and updates the table
  881. $key_query = '';
  882. /**
  883. * this is a little bit more complex
  884. *
  885. * @todo if someone selects A_I when altering a column we need to check:
  886. * - no other column with A_I
  887. * - the column has an index, if not create one
  888. *
  889. */
  890. // To allow replication, we first select the db to use
  891. // and then run queries on this db.
  892. if (! $this->dbi->selectDb($this->db)) {
  893. Util::mysqlDie(
  894. $this->dbi->getError(),
  895. 'USE ' . Util::backquote($this->db) . ';',
  896. false,
  897. $err_url
  898. );
  899. }
  900. $sql_query = 'ALTER TABLE ' . Util::backquote($this->table) . ' ';
  901. $sql_query .= implode(', ', $changes) . $key_query;
  902. $sql_query .= ';';
  903. // If there is a request for SQL previewing.
  904. if (isset($_POST['preview_sql'])) {
  905. Core::previewSQL(count($changes) > 0 ? $sql_query : '');
  906. }
  907. $columns_with_index = $this->dbi
  908. ->getTable($this->db, $this->table)
  909. ->getColumnsWithIndex(
  910. Index::PRIMARY | Index::UNIQUE | Index::INDEX
  911. | Index::SPATIAL | Index::FULLTEXT
  912. );
  913. $changedToBlob = [];
  914. // While changing the Column Collation
  915. // First change to BLOB
  916. for ($i = 0; $i < $field_cnt; $i++) {
  917. if (isset($_POST['field_collation'][$i])
  918. && isset($_POST['field_collation_orig'][$i])
  919. && $_POST['field_collation'][$i] !== $_POST['field_collation_orig'][$i]
  920. && ! in_array($_POST['field_orig'][$i], $columns_with_index)
  921. ) {
  922. $secondary_query = 'ALTER TABLE ' . Util::backquote(
  923. $this->table
  924. )
  925. . ' CHANGE ' . Util::backquote(
  926. $_POST['field_orig'][$i]
  927. )
  928. . ' ' . Util::backquote($_POST['field_orig'][$i])
  929. . ' BLOB';
  930. if (isset($_POST['field_virtuality'][$i])
  931. && isset($_POST['field_expression'][$i])) {
  932. if ($_POST['field_virtuality'][$i]) {
  933. $secondary_query .= ' AS (' . $_POST['field_expression'][$i] . ') '
  934. . $_POST['field_virtuality'][$i];
  935. }
  936. }
  937. $secondary_query .= ';';
  938. $this->dbi->query($secondary_query);
  939. $changedToBlob[$i] = true;
  940. } else {
  941. $changedToBlob[$i] = false;
  942. }
  943. }
  944. // Then make the requested changes
  945. $result = $this->dbi->tryQuery($sql_query);
  946. if ($result !== false) {
  947. $changed_privileges = $this->adjustColumnPrivileges(
  948. $adjust_privileges
  949. );
  950. if ($changed_privileges) {
  951. $message = Message::success(
  952. __(
  953. 'Table %1$s has been altered successfully. Privileges ' .
  954. 'have been adjusted.'
  955. )
  956. );
  957. } else {
  958. $message = Message::success(
  959. __('Table %1$s has been altered successfully.')
  960. );
  961. }
  962. $message->addParam($this->table);
  963. $this->response->addHTML(
  964. Util::getMessage($message, $sql_query, 'success')
  965. );
  966. } else {
  967. // An error happened while inserting/updating a table definition
  968. // Save the Original Error
  969. $orig_error = $this->dbi->getError();
  970. $changes_revert = [];
  971. // Change back to Original Collation and data type
  972. for ($i = 0; $i < $field_cnt; $i++) {
  973. if ($changedToBlob[$i]) {
  974. $changes_revert[] = 'CHANGE ' . Table::generateAlter(
  975. Util::getValueByKey($_POST, "field_orig.${i}", ''),
  976. $_POST['field_name'][$i],
  977. $_POST['field_type_orig'][$i],
  978. $_POST['field_length_orig'][$i],
  979. $_POST['field_attribute_orig'][$i],
  980. Util::getValueByKey($_POST, "field_collation_orig.${i}", ''),
  981. Util::getValueByKey($_POST, "field_null_orig.${i}", 'NO'),
  982. $_POST['field_default_type_orig'][$i],
  983. $_POST['field_default_value_orig'][$i],
  984. Util::getValueByKey($_POST, "field_extra_orig.${i}", false),
  985. Util::getValueByKey($_POST, "field_comments_orig.${i}", ''),
  986. Util::getValueByKey($_POST, "field_virtuality_orig.${i}", ''),
  987. Util::getValueByKey($_POST, "field_expression_orig.${i}", ''),
  988. Util::getValueByKey($_POST, "field_move_to_orig.${i}", '')
  989. );
  990. }
  991. }
  992. $revert_query = 'ALTER TABLE ' . Util::backquote($this->table)
  993. . ' ';
  994. $revert_query .= implode(', ', $changes_revert) . '';
  995. $revert_query .= ';';
  996. // Column reverted back to original
  997. $this->dbi->query($revert_query);
  998. $this->response->setRequestStatus(false);
  999. $this->response->addJSON(
  1000. 'message',
  1001. Message::rawError(
  1002. __('Query error') . ':<br>' . $orig_error
  1003. )
  1004. );
  1005. $regenerate = true;
  1006. }
  1007. }
  1008. // update field names in relation
  1009. if (isset($_POST['field_orig']) && is_array($_POST['field_orig'])) {
  1010. foreach ($_POST['field_orig'] as $fieldindex => $fieldcontent) {
  1011. if ($_POST['field_name'][$fieldindex] != $fieldcontent) {
  1012. $this->relation->renameField(
  1013. $this->db,
  1014. $this->table,
  1015. $fieldcontent,
  1016. $_POST['field_name'][$fieldindex]
  1017. );
  1018. }
  1019. }
  1020. }
  1021. // update mime types
  1022. if (isset($_POST['field_mimetype'])
  1023. && is_array($_POST['field_mimetype'])
  1024. && $GLOBALS['cfg']['BrowseMIME']
  1025. ) {
  1026. foreach ($_POST['field_mimetype'] as $fieldindex => $mimetype) {
  1027. if (isset($_POST['field_name'][$fieldindex])
  1028. && strlen($_POST['field_name'][$fieldindex]) > 0
  1029. ) {
  1030. $this->transformations->setMime(
  1031. $this->db,
  1032. $this->table,
  1033. $_POST['field_name'][$fieldindex],
  1034. $mimetype,
  1035. $_POST['field_transformation'][$fieldindex],
  1036. $_POST['field_transformation_options'][$fieldindex],
  1037. $_POST['field_input_transformation'][$fieldindex],
  1038. $_POST['field_input_transformation_options'][$fieldindex]
  1039. );
  1040. }
  1041. }
  1042. }
  1043. return $regenerate;
  1044. }
  1045. /**
  1046. * Adjusts the Privileges for all the columns whose names have changed
  1047. *
  1048. * @param array $adjust_privileges assoc array of old col names mapped to new
  1049. * cols
  1050. *
  1051. * @return boolean boolean whether at least one column privileges
  1052. * adjusted
  1053. */
  1054. protected function adjustColumnPrivileges(array $adjust_privileges)
  1055. {
  1056. $changed = false;
  1057. if (Util::getValueByKey($GLOBALS, 'col_priv', false)
  1058. && Util::getValueByKey($GLOBALS, 'is_reload_priv', false)
  1059. ) {
  1060. $this->dbi->selectDb('mysql');
  1061. // For Column specific privileges
  1062. foreach ($adjust_privileges as $oldCol => $newCol) {
  1063. $this->dbi->query(
  1064. sprintf(
  1065. 'UPDATE %s SET Column_name = "%s"
  1066. WHERE Db = "%s"
  1067. AND Table_name = "%s"
  1068. AND Column_name = "%s";',
  1069. Util::backquote('columns_priv'),
  1070. $newCol,
  1071. $this->db,
  1072. $this->table,
  1073. $oldCol
  1074. )
  1075. );
  1076. // i.e. if atleast one column privileges adjusted
  1077. $changed = true;
  1078. }
  1079. if ($changed) {
  1080. // Finally FLUSH the new privileges
  1081. $this->dbi->query("FLUSH PRIVILEGES;");
  1082. }
  1083. }
  1084. return $changed;
  1085. }
  1086. /**
  1087. * Verifies if some elements of a column have changed
  1088. *
  1089. * @param integer $i column index in the request
  1090. *
  1091. * @return boolean true if we need to generate ALTER TABLE
  1092. *
  1093. */
  1094. protected function columnNeedsAlterTable($i)
  1095. {
  1096. // these two fields are checkboxes so might not be part of the
  1097. // request; therefore we define them to avoid notices below
  1098. if (! isset($_POST['field_null'][$i])) {
  1099. $_POST['field_null'][$i] = 'NO';
  1100. }
  1101. if (! isset($_POST['field_extra'][$i])) {
  1102. $_POST['field_extra'][$i] = '';
  1103. }
  1104. // field_name does not follow the convention (corresponds to field_orig)
  1105. if ($_POST['field_name'][$i] != $_POST['field_orig'][$i]) {
  1106. return true;
  1107. }
  1108. $fields = [
  1109. 'field_attribute',
  1110. 'field_collation',
  1111. 'field_comments',
  1112. 'field_default_value',
  1113. 'field_default_type',
  1114. 'field_extra',
  1115. 'field_length',
  1116. 'field_null',
  1117. 'field_type',
  1118. ];
  1119. foreach ($fields as $field) {
  1120. if ($_POST[$field][$i] != $_POST[$field . '_orig'][$i]) {
  1121. return true;
  1122. }
  1123. }
  1124. return ! empty($_POST['field_move_to'][$i]);
  1125. }
  1126. /**
  1127. * Displays the table structure ('show table' works correct since 3.23.03)
  1128. *
  1129. * @param array $cfgRelation current relation parameters
  1130. * @param array $columns_with_unique_index Columns with unique index
  1131. * @param mixed $url_params Contains an associative
  1132. * array with url params
  1133. * @param Index|false $primary_index primary index or false if
  1134. * no one exists
  1135. * @param array $fields Fields
  1136. * @param array $columns_with_index Columns with index
  1137. *
  1138. * @return string
  1139. */
  1140. protected function displayStructure(
  1141. array $cfgRelation,
  1142. array $columns_with_unique_index,
  1143. $url_params,
  1144. $primary_index,
  1145. array $fields,
  1146. array $columns_with_index
  1147. ) {
  1148. // prepare comments
  1149. $comments_map = [];
  1150. $mime_map = [];
  1151. if ($GLOBALS['cfg']['ShowPropertyComments']) {
  1152. $comments_map = $this->relation->getComments($this->db, $this->table);
  1153. if ($cfgRelation['mimework'] && $GLOBALS['cfg']['BrowseMIME']) {
  1154. $mime_map = $this->transformations->getMime($this->db, $this->table, true);
  1155. }
  1156. }
  1157. $centralColumns = new CentralColumns($this->dbi);
  1158. $central_list = $centralColumns->getFromTable(
  1159. $this->db,
  1160. $this->table
  1161. );
  1162. $columns_list = [];
  1163. $titles = [
  1164. 'Change' => Util::getIcon('b_edit', __('Change')),
  1165. 'Drop' => Util::getIcon('b_drop', __('Drop')),
  1166. 'NoDrop' => Util::getIcon('b_drop', __('Drop')),
  1167. 'Primary' => Util::getIcon('b_primary', __('Primary')),
  1168. 'Index' => Util::getIcon('b_index', __('Index')),
  1169. 'Unique' => Util::getIcon('b_unique', __('Unique')),
  1170. 'Spatial' => Util::getIcon('b_spatial', __('Spatial')),
  1171. 'IdxFulltext' => Util::getIcon('b_ftext', __('Fulltext')),
  1172. 'NoPrimary' => Util::getIcon('bd_primary', __('Primary')),
  1173. 'NoIndex' => Util::getIcon('bd_index', __('Index')),
  1174. 'NoUnique' => Util::getIcon('bd_unique', __('Unique')),
  1175. 'NoSpatial' => Util::getIcon('bd_spatial', __('Spatial')),
  1176. 'NoIdxFulltext' => Util::getIcon('bd_ftext', __('Fulltext')),
  1177. 'DistinctValues' => Util::getIcon('b_browse', __('Distinct values')),
  1178. ];
  1179. $edit_view_url = '';
  1180. if ($this->_tbl_is_view && ! $this->_db_is_system_schema) {
  1181. $edit_view_url = Url::getCommon([
  1182. 'db' => $this->db,
  1183. 'table' => $this->table,
  1184. ]);
  1185. }
  1186. /**
  1187. * Displays Space usage and row statistics
  1188. */
  1189. // BEGIN - Calc Table Space
  1190. // Get valid statistics whatever is the table type
  1191. if ($GLOBALS['cfg']['ShowStats']) {
  1192. //get table stats in HTML format
  1193. $tablestats = $this->getTableStats();
  1194. //returning the response in JSON format to be used by Ajax
  1195. $this->response->addJSON('tableStat', $tablestats);
  1196. }
  1197. // END - Calc Table Space
  1198. $hideStructureActions = false;
  1199. if ($GLOBALS['cfg']['HideStructureActions'] === true) {
  1200. $hideStructureActions = true;
  1201. }
  1202. // logic removed from Template
  1203. $rownum = 0;
  1204. $columns_list = [];
  1205. $attributes = [];
  1206. $displayed_fields = [];
  1207. $row_comments = [];
  1208. $extracted_columnspecs = [];
  1209. $collations = [];
  1210. foreach ($fields as &$field) {
  1211. $rownum += 1;
  1212. $columns_list[] = $field['Field'];
  1213. $extracted_columnspecs[$rownum] = Util::extractColumnSpec($field['Type']);
  1214. $attributes[$rownum] = $extracted_columnspecs[$rownum]['attribute'];
  1215. if (strpos($field['Extra'], 'on update CURRENT_TIMESTAMP') !== false) {
  1216. $attributes[$rownum] = 'on update CURRENT_TIMESTAMP';
  1217. }
  1218. $displayed_fields[$rownum] = new stdClass();
  1219. $displayed_fields[$rownum]->text = $field['Field'];
  1220. $displayed_fields[$rownum]->icon = "";
  1221. $row_comments[$rownum] = '';
  1222. if (isset($comments_map[$field['Field']])) {
  1223. $displayed_fields[$rownum]->comment = $comments_map[$field['Field']];
  1224. $row_comments[$rownum] = $comments_map[$field['Field']];
  1225. }
  1226. if ($primary_index && $primary_index->hasColumn($field['Field'])) {
  1227. $displayed_fields[$rownum]->icon .=
  1228. Util::getImage('b_primary', __('Primary'));
  1229. }
  1230. if (in_array($field['Field'], $columns_with_index)) {
  1231. $displayed_fields[$rownum]->icon .=
  1232. Util::getImage('bd_primary', __('Index'));
  1233. }
  1234. $collation = Charsets::findCollationByName(
  1235. $this->dbi,
  1236. $GLOBALS['cfg']['Server']['DisableIS'],
  1237. $field['Collation'] ?? ''
  1238. );
  1239. if ($collation !== null) {
  1240. $collations[$collation->getName()] = [
  1241. 'name' => $collation->getName(),
  1242. 'description' => $collation->getDescription(),
  1243. ];
  1244. }
  1245. }
  1246. $engine = $this->table_obj->getStorageEngine();
  1247. return $this->template->render('table/structure/display_structure', [
  1248. 'url_params' => [
  1249. 'db' => $this->db,
  1250. 'table' => $this->table,
  1251. ],
  1252. 'collations' => $collations,
  1253. 'is_foreign_key_supported' => Util::isForeignKeySupported($engine),
  1254. 'displayIndexesHtml' => Index::getHtmlForDisplayIndexes(),
  1255. 'cfg_relation' => $this->relation->getRelationsParam(),
  1256. 'hide_structure_actions' => $hideStructureActions,
  1257. 'db' => $this->db,
  1258. 'table' => $this->table,
  1259. 'db_is_system_schema' => $this->_db_is_system_schema,
  1260. 'tbl_is_view' => $this->_tbl_is_view,
  1261. 'mime_map' => $mime_map,
  1262. 'url_query' => $this->_url_query,
  1263. 'titles' => $titles,
  1264. 'tbl_storage_engine' => $this->_tbl_storage_engine,
  1265. 'primary' => $primary_index,
  1266. 'columns_with_unique_index' => $columns_with_unique_index,
  1267. 'edit_view_url' => $edit_view_url,
  1268. 'columns_list' => $columns_list,
  1269. 'table_stats' => isset($tablestats) ? $tablestats : null,
  1270. 'fields' => $fields,
  1271. 'extracted_columnspecs' => $extracted_columnspecs,
  1272. 'columns_with_index' => $columns_with_index,
  1273. 'central_list' => $central_list,
  1274. 'comments_map' => $comments_map,
  1275. 'browse_mime' => $GLOBALS['cfg']['BrowseMIME'],
  1276. 'show_column_comments' => $GLOBALS['cfg']['ShowColumnComments'],
  1277. 'show_stats' => $GLOBALS['cfg']['ShowStats'],
  1278. 'relation_commwork' => $GLOBALS['cfgRelation']['commwork'],
  1279. 'relation_mimework' => $GLOBALS['cfgRelation']['mimework'],
  1280. 'central_columns_work' => $GLOBALS['cfgRelation']['centralcolumnswork'],
  1281. 'mysql_int_version' => $this->dbi->getVersion(),
  1282. 'is_mariadb' => $this->dbi->isMariaDB(),
  1283. 'pma_theme_image' => $GLOBALS['pmaThemeImage'],
  1284. 'text_dir' => $GLOBALS['text_dir'],
  1285. 'is_active' => Tracker::isActive(),
  1286. 'have_partitioning' => Partition::havePartitioning(),
  1287. 'partitions' => Partition::getPartitions($this->db, $this->table),
  1288. 'partition_names' => Partition::getPartitionNames($this->db, $this->table),
  1289. 'default_sliders_state' => $GLOBALS['cfg']['InitialSlidersState'],
  1290. 'attributes' => $attributes,
  1291. 'displayed_fields' => $displayed_fields,
  1292. 'row_comments' => $row_comments,
  1293. ]);
  1294. }
  1295. /**
  1296. * Get HTML snippet for display table statistics
  1297. *
  1298. * @return string
  1299. */
  1300. protected function getTableStats()
  1301. {
  1302. if (empty($this->_showtable)) {
  1303. $this->_showtable = $this->dbi->getTable(
  1304. $this->db,
  1305. $this->table
  1306. )->getStatusInfo(null, true);
  1307. }
  1308. if (is_string($this->_showtable)) {
  1309. $this->_showtable = [];
  1310. }
  1311. if (empty($this->_showtable['Data_length'])) {
  1312. $this->_showtable['Data_length'] = 0;
  1313. }
  1314. if (empty($this->_showtable['Index_length'])) {
  1315. $this->_showtable['Index_length'] = 0;
  1316. }
  1317. $is_innodb = (isset($this->_showtable['Type'])
  1318. && $this->_showtable['Type'] == 'InnoDB');
  1319. $mergetable = $this->table_obj->isMerge();
  1320. // this is to display for example 261.2 MiB instead of 268k KiB
  1321. $max_digits = 3;
  1322. $decimals = 1;
  1323. list($data_size, $data_unit) = Util::formatByteDown(
  1324. $this->_showtable['Data_length'],
  1325. $max_digits,
  1326. $decimals
  1327. );
  1328. if ($mergetable === false) {
  1329. list($index_size, $index_unit) = Util::formatByteDown(
  1330. $this->_showtable['Index_length'],
  1331. $max_digits,
  1332. $decimals
  1333. );
  1334. }
  1335. if (isset($this->_showtable['Data_free'])) {
  1336. list($free_size, $free_unit) = Util::formatByteDown(
  1337. $this->_showtable['Data_free'],
  1338. $max_digits,
  1339. $decimals
  1340. );
  1341. list($effect_size, $effect_unit) = Util::formatByteDown(
  1342. $this->_showtable['Data_length']
  1343. + $this->_showtable['Index_length']
  1344. - $this->_showtable['Data_free'],
  1345. $max_digits,
  1346. $decimals
  1347. );
  1348. } else {
  1349. list($effect_size, $effect_unit) = Util::formatByteDown(
  1350. $this->_showtable['Data_length']
  1351. + $this->_showtable['Index_length'],
  1352. $max_digits,
  1353. $decimals
  1354. );
  1355. }
  1356. list($tot_size, $tot_unit) = Util::formatByteDown(
  1357. $this->_showtable['Data_length'] + $this->_showtable['Index_length'],
  1358. $max_digits,
  1359. $decimals
  1360. );
  1361. if ($this->_table_info_num_rows > 0) {
  1362. list($avg_size, $avg_unit) = Util::formatByteDown(
  1363. ($this->_showtable['Data_length']
  1364. + $this->_showtable['Index_length'])
  1365. / $this->_showtable['Rows'],
  1366. 6,
  1367. 1
  1368. );
  1369. } else {
  1370. $avg_size = $avg_unit = '';
  1371. }
  1372. /** @var Innodb $innodbEnginePlugin */
  1373. $innodbEnginePlugin = StorageEngine::getEngine('Innodb');
  1374. $innodb_file_per_table = $innodbEnginePlugin->supportsFilePerTable();
  1375. $engine = $this->dbi->getTable($this->db, $this->table)->getStorageEngine();
  1376. $tableCollation = [];
  1377. $collation = Charsets::findCollationByName(
  1378. $this->dbi,
  1379. $GLOBALS['cfg']['Server']['DisableIS'],
  1380. $this->_tbl_collation
  1381. );
  1382. if ($collation !== null) {
  1383. $tableCollation = [
  1384. 'name' => $collation->getName(),
  1385. 'description' => $collation->getDescription(),
  1386. ];
  1387. }
  1388. return $this->template->render('table/structure/display_table_stats', [
  1389. 'url_params' => [
  1390. 'db' => $GLOBALS['db'],
  1391. 'table' => $GLOBALS['table'],
  1392. ],
  1393. 'is_foreign_key_supported' => Util::isForeignKeySupported($engine),
  1394. 'cfg_relation' => $this->relation->getRelationsParam(),
  1395. 'showtable' => $this->_showtable,
  1396. 'table_info_num_rows' => $this->_table_info_num_rows,
  1397. 'tbl_is_view' => $this->_tbl_is_view,
  1398. 'db_is_system_schema' => $this->_db_is_system_schema,
  1399. 'tbl_storage_engine' => $this->_tbl_storage_engine,
  1400. 'url_query' => $this->_url_query,
  1401. 'table_collation' => $tableCollation,
  1402. 'is_innodb' => $is_innodb,
  1403. 'mergetable' => $mergetable,
  1404. 'avg_size' => isset($avg_size) ? $avg_size : null,
  1405. 'avg_unit' => isset($avg_unit) ? $avg_unit : null,
  1406. 'data_size' => $data_size,
  1407. 'data_unit' => $data_unit,
  1408. 'index_size' => isset($index_size) ? $index_size : null,
  1409. 'index_unit' => isset($index_unit) ? $index_unit : null,
  1410. 'innodb_file_per_table' => $innodb_file_per_table,
  1411. 'free_size' => isset($free_size) ? $free_size : null,
  1412. 'free_unit' => isset($free_unit) ? $free_unit : null,
  1413. 'effect_size' => $effect_size,
  1414. 'effect_unit' => $effect_unit,
  1415. 'tot_size' => $tot_size,
  1416. 'tot_unit' => $tot_unit,
  1417. 'table' => $GLOBALS['table'],
  1418. ]);
  1419. }
  1420. /**
  1421. * Gets table primary key
  1422. *
  1423. * @return string
  1424. */
  1425. protected function getKeyForTablePrimary()
  1426. {
  1427. $this->dbi->selectDb($this->db);
  1428. $result = $this->dbi->query(
  1429. 'SHOW KEYS FROM ' . Util::backquote($this->table) . ';'
  1430. );
  1431. $primary = '';
  1432. while ($row = $this->dbi->fetchAssoc($result)) {
  1433. // Backups the list of primary keys
  1434. if ($row['Key_name'] == 'PRIMARY') {
  1435. $primary .= $row['Column_name'] . ', ';
  1436. }
  1437. } // end while
  1438. $this->dbi->freeResult($result);
  1439. return $primary;
  1440. }
  1441. /**
  1442. * Get List of information for Submit Mult
  1443. *
  1444. * @param string $submit_mult mult_submit type
  1445. * @param array $selected the selected columns
  1446. * @param string $action action type
  1447. * @param ContainerBuilder $containerBuilder Container builder instance
  1448. *
  1449. * @return array
  1450. */
  1451. protected function getDataForSubmitMult($submit_mult, $selected, $action, ContainerBuilder $containerBuilder)
  1452. {
  1453. $centralColumns = new CentralColumns($this->dbi);
  1454. $what = null;
  1455. $query_type = null;
  1456. $is_unset_submit_mult = false;
  1457. $mult_btn = null;
  1458. $centralColsError = null;
  1459. switch ($submit_mult) {
  1460. case 'drop':
  1461. $what = 'drop_fld';
  1462. break;
  1463. case 'primary':
  1464. // Gets table primary key
  1465. $primary = $this->getKeyForTablePrimary();
  1466. if (empty($primary)) {
  1467. // no primary key, so we can safely create new
  1468. $is_unset_submit_mult = true;
  1469. $query_type = 'primary_fld';
  1470. $mult_btn = __('Yes');
  1471. } else {
  1472. // primary key exists, so lets as user
  1473. $what = 'primary_fld';
  1474. }
  1475. break;
  1476. case 'index':
  1477. $is_unset_submit_mult = true;
  1478. $query_type = 'index_fld';
  1479. $mult_btn = __('Yes');
  1480. break;
  1481. case 'unique':
  1482. $is_unset_submit_mult = true;
  1483. $query_type = 'unique_fld';
  1484. $mult_btn = __('Yes');
  1485. break;
  1486. case 'spatial':
  1487. $is_unset_submit_mult = true;
  1488. $query_type = 'spatial_fld';
  1489. $mult_btn = __('Yes');
  1490. break;
  1491. case 'ftext':
  1492. $is_unset_submit_mult = true;
  1493. $query_type = 'fulltext_fld';
  1494. $mult_btn = __('Yes');
  1495. break;
  1496. case 'add_to_central_columns':
  1497. $centralColsError = $centralColumns->syncUniqueColumns(
  1498. $selected,
  1499. false
  1500. );
  1501. break;
  1502. case 'remove_from_central_columns':
  1503. $centralColsError = $centralColumns->deleteColumnsFromList(
  1504. $_POST['db'],
  1505. $selected,
  1506. false
  1507. );
  1508. break;
  1509. case 'change':
  1510. $this->displayHtmlForColumnChange($selected, $action, $containerBuilder);
  1511. // execution stops here but PhpMyAdmin\Response correctly finishes
  1512. // the rendering
  1513. exit;
  1514. case 'browse':
  1515. // this should already be handled by tbl_structure.php
  1516. }
  1517. return [
  1518. $what,
  1519. $query_type,
  1520. $is_unset_submit_mult,
  1521. $mult_btn,
  1522. $centralColsError,
  1523. ];
  1524. }
  1525. }