Translated using Weblate (Traditional Chinese)
[phpmyadmin.git] / libraries / Index.class.php
blobc57b3c4eb60c202d73fe90bc0cb801432a8f9ad3
1 <?php
2 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 /**
4 * holds the database index class
6 * @package PhpMyAdmin
7 */
8 if (! defined('PHPMYADMIN')) {
9 exit;
12 /**
13 * Index manipulation class
15 * @package PhpMyAdmin
16 * @since phpMyAdmin 3.0.0
18 class PMA_Index
20 /**
21 * Class-wide storage container for indexes (caching, singleton)
23 * @var array
25 private static $_registry = array();
27 /**
28 * @var string The name of the schema
30 private $_schema = '';
32 /**
33 * @var string The name of the table
35 private $_table = '';
37 /**
38 * @var string The name of the index
40 private $_name = '';
42 /**
43 * Columns in index
45 * @var array
47 private $_columns = array();
49 /**
50 * The index method used (BTREE, SPATIAL, FULLTEXT, HASH, RTREE).
52 * @var string
54 private $_type = '';
56 /**
57 * The index choice (PRIMARY, UNIQUE, INDEX, SPATIAL, FULLTEXT)
59 * @var string
61 private $_choice = '';
63 /**
64 * Various remarks.
66 * @var string
68 private $_remarks = '';
70 /**
71 * Any comment provided for the index with a COMMENT attribute when the
72 * index was created.
74 * @var string
76 private $_comment = '';
78 /**
79 * @var integer 0 if the index cannot contain duplicates, 1 if it can.
81 private $_non_unique = 0;
83 /**
84 * Indicates how the key is packed. NULL if it is not.
86 * @var string
88 private $_packed = null;
90 /**
91 * Constructor
93 * @param array $params parameters
95 public function __construct($params = array())
97 $this->set($params);
101 * Creates(if not already created) and returns the corresponding Index object
103 * @param string $schema database name
104 * @param string $table table name
105 * @param string $index_name index name
107 * @return object corresponding Index object
109 static public function singleton($schema, $table, $index_name = '')
111 PMA_Index::_loadIndexes($table, $schema);
112 if (! isset(PMA_Index::$_registry[$schema][$table][$index_name])) {
113 $index = new PMA_Index;
114 if (strlen($index_name)) {
115 $index->setName($index_name);
116 PMA_Index::$_registry[$schema][$table][$index->getName()] = $index;
118 return $index;
119 } else {
120 return PMA_Index::$_registry[$schema][$table][$index_name];
125 * returns an array with all indexes from the given table
127 * @param string $table table
128 * @param string $schema schema
130 * @return array array of indexes
132 static public function getFromTable($table, $schema)
134 PMA_Index::_loadIndexes($table, $schema);
136 if (isset(PMA_Index::$_registry[$schema][$table])) {
137 return PMA_Index::$_registry[$schema][$table];
138 } else {
139 return array();
144 * return primary if set, false otherwise
146 * @param string $table table
147 * @param string $schema schema
149 * @return mixed primary index or false if no one exists
151 static public function getPrimary($table, $schema)
153 PMA_Index::_loadIndexes($table, $schema);
155 if (isset(PMA_Index::$_registry[$schema][$table]['PRIMARY'])) {
156 return PMA_Index::$_registry[$schema][$table]['PRIMARY'];
157 } else {
158 return false;
163 * Load index data for table
165 * @param string $table table
166 * @param string $schema schema
168 * @return boolean whether loading was successful
170 static private function _loadIndexes($table, $schema)
172 if (isset(PMA_Index::$_registry[$schema][$table])) {
173 return true;
176 $_raw_indexes = $GLOBALS['dbi']->getTableIndexes($schema, $table);
177 foreach ($_raw_indexes as $_each_index) {
178 $_each_index['Schema'] = $schema;
179 if (! isset(PMA_Index::$_registry[$schema][$table][$_each_index['Key_name']])) {
180 $key = new PMA_Index($_each_index);
181 PMA_Index::$_registry[$schema][$table][$_each_index['Key_name']] = $key;
182 } else {
183 $key = PMA_Index::$_registry[$schema][$table][$_each_index['Key_name']];
186 $key->addColumn($_each_index);
189 return true;
193 * Add column to index
195 * @param array $params column params
197 * @return void
199 public function addColumn($params)
201 if (strlen($params['Column_name'])) {
202 $this->_columns[$params['Column_name']] = new PMA_Index_Column($params);
207 * Adds a list of columns to the index
209 * @param array $columns array containing details about the columns
211 * @return void
213 public function addColumns($columns)
215 $_columns = array();
217 if (isset($columns['names'])) {
218 // coming from form
219 // $columns[names][]
220 // $columns[sub_parts][]
221 foreach ($columns['names'] as $key => $name) {
222 $sub_part = isset($columns['sub_parts'][$key])
223 ? $columns['sub_parts'][$key] : '';
224 $_columns[] = array(
225 'Column_name' => $name,
226 'Sub_part' => $sub_part,
229 } else {
230 // coming from SHOW INDEXES
231 // $columns[][name]
232 // $columns[][sub_part]
233 // ...
234 $_columns = $columns;
237 foreach ($_columns as $column) {
238 $this->addColumn($column);
243 * Returns true if $column indexed in this index
245 * @param string $column the column
247 * @return boolean true if $column indexed in this index
249 public function hasColumn($column)
251 return isset($this->_columns[$column]);
255 * Sets index details
257 * @param array $params index details
259 * @return void
261 public function set($params)
263 if (isset($params['columns'])) {
264 $this->addColumns($params['columns']);
266 if (isset($params['Schema'])) {
267 $this->_schema = $params['Schema'];
269 if (isset($params['Table'])) {
270 $this->_table = $params['Table'];
272 if (isset($params['Key_name'])) {
273 $this->_name = $params['Key_name'];
275 if (isset($params['Index_type'])) {
276 $this->_type = $params['Index_type'];
278 if (isset($params['Comment'])) {
279 $this->_remarks = $params['Comment'];
281 if (isset($params['Index_comment'])) {
282 $this->_comment = $params['Index_comment'];
284 if (isset($params['Non_unique'])) {
285 $this->_non_unique = $params['Non_unique'];
287 if (isset($params['Packed'])) {
288 $this->_packed = $params['Packed'];
290 if ('PRIMARY' == $this->_name) {
291 $this->_choice = 'PRIMARY';
292 } elseif ('FULLTEXT' == $this->_type) {
293 $this->_choice = 'FULLTEXT';
294 } elseif ('SPATIAL' == $this->_type) {
295 $this->_choice = 'SPATIAL';
296 } elseif ('0' == $this->_non_unique) {
297 $this->_choice = 'UNIQUE';
298 } else {
299 $this->_choice = 'INDEX';
304 * Returns the number of columns of the index
306 * @return integer the number of the columns
308 public function getColumnCount()
310 return count($this->_columns);
314 * Returns the index comment
316 * @return string index comment
318 public function getComment()
320 return $this->_comment;
324 * Returns index remarks
326 * @return string index remarks
328 public function getRemarks()
330 return $this->_remarks;
334 * Returns concatenated remarks and comment
336 * @return string concatenated remarks and comment
338 public function getComments()
340 $comments = $this->getRemarks();
341 if (strlen($comments)) {
342 $comments .= "\n";
344 $comments .= $this->getComment();
346 return $comments;
350 * Returns index type ((BTREE, SPATIAL, FULLTEXT, HASH, RTREE)
352 * @return string index type
354 public function getType()
356 return $this->_type;
360 * Returns index choice (PRIMARY, UNIQUE, INDEX, SPATIAL, FULLTEXT)
362 * @return string index choice
364 public function getChoice()
366 return $this->_choice;
370 * Return a list of all index choices
372 * @return array index choices
374 static public function getIndexChoices()
376 return array(
377 'PRIMARY',
378 'INDEX',
379 'UNIQUE',
380 'SPATIAL',
381 'FULLTEXT',
386 * Returns HTML for the index choice selector
388 * @return string HTML for the index choice selector
390 public function generateIndexSelector()
392 $html_options = '';
394 foreach (PMA_Index::getIndexChoices() as $each_index_choice) {
395 if ($each_index_choice === 'PRIMARY'
396 && $this->_choice !== 'PRIMARY'
397 && PMA_Index::getPrimary($this->_table, $this->_schema)
399 // skip PRIMARY if there is already one in the table
400 continue;
402 $html_options .= '<option value="' . $each_index_choice . '"'
403 . (($this->_choice == $each_index_choice)
404 ? ' selected="selected"'
405 : '')
406 . '>'. $each_index_choice . '</option>' . "\n";
409 return $html_options;
413 * Returns how the index is packed
415 * @return string how the index is packed
417 public function getPacked()
419 return $this->_packed;
423 * Returns 'No'/false if the index is not packed,
424 * how the index is packed if packed
426 * @param boolean $as_text whether to output should be in text
428 * @return mixed how index is paked
430 public function isPacked($as_text = false)
432 if ($as_text) {
433 $r = array(
434 '0' => __('No'),
435 '1' => __('Yes'),
437 } else {
438 $r = array(
439 '0' => false,
440 '1' => true,
444 if (null === $this->_packed) {
445 return $r[0];
448 return $this->_packed;
452 * Returns integer 0 if the index cannot contain duplicates, 1 if it can
454 * @return integer 0 if the index cannot contain duplicates, 1 if it can
456 public function getNonUnique()
458 return $this->_non_unique;
462 * Returns whether the index is a 'Unique' index
464 * @param boolean $as_text whether to output should be in text
466 * @return mixed whether the index is a 'Unique' index
468 public function isUnique($as_text = false)
470 if ($as_text) {
471 $r = array(
472 '0' => __('Yes'),
473 '1' => __('No'),
475 } else {
476 $r = array(
477 '0' => true,
478 '1' => false,
482 return $r[$this->_non_unique];
486 * Returns the name of the index
488 * @return string the name of the index
490 public function getName()
492 return $this->_name;
496 * Sets the name of the index
498 * @param string $name index name
500 * @return void
502 public function setName($name)
504 $this->_name = (string) $name;
508 * Returns the columns of the index
510 * @return array the columns of the index
512 public function getColumns()
514 return $this->_columns;
518 * Show index data
520 * @param string $table The table name
521 * @param string $schema The schema name
522 * @param boolean $print_mode Whether the output is for the print mode
524 * @return array Index collection array
526 * @access public
528 static public function getView($table, $schema, $print_mode = false)
530 $indexes = PMA_Index::getFromTable($table, $schema);
532 $no_indexes_class = count($indexes) > 0 ? ' hide' : '';
533 $no_indexes = "<div class='no_indexes_defined$no_indexes_class'>";
534 $no_indexes .= PMA_Message::notice(__('No index defined!'))->getDisplay();
535 $no_indexes .= '</div>';
537 if (! $print_mode) {
538 $r = '<fieldset class="index_info">';
539 $r .= '<legend id="index_header">' . __('Indexes');
540 $r .= PMA_Util::showMySQLDocu('optimizing-database-structure');
542 $r .= '</legend>';
543 $r .= $no_indexes;
544 if (count($indexes) < 1) {
545 $r .= '</fieldset>';
546 return $r;
548 $r .= PMA_Index::findDuplicates($table, $schema);
549 } else {
550 $r = '<h3>' . __('Indexes') . '</h3>';
551 $r .= $no_indexes;
552 if (count($indexes) < 1) {
553 return $r;
556 $r .= '<table id="table_index">';
557 $r .= '<thead>';
558 $r .= '<tr>';
559 if (! $print_mode) {
560 $r .= '<th colspan="2">' . __('Action') . '</th>';
562 $r .= '<th>' . __('Keyname') . '</th>';
563 $r .= '<th>' . __('Type') . '</th>';
564 $r .= '<th>' . __('Unique') . '</th>';
565 $r .= '<th>' . __('Packed') . '</th>';
566 $r .= '<th>' . __('Column') . '</th>';
567 $r .= '<th>' . __('Cardinality') . '</th>';
568 $r .= '<th>' . __('Collation') . '</th>';
569 $r .= '<th>' . __('Null') . '</th>';
570 if (PMA_MYSQL_INT_VERSION > 50500) {
571 $r .= '<th>' . __('Comment') . '</th>';
573 $r .= '</tr>';
574 $r .= '</thead>';
575 $r .= '<tbody>';
577 $odd_row = true;
578 foreach ($indexes as $index) {
579 $row_span = ' rowspan="' . $index->getColumnCount() . '" ';
581 $r .= '<tr class="noclick ' . ($odd_row ? 'odd' : 'even') . '">';
583 if (! $print_mode) {
584 $this_params = $GLOBALS['url_params'];
585 $this_params['index'] = $index->getName();
586 $r .= '<td class="edit_index';
587 $r .= ' ajax';
588 $r .= '" ' . $row_span . '>'
589 . ' <a class="';
590 $r .= 'ajax';
591 $r .= '" href="tbl_indexes.php' . PMA_URL_getCommon($this_params)
592 . '">' . PMA_Util::getIcon('b_edit.png', __('Edit')) . '</a>'
593 . '</td>' . "\n";
594 $this_params = $GLOBALS['url_params'];
595 if ($index->getName() == 'PRIMARY') {
596 $this_params['sql_query'] = 'ALTER TABLE '
597 . PMA_Util::backquote($table)
598 . ' DROP PRIMARY KEY;';
599 $this_params['message_to_show']
600 = __('The primary key has been dropped');
601 $js_msg = PMA_jsFormat(
602 'ALTER TABLE ' . $table . ' DROP PRIMARY KEY'
604 } else {
605 $this_params['sql_query'] = 'ALTER TABLE '
606 . PMA_Util::backquote($table) . ' DROP INDEX '
607 . PMA_Util::backquote($index->getName()) . ';';
608 $this_params['message_to_show'] = sprintf(
609 __('Index %s has been dropped.'), $index->getName()
612 $js_msg = PMA_jsFormat(
613 'ALTER TABLE ' . $table . ' DROP INDEX '
614 . $index->getName() . ';'
619 $r .= '<td ' . $row_span . '>';
620 $r .= '<input type="hidden" class="drop_primary_key_index_msg"'
621 . ' value="' . $js_msg . '" />';
622 $r .= ' <a class="drop_primary_key_index_anchor';
623 $r .= ' ajax';
624 $r .= '" href="sql.php' . PMA_URL_getCommon($this_params)
625 . '" >'
626 . PMA_Util::getIcon('b_drop.png', __('Drop')) . '</a>'
627 . '</td>' . "\n";
630 if (! $print_mode) {
631 $r .= '<th ' . $row_span . '>'
632 . htmlspecialchars($index->getName())
633 . '</th>';
634 } else {
635 $r .= '<td ' . $row_span . '>'
636 . htmlspecialchars($index->getName())
637 . '</td>';
639 $r .= '<td ' . $row_span . '>'
640 . htmlspecialchars($index->getType())
641 . '</td>';
642 $r .= '<td ' . $row_span . '>' . $index->isUnique(true) . '</td>';
643 $r .= '<td ' . $row_span . '>' . $index->isPacked(true) . '</td>';
645 foreach ($index->getColumns() as $column) {
646 if ($column->getSeqInIndex() > 1) {
647 $r .= '<tr class="noclick ' . ($odd_row ? 'odd' : 'even') . '">';
649 $r .= '<td>' . htmlspecialchars($column->getName());
650 if ($column->getSubPart()) {
651 $r .= ' (' . $column->getSubPart() . ')';
653 $r .= '</td>';
654 $r .= '<td>'
655 . htmlspecialchars($column->getCardinality())
656 . '</td>';
657 $r .= '<td>'
658 . htmlspecialchars($column->getCollation())
659 . '</td>';
660 $r .= '<td>'
661 . htmlspecialchars($column->getNull(true))
662 . '</td>';
664 if (PMA_MYSQL_INT_VERSION > 50500
665 && $column->getSeqInIndex() == 1
667 $r .= '<td ' . $row_span . '>'
668 . htmlspecialchars($index->getComments()) . '</td>';
670 $r .= '</tr>';
671 } // end foreach $index['Sequences']
673 $odd_row = ! $odd_row;
674 } // end while
675 $r .= '</tbody>';
676 $r .= '</table>';
677 if (! $print_mode) {
678 $r .= '</fieldset>';
681 return $r;
685 * Gets the properties in an array for comparison purposes
687 * @return array an array containing the properties of the index
689 public function getCompareData()
691 $data = array(
692 // 'Non_unique' => $this->_non_unique,
693 'Packed' => $this->_packed,
694 'Index_type' => $this->_type,
697 foreach ($this->_columns as $column) {
698 $data['columns'][] = $column->getCompareData();
701 return $data;
705 * Function to check over array of indexes and look for common problems
707 * @param string $table table name
708 * @param string $schema schema name
710 * @return string Output HTML
711 * @access public
713 static public function findDuplicates($table, $schema)
715 $indexes = PMA_Index::getFromTable($table, $schema);
717 $output = '';
719 // count($indexes) < 2:
720 // there is no need to check if there less than two indexes
721 if (count($indexes) < 2) {
722 return $output;
725 // remove last index from stack and ...
726 while ($while_index = array_pop($indexes)) {
727 // ... compare with every remaining index in stack
728 foreach ($indexes as $each_index) {
729 if ($each_index->getCompareData() !== $while_index->getCompareData()) {
730 continue;
733 // did not find any difference
734 // so it makes no sense to have this two equal indexes
736 $message = PMA_Message::notice(
737 __('The indexes %1$s and %2$s seem to be equal and one of them could possibly be removed.')
739 $message->addParam($each_index->getName());
740 $message->addParam($while_index->getName());
741 $output .= $message->getDisplay();
743 // there is no need to check any further indexes if we have already
744 // found that this one has a duplicate
745 continue 2;
748 return $output;
753 * Index column wrapper
755 * @package PhpMyAdmin
757 class PMA_Index_Column
760 * @var string The column name
762 private $_name = '';
765 * @var integer The column sequence number in the index, starting with 1.
767 private $_seq_in_index = 1;
770 * @var string How the column is sorted in the index. “A” (Ascending) or
771 * NULL (Not sorted)
773 private $_collation = null;
776 * The number of indexed characters if the column is only partly indexed,
777 * NULL if the entire column is indexed.
779 * @var integer
781 private $_sub_part = null;
784 * Contains YES if the column may contain NULL.
785 * If not, the column contains NO.
787 * @var string
789 private $_null = '';
792 * An estimate of the number of unique values in the index. This is updated
793 * by running ANALYZE TABLE or myisamchk -a. Cardinality is counted based on
794 * statistics stored as integers, so the value is not necessarily exact even
795 * for small tables. The higher the cardinality, the greater the chance that
796 * MySQL uses the index when doing joins.
798 * @var integer
800 private $_cardinality = null;
803 * Constructor
805 * @param array $params an array containing the parameters of the index column
807 public function __construct($params = array())
809 $this->set($params);
813 * Sets parameters of the index column
815 * @param array $params an array containing the parameters of the index column
817 * @return void
819 public function set($params)
821 if (isset($params['Column_name'])) {
822 $this->_name = $params['Column_name'];
824 if (isset($params['Seq_in_index'])) {
825 $this->_seq_in_index = $params['Seq_in_index'];
827 if (isset($params['Collation'])) {
828 $this->_collation = $params['Collation'];
830 if (isset($params['Cardinality'])) {
831 $this->_cardinality = $params['Cardinality'];
833 if (isset($params['Sub_part'])) {
834 $this->_sub_part = $params['Sub_part'];
836 if (isset($params['Null'])) {
837 $this->_null = $params['Null'];
842 * Returns the column name
844 * @return string column name
846 public function getName()
848 return $this->_name;
852 * Return the column collation
854 * @return string column collation
856 public function getCollation()
858 return $this->_collation;
862 * Returns the cardinality of the column
864 * @return int cardinality of the column
866 public function getCardinality()
868 return $this->_cardinality;
872 * Returns whether the column is nullable
874 * @param boolean $as_text whether to returned the string representation
876 * @return mixed nullability of the column. True/false or Yes/No depending
877 * on the value of the $as_text parameter
879 public function getNull($as_text = false)
881 return $as_text
882 ? (!$this->_null || $this->_null == 'NO' ? __('No') : __('Yes'))
883 : $this->_null;
887 * Returns the sequence number of the column in the index
889 * @return int sequence number of the column in the index
891 public function getSeqInIndex()
893 return $this->_seq_in_index;
897 * Returns the number of indexed characters if the column is only
898 * partly indexed
900 * @return int the number of indexed characters
902 public function getSubPart()
904 return $this->_sub_part;
908 * Gets the properties in an array for comparison purposes
910 * @return array an array containing the properties of the index column
912 public function getCompareData()
914 return array(
915 'Column_name' => $this->_name,
916 'Seq_in_index' => $this->_seq_in_index,
917 'Collation' => $this->_collation,
918 'Sub_part' => $this->_sub_part,
919 'Null' => $this->_null,