Merge 865af21b3a into 8f42c3b3e7
This commit is contained in:
commit
76e89f3421
@ -163,6 +163,7 @@ function addColumnToIndex (sourceArray, arrayIndex, indexChoice, colIndex): void
|
||||
const keyBlockSize = $('input[name="index[Key_block_size]"]').val();
|
||||
const parser = $('input[name="index[Parser]"]').val();
|
||||
const indexType = $('select[name="index[Index_type]"]').val();
|
||||
const indexVisible = $('select[name="index[Visible]"]').val();
|
||||
const columns = [];
|
||||
$('#index_columns').find('tbody').find('tr').each(function () {
|
||||
// Get columns in particular order.
|
||||
@ -182,6 +183,7 @@ function addColumnToIndex (sourceArray, arrayIndex, indexChoice, colIndex): void
|
||||
'Key_block_size': keyBlockSize,
|
||||
'Parser': parser,
|
||||
'Index_type': indexType,
|
||||
'Visible': indexVisible,
|
||||
'columns': columns
|
||||
};
|
||||
|
||||
|
||||
@ -75,6 +75,7 @@
|
||||
<th>{{ t('Type') }}</th>
|
||||
<th>{{ t('Unique') }}</th>
|
||||
<th>{{ t('Packed') }}</th>
|
||||
<th>{{ t('Visible') }}</th>
|
||||
<th>{{ t('Column') }}</th>
|
||||
<th>{{ t('Cardinality') }}</th>
|
||||
<th>{{ t('Collation') }}</th>
|
||||
@ -91,6 +92,7 @@
|
||||
<td rowspan="{{ columns_count }}">{{ index.getType()|default(index.getChoice()) }}</td>
|
||||
<td rowspan="{{ columns_count }}">{{ index.isUnique() ? t('Yes') : t('No') }}</td>
|
||||
<td rowspan="{{ columns_count }}">{{ index.isPacked()|raw }}</td>
|
||||
<td rowspan="{{ columns_count }}">{{ index.isVisible() ? t('Yes') : t('No') }}</td>
|
||||
|
||||
{% for column in index.getColumns() %}
|
||||
{% if column.getSeqInIndex() > 1 %}
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
<th>{{ t('Type') }}</th>
|
||||
<th>{{ t('Unique') }}</th>
|
||||
<th>{{ t('Packed') }}</th>
|
||||
<th>{{ t('Visible') }}</th>
|
||||
<th>{{ t('Column') }}</th>
|
||||
<th>{{ t('Cardinality') }}</th>
|
||||
<th>{{ t('Collation') }}</th>
|
||||
@ -65,6 +66,7 @@
|
||||
<td rowspan="{{ columns_count }}">{{ index.getType()|default(index.getChoice()) }}</td>
|
||||
<td rowspan="{{ columns_count }}">{{ index.isUnique() ? t('Yes') : t('No') }}</td>
|
||||
<td rowspan="{{ columns_count }}">{{ index.isPacked()|raw }}</td>
|
||||
<td rowspan="{{ columns_count }}">{{ index.isVisible() ? t('Yes') : t('No') }}</td>
|
||||
|
||||
{% for column in index.getColumns() %}
|
||||
{% if column.getSeqInIndex() > 1 %}
|
||||
|
||||
@ -127,6 +127,22 @@
|
||||
value="{{ index.getComment() }}">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="label">
|
||||
<strong>
|
||||
<label for="select_index_visible">
|
||||
{{ t('Visibility:') }}
|
||||
{{ show_mysql_docu('invisible-indexes') }}
|
||||
</label>
|
||||
</strong>
|
||||
</div>
|
||||
|
||||
<select name="index[Visible]" id="select_index_visible">
|
||||
<option value="YES"{{ index.isVisible() ? ' selected' }}>{{ t('Visible') }}</option>
|
||||
<option value="NO"{{ not index.isVisible() ? ' selected' }}>{{ t('Invisible') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{% if default_sliders_state != 'disabled' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@ -473,6 +473,7 @@
|
||||
<th>{{ t('Type') }}</th>
|
||||
<th>{{ t('Unique') }}</th>
|
||||
<th>{{ t('Packed') }}</th>
|
||||
<th>{{ t('Visible') }}</th>
|
||||
<th>{{ t('Column') }}</th>
|
||||
<th>{{ t('Cardinality') }}</th>
|
||||
<th>{{ t('Collation') }}</th>
|
||||
@ -528,6 +529,7 @@
|
||||
<td rowspan="{{ columns_count }}">{{ index.getType()|default(index.getChoice()) }}</td>
|
||||
<td rowspan="{{ columns_count }}">{{ index.isUnique() ? t('Yes') : t('No') }}</td>
|
||||
<td rowspan="{{ columns_count }}">{{ index.isPacked()|raw }}</td>
|
||||
<td rowspan="{{ columns_count }}">{{ index.isVisible() ? t('Yes') : t('No') }}</td>
|
||||
|
||||
{% for column in index.getColumns() %}
|
||||
{% if column.getSeqInIndex() > 1 %}
|
||||
|
||||
@ -132,7 +132,13 @@ final readonly class IndexesController implements InvocableController
|
||||
return $this->response->response();
|
||||
}
|
||||
|
||||
$this->dbi->query($sqlQuery);
|
||||
// The SQL may contain multiple statements (e.g. ALTER TABLE ...; ALTER TABLE ... ALTER INDEX ... VISIBLE;)
|
||||
// when toggling index visibility, so use multi-query and drain any
|
||||
// additional result sets so subsequent queries on the same connection don't fail.
|
||||
$this->dbi->tryMultiQuery($sqlQuery);
|
||||
do {
|
||||
$next = $this->dbi->nextResult();
|
||||
} while ($next !== false);
|
||||
|
||||
if ($request->isAjax()) {
|
||||
$message = Message::success(
|
||||
|
||||
@ -85,6 +85,13 @@ class Index
|
||||
*/
|
||||
private string $parser = '';
|
||||
|
||||
/**
|
||||
* Whether the index is visible to the optimizer.
|
||||
* Invisible indexes (MySQL 8.0+) are not used by the optimizer unless
|
||||
* explicitly forced with USE/FORCE INDEX.
|
||||
*/
|
||||
private bool $visible = true;
|
||||
|
||||
/** @param mixed[] $params parameters */
|
||||
public function __construct(array $params = [])
|
||||
{
|
||||
@ -323,11 +330,17 @@ class Index
|
||||
$this->keyBlockSize = (int) $params['Key_block_size'];
|
||||
}
|
||||
|
||||
if (! isset($params['Parser'])) {
|
||||
if (isset($params['Parser'])) {
|
||||
$this->parser = $params['Parser'];
|
||||
}
|
||||
|
||||
if (! isset($params['Visible'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->parser = $params['Parser'];
|
||||
// SHOW INDEXES on MySQL 8.0+ returns 'YES' / 'NO'.
|
||||
// The form posts the same string values from the dropdown.
|
||||
$this->visible = $params['Visible'] !== 'NO';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -376,6 +389,15 @@ class Index
|
||||
return $this->parser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the index is visible to the optimizer (MySQL 8.0+).
|
||||
* Defaults to true; only false when the index was created with INVISIBLE.
|
||||
*/
|
||||
public function isVisible(): bool
|
||||
{
|
||||
return $this->visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns concatenated remarks and comment
|
||||
*
|
||||
|
||||
@ -153,6 +153,25 @@ final class Indexes
|
||||
|
||||
$sqlQuery .= ';';
|
||||
|
||||
// For non-PRIMARY indexes, append a second statement to set the
|
||||
// index's visibility explicitly. We can't reliably set it inline in
|
||||
// the compound `DROP INDEX x, ADD INDEX x ...` above: MySQL silently
|
||||
// ignores `VISIBLE` in that position (and depending on option order
|
||||
// even `INVISIBLE` may be ignored), so the new index would inherit
|
||||
// the visibility of the dropped one. A separate `ALTER TABLE ...
|
||||
// ALTER INDEX x VISIBLE/INVISIBLE` always applies cleanly. Primary
|
||||
// keys cannot be invisible, so we never emit the keyword for them.
|
||||
$indexName = $index->getName();
|
||||
if ($index->getChoice() !== 'PRIMARY' && $indexName !== '') {
|
||||
$sqlQuery .= sprintf(
|
||||
' ALTER TABLE %s.%s ALTER INDEX %s %s;',
|
||||
Util::backquote($dbName),
|
||||
Util::backquote($tableName),
|
||||
Util::backquote($indexName),
|
||||
$index->isVisible() ? 'VISIBLE' : 'INVISIBLE',
|
||||
);
|
||||
}
|
||||
|
||||
return $sqlQuery;
|
||||
}
|
||||
|
||||
|
||||
@ -72,6 +72,7 @@
|
||||
<th>Type</th>
|
||||
<th>Unique</th>
|
||||
<th>Packed</th>
|
||||
<th>Visible</th>
|
||||
<th>Column</th>
|
||||
<th>Cardinality</th>
|
||||
<th>Collation</th>
|
||||
@ -86,6 +87,7 @@
|
||||
<td rowspan="1">PRIMARY</td>
|
||||
<td rowspan="1">Yes</td>
|
||||
<td rowspan="1">No</td>
|
||||
<td rowspan="1">Yes</td>
|
||||
|
||||
<td>
|
||||
id
|
||||
|
||||
@ -91,7 +91,7 @@ class IndexRenameControllerTest extends AbstractTestCase
|
||||
// phpcs:disable Generic.Files.LineLength.TooLong
|
||||
$expected = <<<'HTML'
|
||||
<div class="preview_sql">
|
||||
<pre><code class="sql" dir="ltr">ALTER TABLE `test_db`.`test_table_index_rename` DROP INDEX `old_name`, ADD INDEX `new_name` (`name`) USING BTREE;</code></pre>
|
||||
<pre><code class="sql" dir="ltr">ALTER TABLE `test_db`.`test_table_index_rename` DROP INDEX `old_name`, ADD INDEX `new_name` (`name`) USING BTREE; ALTER TABLE `test_db`.`test_table_index_rename` ALTER INDEX `new_name` VISIBLE;</code></pre>
|
||||
</div>
|
||||
|
||||
HTML;
|
||||
|
||||
@ -128,6 +128,19 @@ class IndexTest extends AbstractTestCase
|
||||
);
|
||||
}
|
||||
|
||||
public function testIsVisibleDefaultsToTrue(): void
|
||||
{
|
||||
$index = new Index();
|
||||
self::assertTrue($index->isVisible());
|
||||
}
|
||||
|
||||
public function testIsVisibleReadsFromShowIndexes(): void
|
||||
{
|
||||
// SHOW INDEXES on MySQL 8.0+ returns 'YES' or 'NO' for the Visible column.
|
||||
self::assertFalse((new Index(['Visible' => 'NO']))->isVisible());
|
||||
self::assertTrue((new Index(['Visible' => 'YES']))->isVisible());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for get Name & set Name
|
||||
*/
|
||||
|
||||
@ -86,4 +86,92 @@ class IndexesTest extends AbstractTestCase
|
||||
$indexes->getSqlQueryForIndexCreateOrEdit('PRIMARY', $index, $db, $table);
|
||||
self::assertInstanceOf(Message::class, $indexes->getError());
|
||||
}
|
||||
|
||||
public function testGetSqlQueryForInvisibleIndex(): void
|
||||
{
|
||||
$table = $this->getMockBuilder(Table::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$table->expects(self::any())->method('isEngine')->willReturn(false);
|
||||
$this->dbi->expects(self::any())->method('getTable')->willReturn($table);
|
||||
$indexes = new Indexes($this->dbi);
|
||||
|
||||
$index = new Index([
|
||||
'Key_name' => 'idx_email',
|
||||
'Index_choice' => 'INDEX',
|
||||
'Visible' => 'NO',
|
||||
'columns' => [['Column_name' => 'email']],
|
||||
]);
|
||||
|
||||
$expected = 'ALTER TABLE `pma_db`.`pma_table` ADD INDEX `idx_email` (`email`);'
|
||||
. ' ALTER TABLE `pma_db`.`pma_table` ALTER INDEX `idx_email` INVISIBLE;';
|
||||
self::assertSame($expected, $indexes->getSqlQueryForIndexCreateOrEdit(null, $index, 'pma_db', 'pma_table'));
|
||||
}
|
||||
|
||||
public function testGetSqlQueryForEditIndexToInvisible(): void
|
||||
{
|
||||
$table = $this->getMockBuilder(Table::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$table->expects(self::any())->method('isEngine')->willReturn(false);
|
||||
$this->dbi->expects(self::any())->method('getTable')->willReturn($table);
|
||||
$indexes = new Indexes($this->dbi);
|
||||
|
||||
$index = new Index([
|
||||
'Key_name' => 'idx_email',
|
||||
'Index_choice' => 'INDEX',
|
||||
'Visible' => 'NO',
|
||||
'columns' => [['Column_name' => 'email']],
|
||||
]);
|
||||
|
||||
$expected = 'ALTER TABLE `pma_db`.`pma_table` DROP INDEX `idx_email`,'
|
||||
. ' ADD INDEX `idx_email` (`email`);'
|
||||
. ' ALTER TABLE `pma_db`.`pma_table` ALTER INDEX `idx_email` INVISIBLE;';
|
||||
$sql = $indexes->getSqlQueryForIndexCreateOrEdit('idx_email', $index, 'pma_db', 'pma_table');
|
||||
self::assertSame($expected, $sql);
|
||||
}
|
||||
|
||||
public function testGetSqlQueryForVisibleIndexEmitsVisible(): void
|
||||
{
|
||||
// The visibility keyword is set via a separate `ALTER INDEX` statement
|
||||
// because MySQL silently ignores `VISIBLE` inside compound
|
||||
// `DROP INDEX x, ADD INDEX x` clauses (the new index inherits the
|
||||
// previous visibility otherwise).
|
||||
$table = $this->getMockBuilder(Table::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$table->expects(self::any())->method('isEngine')->willReturn(false);
|
||||
$this->dbi->expects(self::any())->method('getTable')->willReturn($table);
|
||||
$indexes = new Indexes($this->dbi);
|
||||
|
||||
$index = new Index([
|
||||
'Key_name' => 'idx_email',
|
||||
'Index_choice' => 'INDEX',
|
||||
'columns' => [['Column_name' => 'email']],
|
||||
]);
|
||||
|
||||
$expected = 'ALTER TABLE `pma_db`.`pma_table` DROP INDEX `idx_email`,'
|
||||
. ' ADD INDEX `idx_email` (`email`);'
|
||||
. ' ALTER TABLE `pma_db`.`pma_table` ALTER INDEX `idx_email` VISIBLE;';
|
||||
$sql = $indexes->getSqlQueryForIndexCreateOrEdit('idx_email', $index, 'pma_db', 'pma_table');
|
||||
self::assertSame($expected, $sql);
|
||||
}
|
||||
|
||||
public function testGetSqlQueryForPrimaryDoesNotEmitVisibilityKeyword(): void
|
||||
{
|
||||
// Primary keys cannot be invisible — never emit the keyword for them.
|
||||
$table = $this->getMockBuilder(Table::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$this->dbi->expects(self::any())->method('getTable')->willReturn($table);
|
||||
$indexes = new Indexes($this->dbi);
|
||||
|
||||
$index = new Index([
|
||||
'Key_name' => 'PRIMARY',
|
||||
'columns' => [['Column_name' => 'id']],
|
||||
]);
|
||||
|
||||
$sql = $indexes->getSqlQueryForIndexCreateOrEdit(null, $index, 'pma_db', 'pma_table');
|
||||
self::assertSame('ALTER TABLE `pma_db`.`pma_table` ADD PRIMARY KEY (`id`);', $sql);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user