2 /* vim: set expandtab sw=4 ts=4 sts=4: */
4 * holds the database index class
8 if (! defined('PHPMYADMIN')) {
15 * @since phpMyAdmin 3.0.0
20 * Class-wide storage container for indexes (caching, singleton)
24 private static $_registry = array();
27 * @var string The name of the schema
29 private $_schema = '';
32 * @var string The name of the table
37 * @var string The name of the index
46 private $_columns = array();
49 * The index method used (BTREE, SPATIAL, FULLTEXT, HASH, RTREE).
56 * The index choice (PRIMARY, UNIQUE, INDEX, SPATIAL, FULLTEXT)
60 private $_choice = '';
67 private $_remarks = '';
70 * Any comment provided for the index with a COMMENT attribute when the
75 private $_comment = '';
78 * @var integer 0 if the index cannot contain duplicates, 1 if it can.
80 private $_non_unique = 0;
83 * Indicates how the key is packed. NULL if it is not.
87 private $_packed = null;
92 * @param array $params parameters
94 public function __construct($params = array())
100 * Creates(if not already created) and returns the corresponding Index object
102 * @param string $schema database name
103 * @param string $table table name
104 * @param string $index_name index name
106 * @return object corresponding Index object
108 static public function singleton($schema, $table, $index_name = '')
110 PMA_Index
::_loadIndexes($table, $schema);
111 if (! isset(PMA_Index
::$_registry[$schema][$table][$index_name])) {
112 $index = new PMA_Index
;
113 if (strlen($index_name)) {
114 $index->setName($index_name);
115 PMA_Index
::$_registry[$schema][$table][$index->getName()] = $index;
119 return PMA_Index
::$_registry[$schema][$table][$index_name];
124 * returns an array with all indexes from the given table
126 * @param string $table table
127 * @param string $schema schema
129 * @return array array of indexes
131 static public function getFromTable($table, $schema)
133 PMA_Index
::_loadIndexes($table, $schema);
135 if (isset(PMA_Index
::$_registry[$schema][$table])) {
136 return PMA_Index
::$_registry[$schema][$table];
143 * return primary if set, false otherwise
145 * @param string $table table
146 * @param string $schema schema
148 * @return mixed primary index or false if no one exists
150 static public function getPrimary($table, $schema)
152 PMA_Index
::_loadIndexes($table, $schema);
154 if (isset(PMA_Index
::$_registry[$schema][$table]['PRIMARY'])) {
155 return PMA_Index
::$_registry[$schema][$table]['PRIMARY'];
162 * Load index data for table
164 * @param string $table table
165 * @param string $schema schema
167 * @return boolean whether loading was successful
169 static private function _loadIndexes($table, $schema)
171 if (isset(PMA_Index
::$_registry[$schema][$table])) {
175 $_raw_indexes = PMA_DBI_get_table_indexes($schema, $table);
176 foreach ($_raw_indexes as $_each_index) {
177 $_each_index['Schema'] = $schema;
178 if (! isset(PMA_Index
::$_registry[$schema][$table][$_each_index['Key_name']])) {
179 $key = new PMA_Index($_each_index);
180 PMA_Index
::$_registry[$schema][$table][$_each_index['Key_name']] = $key;
182 $key = PMA_Index
::$_registry[$schema][$table][$_each_index['Key_name']];
185 $key->addColumn($_each_index);
192 * Add column to index
194 * @param array $params column params
198 public function addColumn($params)
200 if (strlen($params['Column_name'])) {
201 $this->_columns
[$params['Column_name']] = new PMA_Index_Column($params);
206 * Adds a list of columns to the index
208 * @param array $columns array containing details about the columns
212 public function addColumns($columns)
216 if (isset($columns['names'])) {
219 // $columns[sub_parts][]
220 foreach ($columns['names'] as $key => $name) {
221 $sub_part = isset($columns['sub_parts'][$key])
222 ?
$columns['sub_parts'][$key] : '';
224 'Column_name' => $name,
225 'Sub_part' => $sub_part,
229 // coming from SHOW INDEXES
231 // $columns[][sub_part]
233 $_columns = $columns;
236 foreach ($_columns as $column) {
237 $this->addColumn($column);
242 * Returns true if $column indexed in this index
244 * @param string $column the column
246 * @return boolean true if $column indexed in this index
248 public function hasColumn($column)
250 return isset($this->_columns
[$column]);
256 * @param array $params index details
260 public function set($params)
262 if (isset($params['columns'])) {
263 $this->addColumns($params['columns']);
265 if (isset($params['Schema'])) {
266 $this->_schema
= $params['Schema'];
268 if (isset($params['Table'])) {
269 $this->_table
= $params['Table'];
271 if (isset($params['Key_name'])) {
272 $this->_name
= $params['Key_name'];
274 if (isset($params['Index_type'])) {
275 $this->_type
= $params['Index_type'];
277 if (isset($params['Comment'])) {
278 $this->_remarks
= $params['Comment'];
280 if (isset($params['Index_comment'])) {
281 $this->_comment
= $params['Index_comment'];
283 if (isset($params['Non_unique'])) {
284 $this->_non_unique
= $params['Non_unique'];
286 if (isset($params['Packed'])) {
287 $this->_packed
= $params['Packed'];
289 if ('PRIMARY' == $this->_name
) {
290 $this->_choice
= 'PRIMARY';
291 } elseif ('FULLTEXT' == $this->_type
) {
292 $this->_choice
= 'FULLTEXT';
293 } elseif ('SPATIAL' == $this->_type
) {
294 $this->_choice
= 'SPATIAL';
295 } elseif ('0' == $this->_non_unique
) {
296 $this->_choice
= 'UNIQUE';
298 $this->_choice
= 'INDEX';
303 * Returns the number of columns of the index
305 * @return integer the number of the columns
307 public function getColumnCount()
309 return count($this->_columns
);
313 * Returns the index comment
315 * @return string index comment
317 public function getComment()
319 return $this->_comment
;
323 * Returns index remarks
325 * @return string index remarks
327 public function getRemarks()
329 return $this->_remarks
;
333 * Returns concatenated remarks and comment
335 * @return string concatenated remarks and comment
337 public function getComments()
339 $comments = $this->getRemarks();
340 if (strlen($comments)) {
343 $comments .= $this->getComment();
349 * Returns index type ((BTREE, SPATIAL, FULLTEXT, HASH, RTREE)
351 * @return string index type
353 public function getType()
359 * Returns index choice (PRIMARY, UNIQUE, INDEX, SPATIAL, FULLTEXT)
361 * @return index choice
363 public function getChoice()
365 return $this->_choice
;
369 * Return a list of all index choices
371 * @return array index choices
373 static public function getIndexChoices()
385 * Returns HTML for the index choice selector
387 * @return string HTML for the index choice selector
389 public function generateIndexSelector()
393 foreach (PMA_Index
::getIndexChoices() as $each_index_choice) {
394 if ($each_index_choice === 'PRIMARY'
395 && $this->_choice
!== 'PRIMARY'
396 && PMA_Index
::getPrimary($this->_table
, $this->_schema
)
398 // skip PRIMARY if there is already one in the table
401 $html_options .= '<option value="' . $each_index_choice . '"'
402 . (($this->_choice
== $each_index_choice) ?
' selected="selected"' : '')
403 . '>'. $each_index_choice . '</option>' . "\n";
406 return $html_options;
410 * Returns how the index is packed
412 * @return string how the index is packed
414 public function getPacked()
416 return $this->_packed
;
420 * Returns 'No'/false if the index is not packed,
421 * how the index is packed if packed
423 * @param boolean $as_text whether to output should be in text
425 * @return mixed how index is paked
427 public function isPacked($as_text = false)
441 if (null === $this->_packed
) {
445 return $this->_packed
;
449 * Returns integer 0 if the index cannot contain duplicates, 1 if it can
451 * @return integer 0 if the index cannot contain duplicates, 1 if it can
453 public function getNonUnique()
455 return $this->_non_unique
;
459 * Returns whether the index is a 'Unique' index
461 * @param boolean $as_text whether to output should be in text
463 * @return mixed whether the index is a 'Unique' index
465 public function isUnique($as_text = false)
479 return $r[$this->_non_unique
];
483 * Returns the name of the index
485 * @return string the name of the index
487 public function getName()
493 * Sets the name of the index
495 * @param string $name index name
499 public function setName($name)
501 $this->_name
= (string) $name;
505 * Returns the columns of the index
507 * @return array the columns of the index
509 public function getColumns()
511 return $this->_columns
;
517 * @param string $table The table name
518 * @param string $schema The schema name
519 * @param boolean $print_mode Whether the output is for the print mode
521 * @return array Index collection array
525 static public function getView($table, $schema, $print_mode = false)
527 $indexes = PMA_Index
::getFromTable($table, $schema);
529 $no_indexes_class = count($indexes) > 0 ?
' hide' : '';
530 $no_indexes = "<div class='no_indexes_defined$no_indexes_class'>";
531 $no_indexes .= PMA_Message
::notice(__('No index defined!'))->getDisplay();
532 $no_indexes .= '</div>';
535 $r = '<fieldset class="index_info">';
536 $r .= '<legend id="index_header">' . __('Indexes');
537 $r .= PMA_Util
::showMySQLDocu(
538 'optimization', 'optimizing-database-structure'
543 if (count($indexes) < 1) {
547 $r .= PMA_Index
::findDuplicates($table, $schema);
549 $r = '<h3>' . __('Indexes') . '</h3>';
551 if (count($indexes) < 1) {
555 $r .= '<table id="table_index">';
559 $r .= '<th colspan="2">' . __('Action') . '</th>';
561 $r .= '<th>' . __('Keyname') . '</th>';
562 $r .= '<th>' . __('Type') . '</th>';
563 $r .= '<th>' . __('Unique') . '</th>';
564 $r .= '<th>' . __('Packed') . '</th>';
565 $r .= '<th>' . __('Column') . '</th>';
566 $r .= '<th>' . __('Cardinality') . '</th>';
567 $r .= '<th>' . __('Collation') . '</th>';
568 $r .= '<th>' . __('Null') . '</th>';
569 if (PMA_MYSQL_INT_VERSION
> 50500) {
570 $r .= '<th>' . __('Comment') . '</th>';
577 foreach ($indexes as $index) {
578 $row_span = ' rowspan="' . $index->getColumnCount() . '" ';
580 $r .= '<tr class="noclick ' . ($odd_row ?
'odd' : 'even') . '">';
583 $this_params = $GLOBALS['url_params'];
584 $this_params['index'] = $index->getName();
585 $r .= '<td class="edit_index';
587 $r .= '" ' . $row_span . '>'
590 $r .= '" href="tbl_indexes.php' . PMA_generate_common_url($this_params)
591 . '">' . PMA_Util
::getIcon('b_edit.png', __('Edit')) . '</a>'
593 $this_params = $GLOBALS['url_params'];
594 if ($index->getName() == 'PRIMARY') {
595 $this_params['sql_query'] = 'ALTER TABLE '
596 . PMA_Util
::backquote($table)
597 . ' DROP PRIMARY KEY;';
598 $this_params['message_to_show']
599 = __('The primary key has been dropped');
600 $js_msg = PMA_jsFormat(
601 'ALTER TABLE ' . $table . ' DROP PRIMARY KEY'
604 $this_params['sql_query'] = 'ALTER TABLE '
605 . PMA_Util
::backquote($table) . ' DROP INDEX '
606 . PMA_Util
::backquote($index->getName()) . ';';
607 $this_params['message_to_show'] = sprintf(
608 __('Index %s has been dropped'), $index->getName()
611 $js_msg = PMA_jsFormat(
612 'ALTER TABLE ' . $table . ' DROP INDEX '
613 . $index->getName() . ';'
618 $r .= '<td ' . $row_span . '>';
619 $r .= '<input type="hidden" class="drop_primary_key_index_msg"'
620 . ' value="' . $js_msg . '" />';
621 $r .= ' <a class="drop_primary_key_index_anchor';
623 $r .= '" href="sql.php' . PMA_generate_common_url($this_params)
625 . PMA_Util
::getIcon('b_drop.png', __('Drop')) . '</a>'
630 $r .= '<th ' . $row_span . '>'
631 . htmlspecialchars($index->getName())
634 $r .= '<td ' . $row_span . '>'
635 . htmlspecialchars($index->getName())
638 $r .= '<td ' . $row_span . '>'
639 . htmlspecialchars($index->getType())
641 $r .= '<td ' . $row_span . '>' . $index->isUnique(true) . '</td>';
642 $r .= '<td ' . $row_span . '>' . $index->isPacked(true) . '</td>';
644 foreach ($index->getColumns() as $column) {
645 if ($column->getSeqInIndex() > 1) {
646 $r .= '<tr class="noclick ' . ($odd_row ?
'odd' : 'even') . '">';
648 $r .= '<td>' . htmlspecialchars($column->getName());
649 if ($column->getSubPart()) {
650 $r .= ' (' . $column->getSubPart() . ')';
654 . htmlspecialchars($column->getCardinality())
657 . htmlspecialchars($column->getCollation())
660 . htmlspecialchars($column->getNull(true))
663 if (PMA_MYSQL_INT_VERSION
> 50500
664 && $column->getSeqInIndex() == 1) {
665 $r .= '<td ' . $row_span . '>'
666 . htmlspecialchars($index->getComments()) . '</td>';
669 } // end foreach $index['Sequences']
671 $odd_row = ! $odd_row;
682 public function getCompareData()
685 // 'Non_unique' => $this->_non_unique,
686 'Packed' => $this->_packed
,
687 'Index_type' => $this->_type
,
690 foreach ($this->_columns
as $column) {
691 $data['columns'][] = $column->getCompareData();
698 * Function to check over array of indexes and look for common problems
700 * @param string $table table name
701 * @param string $schema schema name
703 * @return string Output HTML
706 static public function findDuplicates($table, $schema)
708 $indexes = PMA_Index
::getFromTable($table, $schema);
712 // count($indexes) < 2:
713 // there is no need to check if there less than two indexes
714 if (count($indexes) < 2) {
718 // remove last index from stack and ...
719 while ($while_index = array_pop($indexes)) {
720 // ... compare with every remaining index in stack
721 foreach ($indexes as $each_index) {
722 if ($each_index->getCompareData() !== $while_index->getCompareData()) {
726 // did not find any difference
727 // so it makes no sense to have this two equal indexes
729 $message = PMA_Message
::notice(
730 __('The indexes %1$s and %2$s seem to be equal and one of them could possibly be removed.')
732 $message->addParam($each_index->getName());
733 $message->addParam($while_index->getName());
734 $output .= $message->getDisplay();
736 // there is no need to check any further indexes if we have already
737 // found that this one has a duplicate
746 * @package PhpMyAdmin
748 class PMA_Index_Column
751 * @var string The column name
756 * @var integer The column sequence number in the index, starting with 1.
758 private $_seq_in_index = 1;
761 * @var string How the column is sorted in the index. “A” (Ascending) or
764 private $_collation = null;
767 * The number of indexed characters if the column is only partly indexed,
768 * NULL if the entire column is indexed.
772 private $_sub_part = null;
775 * Contains YES if the column may contain NULL.
776 * If not, the column contains NO.
783 * An estimate of the number of unique values in the index. This is updated
784 * by running ANALYZE TABLE or myisamchk -a. Cardinality is counted based on
785 * statistics stored as integers, so the value is not necessarily exact even
786 * for small tables. The higher the cardinality, the greater the chance that
787 * MySQL uses the index when doing joins.
791 private $_cardinality = null;
793 public function __construct($params = array())
798 public function set($params)
800 if (isset($params['Column_name'])) {
801 $this->_name
= $params['Column_name'];
803 if (isset($params['Seq_in_index'])) {
804 $this->_seq_in_index
= $params['Seq_in_index'];
806 if (isset($params['Collation'])) {
807 $this->_collation
= $params['Collation'];
809 if (isset($params['Cardinality'])) {
810 $this->_cardinality
= $params['Cardinality'];
812 if (isset($params['Sub_part'])) {
813 $this->_sub_part
= $params['Sub_part'];
815 if (isset($params['Null'])) {
816 $this->_null
= $params['Null'];
820 public function getName()
825 public function getCollation()
827 return $this->_collation
;
830 public function getCardinality()
832 return $this->_cardinality
;
835 public function getNull($as_text = false)
838 ?
(!$this->_null ||
$this->_null
== 'NO' ?
__('No') : __('Yes'))
842 public function getSeqInIndex()
844 return $this->_seq_in_index
;
847 public function getSubPart()
849 return $this->_sub_part
;
852 public function getCompareData()
855 'Column_name' => $this->_name
,
856 'Seq_in_index' => $this->_seq_in_index
,
857 'Collation' => $this->_collation
,
858 'Sub_part' => $this->_sub_part
,
859 'Null' => $this->_null
,