TableStructureController.php 56 KB

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