TableStructureController.php 55 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502
  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. foreach ($fields as $key => $row) {
  351. if ('text' === substr($row['Type'], -4)) {
  352. $fields[$key]['Default'] = stripcslashes(substr($row['Default'], 1, -1));
  353. }
  354. }
  355. //display table structure
  356. $this->response->addHTML(
  357. $this->displayStructure(
  358. $cfgRelation, $columns_with_unique_index, $url_params,
  359. $primary, $fields, $columns_with_index
  360. )
  361. );
  362. $this->response->addHTML('</div>');
  363. }
  364. /**
  365. * Moves columns in the table's structure based on $_REQUEST
  366. *
  367. * @return void
  368. */
  369. protected function moveColumns()
  370. {
  371. $this->dbi->selectDb($this->db);
  372. /*
  373. * load the definitions for all columns
  374. */
  375. $columns = $this->dbi->getColumnsFull($this->db, $this->table);
  376. $column_names = array_keys($columns);
  377. $changes = array();
  378. // @see https://mariadb.com/kb/en/library/changes-improvements-in-mariadb-102/#information-schema
  379. $usesLiteralNull = $this->dbi->isMariaDB() && $this->dbi->getVersion() >= 100200;
  380. $defaultNullValue = $usesLiteralNull ? 'NULL' : null;
  381. // move columns from first to last
  382. for ($i = 0, $l = count($_POST['move_columns']); $i < $l; $i++) {
  383. $column = $_POST['move_columns'][$i];
  384. // is this column already correctly placed?
  385. if ($column_names[$i] == $column) {
  386. continue;
  387. }
  388. // it is not, let's move it to index $i
  389. $data = $columns[$column];
  390. $extracted_columnspec = Util::extractColumnSpec($data['Type']);
  391. if (isset($data['Extra'])
  392. && $data['Extra'] == 'on update CURRENT_TIMESTAMP'
  393. ) {
  394. $extracted_columnspec['attribute'] = $data['Extra'];
  395. unset($data['Extra']);
  396. }
  397. $current_timestamp = ($data['Type'] == 'timestamp'
  398. || $data['Type'] == 'datetime' || $data['DATA_TYPE'] == 'int')
  399. && ($data['Default'] == 'CURRENT_TIMESTAMP'
  400. || $data['Default'] == 'current_timestamp()');
  401. // @see https://mariadb.com/kb/en/library/information-schema-columns-table/#examples
  402. if ($data['Null'] === 'YES' && in_array($data['Default'], [$defaultNullValue, null])) {
  403. $default_type = 'NULL';
  404. } elseif ($current_timestamp) {
  405. $default_type = 'CURRENT_TIMESTAMP';
  406. } elseif ($data['Default'] === null) {
  407. $default_type = 'NONE';
  408. } else {
  409. $default_type = 'USER_DEFINED';
  410. }
  411. $virtual = array(
  412. 'VIRTUAL', 'PERSISTENT', 'VIRTUAL GENERATED', 'STORED GENERATED'
  413. );
  414. $data['Virtuality'] = '';
  415. $data['Expression'] = '';
  416. if (isset($data['Extra']) && in_array($data['Extra'], $virtual)) {
  417. $data['Virtuality'] = str_replace(' GENERATED', '', $data['Extra']);
  418. $expressions = $this->table_obj->getColumnGenerationExpression($column);
  419. $data['Expression'] = $expressions[$column];
  420. }
  421. $changes[] = 'CHANGE ' . Table::generateAlter(
  422. $column,
  423. $column,
  424. mb_strtoupper($extracted_columnspec['type']),
  425. $extracted_columnspec['spec_in_brackets'],
  426. $extracted_columnspec['attribute'],
  427. isset($data['Collation']) ? $data['Collation'] : '',
  428. $data['Null'] === 'YES' ? 'YES' : 'NO',
  429. $default_type,
  430. $current_timestamp ? '' : $data['Default'],
  431. isset($data['Extra']) && $data['Extra'] !== '' ? $data['Extra']
  432. : false,
  433. isset($data['COLUMN_COMMENT']) && $data['COLUMN_COMMENT'] !== ''
  434. ? $data['COLUMN_COMMENT'] : false,
  435. $data['Virtuality'],
  436. $data['Expression'],
  437. $i === 0 ? '-first' : $column_names[$i - 1]
  438. );
  439. // update current column_names array, first delete old position
  440. for ($j = 0, $ll = count($column_names); $j < $ll; $j++) {
  441. if ($column_names[$j] == $column) {
  442. unset($column_names[$j]);
  443. }
  444. }
  445. // insert moved column
  446. array_splice($column_names, $i, 0, $column);
  447. }
  448. if (empty($changes)) { // should never happen
  449. $this->response->setRequestStatus(false);
  450. return;
  451. }
  452. // move columns
  453. $this->dbi->tryQuery(
  454. sprintf(
  455. 'ALTER TABLE %s %s',
  456. Util::backquote($this->table),
  457. implode(', ', $changes)
  458. )
  459. );
  460. $tmp_error = $this->dbi->getError();
  461. if ($tmp_error) {
  462. $this->response->setRequestStatus(false);
  463. $this->response->addJSON('message', Message::error($tmp_error));
  464. } else {
  465. $message = Message::success(
  466. __('The columns have been moved successfully.')
  467. );
  468. $this->response->addJSON('message', $message);
  469. $this->response->addJSON('columns', $column_names);
  470. }
  471. }
  472. /**
  473. * Displays HTML for changing one or more columns
  474. *
  475. * @param array $selected the selected columns
  476. * @param string $action target script to call
  477. *
  478. * @return boolean $regenerate true if error occurred
  479. *
  480. */
  481. protected function displayHtmlForColumnChange($selected, $action)
  482. {
  483. // $selected comes from mult_submits.inc.php
  484. if (empty($selected)) {
  485. $selected[] = $_REQUEST['field'];
  486. $selected_cnt = 1;
  487. } else { // from a multiple submit
  488. $selected_cnt = count($selected);
  489. }
  490. /**
  491. * @todo optimize in case of multiple fields to modify
  492. */
  493. $fields_meta = array();
  494. for ($i = 0; $i < $selected_cnt; $i++) {
  495. $value = $this->dbi->getColumns(
  496. $this->db, $this->table, $this->dbi->escapeString($selected[$i]), true
  497. );
  498. if (count($value) == 0) {
  499. $message = Message::error(
  500. __('Failed to get description of column %s!')
  501. );
  502. $message->addParam($selected[$i]);
  503. $this->response->addHTML($message);
  504. } else {
  505. $fields_meta[] = $value;
  506. }
  507. }
  508. $num_fields = count($fields_meta);
  509. // set these globals because tbl_columns_definition_form.inc.php
  510. // verifies them
  511. // @todo: refactor tbl_columns_definition_form.inc.php so that it uses
  512. // protected function params
  513. $GLOBALS['action'] = $action;
  514. $GLOBALS['num_fields'] = $num_fields;
  515. /**
  516. * Form for changing properties.
  517. */
  518. include_once 'libraries/check_user_privileges.inc.php';
  519. include 'libraries/tbl_columns_definition_form.inc.php';
  520. }
  521. /**
  522. * Displays HTML for partition change
  523. *
  524. * @return string HTML for partition change
  525. */
  526. protected function displayHtmlForPartitionChange()
  527. {
  528. $partitionDetails = null;
  529. if (! isset($_POST['partition_by'])) {
  530. $partitionDetails = $this->_extractPartitionDetails();
  531. }
  532. include 'libraries/tbl_partition_definition.inc.php';
  533. $this->response->addHTML(
  534. Template::get('table/structure/partition_definition_form')
  535. ->render(
  536. array(
  537. 'db' => $this->db,
  538. 'table' => $this->table,
  539. 'partition_details' => $partitionDetails,
  540. )
  541. )
  542. );
  543. }
  544. /**
  545. * Extracts partition details from CREATE TABLE statement
  546. *
  547. * @return array[] array of partition details
  548. */
  549. private function _extractPartitionDetails()
  550. {
  551. $createTable = (new Table($this->table, $this->db))->showCreate();
  552. if (! $createTable) {
  553. return null;
  554. }
  555. $parser = new Parser($createTable);
  556. /**
  557. * @var $stmt PhpMyAdmin\SqlParser\Statements\CreateStatement
  558. */
  559. $stmt = $parser->statements[0];
  560. $partitionDetails = array();
  561. $partitionDetails['partition_by'] = '';
  562. $partitionDetails['partition_expr'] = '';
  563. $partitionDetails['partition_count'] = '';
  564. if (! empty($stmt->partitionBy)) {
  565. $openPos = strpos($stmt->partitionBy, "(");
  566. $closePos = strrpos($stmt->partitionBy, ")");
  567. $partitionDetails['partition_by']
  568. = trim(substr($stmt->partitionBy, 0, $openPos));
  569. $partitionDetails['partition_expr']
  570. = trim(substr($stmt->partitionBy, $openPos + 1, $closePos - ($openPos + 1)));
  571. if (isset($stmt->partitionsNum)) {
  572. $count = $stmt->partitionsNum;
  573. } else {
  574. $count = count($stmt->partitions);
  575. }
  576. $partitionDetails['partition_count'] = $count;
  577. }
  578. $partitionDetails['subpartition_by'] = '';
  579. $partitionDetails['subpartition_expr'] = '';
  580. $partitionDetails['subpartition_count'] = '';
  581. if (! empty($stmt->subpartitionBy)) {
  582. $openPos = strpos($stmt->subpartitionBy, "(");
  583. $closePos = strrpos($stmt->subpartitionBy, ")");
  584. $partitionDetails['subpartition_by']
  585. = trim(substr($stmt->subpartitionBy, 0, $openPos));
  586. $partitionDetails['subpartition_expr']
  587. = trim(substr($stmt->subpartitionBy, $openPos + 1, $closePos - ($openPos + 1)));
  588. if (isset($stmt->subpartitionsNum)) {
  589. $count = $stmt->subpartitionsNum;
  590. } else {
  591. $count = count($stmt->partitions[0]->subpartitions);
  592. }
  593. $partitionDetails['subpartition_count'] = $count;
  594. }
  595. // Only LIST and RANGE type parameters allow subpartitioning
  596. $partitionDetails['can_have_subpartitions']
  597. = $partitionDetails['partition_count'] > 1
  598. && ($partitionDetails['partition_by'] == 'RANGE'
  599. || $partitionDetails['partition_by'] == 'RANGE COLUMNS'
  600. || $partitionDetails['partition_by'] == 'LIST'
  601. || $partitionDetails['partition_by'] == 'LIST COLUMNS');
  602. // Values are specified only for LIST and RANGE type partitions
  603. $partitionDetails['value_enabled'] = isset($partitionDetails['partition_by'])
  604. && ($partitionDetails['partition_by'] == 'RANGE'
  605. || $partitionDetails['partition_by'] == 'RANGE COLUMNS'
  606. || $partitionDetails['partition_by'] == 'LIST'
  607. || $partitionDetails['partition_by'] == 'LIST COLUMNS');
  608. $partitionDetails['partitions'] = array();
  609. for ($i = 0; $i < intval($partitionDetails['partition_count']); $i++) {
  610. if (! isset($stmt->partitions[$i])) {
  611. $partitionDetails['partitions'][$i] = array(
  612. 'name' => 'p' . $i,
  613. 'value_type' => '',
  614. 'value' => '',
  615. 'engine' => '',
  616. 'comment' => '',
  617. 'data_directory' => '',
  618. 'index_directory' => '',
  619. 'max_rows' => '',
  620. 'min_rows' => '',
  621. 'tablespace' => '',
  622. 'node_group' => '',
  623. );
  624. } else {
  625. $p = $stmt->partitions[$i];
  626. $type = $p->type;
  627. $expr = trim($p->expr, '()');
  628. if ($expr == 'MAXVALUE') {
  629. $type .= ' MAXVALUE';
  630. $expr = '';
  631. }
  632. $partitionDetails['partitions'][$i] = array(
  633. 'name' => $p->name,
  634. 'value_type' => $type,
  635. 'value' => $expr,
  636. 'engine' => $p->options->has('ENGINE', true),
  637. 'comment' => trim($p->options->has('COMMENT', true), "'"),
  638. 'data_directory' => trim($p->options->has('DATA DIRECTORY', true), "'"),
  639. 'index_directory' => trim($p->options->has('INDEX_DIRECTORY', true), "'"),
  640. 'max_rows' => $p->options->has('MAX_ROWS', true),
  641. 'min_rows' => $p->options->has('MIN_ROWS', true),
  642. 'tablespace' => $p->options->has('TABLESPACE', true),
  643. 'node_group' => $p->options->has('NODEGROUP', true),
  644. );
  645. }
  646. $partition =& $partitionDetails['partitions'][$i];
  647. $partition['prefix'] = 'partitions[' . $i . ']';
  648. if ($partitionDetails['subpartition_count'] > 1) {
  649. $partition['subpartition_count'] = $partitionDetails['subpartition_count'];
  650. $partition['subpartitions'] = array();
  651. for ($j = 0; $j < intval($partitionDetails['subpartition_count']); $j++) {
  652. if (! isset($stmt->partitions[$i]->subpartitions[$j])) {
  653. $partition['subpartitions'][$j] = array(
  654. 'name' => $partition['name'] . '_s' . $j,
  655. 'engine' => '',
  656. 'comment' => '',
  657. 'data_directory' => '',
  658. 'index_directory' => '',
  659. 'max_rows' => '',
  660. 'min_rows' => '',
  661. 'tablespace' => '',
  662. 'node_group' => '',
  663. );
  664. } else {
  665. $sp = $stmt->partitions[$i]->subpartitions[$j];
  666. $partition['subpartitions'][$j] = array(
  667. 'name' => $sp->name,
  668. 'engine' => $sp->options->has('ENGINE', true),
  669. 'comment' => trim($sp->options->has('COMMENT', true), "'"),
  670. 'data_directory' => trim($sp->options->has('DATA DIRECTORY', true), "'"),
  671. 'index_directory' => trim($sp->options->has('INDEX_DIRECTORY', true), "'"),
  672. 'max_rows' => $sp->options->has('MAX_ROWS', true),
  673. 'min_rows' => $sp->options->has('MIN_ROWS', true),
  674. 'tablespace' => $sp->options->has('TABLESPACE', true),
  675. 'node_group' => $sp->options->has('NODEGROUP', true),
  676. );
  677. }
  678. $subpartition =& $partition['subpartitions'][$j];
  679. $subpartition['prefix'] = 'partitions[' . $i . ']'
  680. . '[subpartitions][' . $j . ']';
  681. }
  682. }
  683. }
  684. return $partitionDetails;
  685. }
  686. /**
  687. * Update the table's partitioning based on $_REQUEST
  688. *
  689. * @return void
  690. */
  691. protected function updatePartitioning()
  692. {
  693. $sql_query = "ALTER TABLE " . Util::backquote($this->table) . " "
  694. . $this->createAddField->getPartitionsDefinition();
  695. // Execute alter query
  696. $result = $this->dbi->tryQuery($sql_query);
  697. if ($result !== false) {
  698. $message = Message::success(
  699. __('Table %1$s has been altered successfully.')
  700. );
  701. $message->addParam($this->table);
  702. $this->response->addHTML(
  703. Util::getMessage($message, $sql_query, 'success')
  704. );
  705. } else {
  706. $this->response->setRequestStatus(false);
  707. $this->response->addJSON(
  708. 'message',
  709. Message::rawError(
  710. __('Query error') . ':<br />' . $this->dbi->getError()
  711. )
  712. );
  713. }
  714. }
  715. /**
  716. * Function to get the type of command for multiple field handling
  717. *
  718. * @return string
  719. */
  720. protected function getMultipleFieldCommandType()
  721. {
  722. $types = array(
  723. 'change', 'drop', 'primary',
  724. 'index', 'unique', 'spatial',
  725. 'fulltext', 'browse'
  726. );
  727. foreach ($types as $type) {
  728. if (isset($_POST['submit_mult_' . $type . '_x'])) {
  729. return $type;
  730. }
  731. }
  732. if (isset($_POST['submit_mult'])) {
  733. return $_POST['submit_mult'];
  734. } elseif (isset($_POST['mult_btn'])
  735. && $_POST['mult_btn'] == __('Yes')
  736. ) {
  737. if (isset($_POST['selected'])) {
  738. $_POST['selected_fld'] = $_POST['selected'];
  739. }
  740. return 'row_delete';
  741. }
  742. return null;
  743. }
  744. /**
  745. * Function to display table browse for selected columns
  746. *
  747. * @param string $goto goto page url
  748. * @param string $pmaThemeImage URI of the pma theme image
  749. *
  750. * @return void
  751. */
  752. protected function displayTableBrowseForSelectedColumns($goto, $pmaThemeImage)
  753. {
  754. $GLOBALS['active_page'] = 'sql.php';
  755. $fields = array();
  756. foreach ($_POST['selected_fld'] as $sval) {
  757. $fields[] = Util::backquote($sval);
  758. }
  759. $sql_query = sprintf(
  760. 'SELECT %s FROM %s.%s',
  761. implode(', ', $fields),
  762. Util::backquote($this->db),
  763. Util::backquote($this->table)
  764. );
  765. // Parse and analyze the query
  766. $db = &$this->db;
  767. list(
  768. $analyzed_sql_results,
  769. $db,
  770. ) = ParseAnalyze::sqlQuery($sql_query, $db);
  771. // @todo: possibly refactor
  772. extract($analyzed_sql_results);
  773. $sql = new Sql();
  774. $this->response->addHTML(
  775. $sql->executeQueryAndGetQueryResponse(
  776. isset($analyzed_sql_results) ? $analyzed_sql_results : '',
  777. false, // is_gotofile
  778. $this->db, // db
  779. $this->table, // table
  780. null, // find_real_end
  781. null, // sql_query_for_bookmark
  782. null, // extra_data
  783. null, // message_to_show
  784. null, // message
  785. null, // sql_data
  786. $goto, // goto
  787. $pmaThemeImage, // pmaThemeImage
  788. null, // disp_query
  789. null, // disp_message
  790. null, // query_type
  791. $sql_query, // sql_query
  792. null, // selectedTables
  793. null // complete_query
  794. )
  795. );
  796. }
  797. /**
  798. * Update the table's structure based on $_REQUEST
  799. *
  800. * @return boolean $regenerate true if error occurred
  801. *
  802. */
  803. protected function updateColumns()
  804. {
  805. $err_url = 'tbl_structure.php' . Url::getCommon(
  806. array(
  807. 'db' => $this->db, 'table' => $this->table
  808. )
  809. );
  810. $regenerate = false;
  811. $field_cnt = count($_POST['field_name']);
  812. $changes = array();
  813. $adjust_privileges = array();
  814. for ($i = 0; $i < $field_cnt; $i++) {
  815. if (!$this->columnNeedsAlterTable($i)) {
  816. continue;
  817. }
  818. $changes[] = 'CHANGE ' . Table::generateAlter(
  819. Util::getValueByKey($_POST, "field_orig.${i}", ''),
  820. $_POST['field_name'][$i],
  821. $_POST['field_type'][$i],
  822. $_POST['field_length'][$i],
  823. $_POST['field_attribute'][$i],
  824. Util::getValueByKey($_POST, "field_collation.${i}", ''),
  825. Util::getValueByKey($_POST, "field_null.${i}", 'NO'),
  826. $_POST['field_default_type'][$i],
  827. $_POST['field_default_value'][$i],
  828. Util::getValueByKey($_POST, "field_extra.${i}", false),
  829. Util::getValueByKey($_POST, "field_comments.${i}", ''),
  830. Util::getValueByKey($_POST, "field_virtuality.${i}", ''),
  831. Util::getValueByKey($_POST, "field_expression.${i}", ''),
  832. Util::getValueByKey($_POST, "field_move_to.${i}", '')
  833. );
  834. // find the remembered sort expression
  835. $sorted_col = $this->table_obj->getUiProp(
  836. Table::PROP_SORTED_COLUMN
  837. );
  838. // if the old column name is part of the remembered sort expression
  839. if (mb_strpos(
  840. $sorted_col,
  841. Util::backquote($_POST['field_orig'][$i])
  842. ) !== false) {
  843. // delete the whole remembered sort expression
  844. $this->table_obj->removeUiProp(Table::PROP_SORTED_COLUMN);
  845. }
  846. if (isset($_POST['field_adjust_privileges'][$i])
  847. && ! empty($_POST['field_adjust_privileges'][$i])
  848. && $_POST['field_orig'][$i] != $_POST['field_name'][$i]
  849. ) {
  850. $adjust_privileges[$_POST['field_orig'][$i]]
  851. = $_POST['field_name'][$i];
  852. }
  853. } // end for
  854. if (count($changes) > 0 || isset($_POST['preview_sql'])) {
  855. // Builds the primary keys statements and updates the table
  856. $key_query = '';
  857. /**
  858. * this is a little bit more complex
  859. *
  860. * @todo if someone selects A_I when altering a column we need to check:
  861. * - no other column with A_I
  862. * - the column has an index, if not create one
  863. *
  864. */
  865. // To allow replication, we first select the db to use
  866. // and then run queries on this db.
  867. if (!$this->dbi->selectDb($this->db)) {
  868. Util::mysqlDie(
  869. $this->dbi->getError(),
  870. 'USE ' . Util::backquote($this->db) . ';',
  871. false,
  872. $err_url
  873. );
  874. }
  875. $sql_query = 'ALTER TABLE ' . Util::backquote($this->table) . ' ';
  876. $sql_query .= implode(', ', $changes) . $key_query;
  877. $sql_query .= ';';
  878. // If there is a request for SQL previewing.
  879. if (isset($_POST['preview_sql'])) {
  880. Core::previewSQL(count($changes) > 0 ? $sql_query : '');
  881. }
  882. $columns_with_index = $this->dbi
  883. ->getTable($this->db, $this->table)
  884. ->getColumnsWithIndex(
  885. Index::PRIMARY | Index::UNIQUE | Index::INDEX
  886. | Index::SPATIAL | Index::FULLTEXT
  887. );
  888. $changedToBlob = array();
  889. // While changing the Column Collation
  890. // First change to BLOB
  891. for ($i = 0; $i < $field_cnt; $i++ ) {
  892. if (isset($_POST['field_collation'][$i])
  893. && isset($_POST['field_collation_orig'][$i])
  894. && $_POST['field_collation'][$i] !== $_POST['field_collation_orig'][$i]
  895. && ! in_array($_POST['field_orig'][$i], $columns_with_index)
  896. ) {
  897. $secondary_query = 'ALTER TABLE ' . Util::backquote(
  898. $this->table
  899. )
  900. . ' CHANGE ' . Util::backquote(
  901. $_POST['field_orig'][$i]
  902. )
  903. . ' ' . Util::backquote($_POST['field_orig'][$i])
  904. . ' BLOB';
  905. if (isset($_POST['field_virtuality'][$i])
  906. && isset($_POST['field_expression'][$i])) {
  907. if ($_POST['field_virtuality'][$i]) {
  908. $secondary_query .= ' AS (' . $_POST['field_expression'][$i] . ') '
  909. . $_POST['field_virtuality'][$i];
  910. }
  911. }
  912. $secondary_query .= ';';
  913. $this->dbi->query($secondary_query);
  914. $changedToBlob[$i] = true;
  915. } else {
  916. $changedToBlob[$i] = false;
  917. }
  918. }
  919. // Then make the requested changes
  920. $result = $this->dbi->tryQuery($sql_query);
  921. if ($result !== false) {
  922. $changed_privileges = $this->adjustColumnPrivileges(
  923. $adjust_privileges
  924. );
  925. if ($changed_privileges) {
  926. $message = Message::success(
  927. __(
  928. 'Table %1$s has been altered successfully. Privileges ' .
  929. 'have been adjusted.'
  930. )
  931. );
  932. } else {
  933. $message = Message::success(
  934. __('Table %1$s has been altered successfully.')
  935. );
  936. }
  937. $message->addParam($this->table);
  938. $this->response->addHTML(
  939. Util::getMessage($message, $sql_query, 'success')
  940. );
  941. } else {
  942. // An error happened while inserting/updating a table definition
  943. // Save the Original Error
  944. $orig_error = $this->dbi->getError();
  945. $changes_revert = array();
  946. // Change back to Original Collation and data type
  947. for ($i = 0; $i < $field_cnt; $i++) {
  948. if ($changedToBlob[$i]) {
  949. $changes_revert[] = 'CHANGE ' . Table::generateAlter(
  950. Util::getValueByKey($_POST, "field_orig.${i}", ''),
  951. $_POST['field_name'][$i],
  952. $_POST['field_type_orig'][$i],
  953. $_POST['field_length_orig'][$i],
  954. $_POST['field_attribute_orig'][$i],
  955. Util::getValueByKey($_POST, "field_collation_orig.${i}", ''),
  956. Util::getValueByKey($_POST, "field_null_orig.${i}", 'NO'),
  957. $_POST['field_default_type_orig'][$i],
  958. $_POST['field_default_value_orig'][$i],
  959. Util::getValueByKey($_POST, "field_extra_orig.${i}", false),
  960. Util::getValueByKey($_POST, "field_comments_orig.${i}", ''),
  961. Util::getValueByKey($_POST, "field_virtuality_orig.${i}", ''),
  962. Util::getValueByKey($_POST, "field_expression_orig.${i}", ''),
  963. Util::getValueByKey($_POST, "field_move_to_orig.${i}", '')
  964. );
  965. }
  966. }
  967. $revert_query = 'ALTER TABLE ' . Util::backquote($this->table)
  968. . ' ';
  969. $revert_query .= implode(', ', $changes_revert) . '';
  970. $revert_query .= ';';
  971. // Column reverted back to original
  972. $this->dbi->query($revert_query);
  973. $this->response->setRequestStatus(false);
  974. $this->response->addJSON(
  975. 'message',
  976. Message::rawError(
  977. __('Query error') . ':<br />' . $orig_error
  978. )
  979. );
  980. $regenerate = true;
  981. }
  982. }
  983. // update field names in relation
  984. if (isset($_POST['field_orig']) && is_array($_POST['field_orig'])) {
  985. foreach ($_POST['field_orig'] as $fieldindex => $fieldcontent) {
  986. if ($_POST['field_name'][$fieldindex] != $fieldcontent) {
  987. $this->relation->renameField(
  988. $this->db, $this->table, $fieldcontent,
  989. $_POST['field_name'][$fieldindex]
  990. );
  991. }
  992. }
  993. }
  994. // update mime types
  995. if (isset($_POST['field_mimetype'])
  996. && is_array($_POST['field_mimetype'])
  997. && $GLOBALS['cfg']['BrowseMIME']
  998. ) {
  999. foreach ($_POST['field_mimetype'] as $fieldindex => $mimetype) {
  1000. if (isset($_POST['field_name'][$fieldindex])
  1001. && strlen($_POST['field_name'][$fieldindex]) > 0
  1002. ) {
  1003. Transformations::setMIME(
  1004. $this->db, $this->table,
  1005. $_POST['field_name'][$fieldindex],
  1006. $mimetype,
  1007. $_POST['field_transformation'][$fieldindex],
  1008. $_POST['field_transformation_options'][$fieldindex],
  1009. $_POST['field_input_transformation'][$fieldindex],
  1010. $_POST['field_input_transformation_options'][$fieldindex]
  1011. );
  1012. }
  1013. }
  1014. }
  1015. return $regenerate;
  1016. }
  1017. /**
  1018. * Adjusts the Privileges for all the columns whose names have changed
  1019. *
  1020. * @param array $adjust_privileges assoc array of old col names mapped to new
  1021. * cols
  1022. *
  1023. * @return boolean $changed boolean whether at least one column privileges
  1024. * adjusted
  1025. */
  1026. protected function adjustColumnPrivileges(array $adjust_privileges)
  1027. {
  1028. $changed = false;
  1029. if (Util::getValueByKey($GLOBALS, 'col_priv', false)
  1030. && Util::getValueByKey($GLOBALS, 'is_reload_priv', false)
  1031. ) {
  1032. $this->dbi->selectDb('mysql');
  1033. // For Column specific privileges
  1034. foreach ($adjust_privileges as $oldCol => $newCol) {
  1035. $this->dbi->query(
  1036. sprintf(
  1037. 'UPDATE %s SET Column_name = "%s"
  1038. WHERE Db = "%s"
  1039. AND Table_name = "%s"
  1040. AND Column_name = "%s";',
  1041. Util::backquote('columns_priv'),
  1042. $newCol, $this->db, $this->table, $oldCol
  1043. )
  1044. );
  1045. // i.e. if atleast one column privileges adjusted
  1046. $changed = true;
  1047. }
  1048. if ($changed) {
  1049. // Finally FLUSH the new privileges
  1050. $this->dbi->query("FLUSH PRIVILEGES;");
  1051. }
  1052. }
  1053. return $changed;
  1054. }
  1055. /**
  1056. * Verifies if some elements of a column have changed
  1057. *
  1058. * @param integer $i column index in the request
  1059. *
  1060. * @return boolean $alterTableNeeded true if we need to generate ALTER TABLE
  1061. *
  1062. */
  1063. protected function columnNeedsAlterTable($i)
  1064. {
  1065. // these two fields are checkboxes so might not be part of the
  1066. // request; therefore we define them to avoid notices below
  1067. if (! isset($_POST['field_null'][$i])) {
  1068. $_POST['field_null'][$i] = 'NO';
  1069. }
  1070. if (! isset($_POST['field_extra'][$i])) {
  1071. $_POST['field_extra'][$i] = '';
  1072. }
  1073. // field_name does not follow the convention (corresponds to field_orig)
  1074. if ($_POST['field_name'][$i] != $_POST['field_orig'][$i]) {
  1075. return true;
  1076. }
  1077. $fields = array(
  1078. 'field_attribute', 'field_collation', 'field_comments',
  1079. 'field_default_value', 'field_default_type', 'field_extra',
  1080. 'field_length', 'field_null', 'field_type'
  1081. );
  1082. foreach ($fields as $field) {
  1083. if ($_POST[$field][$i] != $_POST[$field . '_orig'][$i]) {
  1084. return true;
  1085. }
  1086. }
  1087. return !empty($_POST['field_move_to'][$i]);
  1088. }
  1089. /**
  1090. * Displays the table structure ('show table' works correct since 3.23.03)
  1091. *
  1092. * @param array $cfgRelation current relation parameters
  1093. * @param array $columns_with_unique_index Columns with unique index
  1094. * @param mixed $url_params Contains an associative
  1095. * array with url params
  1096. * @param Index|false $primary_index primary index or false if
  1097. * no one exists
  1098. * @param array $fields Fields
  1099. * @param array $columns_with_index Columns with index
  1100. *
  1101. * @return string
  1102. */
  1103. protected function displayStructure(
  1104. array $cfgRelation, array $columns_with_unique_index, $url_params,
  1105. $primary_index, array $fields, array $columns_with_index
  1106. ) {
  1107. // prepare comments
  1108. $comments_map = array();
  1109. $mime_map = array();
  1110. if ($GLOBALS['cfg']['ShowPropertyComments']) {
  1111. $comments_map = $this->relation->getComments($this->db, $this->table);
  1112. if ($cfgRelation['mimework'] && $GLOBALS['cfg']['BrowseMIME']) {
  1113. $mime_map = Transformations::getMIME($this->db, $this->table, true);
  1114. }
  1115. }
  1116. $centralColumns = new CentralColumns($GLOBALS['dbi']);
  1117. $central_list = $centralColumns->getFromTable(
  1118. $this->db,
  1119. $this->table
  1120. );
  1121. $columns_list = array();
  1122. $titles = array(
  1123. 'Change' => Util::getIcon('b_edit', __('Change')),
  1124. 'Drop' => Util::getIcon('b_drop', __('Drop')),
  1125. 'NoDrop' => Util::getIcon('b_drop', __('Drop')),
  1126. 'Primary' => Util::getIcon('b_primary', __('Primary')),
  1127. 'Index' => Util::getIcon('b_index', __('Index')),
  1128. 'Unique' => Util::getIcon('b_unique', __('Unique')),
  1129. 'Spatial' => Util::getIcon('b_spatial', __('Spatial')),
  1130. 'IdxFulltext' => Util::getIcon('b_ftext', __('Fulltext')),
  1131. 'NoPrimary' => Util::getIcon('bd_primary', __('Primary')),
  1132. 'NoIndex' => Util::getIcon('bd_index', __('Index')),
  1133. 'NoUnique' => Util::getIcon('bd_unique', __('Unique')),
  1134. 'NoSpatial' => Util::getIcon('bd_spatial', __('Spatial')),
  1135. 'NoIdxFulltext' => Util::getIcon('bd_ftext', __('Fulltext')),
  1136. 'DistinctValues' => Util::getIcon('b_browse', __('Distinct values')),
  1137. );
  1138. /**
  1139. * Displays Space usage and row statistics
  1140. */
  1141. // BEGIN - Calc Table Space
  1142. // Get valid statistics whatever is the table type
  1143. if ($GLOBALS['cfg']['ShowStats']) {
  1144. //get table stats in HTML format
  1145. $tablestats = $this->getTableStats();
  1146. //returning the response in JSON format to be used by Ajax
  1147. $this->response->addJSON('tableStat', $tablestats);
  1148. }
  1149. // END - Calc Table Space
  1150. $hideStructureActions = false;
  1151. if ($GLOBALS['cfg']['HideStructureActions'] === true) {
  1152. $hideStructureActions = true;
  1153. }
  1154. return Template::get('table/structure/display_structure')->render(
  1155. array(
  1156. 'hide_structure_actions' => $hideStructureActions,
  1157. 'db' => $this->db,
  1158. 'table' => $this->table,
  1159. 'db_is_system_schema' => $this->_db_is_system_schema,
  1160. 'tbl_is_view' => $this->_tbl_is_view,
  1161. 'mime_map' => $mime_map,
  1162. 'url_query' => $this->_url_query,
  1163. 'url_params' => $url_params,
  1164. 'titles' => $titles,
  1165. 'tbl_storage_engine' => $this->_tbl_storage_engine,
  1166. 'primary' => $primary_index,
  1167. 'columns_with_unique_index' => $columns_with_unique_index,
  1168. 'columns_list' => $columns_list,
  1169. 'table_stats' => isset($tablestats) ? $tablestats : null,
  1170. 'fields' => $fields,
  1171. 'columns_with_index' => $columns_with_index,
  1172. 'central_list' => $central_list,
  1173. 'comments_map' => $comments_map,
  1174. 'browse_mime' => $GLOBALS['cfg']['BrowseMIME'],
  1175. 'show_column_comments' => $GLOBALS['cfg']['ShowColumnComments'],
  1176. 'show_stats' => $GLOBALS['cfg']['ShowStats'],
  1177. 'relation_commwork' => $GLOBALS['cfgRelation']['commwork'],
  1178. 'relation_mimework' => $GLOBALS['cfgRelation']['mimework'],
  1179. 'central_columns_work' => $GLOBALS['cfgRelation']['centralcolumnswork'],
  1180. 'mysql_int_version' => $GLOBALS['dbi']->getVersion(),
  1181. 'is_mariadb' => $GLOBALS['dbi']->isMariaDB(),
  1182. 'pma_theme_image' => $GLOBALS['pmaThemeImage'],
  1183. 'text_dir' => $GLOBALS['text_dir'],
  1184. 'is_active' => Tracker::isActive(),
  1185. 'have_partitioning' => Partition::havePartitioning(),
  1186. 'partition_names' => Partition::getPartitionNames($this->db, $this->table),
  1187. )
  1188. );
  1189. }
  1190. /**
  1191. * Get HTML snippet for display table statistics
  1192. *
  1193. * @return string $html_output
  1194. */
  1195. protected function getTableStats()
  1196. {
  1197. if (empty($this->_showtable)) {
  1198. $this->_showtable = $this->dbi->getTable(
  1199. $this->db, $this->table
  1200. )->getStatusInfo(null, true);
  1201. }
  1202. if (empty($this->_showtable['Data_length'])) {
  1203. $this->_showtable['Data_length'] = 0;
  1204. }
  1205. if (empty($this->_showtable['Index_length'])) {
  1206. $this->_showtable['Index_length'] = 0;
  1207. }
  1208. $is_innodb = (isset($this->_showtable['Type'])
  1209. && $this->_showtable['Type'] == 'InnoDB');
  1210. $mergetable = $this->table_obj->isMerge();
  1211. // this is to display for example 261.2 MiB instead of 268k KiB
  1212. $max_digits = 3;
  1213. $decimals = 1;
  1214. list($data_size, $data_unit) = Util::formatByteDown(
  1215. $this->_showtable['Data_length'], $max_digits, $decimals
  1216. );
  1217. if ($mergetable == false) {
  1218. list($index_size, $index_unit) = Util::formatByteDown(
  1219. $this->_showtable['Index_length'], $max_digits, $decimals
  1220. );
  1221. }
  1222. // InnoDB returns a huge value in Data_free, do not use it
  1223. if (! $is_innodb && isset($this->_showtable['Data_free'])
  1224. && $this->_showtable['Data_free'] > 0
  1225. ) {
  1226. list($free_size, $free_unit) = Util::formatByteDown(
  1227. $this->_showtable['Data_free'], $max_digits, $decimals
  1228. );
  1229. list($effect_size, $effect_unit) = Util::formatByteDown(
  1230. $this->_showtable['Data_length']
  1231. + $this->_showtable['Index_length']
  1232. - $this->_showtable['Data_free'],
  1233. $max_digits, $decimals
  1234. );
  1235. } else {
  1236. list($effect_size, $effect_unit) = Util::formatByteDown(
  1237. $this->_showtable['Data_length']
  1238. + $this->_showtable['Index_length'],
  1239. $max_digits, $decimals
  1240. );
  1241. }
  1242. list($tot_size, $tot_unit) = Util::formatByteDown(
  1243. $this->_showtable['Data_length'] + $this->_showtable['Index_length'],
  1244. $max_digits, $decimals
  1245. );
  1246. if ($this->_table_info_num_rows > 0) {
  1247. list($avg_size, $avg_unit) = Util::formatByteDown(
  1248. ($this->_showtable['Data_length']
  1249. + $this->_showtable['Index_length'])
  1250. / $this->_showtable['Rows'],
  1251. 6,
  1252. 1
  1253. );
  1254. } else {
  1255. $avg_size = $avg_unit = '';
  1256. }
  1257. return Template::get('table/structure/display_table_stats')->render(
  1258. array(
  1259. 'showtable' => $this->_showtable,
  1260. 'table_info_num_rows' => $this->_table_info_num_rows,
  1261. 'tbl_is_view' => $this->_tbl_is_view,
  1262. 'db_is_system_schema' => $this->_db_is_system_schema,
  1263. 'tbl_storage_engine' => $this->_tbl_storage_engine,
  1264. 'url_query' => $this->_url_query,
  1265. 'tbl_collation' => $this->_tbl_collation,
  1266. 'is_innodb' => $is_innodb,
  1267. 'mergetable' => $mergetable,
  1268. 'avg_size' => isset($avg_size) ? $avg_size : null,
  1269. 'avg_unit' => isset($avg_unit) ? $avg_unit : null,
  1270. 'data_size' => $data_size,
  1271. 'data_unit' => $data_unit,
  1272. 'index_size' => isset($index_size) ? $index_size : null,
  1273. 'index_unit' => isset($index_unit) ? $index_unit : null,
  1274. 'free_size' => isset($free_size) ? $free_size : null,
  1275. 'free_unit' => isset($free_unit) ? $free_unit : null,
  1276. 'effect_size' => $effect_size,
  1277. 'effect_unit' => $effect_unit,
  1278. 'tot_size' => $tot_size,
  1279. 'tot_unit' => $tot_unit,
  1280. 'table' => $GLOBALS['table']
  1281. )
  1282. );
  1283. }
  1284. /**
  1285. * Gets table primary key
  1286. *
  1287. * @return string
  1288. */
  1289. protected function getKeyForTablePrimary()
  1290. {
  1291. $this->dbi->selectDb($this->db);
  1292. $result = $this->dbi->query(
  1293. 'SHOW KEYS FROM ' . Util::backquote($this->table) . ';'
  1294. );
  1295. $primary = '';
  1296. while ($row = $this->dbi->fetchAssoc($result)) {
  1297. // Backups the list of primary keys
  1298. if ($row['Key_name'] == 'PRIMARY') {
  1299. $primary .= $row['Column_name'] . ', ';
  1300. }
  1301. } // end while
  1302. $this->dbi->freeResult($result);
  1303. return $primary;
  1304. }
  1305. /**
  1306. * Get List of information for Submit Mult
  1307. *
  1308. * @param string $submit_mult mult_submit type
  1309. * @param array $selected the selected columns
  1310. * @param string $action action type
  1311. *
  1312. * @return array
  1313. */
  1314. protected function getDataForSubmitMult($submit_mult, $selected, $action)
  1315. {
  1316. $centralColumns = new CentralColumns($GLOBALS['dbi']);
  1317. $what = null;
  1318. $query_type = null;
  1319. $is_unset_submit_mult = false;
  1320. $mult_btn = null;
  1321. $centralColsError = null;
  1322. switch ($submit_mult) {
  1323. case 'drop':
  1324. $what = 'drop_fld';
  1325. break;
  1326. case 'primary':
  1327. // Gets table primary key
  1328. $primary = $this->getKeyForTablePrimary();
  1329. if (empty($primary)) {
  1330. // no primary key, so we can safely create new
  1331. $is_unset_submit_mult = true;
  1332. $query_type = 'primary_fld';
  1333. $mult_btn = __('Yes');
  1334. } else {
  1335. // primary key exists, so lets as user
  1336. $what = 'primary_fld';
  1337. }
  1338. break;
  1339. case 'index':
  1340. $is_unset_submit_mult = true;
  1341. $query_type = 'index_fld';
  1342. $mult_btn = __('Yes');
  1343. break;
  1344. case 'unique':
  1345. $is_unset_submit_mult = true;
  1346. $query_type = 'unique_fld';
  1347. $mult_btn = __('Yes');
  1348. break;
  1349. case 'spatial':
  1350. $is_unset_submit_mult = true;
  1351. $query_type = 'spatial_fld';
  1352. $mult_btn = __('Yes');
  1353. break;
  1354. case 'ftext':
  1355. $is_unset_submit_mult = true;
  1356. $query_type = 'fulltext_fld';
  1357. $mult_btn = __('Yes');
  1358. break;
  1359. case 'add_to_central_columns':
  1360. $centralColsError = $centralColumns->syncUniqueColumns(
  1361. $selected,
  1362. false
  1363. );
  1364. break;
  1365. case 'remove_from_central_columns':
  1366. $centralColsError = $centralColumns->deleteColumnsFromList(
  1367. $selected,
  1368. false
  1369. );
  1370. break;
  1371. case 'change':
  1372. $this->displayHtmlForColumnChange($selected, $action);
  1373. // execution stops here but PhpMyAdmin\Response correctly finishes
  1374. // the rendering
  1375. exit;
  1376. case 'browse':
  1377. // this should already be handled by tbl_structure.php
  1378. }
  1379. return array(
  1380. $what, $query_type, $is_unset_submit_mult, $mult_btn,
  1381. $centralColsError
  1382. );
  1383. }
  1384. }