2 /* vim: set expandtab sw=4 ts=4 sts=4: */
4 * holds the database index class
8 if (! defined('PHPMYADMIN')) {
13 * Index manipulation class
16 * @since phpMyAdmin 3.0.0
27 * Class-wide storage container for indexes (caching, singleton)
31 private static $_registry = array();
34 * @var string The name of the schema
36 private $_schema = '';
39 * @var string The name of the table
44 * @var string The name of the index
53 private $_columns = array();
56 * The index method used (BTREE, HASH, RTREE).
63 * The index choice (PRIMARY, UNIQUE, INDEX, SPATIAL, FULLTEXT)
67 private $_choice = '';
74 private $_remarks = '';
77 * Any comment provided for the index with a COMMENT attribute when the
82 private $_comment = '';
85 * @var integer 0 if the index cannot contain duplicates, 1 if it can.
87 private $_non_unique = 0;
90 * Indicates how the key is packed. NULL if it is not.
94 private $_packed = null;
97 * Block size for the index
101 private $_key_block_size = null;
104 * Parser option for the index
108 private $_parser = null;
113 * @param array $params parameters
115 public function __construct($params = array())
121 * Creates(if not already created) and returns the corresponding Index object
123 * @param string $schema database name
124 * @param string $table table name
125 * @param string $index_name index name
127 * @return PMA_Index corresponding Index object
129 static public function singleton($schema, $table, $index_name = '')
131 PMA_Index
::_loadIndexes($table, $schema);
132 if (! isset(PMA_Index
::$_registry[$schema][$table][$index_name])) {
133 $index = new PMA_Index
;
134 if (/*overload*/mb_strlen($index_name)) {
135 $index->setName($index_name);
136 PMA_Index
::$_registry[$schema][$table][$index->getName()] = $index;
140 return PMA_Index
::$_registry[$schema][$table][$index_name];
145 * returns an array with all indexes from the given table
147 * @param string $table table
148 * @param string $schema schema
150 * @return PMA_Index[] array of indexes
152 static public function getFromTable($table, $schema)
154 PMA_Index
::_loadIndexes($table, $schema);
156 if (isset(PMA_Index
::$_registry[$schema][$table])) {
157 return PMA_Index
::$_registry[$schema][$table];
164 * Returns an array with all indexes from the given table of the requested types
166 * @param string $table table
167 * @param string $schema schema
168 * @param int $choices choices
170 * @return PMA_Index[] array of indexes
172 static public function getFromTableByChoice($table, $schema, $choices = 31)
175 foreach (self
::getFromTable($table, $schema) as $index) {
176 if (($choices & PMA_Index
::PRIMARY
)
177 && $index->getChoice() == 'PRIMARY'
181 if (($choices & PMA_Index
::UNIQUE
)
182 && $index->getChoice() == 'UNIQUE'
186 if (($choices & PMA_Index
::INDEX
)
187 && $index->getChoice() == 'INDEX'
191 if (($choices & PMA_Index
::SPATIAL
)
192 && $index->getChoice() == 'SPATIAL'
196 if (($choices & PMA_Index
::FULLTEXT
)
197 && $index->getChoice() == 'FULLTEXT'
206 * return primary if set, false otherwise
208 * @param string $table table
209 * @param string $schema schema
211 * @return mixed primary index or false if no one exists
213 static public function getPrimary($table, $schema)
215 PMA_Index
::_loadIndexes($table, $schema);
217 if (isset(PMA_Index
::$_registry[$schema][$table]['PRIMARY'])) {
218 return PMA_Index
::$_registry[$schema][$table]['PRIMARY'];
225 * Load index data for table
227 * @param string $table table
228 * @param string $schema schema
230 * @return boolean whether loading was successful
232 static private function _loadIndexes($table, $schema)
234 if (isset(PMA_Index
::$_registry[$schema][$table])) {
238 $_raw_indexes = $GLOBALS['dbi']->getTableIndexes($schema, $table);
239 foreach ($_raw_indexes as $_each_index) {
240 $_each_index['Schema'] = $schema;
241 $keyName = $_each_index['Key_name'];
242 if (! isset(PMA_Index
::$_registry[$schema][$table][$keyName])) {
243 $key = new PMA_Index($_each_index);
244 PMA_Index
::$_registry[$schema][$table][$keyName] = $key;
246 $key = PMA_Index
::$_registry[$schema][$table][$keyName];
249 $key->addColumn($_each_index);
256 * Add column to index
258 * @param array $params column params
262 public function addColumn($params)
264 if (isset($params['Column_name'])
265 && /*overload*/mb_strlen($params['Column_name'])
267 $this->_columns
[$params['Column_name']] = new PMA_Index_Column($params);
272 * Adds a list of columns to the index
274 * @param array $columns array containing details about the columns
278 public function addColumns($columns)
282 if (isset($columns['names'])) {
285 // $columns[sub_parts][]
286 foreach ($columns['names'] as $key => $name) {
287 $sub_part = isset($columns['sub_parts'][$key])
288 ?
$columns['sub_parts'][$key] : '';
290 'Column_name' => $name,
291 'Sub_part' => $sub_part,
295 // coming from SHOW INDEXES
297 // $columns[][sub_part]
299 $_columns = $columns;
302 foreach ($_columns as $column) {
303 $this->addColumn($column);
308 * Returns true if $column indexed in this index
310 * @param string $column the column
312 * @return boolean true if $column indexed in this index
314 public function hasColumn($column)
316 return isset($this->_columns
[$column]);
322 * @param array $params index details
326 public function set($params)
328 if (isset($params['columns'])) {
329 $this->addColumns($params['columns']);
331 if (isset($params['Schema'])) {
332 $this->_schema
= $params['Schema'];
334 if (isset($params['Table'])) {
335 $this->_table
= $params['Table'];
337 if (isset($params['Key_name'])) {
338 $this->_name
= $params['Key_name'];
340 if (isset($params['Index_type'])) {
341 $this->_type
= $params['Index_type'];
343 if (isset($params['Comment'])) {
344 $this->_remarks
= $params['Comment'];
346 if (isset($params['Index_comment'])) {
347 $this->_comment
= $params['Index_comment'];
349 if (isset($params['Non_unique'])) {
350 $this->_non_unique
= $params['Non_unique'];
352 if (isset($params['Packed'])) {
353 $this->_packed
= $params['Packed'];
355 if (isset($params['Index_choice'])) {
356 $this->_choice
= $params['Index_choice'];
358 if ('PRIMARY' == $this->_name
) {
359 $this->_choice
= 'PRIMARY';
360 } elseif ('FULLTEXT' == $this->_type
) {
361 $this->_choice
= 'FULLTEXT';
363 } elseif ('SPATIAL' == $this->_type
) {
364 $this->_choice
= 'SPATIAL';
366 } elseif ('0' == $this->_non_unique
) {
367 $this->_choice
= 'UNIQUE';
369 $this->_choice
= 'INDEX';
372 if (isset($params['Key_block_size'])) {
373 $this->_key_block_size
= $params['Key_block_size'];
375 if (isset($params['Parser'])) {
376 $this->_parser
= $params['Parser'];
381 * Returns the number of columns of the index
383 * @return integer the number of the columns
385 public function getColumnCount()
387 return count($this->_columns
);
391 * Returns the index comment
393 * @return string index comment
395 public function getComment()
397 return $this->_comment
;
401 * Returns index remarks
403 * @return string index remarks
405 public function getRemarks()
407 return $this->_remarks
;
411 * Return the key block size
415 public function getKeyBlockSize()
417 return $this->_key_block_size
;
425 public function getParser()
427 return $this->_parser
;
431 * Returns concatenated remarks and comment
433 * @return string concatenated remarks and comment
435 public function getComments()
437 $comments = $this->getRemarks();
438 if (/*overload*/mb_strlen($comments)) {
441 $comments .= $this->getComment();
447 * Returns index type (BTREE, HASH, RTREE)
449 * @return string index type
451 public function getType()
457 * Returns index choice (PRIMARY, UNIQUE, INDEX, SPATIAL, FULLTEXT)
459 * @return string index choice
461 public function getChoice()
463 return $this->_choice
;
467 * Return a list of all index choices
469 * @return string[] index choices
471 static public function getIndexChoices()
483 * Returns a lit of all index types
485 * @return string[] index types
487 static public function getIndexTypes()
496 * Returns HTML for the index choice selector
498 * @param boolean $edit_table whether this is table editing
500 * @return string HTML for the index choice selector
502 public function generateIndexChoiceSelector($edit_table)
504 $html_options = '<select name="index[Index_choice]"'
505 . ' id="select_index_choice" '
506 . ($edit_table ?
'disabled="disabled"' : '') . '>';
508 foreach (PMA_Index
::getIndexChoices() as $each_index_choice) {
509 if ($each_index_choice === 'PRIMARY'
510 && $this->_choice
!== 'PRIMARY'
511 && PMA_Index
::getPrimary($this->_table
, $this->_schema
)
513 // skip PRIMARY if there is already one in the table
516 $html_options .= '<option value="' . $each_index_choice . '"'
517 . (($this->_choice
== $each_index_choice)
518 ?
' selected="selected"'
520 . '>' . $each_index_choice . '</option>' . "\n";
522 $html_options .= '</select>';
524 return $html_options;
528 * Returns HTML for the index type selector
530 * @return string HTML for the index type selector
532 public function generateIndexTypeSelector()
534 $types = array("" => "--");
535 foreach (PMA_Index
::getIndexTypes() as $type) {
536 $types[$type] = $type;
539 return PMA_Util
::getDropdown(
540 "index[Index_type]", $types,
541 $this->_type
, "select_index_type"
546 * Returns how the index is packed
548 * @return string how the index is packed
550 public function getPacked()
552 return $this->_packed
;
556 * Returns 'No'/false if the index is not packed,
557 * how the index is packed if packed
559 * @param boolean $as_text whether to output should be in text
561 * @return mixed how index is packed
563 public function isPacked($as_text = false)
577 if (null === $this->_packed
) {
581 return $this->_packed
;
585 * Returns integer 0 if the index cannot contain duplicates, 1 if it can
587 * @return integer 0 if the index cannot contain duplicates, 1 if it can
589 public function getNonUnique()
591 return $this->_non_unique
;
595 * Returns whether the index is a 'Unique' index
597 * @param boolean $as_text whether to output should be in text
599 * @return mixed whether the index is a 'Unique' index
601 public function isUnique($as_text = false)
615 return $r[$this->_non_unique
];
619 * Returns the name of the index
621 * @return string the name of the index
623 public function getName()
629 * Sets the name of the index
631 * @param string $name index name
635 public function setName($name)
637 $this->_name
= (string) $name;
641 * Returns the columns of the index
643 * @return PMA_Index_Column[] the columns of the index
645 public function getColumns()
647 return $this->_columns
;
653 * @param string $table The table name
654 * @param string $schema The schema name
655 * @param boolean $print_mode Whether the output is for the print mode
657 * @return string HTML for showing index
661 static public function getHtmlForIndexes($table, $schema, $print_mode = false)
663 $indexes = PMA_Index
::getFromTable($table, $schema);
665 $no_indexes_class = count($indexes) > 0 ?
' hide' : '';
666 $no_indexes = "<div class='no_indexes_defined$no_indexes_class'>";
667 $no_indexes .= PMA_Message
::notice(__('No index defined!'))->getDisplay();
668 $no_indexes .= '</div>';
671 $r = '<fieldset class="index_info">';
672 $r .= '<legend id="index_header">' . __('Indexes');
673 $r .= PMA_Util
::showMySQLDocu('optimizing-database-structure');
677 if (count($indexes) < 1) {
681 $r .= PMA_Index
::findDuplicates($table, $schema);
683 $r = '<h3>' . __('Indexes') . '</h3>';
685 if (count($indexes) < 1) {
689 $r .= '<table id="table_index">';
693 $r .= '<th colspan="2" class="print_ignore">' . __('Action') . '</th>';
695 $r .= '<th>' . __('Keyname') . '</th>';
696 $r .= '<th>' . __('Type') . '</th>';
697 $r .= '<th>' . __('Unique') . '</th>';
698 $r .= '<th>' . __('Packed') . '</th>';
699 $r .= '<th>' . __('Column') . '</th>';
700 $r .= '<th>' . __('Cardinality') . '</th>';
701 $r .= '<th>' . __('Collation') . '</th>';
702 $r .= '<th>' . __('Null') . '</th>';
703 $r .= '<th>' . __('Comment') . '</th>';
709 foreach ($indexes as $index) {
710 $row_span = ' rowspan="' . $index->getColumnCount() . '" ';
712 $r .= '<tr class="noclick ' . ($odd_row ?
'odd' : 'even') . '">';
715 $this_params = $GLOBALS['url_params'];
716 $this_params['index'] = $index->getName();
717 $r .= '<td class="edit_index print_ignore';
719 $r .= '" ' . $row_span . '>'
722 $r .= '" href="tbl_indexes.php' . PMA_URL_getCommon($this_params)
723 . '">' . PMA_Util
::getIcon('b_edit.png', __('Edit')) . '</a>'
725 $this_params = $GLOBALS['url_params'];
726 if ($index->getName() == 'PRIMARY') {
727 $this_params['sql_query'] = 'ALTER TABLE '
728 . PMA_Util
::backquote($table)
729 . ' DROP PRIMARY KEY;';
730 $this_params['message_to_show']
731 = __('The primary key has been dropped.');
732 $js_msg = PMA_jsFormat(
733 'ALTER TABLE ' . $table . ' DROP PRIMARY KEY'
736 $this_params['sql_query'] = 'ALTER TABLE '
737 . PMA_Util
::backquote($table) . ' DROP INDEX '
738 . PMA_Util
::backquote($index->getName()) . ';';
739 $this_params['message_to_show'] = sprintf(
740 __('Index %s has been dropped.'), $index->getName()
743 $js_msg = PMA_jsFormat(
744 'ALTER TABLE ' . $table . ' DROP INDEX '
745 . $index->getName() . ';'
750 $r .= '<td ' . $row_span . ' class="print_ignore">';
751 $r .= '<input type="hidden" class="drop_primary_key_index_msg"'
752 . ' value="' . $js_msg . '" />';
753 $r .= ' <a class="drop_primary_key_index_anchor';
755 $r .= '" href="sql.php' . PMA_URL_getCommon($this_params)
757 . PMA_Util
::getIcon('b_drop.png', __('Drop')) . '</a>'
762 $r .= '<th ' . $row_span . '>'
763 . htmlspecialchars($index->getName())
766 $r .= '<td ' . $row_span . '>'
767 . htmlspecialchars($index->getName())
770 $r .= '<td ' . $row_span . '>';
771 $type = $index->getType();
772 if (! empty($type)) {
773 $r .= htmlspecialchars($type);
775 $r .= htmlspecialchars($index->getChoice());
778 $r .= '<td ' . $row_span . '>' . $index->isUnique(true) . '</td>';
779 $r .= '<td ' . $row_span . '>' . $index->isPacked(true) . '</td>';
781 foreach ($index->getColumns() as $column) {
782 if ($column->getSeqInIndex() > 1) {
783 $r .= '<tr class="noclick ' . ($odd_row ?
'odd' : 'even') . '">';
785 $r .= '<td>' . htmlspecialchars($column->getName());
786 if ($column->getSubPart()) {
787 $r .= ' (' . $column->getSubPart() . ')';
791 . htmlspecialchars($column->getCardinality())
794 . htmlspecialchars($column->getCollation())
797 . htmlspecialchars($column->getNull(true))
800 if ($column->getSeqInIndex() == 1
802 $r .= '<td ' . $row_span . '>'
803 . htmlspecialchars($index->getComments()) . '</td>';
806 } // end foreach $index['Sequences']
808 $odd_row = ! $odd_row;
820 * Gets the properties in an array for comparison purposes
822 * @return array an array containing the properties of the index
824 public function getCompareData()
827 // 'Non_unique' => $this->_non_unique,
828 'Packed' => $this->_packed
,
829 'Index_choice' => $this->_choice
,
832 foreach ($this->_columns
as $column) {
833 $data['columns'][] = $column->getCompareData();
840 * Function to check over array of indexes and look for common problems
842 * @param string $table table name
843 * @param string $schema schema name
845 * @return string Output HTML
848 static public function findDuplicates($table, $schema)
850 $indexes = PMA_Index
::getFromTable($table, $schema);
854 // count($indexes) < 2:
855 // there is no need to check if there less than two indexes
856 if (count($indexes) < 2) {
860 // remove last index from stack and ...
861 while ($while_index = array_pop($indexes)) {
862 // ... compare with every remaining index in stack
863 foreach ($indexes as $each_index) {
864 if ($each_index->getCompareData() !== $while_index->getCompareData()
869 // did not find any difference
870 // so it makes no sense to have this two equal indexes
872 $message = PMA_Message
::notice(
874 'The indexes %1$s and %2$s seem to be equal and one of them '
875 . 'could possibly be removed.'
878 $message->addParam($each_index->getName());
879 $message->addParam($while_index->getName());
880 $output .= $message->getDisplay();
882 // there is no need to check any further indexes if we have already
883 // found that this one has a duplicate
892 * Index column wrapper
894 * @package PhpMyAdmin
896 class PMA_Index_Column
899 * @var string The column name
904 * @var integer The column sequence number in the index, starting with 1.
906 private $_seq_in_index = 1;
909 * @var string How the column is sorted in the index. “A” (Ascending) or
912 private $_collation = null;
915 * The number of indexed characters if the column is only partly indexed,
916 * NULL if the entire column is indexed.
920 private $_sub_part = null;
923 * Contains YES if the column may contain NULL.
924 * If not, the column contains NO.
931 * An estimate of the number of unique values in the index. This is updated
932 * by running ANALYZE TABLE or myisamchk -a. Cardinality is counted based on
933 * statistics stored as integers, so the value is not necessarily exact even
934 * for small tables. The higher the cardinality, the greater the chance that
935 * MySQL uses the index when doing joins.
939 private $_cardinality = null;
944 * @param array $params an array containing the parameters of the index column
946 public function __construct($params = array())
952 * Sets parameters of the index column
954 * @param array $params an array containing the parameters of the index column
958 public function set($params)
960 if (isset($params['Column_name'])) {
961 $this->_name
= $params['Column_name'];
963 if (isset($params['Seq_in_index'])) {
964 $this->_seq_in_index
= $params['Seq_in_index'];
966 if (isset($params['Collation'])) {
967 $this->_collation
= $params['Collation'];
969 if (isset($params['Cardinality'])) {
970 $this->_cardinality
= $params['Cardinality'];
972 if (isset($params['Sub_part'])) {
973 $this->_sub_part
= $params['Sub_part'];
975 if (isset($params['Null'])) {
976 $this->_null
= $params['Null'];
981 * Returns the column name
983 * @return string column name
985 public function getName()
991 * Return the column collation
993 * @return string column collation
995 public function getCollation()
997 return $this->_collation
;
1001 * Returns the cardinality of the column
1003 * @return int cardinality of the column
1005 public function getCardinality()
1007 return $this->_cardinality
;
1011 * Returns whether the column is nullable
1013 * @param boolean $as_text whether to returned the string representation
1015 * @return mixed nullability of the column. True/false or Yes/No depending
1016 * on the value of the $as_text parameter
1018 public function getNull($as_text = false)
1021 ?
(!$this->_null ||
$this->_null
== 'NO' ?
__('No') : __('Yes'))
1026 * Returns the sequence number of the column in the index
1028 * @return int sequence number of the column in the index
1030 public function getSeqInIndex()
1032 return $this->_seq_in_index
;
1036 * Returns the number of indexed characters if the column is only
1039 * @return int the number of indexed characters
1041 public function getSubPart()
1043 return $this->_sub_part
;
1047 * Gets the properties in an array for comparison purposes
1049 * @return array an array containing the properties of the index column
1051 public function getCompareData()
1054 'Column_name' => $this->_name
,
1055 'Seq_in_index' => $this->_seq_in_index
,
1056 'Collation' => $this->_collation
,
1057 'Sub_part' => $this->_sub_part
,
1058 'Null' => $this->_null
,