Index.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  1. <?php
  2. declare(strict_types=1);
  3. namespace PhpMyAdmin;
  4. use function array_pop;
  5. use function count;
  6. use function htmlspecialchars;
  7. use function strlen;
  8. /**
  9. * Index manipulation class
  10. */
  11. class Index
  12. {
  13. public const PRIMARY = 1;
  14. public const UNIQUE = 2;
  15. public const INDEX = 4;
  16. public const SPATIAL = 8;
  17. public const FULLTEXT = 16;
  18. /**
  19. * Class-wide storage container for indexes (caching, singleton)
  20. *
  21. * @var array
  22. */
  23. private static $registry = [];
  24. /** @var string The name of the schema */
  25. private $schema = '';
  26. /** @var string The name of the table */
  27. private $table = '';
  28. /** @var string The name of the index */
  29. private $name = '';
  30. /**
  31. * Columns in index
  32. *
  33. * @var array
  34. */
  35. private $columns = [];
  36. /**
  37. * The index method used (BTREE, HASH, RTREE).
  38. *
  39. * @var string
  40. */
  41. private $type = '';
  42. /**
  43. * The index choice (PRIMARY, UNIQUE, INDEX, SPATIAL, FULLTEXT)
  44. *
  45. * @var string
  46. */
  47. private $choice = '';
  48. /**
  49. * Various remarks.
  50. *
  51. * @var string
  52. */
  53. private $remarks = '';
  54. /**
  55. * Any comment provided for the index with a COMMENT attribute when the
  56. * index was created.
  57. *
  58. * @var string
  59. */
  60. private $comment = '';
  61. /** @var int 0 if the index cannot contain duplicates, 1 if it can. */
  62. private $nonUnique = 0;
  63. /**
  64. * Indicates how the key is packed. NULL if it is not.
  65. *
  66. * @var string
  67. */
  68. private $packed = null;
  69. /**
  70. * Block size for the index
  71. *
  72. * @var int
  73. */
  74. private $keyBlockSize = null;
  75. /**
  76. * Parser option for the index
  77. *
  78. * @var string
  79. */
  80. private $parser = null;
  81. /**
  82. * @param array $params parameters
  83. */
  84. public function __construct(array $params = [])
  85. {
  86. $this->set($params);
  87. }
  88. /**
  89. * Creates(if not already created) and returns the corresponding Index object
  90. *
  91. * @param string $schema database name
  92. * @param string $table table name
  93. * @param string $index_name index name
  94. *
  95. * @return Index corresponding Index object
  96. */
  97. public static function singleton($schema, $table, $index_name = '')
  98. {
  99. self::loadIndexes($table, $schema);
  100. if (! isset(self::$registry[$schema][$table][$index_name])) {
  101. $index = new Index();
  102. if (strlen($index_name) > 0) {
  103. $index->setName($index_name);
  104. self::$registry[$schema][$table][$index->getName()] = $index;
  105. }
  106. return $index;
  107. }
  108. return self::$registry[$schema][$table][$index_name];
  109. }
  110. /**
  111. * returns an array with all indexes from the given table
  112. *
  113. * @param string $table table
  114. * @param string $schema schema
  115. *
  116. * @return Index[] array of indexes
  117. */
  118. public static function getFromTable($table, $schema)
  119. {
  120. self::loadIndexes($table, $schema);
  121. if (isset(self::$registry[$schema][$table])) {
  122. return self::$registry[$schema][$table];
  123. }
  124. return [];
  125. }
  126. /**
  127. * Returns an array with all indexes from the given table of the requested types
  128. *
  129. * @param string $table table
  130. * @param string $schema schema
  131. * @param int $choices choices
  132. *
  133. * @return Index[] array of indexes
  134. */
  135. public static function getFromTableByChoice($table, $schema, $choices = 31)
  136. {
  137. $indexes = [];
  138. foreach (self::getFromTable($table, $schema) as $index) {
  139. if (($choices & self::PRIMARY)
  140. && $index->getChoice() === 'PRIMARY'
  141. ) {
  142. $indexes[] = $index;
  143. }
  144. if (($choices & self::UNIQUE)
  145. && $index->getChoice() === 'UNIQUE'
  146. ) {
  147. $indexes[] = $index;
  148. }
  149. if (($choices & self::INDEX)
  150. && $index->getChoice() === 'INDEX'
  151. ) {
  152. $indexes[] = $index;
  153. }
  154. if (($choices & self::SPATIAL)
  155. && $index->getChoice() === 'SPATIAL'
  156. ) {
  157. $indexes[] = $index;
  158. }
  159. if ((! ($choices & self::FULLTEXT))
  160. || $index->getChoice() !== 'FULLTEXT'
  161. ) {
  162. continue;
  163. }
  164. $indexes[] = $index;
  165. }
  166. return $indexes;
  167. }
  168. /**
  169. * return primary if set, false otherwise
  170. *
  171. * @param string $table table
  172. * @param string $schema schema
  173. *
  174. * @return Index|false primary index or false if no one exists
  175. */
  176. public static function getPrimary($table, $schema)
  177. {
  178. self::loadIndexes($table, $schema);
  179. if (isset(self::$registry[$schema][$table]['PRIMARY'])) {
  180. return self::$registry[$schema][$table]['PRIMARY'];
  181. }
  182. return false;
  183. }
  184. /**
  185. * Load index data for table
  186. *
  187. * @param string $table table
  188. * @param string $schema schema
  189. *
  190. * @return bool whether loading was successful
  191. */
  192. private static function loadIndexes($table, $schema)
  193. {
  194. global $dbi;
  195. if (isset(self::$registry[$schema][$table])) {
  196. return true;
  197. }
  198. $_raw_indexes = $dbi->getTableIndexes($schema, $table);
  199. foreach ($_raw_indexes as $_each_index) {
  200. $_each_index['Schema'] = $schema;
  201. $keyName = $_each_index['Key_name'];
  202. if (! isset(self::$registry[$schema][$table][$keyName])) {
  203. $key = new Index($_each_index);
  204. self::$registry[$schema][$table][$keyName] = $key;
  205. } else {
  206. $key = self::$registry[$schema][$table][$keyName];
  207. }
  208. $key->addColumn($_each_index);
  209. }
  210. return true;
  211. }
  212. /**
  213. * Add column to index
  214. *
  215. * @param array $params column params
  216. *
  217. * @return void
  218. */
  219. public function addColumn(array $params)
  220. {
  221. if (! isset($params['Column_name'])
  222. || strlen($params['Column_name']) <= 0
  223. ) {
  224. return;
  225. }
  226. $this->columns[$params['Column_name']] = new IndexColumn($params);
  227. }
  228. /**
  229. * Adds a list of columns to the index
  230. *
  231. * @param array $columns array containing details about the columns
  232. *
  233. * @return void
  234. */
  235. public function addColumns(array $columns)
  236. {
  237. $_columns = [];
  238. if (isset($columns['names'])) {
  239. // coming from form
  240. // $columns[names][]
  241. // $columns[sub_parts][]
  242. foreach ($columns['names'] as $key => $name) {
  243. $sub_part = $columns['sub_parts'][$key] ?? '';
  244. $_columns[] = [
  245. 'Column_name' => $name,
  246. 'Sub_part' => $sub_part,
  247. ];
  248. }
  249. } else {
  250. // coming from SHOW INDEXES
  251. // $columns[][name]
  252. // $columns[][sub_part]
  253. // ...
  254. $_columns = $columns;
  255. }
  256. foreach ($_columns as $column) {
  257. $this->addColumn($column);
  258. }
  259. }
  260. /**
  261. * Returns true if $column indexed in this index
  262. *
  263. * @param string $column the column
  264. *
  265. * @return bool true if $column indexed in this index
  266. */
  267. public function hasColumn($column)
  268. {
  269. return isset($this->columns[$column]);
  270. }
  271. /**
  272. * Sets index details
  273. *
  274. * @param array $params index details
  275. *
  276. * @return void
  277. */
  278. public function set(array $params)
  279. {
  280. if (isset($params['columns'])) {
  281. $this->addColumns($params['columns']);
  282. }
  283. if (isset($params['Schema'])) {
  284. $this->schema = $params['Schema'];
  285. }
  286. if (isset($params['Table'])) {
  287. $this->table = $params['Table'];
  288. }
  289. if (isset($params['Key_name'])) {
  290. $this->name = $params['Key_name'];
  291. }
  292. if (isset($params['Index_type'])) {
  293. $this->type = $params['Index_type'];
  294. }
  295. if (isset($params['Comment'])) {
  296. $this->remarks = $params['Comment'];
  297. }
  298. if (isset($params['Index_comment'])) {
  299. $this->comment = $params['Index_comment'];
  300. }
  301. if (isset($params['Non_unique'])) {
  302. $this->nonUnique = $params['Non_unique'];
  303. }
  304. if (isset($params['Packed'])) {
  305. $this->packed = $params['Packed'];
  306. }
  307. if (isset($params['Index_choice'])) {
  308. $this->choice = $params['Index_choice'];
  309. } elseif ($this->name === 'PRIMARY') {
  310. $this->choice = 'PRIMARY';
  311. } elseif ($this->type === 'FULLTEXT') {
  312. $this->choice = 'FULLTEXT';
  313. $this->type = '';
  314. } elseif ($this->type === 'SPATIAL') {
  315. $this->choice = 'SPATIAL';
  316. $this->type = '';
  317. } elseif ($this->nonUnique == '0') {
  318. $this->choice = 'UNIQUE';
  319. } else {
  320. $this->choice = 'INDEX';
  321. }
  322. if (isset($params['Key_block_size'])) {
  323. $this->keyBlockSize = $params['Key_block_size'];
  324. }
  325. if (! isset($params['Parser'])) {
  326. return;
  327. }
  328. $this->parser = $params['Parser'];
  329. }
  330. /**
  331. * Returns the number of columns of the index
  332. *
  333. * @return int the number of the columns
  334. */
  335. public function getColumnCount()
  336. {
  337. return count($this->columns);
  338. }
  339. /**
  340. * Returns the index comment
  341. *
  342. * @return string index comment
  343. */
  344. public function getComment()
  345. {
  346. return $this->comment;
  347. }
  348. /**
  349. * Returns index remarks
  350. *
  351. * @return string index remarks
  352. */
  353. public function getRemarks()
  354. {
  355. return $this->remarks;
  356. }
  357. /**
  358. * Return the key block size
  359. *
  360. * @return int
  361. */
  362. public function getKeyBlockSize()
  363. {
  364. return $this->keyBlockSize;
  365. }
  366. /**
  367. * Return the parser
  368. *
  369. * @return string
  370. */
  371. public function getParser()
  372. {
  373. return $this->parser;
  374. }
  375. /**
  376. * Returns concatenated remarks and comment
  377. *
  378. * @return string concatenated remarks and comment
  379. */
  380. public function getComments()
  381. {
  382. $comments = $this->getRemarks();
  383. if (strlen($comments) > 0) {
  384. $comments .= "\n";
  385. }
  386. $comments .= $this->getComment();
  387. return $comments;
  388. }
  389. /**
  390. * Returns index type (BTREE, HASH, RTREE)
  391. *
  392. * @return string index type
  393. */
  394. public function getType()
  395. {
  396. return $this->type;
  397. }
  398. /**
  399. * Returns index choice (PRIMARY, UNIQUE, INDEX, SPATIAL, FULLTEXT)
  400. *
  401. * @return string index choice
  402. */
  403. public function getChoice()
  404. {
  405. return $this->choice;
  406. }
  407. /**
  408. * Returns a lit of all index types
  409. *
  410. * @return string[] index types
  411. */
  412. public static function getIndexTypes()
  413. {
  414. return [
  415. 'BTREE',
  416. 'HASH',
  417. ];
  418. }
  419. public function hasPrimary(): bool
  420. {
  421. return (bool) self::getPrimary($this->table, $this->schema);
  422. }
  423. /**
  424. * Returns how the index is packed
  425. *
  426. * @return string how the index is packed
  427. */
  428. public function getPacked()
  429. {
  430. return $this->packed;
  431. }
  432. /**
  433. * Returns 'No' if the index is not packed,
  434. * how the index is packed if packed
  435. *
  436. * @return string
  437. */
  438. public function isPacked()
  439. {
  440. if ($this->packed === null) {
  441. return __('No');
  442. }
  443. return htmlspecialchars($this->packed);
  444. }
  445. /**
  446. * Returns integer 0 if the index cannot contain duplicates, 1 if it can
  447. *
  448. * @return int 0 if the index cannot contain duplicates, 1 if it can
  449. */
  450. public function getNonUnique()
  451. {
  452. return $this->nonUnique;
  453. }
  454. /**
  455. * Returns whether the index is a 'Unique' index
  456. *
  457. * @param bool $as_text whether to output should be in text
  458. *
  459. * @return mixed whether the index is a 'Unique' index
  460. */
  461. public function isUnique($as_text = false)
  462. {
  463. if ($as_text) {
  464. $r = [
  465. '0' => __('Yes'),
  466. '1' => __('No'),
  467. ];
  468. } else {
  469. $r = [
  470. '0' => true,
  471. '1' => false,
  472. ];
  473. }
  474. return $r[$this->nonUnique];
  475. }
  476. /**
  477. * Returns the name of the index
  478. *
  479. * @return string the name of the index
  480. */
  481. public function getName()
  482. {
  483. return $this->name;
  484. }
  485. /**
  486. * Sets the name of the index
  487. *
  488. * @param string $name index name
  489. *
  490. * @return void
  491. */
  492. public function setName($name)
  493. {
  494. $this->name = (string) $name;
  495. }
  496. /**
  497. * Returns the columns of the index
  498. *
  499. * @return IndexColumn[] the columns of the index
  500. */
  501. public function getColumns()
  502. {
  503. return $this->columns;
  504. }
  505. /**
  506. * Gets the properties in an array for comparison purposes
  507. *
  508. * @return array an array containing the properties of the index
  509. */
  510. public function getCompareData()
  511. {
  512. $data = [
  513. 'Packed' => $this->packed,
  514. 'Index_choice' => $this->choice,
  515. ];
  516. foreach ($this->columns as $column) {
  517. $data['columns'][] = $column->getCompareData();
  518. }
  519. return $data;
  520. }
  521. /**
  522. * Function to check over array of indexes and look for common problems
  523. *
  524. * @param string $table table name
  525. * @param string $schema schema name
  526. *
  527. * @return string Output HTML
  528. *
  529. * @access public
  530. */
  531. public static function findDuplicates($table, $schema)
  532. {
  533. $indexes = self::getFromTable($table, $schema);
  534. $output = '';
  535. // count($indexes) < 2:
  536. // there is no need to check if there less than two indexes
  537. if (count($indexes) < 2) {
  538. return $output;
  539. }
  540. // remove last index from stack and ...
  541. while ($while_index = array_pop($indexes)) {
  542. // ... compare with every remaining index in stack
  543. foreach ($indexes as $each_index) {
  544. if ($each_index->getCompareData() !== $while_index->getCompareData()
  545. ) {
  546. continue;
  547. }
  548. // did not find any difference
  549. // so it makes no sense to have this two equal indexes
  550. $message = Message::notice(
  551. __(
  552. 'The indexes %1$s and %2$s seem to be equal and one of them '
  553. . 'could possibly be removed.'
  554. )
  555. );
  556. $message->addParam($each_index->getName());
  557. $message->addParam($while_index->getName());
  558. $output .= $message->getDisplay();
  559. // there is no need to check any further indexes if we have already
  560. // found that this one has a duplicate
  561. continue 2;
  562. }
  563. }
  564. return $output;
  565. }
  566. }