Index.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * holds the database index class
  5. *
  6. * @package PhpMyAdmin
  7. */
  8. declare(strict_types=1);
  9. namespace PhpMyAdmin;
  10. /**
  11. * Index manipulation class
  12. *
  13. * @package PhpMyAdmin
  14. * @since phpMyAdmin 3.0.0
  15. */
  16. class Index
  17. {
  18. public const PRIMARY = 1;
  19. public const UNIQUE = 2;
  20. public const INDEX = 4;
  21. public const SPATIAL = 8;
  22. public const FULLTEXT = 16;
  23. /**
  24. * Class-wide storage container for indexes (caching, singleton)
  25. *
  26. * @var array
  27. */
  28. private static $_registry = [];
  29. /**
  30. * @var string The name of the schema
  31. */
  32. private $_schema = '';
  33. /**
  34. * @var string The name of the table
  35. */
  36. private $_table = '';
  37. /**
  38. * @var string The name of the index
  39. */
  40. private $_name = '';
  41. /**
  42. * Columns in index
  43. *
  44. * @var array
  45. */
  46. private $_columns = [];
  47. /**
  48. * The index method used (BTREE, HASH, RTREE).
  49. *
  50. * @var string
  51. */
  52. private $_type = '';
  53. /**
  54. * The index choice (PRIMARY, UNIQUE, INDEX, SPATIAL, FULLTEXT)
  55. *
  56. * @var string
  57. */
  58. private $_choice = '';
  59. /**
  60. * Various remarks.
  61. *
  62. * @var string
  63. */
  64. private $_remarks = '';
  65. /**
  66. * Any comment provided for the index with a COMMENT attribute when the
  67. * index was created.
  68. *
  69. * @var string
  70. */
  71. private $_comment = '';
  72. /**
  73. * @var integer 0 if the index cannot contain duplicates, 1 if it can.
  74. */
  75. private $_non_unique = 0;
  76. /**
  77. * Indicates how the key is packed. NULL if it is not.
  78. *
  79. * @var string
  80. */
  81. private $_packed = null;
  82. /**
  83. * Block size for the index
  84. *
  85. * @var int
  86. */
  87. private $_key_block_size = null;
  88. /**
  89. * Parser option for the index
  90. *
  91. * @var string
  92. */
  93. private $_parser = null;
  94. /**
  95. * Constructor
  96. *
  97. * @param array $params parameters
  98. */
  99. public function __construct(array $params = [])
  100. {
  101. $this->set($params);
  102. }
  103. /**
  104. * Creates(if not already created) and returns the corresponding Index object
  105. *
  106. * @param string $schema database name
  107. * @param string $table table name
  108. * @param string $index_name index name
  109. *
  110. * @return Index corresponding Index object
  111. */
  112. public static function singleton($schema, $table, $index_name = '')
  113. {
  114. Index::_loadIndexes($table, $schema);
  115. if (! isset(Index::$_registry[$schema][$table][$index_name])) {
  116. $index = new Index();
  117. if (strlen($index_name) > 0) {
  118. $index->setName($index_name);
  119. Index::$_registry[$schema][$table][$index->getName()] = $index;
  120. }
  121. return $index;
  122. }
  123. return Index::$_registry[$schema][$table][$index_name];
  124. }
  125. /**
  126. * returns an array with all indexes from the given table
  127. *
  128. * @param string $table table
  129. * @param string $schema schema
  130. *
  131. * @return Index[] array of indexes
  132. */
  133. public static function getFromTable($table, $schema)
  134. {
  135. Index::_loadIndexes($table, $schema);
  136. if (isset(Index::$_registry[$schema][$table])) {
  137. return Index::$_registry[$schema][$table];
  138. }
  139. return [];
  140. }
  141. /**
  142. * Returns an array with all indexes from the given table of the requested types
  143. *
  144. * @param string $table table
  145. * @param string $schema schema
  146. * @param int $choices choices
  147. *
  148. * @return Index[] array of indexes
  149. */
  150. public static function getFromTableByChoice($table, $schema, $choices = 31)
  151. {
  152. $indexes = [];
  153. foreach (self::getFromTable($table, $schema) as $index) {
  154. if (($choices & Index::PRIMARY)
  155. && $index->getChoice() == 'PRIMARY'
  156. ) {
  157. $indexes[] = $index;
  158. }
  159. if (($choices & Index::UNIQUE)
  160. && $index->getChoice() == 'UNIQUE'
  161. ) {
  162. $indexes[] = $index;
  163. }
  164. if (($choices & Index::INDEX)
  165. && $index->getChoice() == 'INDEX'
  166. ) {
  167. $indexes[] = $index;
  168. }
  169. if (($choices & Index::SPATIAL)
  170. && $index->getChoice() == 'SPATIAL'
  171. ) {
  172. $indexes[] = $index;
  173. }
  174. if (($choices & Index::FULLTEXT)
  175. && $index->getChoice() == 'FULLTEXT'
  176. ) {
  177. $indexes[] = $index;
  178. }
  179. }
  180. return $indexes;
  181. }
  182. /**
  183. * return primary if set, false otherwise
  184. *
  185. * @param string $table table
  186. * @param string $schema schema
  187. *
  188. * @return mixed primary index or false if no one exists
  189. */
  190. public static function getPrimary($table, $schema)
  191. {
  192. Index::_loadIndexes($table, $schema);
  193. if (isset(Index::$_registry[$schema][$table]['PRIMARY'])) {
  194. return Index::$_registry[$schema][$table]['PRIMARY'];
  195. }
  196. return false;
  197. }
  198. /**
  199. * Load index data for table
  200. *
  201. * @param string $table table
  202. * @param string $schema schema
  203. *
  204. * @return boolean whether loading was successful
  205. */
  206. private static function _loadIndexes($table, $schema)
  207. {
  208. if (isset(Index::$_registry[$schema][$table])) {
  209. return true;
  210. }
  211. $_raw_indexes = $GLOBALS['dbi']->getTableIndexes($schema, $table);
  212. foreach ($_raw_indexes as $_each_index) {
  213. $_each_index['Schema'] = $schema;
  214. $keyName = $_each_index['Key_name'];
  215. if (! isset(Index::$_registry[$schema][$table][$keyName])) {
  216. $key = new Index($_each_index);
  217. Index::$_registry[$schema][$table][$keyName] = $key;
  218. } else {
  219. $key = Index::$_registry[$schema][$table][$keyName];
  220. }
  221. $key->addColumn($_each_index);
  222. }
  223. return true;
  224. }
  225. /**
  226. * Add column to index
  227. *
  228. * @param array $params column params
  229. *
  230. * @return void
  231. */
  232. public function addColumn(array $params)
  233. {
  234. if (isset($params['Column_name'])
  235. && strlen($params['Column_name']) > 0
  236. ) {
  237. $this->_columns[$params['Column_name']] = new IndexColumn($params);
  238. }
  239. }
  240. /**
  241. * Adds a list of columns to the index
  242. *
  243. * @param array $columns array containing details about the columns
  244. *
  245. * @return void
  246. */
  247. public function addColumns(array $columns)
  248. {
  249. $_columns = [];
  250. if (isset($columns['names'])) {
  251. // coming from form
  252. // $columns[names][]
  253. // $columns[sub_parts][]
  254. foreach ($columns['names'] as $key => $name) {
  255. $sub_part = isset($columns['sub_parts'][$key])
  256. ? $columns['sub_parts'][$key] : '';
  257. $_columns[] = [
  258. 'Column_name' => $name,
  259. 'Sub_part' => $sub_part,
  260. ];
  261. }
  262. } else {
  263. // coming from SHOW INDEXES
  264. // $columns[][name]
  265. // $columns[][sub_part]
  266. // ...
  267. $_columns = $columns;
  268. }
  269. foreach ($_columns as $column) {
  270. $this->addColumn($column);
  271. }
  272. }
  273. /**
  274. * Returns true if $column indexed in this index
  275. *
  276. * @param string $column the column
  277. *
  278. * @return boolean true if $column indexed in this index
  279. */
  280. public function hasColumn($column)
  281. {
  282. return isset($this->_columns[$column]);
  283. }
  284. /**
  285. * Sets index details
  286. *
  287. * @param array $params index details
  288. *
  289. * @return void
  290. */
  291. public function set(array $params)
  292. {
  293. if (isset($params['columns'])) {
  294. $this->addColumns($params['columns']);
  295. }
  296. if (isset($params['Schema'])) {
  297. $this->_schema = $params['Schema'];
  298. }
  299. if (isset($params['Table'])) {
  300. $this->_table = $params['Table'];
  301. }
  302. if (isset($params['Key_name'])) {
  303. $this->_name = $params['Key_name'];
  304. }
  305. if (isset($params['Index_type'])) {
  306. $this->_type = $params['Index_type'];
  307. }
  308. if (isset($params['Comment'])) {
  309. $this->_remarks = $params['Comment'];
  310. }
  311. if (isset($params['Index_comment'])) {
  312. $this->_comment = $params['Index_comment'];
  313. }
  314. if (isset($params['Non_unique'])) {
  315. $this->_non_unique = $params['Non_unique'];
  316. }
  317. if (isset($params['Packed'])) {
  318. $this->_packed = $params['Packed'];
  319. }
  320. if (isset($params['Index_choice'])) {
  321. $this->_choice = $params['Index_choice'];
  322. } elseif ('PRIMARY' == $this->_name) {
  323. $this->_choice = 'PRIMARY';
  324. } elseif ('FULLTEXT' == $this->_type) {
  325. $this->_choice = 'FULLTEXT';
  326. $this->_type = '';
  327. } elseif ('SPATIAL' == $this->_type) {
  328. $this->_choice = 'SPATIAL';
  329. $this->_type = '';
  330. } elseif ('0' == $this->_non_unique) {
  331. $this->_choice = 'UNIQUE';
  332. } else {
  333. $this->_choice = 'INDEX';
  334. }
  335. if (isset($params['Key_block_size'])) {
  336. $this->_key_block_size = $params['Key_block_size'];
  337. }
  338. if (isset($params['Parser'])) {
  339. $this->_parser = $params['Parser'];
  340. }
  341. }
  342. /**
  343. * Returns the number of columns of the index
  344. *
  345. * @return integer the number of the columns
  346. */
  347. public function getColumnCount()
  348. {
  349. return count($this->_columns);
  350. }
  351. /**
  352. * Returns the index comment
  353. *
  354. * @return string index comment
  355. */
  356. public function getComment()
  357. {
  358. return $this->_comment;
  359. }
  360. /**
  361. * Returns index remarks
  362. *
  363. * @return string index remarks
  364. */
  365. public function getRemarks()
  366. {
  367. return $this->_remarks;
  368. }
  369. /**
  370. * Return the key block size
  371. *
  372. * @return int
  373. */
  374. public function getKeyBlockSize()
  375. {
  376. return $this->_key_block_size;
  377. }
  378. /**
  379. * Return the parser
  380. *
  381. * @return string
  382. */
  383. public function getParser()
  384. {
  385. return $this->_parser;
  386. }
  387. /**
  388. * Returns concatenated remarks and comment
  389. *
  390. * @return string concatenated remarks and comment
  391. */
  392. public function getComments()
  393. {
  394. $comments = $this->getRemarks();
  395. if (strlen($comments) > 0) {
  396. $comments .= "\n";
  397. }
  398. $comments .= $this->getComment();
  399. return $comments;
  400. }
  401. /**
  402. * Returns index type (BTREE, HASH, RTREE)
  403. *
  404. * @return string index type
  405. */
  406. public function getType()
  407. {
  408. return $this->_type;
  409. }
  410. /**
  411. * Returns index choice (PRIMARY, UNIQUE, INDEX, SPATIAL, FULLTEXT)
  412. *
  413. * @return string index choice
  414. */
  415. public function getChoice()
  416. {
  417. return $this->_choice;
  418. }
  419. /**
  420. * Return a list of all index choices
  421. *
  422. * @return string[] index choices
  423. */
  424. public static function getIndexChoices()
  425. {
  426. return [
  427. 'PRIMARY',
  428. 'INDEX',
  429. 'UNIQUE',
  430. 'SPATIAL',
  431. 'FULLTEXT',
  432. ];
  433. }
  434. /**
  435. * Returns a lit of all index types
  436. *
  437. * @return string[] index types
  438. */
  439. public static function getIndexTypes()
  440. {
  441. return [
  442. 'BTREE',
  443. 'HASH',
  444. ];
  445. }
  446. /**
  447. * Returns HTML for the index choice selector
  448. *
  449. * @param boolean $edit_table whether this is table editing
  450. *
  451. * @return string HTML for the index choice selector
  452. */
  453. public function generateIndexChoiceSelector($edit_table)
  454. {
  455. $html_options = '<select name="index[Index_choice]"'
  456. . ' id="select_index_choice" '
  457. . ($edit_table ? 'disabled="disabled"' : '') . '>';
  458. foreach (Index::getIndexChoices() as $each_index_choice) {
  459. if ($each_index_choice === 'PRIMARY'
  460. && $this->_choice !== 'PRIMARY'
  461. && Index::getPrimary($this->_table, $this->_schema)
  462. ) {
  463. // skip PRIMARY if there is already one in the table
  464. continue;
  465. }
  466. $html_options .= '<option value="' . $each_index_choice . '"'
  467. . ($this->_choice == $each_index_choice
  468. ? ' selected="selected"'
  469. : '')
  470. . '>' . $each_index_choice . '</option>' . "\n";
  471. }
  472. $html_options .= '</select>';
  473. return $html_options;
  474. }
  475. /**
  476. * Returns HTML for the index type selector
  477. *
  478. * @return string HTML for the index type selector
  479. */
  480. public function generateIndexTypeSelector()
  481. {
  482. $types = ["" => "--"];
  483. foreach (Index::getIndexTypes() as $type) {
  484. $types[$type] = $type;
  485. }
  486. return Util::getDropdown(
  487. "index[Index_type]",
  488. $types,
  489. $this->_type,
  490. "select_index_type"
  491. );
  492. }
  493. /**
  494. * Returns how the index is packed
  495. *
  496. * @return string how the index is packed
  497. */
  498. public function getPacked()
  499. {
  500. return $this->_packed;
  501. }
  502. /**
  503. * Returns 'No' if the index is not packed,
  504. * how the index is packed if packed
  505. *
  506. * @return string
  507. */
  508. public function isPacked()
  509. {
  510. if (null === $this->_packed) {
  511. return __('No');
  512. }
  513. return htmlspecialchars($this->_packed);
  514. }
  515. /**
  516. * Returns integer 0 if the index cannot contain duplicates, 1 if it can
  517. *
  518. * @return integer 0 if the index cannot contain duplicates, 1 if it can
  519. */
  520. public function getNonUnique()
  521. {
  522. return $this->_non_unique;
  523. }
  524. /**
  525. * Returns whether the index is a 'Unique' index
  526. *
  527. * @param boolean $as_text whether to output should be in text
  528. *
  529. * @return mixed whether the index is a 'Unique' index
  530. */
  531. public function isUnique($as_text = false)
  532. {
  533. if ($as_text) {
  534. $r = [
  535. '0' => __('Yes'),
  536. '1' => __('No'),
  537. ];
  538. } else {
  539. $r = [
  540. '0' => true,
  541. '1' => false,
  542. ];
  543. }
  544. return $r[$this->_non_unique];
  545. }
  546. /**
  547. * Returns the name of the index
  548. *
  549. * @return string the name of the index
  550. */
  551. public function getName()
  552. {
  553. return $this->_name;
  554. }
  555. /**
  556. * Sets the name of the index
  557. *
  558. * @param string $name index name
  559. *
  560. * @return void
  561. */
  562. public function setName($name)
  563. {
  564. $this->_name = (string) $name;
  565. }
  566. /**
  567. * Returns the columns of the index
  568. *
  569. * @return IndexColumn[] the columns of the index
  570. */
  571. public function getColumns()
  572. {
  573. return $this->_columns;
  574. }
  575. /**
  576. * Get HTML for display indexes
  577. *
  578. * @return string
  579. */
  580. public static function getHtmlForDisplayIndexes()
  581. {
  582. $html_output = '<div id="index_div" class="width100 ajax" >';
  583. $html_output .= self::getHtmlForIndexes(
  584. $GLOBALS['table'],
  585. $GLOBALS['db']
  586. );
  587. $html_output .= '<fieldset class="tblFooters print_ignore" style="text-align: '
  588. . 'left;"><form action="tbl_indexes.php" method="post">';
  589. $html_output .= Url::getHiddenInputs(
  590. $GLOBALS['db'],
  591. $GLOBALS['table']
  592. );
  593. $html_output .= sprintf(
  594. __('Create an index on &nbsp;%s&nbsp;columns'),
  595. '<input type="number" name="added_fields" value="1" '
  596. . 'min="1" required="required">'
  597. );
  598. $html_output .= '<input type="hidden" name="create_index" value="1">'
  599. . '<input class="btn btn-primary add_index ajax"'
  600. . ' type="submit" value="' . __('Go') . '">';
  601. $html_output .= '</form>'
  602. . '</fieldset>'
  603. . '</div>';
  604. return $html_output;
  605. }
  606. /**
  607. * Show index data
  608. *
  609. * @param string $table The table name
  610. * @param string $schema The schema name
  611. * @param boolean $print_mode Whether the output is for the print mode
  612. *
  613. * @return string HTML for showing index
  614. *
  615. * @access public
  616. */
  617. public static function getHtmlForIndexes($table, $schema, $print_mode = false)
  618. {
  619. $indexes = Index::getFromTable($table, $schema);
  620. $no_indexes_class = count($indexes) > 0 ? ' hide' : '';
  621. $no_indexes = "<div class='no_indexes_defined$no_indexes_class'>";
  622. $no_indexes .= Message::notice(__('No index defined!'))->getDisplay();
  623. $no_indexes .= '</div>';
  624. if (! $print_mode) {
  625. $r = '<fieldset class="index_info">';
  626. $r .= '<legend id="index_header">' . __('Indexes');
  627. $r .= Util::showMySQLDocu('optimizing-database-structure');
  628. $r .= '</legend>';
  629. $r .= $no_indexes;
  630. if (count($indexes) < 1) {
  631. $r .= '</fieldset>';
  632. return $r;
  633. }
  634. $r .= Index::findDuplicates($table, $schema);
  635. } else {
  636. $r = '<h3>' . __('Indexes') . '</h3>';
  637. $r .= $no_indexes;
  638. if (count($indexes) < 1) {
  639. return $r;
  640. }
  641. }
  642. $r .= '<div class="responsivetable jsresponsive">';
  643. $r .= '<table id="table_index">';
  644. $r .= '<thead>';
  645. $r .= '<tr>';
  646. if (! $print_mode) {
  647. $r .= '<th colspan="2" class="print_ignore">' . __('Action') . '</th>';
  648. }
  649. $r .= '<th>' . __('Keyname') . '</th>';
  650. $r .= '<th>' . __('Type') . '</th>';
  651. $r .= '<th>' . __('Unique') . '</th>';
  652. $r .= '<th>' . __('Packed') . '</th>';
  653. $r .= '<th>' . __('Column') . '</th>';
  654. $r .= '<th>' . __('Cardinality') . '</th>';
  655. $r .= '<th>' . __('Collation') . '</th>';
  656. $r .= '<th>' . __('Null') . '</th>';
  657. $r .= '<th>' . __('Comment') . '</th>';
  658. $r .= '</tr>';
  659. $r .= '</thead>';
  660. foreach ($indexes as $index) {
  661. $row_span = ' rowspan="' . $index->getColumnCount() . '" ';
  662. $r .= '<tbody class="row_span">';
  663. $r .= '<tr class="noclick" >';
  664. if (! $print_mode) {
  665. $this_params = $GLOBALS['url_params'];
  666. $this_params['index'] = $index->getName();
  667. $r .= '<td class="edit_index print_ignore';
  668. $r .= ' ajax';
  669. $r .= '" ' . $row_span . '>'
  670. . ' <a class="';
  671. $r .= 'ajax';
  672. $r .= '" href="tbl_indexes.php" data-post="' . Url::getCommon($this_params, '')
  673. . '">' . Util::getIcon('b_edit', __('Edit')) . '</a>'
  674. . '</td>' . "\n";
  675. $this_params = $GLOBALS['url_params'];
  676. if ($index->getName() == 'PRIMARY') {
  677. $this_params['sql_query'] = 'ALTER TABLE '
  678. . Util::backquote($table)
  679. . ' DROP PRIMARY KEY;';
  680. $this_params['message_to_show']
  681. = __('The primary key has been dropped.');
  682. $js_msg = Sanitize::jsFormat($this_params['sql_query'], false);
  683. } else {
  684. $this_params['sql_query'] = 'ALTER TABLE '
  685. . Util::backquote($table) . ' DROP INDEX '
  686. . Util::backquote($index->getName()) . ';';
  687. $this_params['message_to_show'] = sprintf(
  688. __('Index %s has been dropped.'),
  689. htmlspecialchars($index->getName())
  690. );
  691. $js_msg = Sanitize::jsFormat($this_params['sql_query'], false);
  692. }
  693. $r .= '<td ' . $row_span . ' class="print_ignore">';
  694. $r .= '<input type="hidden" class="drop_primary_key_index_msg"'
  695. . ' value="' . $js_msg . '">';
  696. $r .= Util::linkOrButton(
  697. 'sql.php' . Url::getCommon($this_params),
  698. Util::getIcon('b_drop', __('Drop')),
  699. ['class' => 'drop_primary_key_index_anchor ajax']
  700. );
  701. $r .= '</td>' . "\n";
  702. }
  703. if (! $print_mode) {
  704. $r .= '<th ' . $row_span . '>'
  705. . htmlspecialchars($index->getName())
  706. . '</th>';
  707. } else {
  708. $r .= '<td ' . $row_span . '>'
  709. . htmlspecialchars($index->getName())
  710. . '</td>';
  711. }
  712. $r .= '<td ' . $row_span . '>';
  713. $type = $index->getType();
  714. if (! empty($type)) {
  715. $r .= htmlspecialchars($type);
  716. } else {
  717. $r .= htmlspecialchars($index->getChoice());
  718. }
  719. $r .= '</td>';
  720. $r .= '<td ' . $row_span . '>' . $index->isUnique(true) . '</td>';
  721. $r .= '<td ' . $row_span . '>' . $index->isPacked() . '</td>';
  722. foreach ($index->getColumns() as $column) {
  723. if ($column->getSeqInIndex() > 1) {
  724. $r .= '<tr class="noclick" >';
  725. }
  726. $r .= '<td>' . htmlspecialchars($column->getName());
  727. if ($column->getSubPart()) {
  728. $r .= ' (' . htmlspecialchars($column->getSubPart()) . ')';
  729. }
  730. $r .= '</td>';
  731. $r .= '<td>'
  732. . htmlspecialchars((string) $column->getCardinality())
  733. . '</td>';
  734. $r .= '<td>'
  735. . htmlspecialchars((string) $column->getCollation())
  736. . '</td>';
  737. $r .= '<td>'
  738. . htmlspecialchars($column->getNull(true))
  739. . '</td>';
  740. if ($column->getSeqInIndex() == 1
  741. ) {
  742. $r .= '<td ' . $row_span . '>'
  743. . htmlspecialchars($index->getComments()) . '</td>';
  744. }
  745. $r .= '</tr>';
  746. } // end foreach $index['Sequences']
  747. $r .= '</tbody>';
  748. } // end while
  749. $r .= '</table>';
  750. $r .= '</div>';
  751. if (! $print_mode) {
  752. $r .= '</fieldset>';
  753. }
  754. return $r;
  755. }
  756. /**
  757. * Gets the properties in an array for comparison purposes
  758. *
  759. * @return array an array containing the properties of the index
  760. */
  761. public function getCompareData()
  762. {
  763. $data = [
  764. // 'Non_unique' => $this->_non_unique,
  765. 'Packed' => $this->_packed,
  766. 'Index_choice' => $this->_choice,
  767. ];
  768. foreach ($this->_columns as $column) {
  769. $data['columns'][] = $column->getCompareData();
  770. }
  771. return $data;
  772. }
  773. /**
  774. * Function to check over array of indexes and look for common problems
  775. *
  776. * @param string $table table name
  777. * @param string $schema schema name
  778. *
  779. * @return string Output HTML
  780. * @access public
  781. */
  782. public static function findDuplicates($table, $schema)
  783. {
  784. $indexes = Index::getFromTable($table, $schema);
  785. $output = '';
  786. // count($indexes) < 2:
  787. // there is no need to check if there less than two indexes
  788. if (count($indexes) < 2) {
  789. return $output;
  790. }
  791. // remove last index from stack and ...
  792. while ($while_index = array_pop($indexes)) {
  793. // ... compare with every remaining index in stack
  794. foreach ($indexes as $each_index) {
  795. if ($each_index->getCompareData() !== $while_index->getCompareData()
  796. ) {
  797. continue;
  798. }
  799. // did not find any difference
  800. // so it makes no sense to have this two equal indexes
  801. $message = Message::notice(
  802. __(
  803. 'The indexes %1$s and %2$s seem to be equal and one of them '
  804. . 'could possibly be removed.'
  805. )
  806. );
  807. $message->addParam($each_index->getName());
  808. $message->addParam($while_index->getName());
  809. $output .= $message->getDisplay();
  810. // there is no need to check any further indexes if we have already
  811. // found that this one has a duplicate
  812. continue 2;
  813. }
  814. }
  815. return $output;
  816. }
  817. }