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
21 * Class-wide storage container for indexes (caching, singleton)
25 private static $_registry = array();
28 * @var string The name of the schema
30 private $_schema = '';
33 * @var string The name of the table
38 * @var string The name of the index
47 private $_columns = array();
50 * The index method used (BTREE, SPATIAL, FULLTEXT, HASH, RTREE).
57 * The index choice (PRIMARY, UNIQUE, INDEX, SPATIAL, FULLTEXT)
61 private $_choice = '';
68 private $_remarks = '';
71 * Any comment provided for the index with a COMMENT attribute when the
76 private $_comment = '';
79 * @var integer 0 if the index cannot contain duplicates, 1 if it can.
81 private $_non_unique = 0;
84 * Indicates how the key is packed. NULL if it is not.
88 private $_packed = null;
93 * @param array $params parameters
95 public function __construct($params = array())
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;
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];
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'];
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])) {
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;
183 $key = PMA_Index
::$_registry[$schema][$table][$_each_index['Key_name']];
186 $key->addColumn($_each_index);
193 * Add column to index
195 * @param array $params column params
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
213 public function addColumns($columns)
217 if (isset($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] : '';
225 'Column_name' => $name,
226 'Sub_part' => $sub_part,
230 // coming from SHOW INDEXES
232 // $columns[][sub_part]
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]);
257 * @param array $params index details
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';
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)) {
344 $comments .= $this->getComment();
350 * Returns index type ((BTREE, SPATIAL, FULLTEXT, HASH, RTREE)
352 * @return string index type
354 public function getType()
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()
386 * Returns HTML for the index choice selector
388 * @return string HTML for the index choice selector
390 public function generateIndexSelector()
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
402 $html_options .= '<option value="' . $each_index_choice . '"'
403 . (($this->_choice
== $each_index_choice)
404 ?
' selected="selected"'
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)
444 if (null === $this->_packed
) {
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)
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()
496 * Sets the name of the index
498 * @param string $name index name
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
;
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
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>';
538 $r = '<fieldset class="index_info">';
539 $r .= '<legend id="index_header">' . __('Indexes');
540 $r .= PMA_Util
::showMySQLDocu('optimizing-database-structure');
544 if (count($indexes) < 1) {
548 $r .= PMA_Index
::findDuplicates($table, $schema);
550 $r = '<h3>' . __('Indexes') . '</h3>';
552 if (count($indexes) < 1) {
556 $r .= '<table id="table_index">';
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>';
578 foreach ($indexes as $index) {
579 $row_span = ' rowspan="' . $index->getColumnCount() . '" ';
581 $r .= '<tr class="noclick ' . ($odd_row ?
'odd' : 'even') . '">';
584 $this_params = $GLOBALS['url_params'];
585 $this_params['index'] = $index->getName();
586 $r .= '<td class="edit_index';
588 $r .= '" ' . $row_span . '>'
591 $r .= '" href="tbl_indexes.php' . PMA_URL_getCommon($this_params)
592 . '">' . PMA_Util
::getIcon('b_edit.png', __('Edit')) . '</a>'
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'
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';
624 $r .= '" href="sql.php' . PMA_URL_getCommon($this_params)
626 . PMA_Util
::getIcon('b_drop.png', __('Drop')) . '</a>'
631 $r .= '<th ' . $row_span . '>'
632 . htmlspecialchars($index->getName())
635 $r .= '<td ' . $row_span . '>'
636 . htmlspecialchars($index->getName())
639 $r .= '<td ' . $row_span . '>'
640 . htmlspecialchars($index->getType())
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() . ')';
655 . htmlspecialchars($column->getCardinality())
658 . htmlspecialchars($column->getCollation())
661 . htmlspecialchars($column->getNull(true))
664 if (PMA_MYSQL_INT_VERSION
> 50500
665 && $column->getSeqInIndex() == 1
667 $r .= '<td ' . $row_span . '>'
668 . htmlspecialchars($index->getComments()) . '</td>';
671 } // end foreach $index['Sequences']
673 $odd_row = ! $odd_row;
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()
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();
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
713 static public function findDuplicates($table, $schema)
715 $indexes = PMA_Index
::getFromTable($table, $schema);
719 // count($indexes) < 2:
720 // there is no need to check if there less than two indexes
721 if (count($indexes) < 2) {
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()) {
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
753 * Index column wrapper
755 * @package PhpMyAdmin
757 class PMA_Index_Column
760 * @var string The column 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
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.
781 private $_sub_part = null;
784 * Contains YES if the column may contain NULL.
785 * If not, the column contains NO.
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.
800 private $_cardinality = null;
805 * @param array $params an array containing the parameters of the index column
807 public function __construct($params = array())
813 * Sets parameters of the index column
815 * @param array $params an array containing the parameters of the index column
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()
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)
882 ?
(!$this->_null ||
$this->_null
== 'NO' ?
__('No') : __('Yes'))
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
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()
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
,