TableStructureController.class.php 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * Holds the PMA\TableStructureController
  5. *
  6. * @package PMA
  7. */
  8. namespace PMA\Controllers;
  9. use PMA\Template;
  10. use PMA_Index;
  11. use PMA_Partition;
  12. use PMA_Table;
  13. use PMA_Message;
  14. use PMA_PageSettings;
  15. use PMA_Util;
  16. use PMA\Util;
  17. use SqlParser;
  18. require_once 'libraries/Index.class.php';
  19. require_once 'libraries/Partition.class.php';
  20. require_once 'libraries/mysql_charsets.inc.php';
  21. require_once 'libraries/config/page_settings.class.php';
  22. require_once 'libraries/transformations.lib.php';
  23. require_once 'libraries/Template.class.php';
  24. require_once 'libraries/util.lib.php';
  25. require_once 'libraries/controllers/TableController.class.php';
  26. /**
  27. * Handles table structure logic
  28. *
  29. * @package PhpMyAdmin
  30. */
  31. class TableStructureController extends TableController
  32. {
  33. /**
  34. * @var PMA_Table The table object
  35. */
  36. protected $_table_obj;
  37. /**
  38. * @var string The URL query string
  39. */
  40. protected $_url_query;
  41. /**
  42. * @var bool DB is information_schema
  43. */
  44. protected $_db_is_system_schema;
  45. /**
  46. * @var bool Table is a view
  47. */
  48. protected $_tbl_is_view;
  49. /**
  50. * @var string Table storage engine
  51. */
  52. protected $_tbl_storage_engine;
  53. /**
  54. * @var int Number of rows
  55. */
  56. protected $_table_info_num_rows;
  57. /**
  58. * @var string Table collation
  59. */
  60. protected $_tbl_collation;
  61. /**
  62. * @var array Show table info
  63. */
  64. protected $_showtable;
  65. /**
  66. * TableStructureController constructor
  67. *
  68. * @param string $type Indicate the db_structure or tbl_structure
  69. * @param string $db DB name
  70. * @param string $table Table name
  71. * @param string $url_query URL query
  72. * @param int $num_tables Number of tables
  73. * @param int $pos Current position in the list
  74. * @param bool $db_is_system_schema DB is information_schema
  75. * @param int $total_num_tables Number of tables
  76. * @param array $tables Tables in the DB
  77. * @param bool $is_show_stats Whether stats show or not
  78. * @param bool $tbl_is_view Table is a view
  79. * @param string $tbl_storage_engine Table storage engine
  80. * @param int $table_info_num_rows Number of rows
  81. * @param string $tbl_collation Table collation
  82. * @param array $showtable Show table info
  83. */
  84. public function __construct(
  85. $type, $db, $table, $url_query, $num_tables, $pos, $db_is_system_schema,
  86. $total_num_tables, $tables, $is_show_stats, $tbl_is_view,
  87. $tbl_storage_engine, $table_info_num_rows, $tbl_collation, $showtable
  88. ) {
  89. parent::__construct();
  90. $this->_db_is_system_schema = $db_is_system_schema;
  91. $this->_url_query = $url_query;
  92. $this->_tbl_is_view = $tbl_is_view;
  93. $this->_tbl_storage_engine = $tbl_storage_engine;
  94. $this->_table_info_num_rows = $table_info_num_rows;
  95. $this->_tbl_collation = $tbl_collation;
  96. $this->_showtable = $showtable;
  97. $this->table_obj = $this->dbi->getTable($this->db, $this->table);
  98. }
  99. /**
  100. * Index action
  101. *
  102. * @return void
  103. */
  104. public function indexAction()
  105. {
  106. PMA_PageSettings::showGroup('TableStructure');
  107. /**
  108. * Function implementations for this script
  109. */
  110. include_once 'libraries/check_user_privileges.lib.php';
  111. include_once 'libraries/index.lib.php';
  112. include_once 'libraries/sql.lib.php';
  113. include_once 'libraries/bookmark.lib.php';
  114. $this->response->getHeader()->getScripts()->addFiles(
  115. array(
  116. 'tbl_structure.js',
  117. 'indexes.js'
  118. )
  119. );
  120. /**
  121. * Handle column moving
  122. */
  123. if (isset($_REQUEST['move_columns'])
  124. && is_array($_REQUEST['move_columns'])
  125. && $this->response->isAjax()
  126. ) {
  127. $this->moveColumns();
  128. return;
  129. }
  130. /**
  131. * handle MySQL reserved words columns check
  132. */
  133. if (isset($_REQUEST['reserved_word_check'])) {
  134. if ($GLOBALS['cfg']['ReservedWordDisableWarning'] === false) {
  135. $columns_names = $_REQUEST['field_name'];
  136. $reserved_keywords_names = array();
  137. foreach ($columns_names as $column) {
  138. if (SqlParser\Context::isKeyword(trim($column), true)) {
  139. $reserved_keywords_names[] = trim($column);
  140. }
  141. }
  142. if (SqlParser\Context::isKeyword(trim($this->table), true)) {
  143. $reserved_keywords_names[] = trim($this->table);
  144. }
  145. if (count($reserved_keywords_names) == 0) {
  146. $this->response->isSuccess(false);
  147. }
  148. $this->response->addJSON(
  149. 'message', sprintf(
  150. _ngettext(
  151. 'The name \'%s\' is a MySQL reserved keyword.',
  152. 'The names \'%s\' are MySQL reserved keywords.',
  153. count($reserved_keywords_names)
  154. ),
  155. implode(',', $reserved_keywords_names)
  156. )
  157. );
  158. } else {
  159. $this->response->isSuccess(false);
  160. }
  161. return;
  162. }
  163. /**
  164. * A click on Change has been made for one column
  165. */
  166. if (isset($_REQUEST['change_column'])) {
  167. $this->displayHtmlForColumnChange(null, 'tbl_structure.php');
  168. return;
  169. }
  170. /**
  171. * handle multiple field commands if required
  172. *
  173. * submit_mult_*_x comes from IE if <input type="img" ...> is used
  174. */
  175. $submit_mult = $this->getMultipleFieldCommandType();
  176. if (! empty($submit_mult)) {
  177. if (isset($_REQUEST['selected_fld'])) {
  178. if ($submit_mult == 'browse') {
  179. // browsing the table displaying only selected columns
  180. $this->displayTableBrowseForSelectedColumns(
  181. $GLOBALS['goto'], $GLOBALS['pmaThemeImage']
  182. );
  183. } else {
  184. // handle multiple field commands
  185. // handle confirmation of deleting multiple columns
  186. $action = 'tbl_structure.php';
  187. $GLOBALS['selected'] = $_REQUEST['selected_fld'];
  188. list(
  189. $what_ret, $query_type_ret, $is_unset_submit_mult,
  190. $mult_btn_ret, $centralColsError
  191. )
  192. = $this->getDataForSubmitMult(
  193. $submit_mult, $_REQUEST['selected_fld'], $action
  194. );
  195. //update the existing variables
  196. // todo: refactor mult_submits.inc.php such as
  197. // below globals are not needed anymore
  198. if (isset($what_ret)) {
  199. $GLOBALS['what'] = $what_ret;
  200. global $what;
  201. }
  202. if (isset($query_type_ret)) {
  203. $GLOBALS['query_type'] = $query_type_ret;
  204. global $query_type;
  205. }
  206. if ($is_unset_submit_mult) {
  207. unset($submit_mult);
  208. }
  209. if (isset($mult_btn_ret)) {
  210. $GLOBALS['mult_btn'] = $mult_btn_ret;
  211. global $mult_btn;
  212. }
  213. include 'libraries/mult_submits.inc.php';
  214. /**
  215. * if $submit_mult == 'change', execution will have stopped
  216. * at this point
  217. */
  218. if (empty($message)) {
  219. $message = PMA_Message::success();
  220. }
  221. $this->response->addHTML(
  222. PMA_Util::getMessage($message, $sql_query)
  223. );
  224. }
  225. } else {
  226. $this->response->isSuccess(false);
  227. $this->response->addJSON('message', __('No column selected.'));
  228. }
  229. }
  230. // display secondary level tabs if necessary
  231. $engine = $this->table_obj->getStatusInfo('ENGINE');
  232. $this->response->addHTML(
  233. Template::get('table/secondary_tabs')->render(
  234. array(
  235. 'url_params' => array(
  236. 'db' => $this->db,
  237. 'table' => $this->table
  238. ),
  239. 'engine' => $engine
  240. )
  241. )
  242. );
  243. $this->response->addHTML('<div id="structure_content">');
  244. /**
  245. * Modifications have been submitted -> updates the table
  246. */
  247. if (isset($_REQUEST['do_save_data'])) {
  248. $regenerate = $this->updateColumns();
  249. if ($regenerate) {
  250. // This happens when updating failed
  251. // @todo: do something appropriate
  252. } else {
  253. // continue to show the table's structure
  254. unset($_REQUEST['selected']);
  255. }
  256. }
  257. /**
  258. * Adding indexes
  259. */
  260. if (isset($_REQUEST['add_key'])
  261. || isset($_REQUEST['partition_maintenance'])
  262. ) {
  263. //todo: set some variables for sql.php include, to be eliminated
  264. //after refactoring sql.php
  265. $db = $this->db;
  266. $table = $this->table;
  267. $cfg = $GLOBALS['cfg'];
  268. $is_superuser = $GLOBALS['dbi']->isSuperuser();
  269. $pmaThemeImage = $GLOBALS['pmaThemeImage'];
  270. include 'sql.php';
  271. $GLOBALS['reload'] = true;
  272. }
  273. /**
  274. * Gets the relation settings
  275. */
  276. $cfgRelation = PMA_getRelationsParam();
  277. /**
  278. * Runs common work
  279. */
  280. // set db, table references, for require_once that follows
  281. // got to be eliminated in long run
  282. $db = &$this->db;
  283. $table = &$this->table;
  284. include_once 'libraries/tbl_common.inc.php';
  285. $this->_db_is_system_schema = $db_is_system_schema;
  286. $this->_url_query = $url_query
  287. . '&amp;goto=tbl_structure.php&amp;back=tbl_structure.php';
  288. $url_params['goto'] = 'tbl_structure.php';
  289. $url_params['back'] = 'tbl_structure.php';
  290. /**
  291. * Gets tables information
  292. */
  293. include_once 'libraries/tbl_info.inc.php';
  294. include_once 'libraries/Index.class.php';
  295. // 2. Gets table keys and retains them
  296. // @todo should be: $server->db($db)->table($table)->primary()
  297. $primary = PMA_Index::getPrimary($this->table, $this->db);
  298. $columns_with_index = $this->dbi
  299. ->getTable($this->db, $this->table)
  300. ->getColumnsWithIndex(
  301. PMA_Index::UNIQUE | PMA_Index::INDEX | PMA_Index::SPATIAL
  302. | PMA_Index::FULLTEXT
  303. );
  304. $columns_with_unique_index = $this->dbi
  305. ->getTable($this->db, $this->table)
  306. ->getColumnsWithIndex(PMA_Index::UNIQUE);
  307. // 3. Get fields
  308. $fields = (array)$this->dbi->getColumns(
  309. $this->db, $this->table, null, true
  310. );
  311. // Get more complete field information
  312. // For now, this is done just for MySQL 4.1.2+ new TIMESTAMP options
  313. // but later, if the analyser returns more information, it
  314. // could be executed for any MySQL version and replace
  315. // the info given by SHOW FULL COLUMNS FROM.
  316. //
  317. // We also need this to correctly learn if a TIMESTAMP is NOT NULL, since
  318. // SHOW FULL COLUMNS or INFORMATION_SCHEMA incorrectly says NULL
  319. // and SHOW CREATE TABLE says NOT NULL (tested
  320. // in MySQL 4.0.25 and 5.0.21, http://bugs.mysql.com/20910).
  321. $show_create_table = $this->table_obj->showCreate();
  322. $parser = new SqlParser\Parser($show_create_table);
  323. /**
  324. * @var CreateStatement $stmt
  325. */
  326. $stmt = $parser->statements[0];
  327. $create_table_fields = SqlParser\Utils\Table::getFields($stmt);
  328. //display table structure
  329. $this->response->addHTML(
  330. $this->displayStructure(
  331. $cfgRelation, $columns_with_unique_index, $url_params, $primary,
  332. $fields, $columns_with_index, $create_table_fields
  333. )
  334. );
  335. $this->response->addHTML('</div>');
  336. }
  337. /**
  338. * Moves columns in the table's structure based on $_REQUEST
  339. *
  340. * @return void
  341. */
  342. protected function moveColumns()
  343. {
  344. $this->dbi->selectDb($this->db);
  345. /*
  346. * load the definitions for all columns
  347. */
  348. $columns = $this->dbi->getColumnsFull($this->db, $this->table);
  349. $column_names = array_keys($columns);
  350. $changes = array();
  351. // move columns from first to last
  352. for ($i = 0, $l = count($_REQUEST['move_columns']); $i < $l; $i++) {
  353. $column = $_REQUEST['move_columns'][$i];
  354. // is this column already correctly placed?
  355. if ($column_names[$i] == $column) {
  356. continue;
  357. }
  358. // it is not, let's move it to index $i
  359. $data = $columns[$column];
  360. $extracted_columnspec = PMA_Util::extractColumnSpec($data['Type']);
  361. if (isset($data['Extra'])
  362. && $data['Extra'] == 'on update CURRENT_TIMESTAMP'
  363. ) {
  364. $extracted_columnspec['attribute'] = $data['Extra'];
  365. unset($data['Extra']);
  366. }
  367. $current_timestamp = ($data['Type'] == 'timestamp'
  368. || $data['Type'] == 'datetime')
  369. && $data['Default'] == 'CURRENT_TIMESTAMP';
  370. if ($data['Null'] === 'YES' && $data['Default'] === null) {
  371. $default_type = 'NULL';
  372. } elseif ($current_timestamp) {
  373. $default_type = 'CURRENT_TIMESTAMP';
  374. } elseif ($data['Default'] === null) {
  375. $default_type = 'NONE';
  376. } else {
  377. $default_type = 'USER_DEFINED';
  378. }
  379. $virtual = array(
  380. 'VIRTUAL', 'PERSISTENT', 'VIRTUAL GENERATED', 'STORED GENERATED'
  381. );
  382. $data['Virtuality'] = '';
  383. $data['Expression'] = '';
  384. if (isset($data['Extra']) && in_array($data['Extra'], $virtual)) {
  385. $data['Virtuality'] = str_replace(' GENERATED', '', $data['Extra']);
  386. $expressions = $this->table->getColumnGenerationExpression($column);
  387. $data['Expression'] = $expressions[$column];
  388. }
  389. $changes[] = 'CHANGE ' . PMA_Table::generateAlter(
  390. $column,
  391. $column,
  392. /*overload*/mb_strtoupper($extracted_columnspec['type']),
  393. $extracted_columnspec['spec_in_brackets'],
  394. $extracted_columnspec['attribute'],
  395. isset($data['Collation']) ? $data['Collation'] : '',
  396. $data['Null'] === 'YES' ? 'NULL' : 'NOT NULL',
  397. $default_type,
  398. $current_timestamp ? '' : $data['Default'],
  399. isset($data['Extra']) && $data['Extra'] !== '' ? $data['Extra']
  400. : false,
  401. isset($data['COLUMN_COMMENT']) && $data['COLUMN_COMMENT'] !== ''
  402. ? $data['COLUMN_COMMENT'] : false,
  403. $data['Virtuality'],
  404. $data['Expression'],
  405. $i === 0 ? '-first' : $column_names[$i - 1]
  406. );
  407. // update current column_names array, first delete old position
  408. for ($j = 0, $ll = count($column_names); $j < $ll; $j++) {
  409. if ($column_names[$j] == $column) {
  410. unset($column_names[$j]);
  411. }
  412. }
  413. // insert moved column
  414. array_splice($column_names, $i, 0, $column);
  415. }
  416. if (empty($changes)) { // should never happen
  417. $this->response->isSuccess(false);
  418. return;
  419. }
  420. // move columns
  421. $this->dbi->tryQuery(
  422. sprintf(
  423. 'ALTER TABLE %s %s',
  424. PMA_Util::backquote($this->table),
  425. implode(', ', $changes)
  426. )
  427. );
  428. $tmp_error = $this->dbi->getError();
  429. if ($tmp_error) {
  430. $this->response->isSuccess(false);
  431. $this->response->addJSON('message', PMA_Message::error($tmp_error));
  432. } else {
  433. $message = PMA_Message::success(
  434. __('The columns have been moved successfully.')
  435. );
  436. $this->response->addJSON('message', $message);
  437. $this->response->addJSON('columns', $column_names);
  438. }
  439. }
  440. /**
  441. * Displays HTML for changing one or more columns
  442. *
  443. * @param array $selected the selected columns
  444. * @param string $action target script to call
  445. *
  446. * @return boolean $regenerate true if error occurred
  447. *
  448. */
  449. protected function displayHtmlForColumnChange($selected, $action)
  450. {
  451. // $selected comes from mult_submits.inc.php
  452. if (empty($selected)) {
  453. $selected[] = $_REQUEST['field'];
  454. $selected_cnt = 1;
  455. } else { // from a multiple submit
  456. $selected_cnt = count($selected);
  457. }
  458. /**
  459. * @todo optimize in case of multiple fields to modify
  460. */
  461. $fields_meta = array();
  462. for ($i = 0; $i < $selected_cnt; $i++) {
  463. $fields_meta[] = $this->dbi->getColumns(
  464. $this->db, $this->table, $selected[$i], true
  465. );
  466. }
  467. $num_fields = count($fields_meta);
  468. // set these globals because tbl_columns_definition_form.inc.php
  469. // verifies them
  470. // @todo: refactor tbl_columns_definition_form.inc.php so that it uses
  471. // protected function params
  472. $GLOBALS['action'] = $action;
  473. $GLOBALS['num_fields'] = $num_fields;
  474. /**
  475. * Form for changing properties.
  476. */
  477. include_once 'libraries/check_user_privileges.lib.php';
  478. include 'libraries/tbl_columns_definition_form.inc.php';
  479. }
  480. /**
  481. * Function to get the type of command for multiple field handling
  482. *
  483. * @return string
  484. */
  485. protected function getMultipleFieldCommandType()
  486. {
  487. $types = array(
  488. 'change', 'drop', 'primary',
  489. 'index', 'unique', 'spatial',
  490. 'fulltext', 'browse'
  491. );
  492. foreach ($types as $type) {
  493. if (isset($_REQUEST['submit_mult_' . $type . '_x'])) {
  494. return $type;
  495. }
  496. }
  497. if (isset($_REQUEST['submit_mult'])) {
  498. return $_REQUEST['submit_mult'];
  499. } elseif (isset($_REQUEST['mult_btn'])
  500. && $_REQUEST['mult_btn'] == __('Yes')
  501. ) {
  502. if (isset($_REQUEST['selected'])) {
  503. $_REQUEST['selected_fld'] = $_REQUEST['selected'];
  504. }
  505. return 'row_delete';
  506. }
  507. return null;
  508. }
  509. /**
  510. * Function to display table browse for selected columns
  511. *
  512. * @param string $goto goto page url
  513. * @param string $pmaThemeImage URI of the pma theme image
  514. *
  515. * @return void
  516. */
  517. protected function displayTableBrowseForSelectedColumns($goto, $pmaThemeImage)
  518. {
  519. $GLOBALS['active_page'] = 'sql.php';
  520. $fields = array();
  521. foreach ($_REQUEST['selected_fld'] as $sval) {
  522. $fields[] = PMA_Util::backquote($sval);
  523. }
  524. $sql_query = sprintf(
  525. 'SELECT %s FROM %s.%s',
  526. implode(', ', $fields),
  527. PMA_Util::backquote($this->db),
  528. PMA_Util::backquote($this->table)
  529. );
  530. // Parse and analyze the query
  531. // @todo Refactor parse_analyze.inc to protected function
  532. $db = &$this->db;
  533. include_once 'libraries/parse_analyze.inc.php';
  534. include_once 'libraries/sql.lib.php';
  535. $this->response->addHTML(
  536. PMA_executeQueryAndGetQueryResponse(
  537. isset($analyzed_sql_results) ? $analyzed_sql_results : '',
  538. false, // is_gotofile
  539. $this->db, // db
  540. $this->table, // table
  541. null, // find_real_end
  542. null, // sql_query_for_bookmark
  543. null, // extra_data
  544. null, // message_to_show
  545. null, // message
  546. null, // sql_data
  547. $goto, // goto
  548. $pmaThemeImage, // pmaThemeImage
  549. null, // disp_query
  550. null, // disp_message
  551. null, // query_type
  552. $sql_query, // sql_query
  553. null, // selectedTables
  554. null // complete_query
  555. )
  556. );
  557. }
  558. /**
  559. * Update the table's structure based on $_REQUEST
  560. *
  561. * @return boolean $regenerate true if error occurred
  562. *
  563. */
  564. protected function updateColumns()
  565. {
  566. $err_url = 'tbl_structure.php' . PMA_URL_getCommon(
  567. array(
  568. 'db' => $this->db, 'table' => $this->table
  569. )
  570. );
  571. $regenerate = false;
  572. $field_cnt = count($_REQUEST['field_name']);
  573. $changes = array();
  574. $adjust_privileges = array();
  575. for ($i = 0; $i < $field_cnt; $i++) {
  576. if (!$this->columnNeedsAlterTable($i)) {
  577. continue;
  578. }
  579. $changes[] = 'CHANGE ' . PMA_Table::generateAlter(
  580. Util\get($_REQUEST, "field_orig.${i}", ''),
  581. $_REQUEST['field_name'][$i],
  582. $_REQUEST['field_type'][$i],
  583. $_REQUEST['field_length'][$i],
  584. $_REQUEST['field_attribute'][$i],
  585. Util\get($_REQUEST, "field_collation.${i}", ''),
  586. Util\get($_REQUEST, "field_null.${i}", 'NOT NULL'),
  587. $_REQUEST['field_default_type'][$i],
  588. $_REQUEST['field_default_value'][$i],
  589. Util\get($_REQUEST, "field_extra.${i}", false),
  590. Util\get($_REQUEST, "field_comments.${i}", ''),
  591. Util\get($_REQUEST, "field_virtuality.${i}", ''),
  592. Util\get($_REQUEST, "field_expression.${i}", ''),
  593. Util\get($_REQUEST, "field_move_to.${i}", '')
  594. );
  595. // find the remembered sort expression
  596. $sorted_col = $this->table_obj->getUiProp(
  597. PMA_Table::PROP_SORTED_COLUMN
  598. );
  599. // if the old column name is part of the remembered sort expression
  600. if (/*overload*/mb_strpos(
  601. $sorted_col,
  602. PMA_Util::backquote($_REQUEST['field_orig'][$i])
  603. ) !== false) {
  604. // delete the whole remembered sort expression
  605. $this->table_obj->removeUiProp(PMA_Table::PROP_SORTED_COLUMN);
  606. }
  607. if (isset($_REQUEST['field_adjust_privileges'][$i])
  608. && ! empty($_REQUEST['field_adjust_privileges'][$i])
  609. && $_REQUEST['field_orig'][$i] != $_REQUEST['field_name'][$i]
  610. ) {
  611. $adjust_privileges[$_REQUEST['field_orig'][$i]]
  612. = $_REQUEST['field_name'][$i];
  613. }
  614. } // end for
  615. if (count($changes) > 0 || isset($_REQUEST['preview_sql'])) {
  616. // Builds the primary keys statements and updates the table
  617. $key_query = '';
  618. /**
  619. * this is a little bit more complex
  620. *
  621. * @todo if someone selects A_I when altering a column we need to check:
  622. * - no other column with A_I
  623. * - the column has an index, if not create one
  624. *
  625. */
  626. // To allow replication, we first select the db to use
  627. // and then run queries on this db.
  628. if (!$this->dbi->selectDb($this->db)) {
  629. PMA_Util::mysqlDie(
  630. $this->dbi->getError(),
  631. 'USE ' . PMA_Util::backquote($this->db) . ';',
  632. false,
  633. $err_url
  634. );
  635. }
  636. $sql_query = 'ALTER TABLE ' . PMA_Util::backquote($this->table) . ' ';
  637. $sql_query .= implode(', ', $changes) . $key_query;
  638. $sql_query .= ';';
  639. // If there is a request for SQL previewing.
  640. if (isset($_REQUEST['preview_sql'])) {
  641. PMA_previewSQL(count($changes) > 0 ? $sql_query : '');
  642. }
  643. $columns_with_index = $this->dbi
  644. ->getTable($this->db, $this->table)
  645. ->getColumnsWithIndex(
  646. PMA_Index::PRIMARY | PMA_Index::UNIQUE | PMA_Index::INDEX
  647. | PMA_Index::SPATIAL | PMA_Index::FULLTEXT
  648. );
  649. $changedToBlob = array();
  650. // While changing the Column Collation
  651. // First change to BLOB
  652. for ($i = 0; $i < $field_cnt; $i++ ) {
  653. if (isset($_REQUEST['field_collation'][$i])
  654. && isset($_REQUEST['field_collation_orig'][$i])
  655. && $_REQUEST['field_collation'][$i] !== $_REQUEST['field_collation_orig'][$i]
  656. && ! in_array($_REQUEST['field_orig'][$i], $columns_with_index)
  657. ) {
  658. $secondary_query = 'ALTER TABLE ' . PMA_Util::backquote(
  659. $this->table
  660. )
  661. . ' CHANGE ' . PMA_Util::backquote(
  662. $_REQUEST['field_orig'][$i]
  663. )
  664. . ' ' . PMA_Util::backquote($_REQUEST['field_orig'][$i])
  665. . ' BLOB;';
  666. $this->dbi->query($secondary_query);
  667. $changedToBlob[$i] = true;
  668. } else {
  669. $changedToBlob[$i] = false;
  670. }
  671. }
  672. // Then make the requested changes
  673. $result = $this->dbi->tryQuery($sql_query);
  674. if ($result !== false) {
  675. $changed_privileges = $this->adjustColumnPrivileges(
  676. $adjust_privileges
  677. );
  678. if ($changed_privileges) {
  679. $message = PMA_Message::success(
  680. __(
  681. 'Table %1$s has been altered successfully. Privileges ' .
  682. 'have been adjusted.'
  683. )
  684. );
  685. } else {
  686. $message = PMA_Message::success(
  687. __('Table %1$s has been altered successfully.')
  688. );
  689. }
  690. $message->addParam($this->table);
  691. $this->response->addHTML(
  692. PMA_Util::getMessage($message, $sql_query, 'success')
  693. );
  694. } else {
  695. // An error happened while inserting/updating a table definition
  696. // Save the Original Error
  697. $orig_error = $this->dbi->getError();
  698. $changes_revert = array();
  699. // Change back to Orignal Collation and data type
  700. for ($i = 0; $i < $field_cnt; $i++) {
  701. if ($changedToBlob[$i]) {
  702. $changes_revert[] = 'CHANGE ' . PMA_Table::generateAlter(
  703. Util\get($_REQUEST, "field_orig.${i}", ''),
  704. $_REQUEST['field_name'][$i],
  705. $_REQUEST['field_type_orig'][$i],
  706. $_REQUEST['field_length_orig'][$i],
  707. $_REQUEST['field_attribute_orig'][$i],
  708. Util\get($_REQUEST, "field_collation_orig.${i}", ''),
  709. Util\get($_REQUEST, "field_null_orig.${i}", 'NOT NULL'),
  710. $_REQUEST['field_default_type_orig'][$i],
  711. $_REQUEST['field_default_value_orig'][$i],
  712. Util\get($_REQUEST, "field_extra_orig.${i}", false),
  713. Util\get($_REQUEST, "field_comments_orig.${i}", ''),
  714. Util\get($_REQUEST, "field_virtuality_orig.${i}", ''),
  715. Util\get($_REQUEST, "field_expression_orig.${i}", ''),
  716. Util\get($_REQUEST, "field_move_to_orig.${i}", '')
  717. );
  718. }
  719. }
  720. $revert_query = 'ALTER TABLE ' . PMA_Util::backquote($this->table)
  721. . ' ';
  722. $revert_query .= implode(', ', $changes_revert) . '';
  723. $revert_query .= ';';
  724. // Column reverted back to original
  725. $this->dbi->query($revert_query);
  726. $this->response->isSuccess(false);
  727. $this->response->addJSON(
  728. 'message',
  729. PMA_Message::rawError(
  730. __('Query error') . ':<br />' . $orig_error
  731. )
  732. );
  733. $regenerate = true;
  734. }
  735. }
  736. // update field names in relation
  737. if (isset($_REQUEST['field_orig']) && is_array($_REQUEST['field_orig'])) {
  738. foreach ($_REQUEST['field_orig'] as $fieldindex => $fieldcontent) {
  739. if ($_REQUEST['field_name'][$fieldindex] != $fieldcontent) {
  740. PMA_REL_renameField(
  741. $this->db, $this->table, $fieldcontent,
  742. $_REQUEST['field_name'][$fieldindex]
  743. );
  744. }
  745. }
  746. }
  747. // update mime types
  748. if (isset($_REQUEST['field_mimetype'])
  749. && is_array($_REQUEST['field_mimetype'])
  750. && $GLOBALS['cfg']['BrowseMIME']
  751. ) {
  752. foreach ($_REQUEST['field_mimetype'] as $fieldindex => $mimetype) {
  753. if (isset($_REQUEST['field_name'][$fieldindex])
  754. && /*overload*/mb_strlen(
  755. $_REQUEST['field_name'][$fieldindex]
  756. )
  757. ) {
  758. PMA_setMIME(
  759. $this->db, $this->table,
  760. $_REQUEST['field_name'][$fieldindex],
  761. $mimetype,
  762. $_REQUEST['field_transformation'][$fieldindex],
  763. $_REQUEST['field_transformation_options'][$fieldindex],
  764. $_REQUEST['field_input_transformation'][$fieldindex],
  765. $_REQUEST['field_input_transformation_options'][$fieldindex]
  766. );
  767. }
  768. }
  769. }
  770. return $regenerate;
  771. }
  772. /**
  773. * Adjusts the Privileges for all the columns whose names have changed
  774. *
  775. * @param array $adjust_privileges assoc array of old col names mapped to new
  776. * cols
  777. *
  778. * @return boolean $changed boolean whether at least one column privileges
  779. * adjusted
  780. */
  781. protected function adjustColumnPrivileges($adjust_privileges)
  782. {
  783. $changed = false;
  784. if ((!defined('PMA_DRIZZLE') || !PMA_DRIZZLE)
  785. && Util\get($GLOBALS, 'col_priv', false)
  786. && Util\get($GLOBALS, 'is_reload_priv', false)
  787. ) {
  788. $this->dbi->selectDb('mysql');
  789. // For Column specific privileges
  790. foreach ($adjust_privileges as $oldCol => $newCol) {
  791. $this->dbi->query(
  792. sprintf(
  793. 'UPDATE %s SET Column_name = "%s"
  794. WHERE Db = "%s"
  795. AND Table_name = "%s"
  796. AND Column_name = "%s";',
  797. PMA_Util::backquote('columns_priv'),
  798. $newCol, $this->db, $this->table, $oldCol
  799. )
  800. );
  801. // i.e. if atleast one column privileges adjusted
  802. $changed = true;
  803. }
  804. if ($changed) {
  805. // Finally FLUSH the new privileges
  806. $this->dbi->query("FLUSH PRIVILEGES;");
  807. }
  808. }
  809. return $changed;
  810. }
  811. /**
  812. * Verifies if some elements of a column have changed
  813. *
  814. * @param integer $i column index in the request
  815. *
  816. * @return boolean $alterTableNeeded true if we need to generate ALTER TABLE
  817. *
  818. */
  819. protected function columnNeedsAlterTable($i)
  820. {
  821. // these two fields are checkboxes so might not be part of the
  822. // request; therefore we define them to avoid notices below
  823. if (! isset($_REQUEST['field_null'][$i])) {
  824. $_REQUEST['field_null'][$i] = 'NO';
  825. }
  826. if (! isset($_REQUEST['field_extra'][$i])) {
  827. $_REQUEST['field_extra'][$i] = '';
  828. }
  829. // field_name does not follow the convention (corresponds to field_orig)
  830. if ($_REQUEST['field_name'][$i] != $_REQUEST['field_orig'][$i]) {
  831. return true;
  832. }
  833. $fields = array(
  834. 'field_attribute', 'field_collation', 'field_comments',
  835. 'field_default_value', 'field_default_type', 'field_extra',
  836. 'field_length', 'field_null', 'field_type'
  837. );
  838. foreach ($fields as $field) {
  839. if ($_REQUEST[$field][$i] != $_REQUEST[$field . '_orig'][$i]) {
  840. return true;
  841. }
  842. }
  843. return !empty($_REQUEST['field_move_to'][$i]);
  844. }
  845. /**
  846. * Displays the table structure ('show table' works correct since 3.23.03)
  847. *
  848. * @param array $cfgRelation current relation parameters
  849. * @param array $columns_with_unique_index Columns with unique index
  850. * @param mixed $url_params Contains an associative
  851. * array with url params
  852. * @param PMA_Index|false $primary_index primary index or false if
  853. * no one exists
  854. * @param array $fields Fields
  855. * @param array $columns_with_index Columns with index
  856. * @param array $create_table_fields Fields of the table.
  857. *
  858. * @return string
  859. */
  860. protected function displayStructure(
  861. $cfgRelation, $columns_with_unique_index, $url_params, $primary_index,
  862. $fields, $columns_with_index, $create_table_fields
  863. ) {
  864. /* TABLE INFORMATION */
  865. $HideStructureActions = '';
  866. if ($GLOBALS['cfg']['HideStructureActions'] === true) {
  867. $HideStructureActions .= ' HideStructureActions';
  868. }
  869. // prepare comments
  870. $comments_map = array();
  871. $mime_map = array();
  872. if ($GLOBALS['cfg']['ShowPropertyComments']) {
  873. include_once 'libraries/transformations.lib.php';
  874. $comments_map = PMA_getComments($this->db, $this->table);
  875. if ($cfgRelation['mimework'] && $GLOBALS['cfg']['BrowseMIME']) {
  876. $mime_map = PMA_getMIME($this->db, $this->table, true);
  877. }
  878. }
  879. include_once 'libraries/central_columns.lib.php';
  880. $central_list = PMA_getCentralColumnsFromTable($this->db, $this->table);
  881. $columns_list = array();
  882. $titles = array(
  883. 'Change' => PMA_Util::getIcon('b_edit.png', __('Change')),
  884. 'Drop' => PMA_Util::getIcon('b_drop.png', __('Drop')),
  885. 'NoDrop' => PMA_Util::getIcon('b_drop.png', __('Drop')),
  886. 'Primary' => PMA_Util::getIcon('b_primary.png', __('Primary')),
  887. 'Index' => PMA_Util::getIcon('b_index.png', __('Index')),
  888. 'Unique' => PMA_Util::getIcon('b_unique.png', __('Unique')),
  889. 'Spatial' => PMA_Util::getIcon('b_spatial.png', __('Spatial')),
  890. 'IdxFulltext' => PMA_Util::getIcon('b_ftext.png', __('Fulltext')),
  891. 'NoPrimary' => PMA_Util::getIcon('bd_primary.png', __('Primary')),
  892. 'NoIndex' => PMA_Util::getIcon('bd_index.png', __('Index')),
  893. 'NoUnique' => PMA_Util::getIcon('bd_unique.png', __('Unique')),
  894. 'NoSpatial' => PMA_Util::getIcon('bd_spatial.png', __('Spatial')),
  895. 'NoIdxFulltext' => PMA_Util::getIcon('bd_ftext.png', __('Fulltext')),
  896. 'DistinctValues' => PMA_Util::getIcon(
  897. 'b_browse.png',
  898. __('Distinct values')
  899. ),
  900. );
  901. /**
  902. * Work on the table
  903. */
  904. if ($this->_tbl_is_view && ! $this->_db_is_system_schema) {
  905. $item = $this->dbi->fetchSingleRow(
  906. sprintf(
  907. "SELECT `VIEW_DEFINITION`, `CHECK_OPTION`, `DEFINER`,
  908. `SECURITY_TYPE`
  909. FROM `INFORMATION_SCHEMA`.`VIEWS`
  910. WHERE TABLE_SCHEMA='%s'
  911. AND TABLE_NAME='%s';",
  912. PMA_Util::sqlAddSlashes($this->db),
  913. PMA_Util::sqlAddSlashes($this->table)
  914. )
  915. );
  916. $createView = $this->dbi->getTable($this->db, $this->table)
  917. ->showCreate();
  918. // get algorithm from $createView of the form
  919. // CREATE ALGORITHM=<ALGORITHM> DE...
  920. $parts = explode(" ", substr($createView, 17));
  921. $item['ALGORITHM'] = $parts[0];
  922. $view = array(
  923. 'operation' => 'alter',
  924. 'definer' => $item['DEFINER'],
  925. 'sql_security' => $item['SECURITY_TYPE'],
  926. 'name' => $this->table,
  927. 'as' => $item['VIEW_DEFINITION'],
  928. 'with' => $item['CHECK_OPTION'],
  929. 'algorithm' => $item['ALGORITHM'],
  930. );
  931. $edit_view_url = 'view_create.php'
  932. . PMA_URL_getCommon($url_params) . '&amp;'
  933. . implode(
  934. '&amp;',
  935. array_map(
  936. function ($key, $val) {
  937. return 'view[' . urlencode($key) . ']=' . urlencode(
  938. $val
  939. );
  940. },
  941. array_keys($view), $view
  942. )
  943. );
  944. }
  945. /**
  946. * Displays Space usage and row statistics
  947. */
  948. // BEGIN - Calc Table Space
  949. // Get valid statistics whatever is the table type
  950. if ($GLOBALS['cfg']['ShowStats']) {
  951. //get table stats in HTML format
  952. $tablestats = $this->getTableStats();
  953. //returning the response in JSON format to be used by Ajax
  954. $this->response->addJSON('tableStat', $tablestats);
  955. }
  956. // END - Calc Table Space
  957. return Template::get('table/structure/display_structure')->render(
  958. array(
  959. 'HideStructureActions' => $HideStructureActions,
  960. 'db' => $this->db,
  961. 'table' => $this->table,
  962. 'db_is_system_schema' => $this->_db_is_system_schema,
  963. 'tbl_is_view' => $this->_tbl_is_view,
  964. 'mime_map' => $mime_map,
  965. 'url_query' => $this->_url_query,
  966. 'titles' => $titles,
  967. 'tbl_storage_engine' => $this->_tbl_storage_engine,
  968. 'primary' => $primary_index,
  969. 'columns_with_unique_index' => $columns_with_unique_index,
  970. 'edit_view_url' => isset($edit_view_url) ? $edit_view_url : null,
  971. 'columns_list' => $columns_list,
  972. 'tablestats' => isset($tablestats) ? $tablestats : null,
  973. 'fields' => $fields,
  974. 'columns_with_index' => $columns_with_index,
  975. 'central_list' => $central_list,
  976. 'create_table_fields' => $create_table_fields,
  977. 'comments_map' => $comments_map
  978. )
  979. );
  980. }
  981. /**
  982. * Get HTML snippet for display table statistics
  983. *
  984. * @return string $html_output
  985. */
  986. protected function getTableStats()
  987. {
  988. if (empty($this->_showtable)) {
  989. $this->_showtable = $this->dbi->getTable(
  990. $this->db, $this->table
  991. )->getStatusInfo(null, true);
  992. }
  993. if (empty($this->_showtable['Data_length'])) {
  994. $this->_showtable['Data_length'] = 0;
  995. }
  996. if (empty($this->_showtable['Index_length'])) {
  997. $this->_showtable['Index_length'] = 0;
  998. }
  999. $is_innodb = (isset($this->_showtable['Type'])
  1000. && $this->_showtable['Type'] == 'InnoDB');
  1001. $mergetable = $this->table_obj->isMerge();
  1002. // this is to display for example 261.2 MiB instead of 268k KiB
  1003. $max_digits = 3;
  1004. $decimals = 1;
  1005. list($data_size, $data_unit) = PMA_Util::formatByteDown(
  1006. $this->_showtable['Data_length'], $max_digits, $decimals
  1007. );
  1008. if ($mergetable == false) {
  1009. list($index_size, $index_unit) = PMA_Util::formatByteDown(
  1010. $this->_showtable['Index_length'], $max_digits, $decimals
  1011. );
  1012. }
  1013. // InnoDB returns a huge value in Data_free, do not use it
  1014. if (! $is_innodb && isset($this->_showtable['Data_free'])
  1015. && $this->_showtable['Data_free'] > 0
  1016. ) {
  1017. list($free_size, $free_unit) = PMA_Util::formatByteDown(
  1018. $this->_showtable['Data_free'], $max_digits, $decimals
  1019. );
  1020. list($effect_size, $effect_unit) = PMA_Util::formatByteDown(
  1021. $this->_showtable['Data_length']
  1022. + $this->_showtable['Index_length']
  1023. - $this->_showtable['Data_free'],
  1024. $max_digits, $decimals
  1025. );
  1026. } else {
  1027. list($effect_size, $effect_unit) = PMA_Util::formatByteDown(
  1028. $this->_showtable['Data_length']
  1029. + $this->_showtable['Index_length'],
  1030. $max_digits, $decimals
  1031. );
  1032. }
  1033. list($tot_size, $tot_unit) = PMA_Util::formatByteDown(
  1034. $this->_showtable['Data_length'] + $this->_showtable['Index_length'],
  1035. $max_digits, $decimals
  1036. );
  1037. if ($this->_table_info_num_rows > 0) {
  1038. list($avg_size, $avg_unit) = PMA_Util::formatByteDown(
  1039. ($this->_showtable['Data_length']
  1040. + $this->_showtable['Index_length'])
  1041. / $this->_showtable['Rows'],
  1042. 6,
  1043. 1
  1044. );
  1045. } else {
  1046. $avg_size = $avg_unit = '';
  1047. }
  1048. return Template::get('table/structure/display_table_stats')->render(
  1049. array(
  1050. 'showtable' => $this->_showtable,
  1051. 'table_info_num_rows' => $this->_table_info_num_rows,
  1052. 'tbl_is_view' => $this->_tbl_is_view,
  1053. 'db_is_system_schema' => $this->_db_is_system_schema,
  1054. 'tbl_storage_engine' => $this->_tbl_storage_engine,
  1055. 'url_query' => $this->_url_query,
  1056. 'tbl_collation' => $this->_tbl_collation,
  1057. 'is_innodb' => $is_innodb,
  1058. 'mergetable' => $mergetable,
  1059. 'avg_size' => isset($avg_size) ? $avg_size : null,
  1060. 'avg_unit' => isset($avg_unit) ? $avg_unit : null,
  1061. 'data_size' => $data_size,
  1062. 'data_unit' => $data_unit,
  1063. 'index_size' => isset($index_size) ? $index_size : null,
  1064. 'index_unit' => isset($index_unit) ? $index_unit : null,
  1065. 'free_size' => isset($free_size) ? $free_size : null,
  1066. 'free_unit' => isset($free_unit) ? $free_unit : null,
  1067. 'effect_size' => $effect_size,
  1068. 'effect_unit' => $effect_unit,
  1069. 'tot_size' => $tot_size,
  1070. 'tot_unit' => $tot_unit
  1071. )
  1072. );
  1073. }
  1074. /**
  1075. * Gets table primary key
  1076. *
  1077. * @return string
  1078. */
  1079. protected function getKeyForTablePrimary()
  1080. {
  1081. $this->dbi->selectDb($this->db);
  1082. $result = $this->dbi->query(
  1083. 'SHOW KEYS FROM ' . PMA_Util::backquote($this->table) . ';'
  1084. );
  1085. $primary = '';
  1086. while ($row = $this->dbi->fetchAssoc($result)) {
  1087. // Backups the list of primary keys
  1088. if ($row['Key_name'] == 'PRIMARY') {
  1089. $primary .= $row['Column_name'] . ', ';
  1090. }
  1091. } // end while
  1092. $this->dbi->freeResult($result);
  1093. return $primary;
  1094. }
  1095. /**
  1096. * Get List of information for Submit Mult
  1097. *
  1098. * @param string $submit_mult mult_submit type
  1099. * @param array $selected the selected columns
  1100. * @param string $action action type
  1101. *
  1102. * @return array
  1103. */
  1104. protected function getDataForSubmitMult($submit_mult, $selected, $action)
  1105. {
  1106. $what = null;
  1107. $query_type = null;
  1108. $is_unset_submit_mult = false;
  1109. $mult_btn = null;
  1110. $centralColsError = null;
  1111. switch ($submit_mult) {
  1112. case 'drop':
  1113. $what = 'drop_fld';
  1114. break;
  1115. case 'primary':
  1116. // Gets table primary key
  1117. $primary = $this->getKeyForTablePrimary();
  1118. if (empty($primary)) {
  1119. // no primary key, so we can safely create new
  1120. $is_unset_submit_mult = true;
  1121. $query_type = 'primary_fld';
  1122. $mult_btn = __('Yes');
  1123. } else {
  1124. // primary key exists, so lets as user
  1125. $what = 'primary_fld';
  1126. }
  1127. break;
  1128. case 'index':
  1129. $is_unset_submit_mult = true;
  1130. $query_type = 'index_fld';
  1131. $mult_btn = __('Yes');
  1132. break;
  1133. case 'unique':
  1134. $is_unset_submit_mult = true;
  1135. $query_type = 'unique_fld';
  1136. $mult_btn = __('Yes');
  1137. break;
  1138. case 'spatial':
  1139. $is_unset_submit_mult = true;
  1140. $query_type = 'spatial_fld';
  1141. $mult_btn = __('Yes');
  1142. break;
  1143. case 'ftext':
  1144. $is_unset_submit_mult = true;
  1145. $query_type = 'fulltext_fld';
  1146. $mult_btn = __('Yes');
  1147. break;
  1148. case 'add_to_central_columns':
  1149. include_once 'libraries/central_columns.lib.php';
  1150. $centralColsError = PMA_syncUniqueColumns($selected, false);
  1151. break;
  1152. case 'remove_from_central_columns':
  1153. include_once 'libraries/central_columns.lib.php';
  1154. $centralColsError = PMA_deleteColumnsFromList($selected, false);
  1155. break;
  1156. case 'change':
  1157. $this->displayHtmlForColumnChange($selected, $action);
  1158. // execution stops here but PMA_Response correctly finishes
  1159. // the rendering
  1160. exit;
  1161. case 'browse':
  1162. // this should already be handled by tbl_structure.php
  1163. }
  1164. return array(
  1165. $what, $query_type, $is_unset_submit_mult, $mult_btn,
  1166. $centralColsError
  1167. );
  1168. }
  1169. }