diff --git a/app/services.php b/app/services.php index 51de1ca115..993d5e7f97 100644 --- a/app/services.php +++ b/app/services.php @@ -193,7 +193,7 @@ return [ ], 'table_indexes' => [ 'class' => Indexes::class, - 'arguments' => ['$response' => '@response', '$template' => '@template', '$dbi' => '@dbi'], + 'arguments' => ['$dbi' => '@dbi'], ], 'table_maintenance' => ['class' => PhpMyAdmin\Table\Maintenance::class, 'arguments' => ['$dbi' => '@dbi']], 'table_search' => ['class' => Search::class, 'arguments' => ['$dbi' => '@dbi']], diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 90752741d6..89ad2074db 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -4926,12 +4926,12 @@ parameters: path: src/Controllers/Table/IndexRenameController.php - - message: "#^Parameter \\#1 \\$params of static method PhpMyAdmin\\\\Url\\:\\:getCommon\\(\\) expects array\\, mixed given\\.$#" + message: "#^Parameter \\#1 \\$oldIndexName of method PhpMyAdmin\\\\Table\\\\Indexes\\:\\:getSqlQueryForRename\\(\\) expects string, mixed given\\.$#" count: 1 path: src/Controllers/Table/IndexRenameController.php - - message: "#^Parameter \\#7 \\$oldIndexName of method PhpMyAdmin\\\\Table\\\\Indexes\\:\\:doSaveData\\(\\) expects string, mixed given\\.$#" + message: "#^Parameter \\#1 \\$params of static method PhpMyAdmin\\\\Url\\:\\:getCommon\\(\\) expects array\\, mixed given\\.$#" count: 1 path: src/Controllers/Table/IndexRenameController.php @@ -4955,6 +4955,11 @@ parameters: count: 1 path: src/Controllers/Table/IndexesController.php + - + message: "#^Parameter \\#1 \\$oldIndexName of method PhpMyAdmin\\\\Table\\\\Indexes\\:\\:getSqlQueryForIndexCreateOrEdit\\(\\) expects string\\|null, mixed given\\.$#" + count: 1 + path: src/Controllers/Table/IndexesController.php + - message: "#^Parameter \\#1 \\$params of static method PhpMyAdmin\\\\Url\\:\\:getCommon\\(\\) expects array\\, mixed given\\.$#" count: 1 @@ -15165,6 +15170,11 @@ parameters: count: 1 path: src/Table/Indexes.php + - + message: "#^Only booleans are allowed in a negated boolean, int\\|null given\\.$#" + count: 1 + path: src/Table/Indexes.php + - message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#" count: 2 @@ -15367,7 +15377,7 @@ parameters: - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" - count: 18 + count: 17 path: src/Table/Table.php - @@ -15437,7 +15447,7 @@ parameters: - message: "#^Only booleans are allowed in a negated boolean, int\\|null given\\.$#" - count: 2 + count: 1 path: src/Table/Table.php - @@ -15477,7 +15487,7 @@ parameters: - message: "#^Parameter \\#1 \\$identifier of static method PhpMyAdmin\\\\Util\\:\\:backquote\\(\\) expects string\\|Stringable\\|null, mixed given\\.$#" - count: 7 + count: 6 path: src/Table/Table.php - @@ -17895,26 +17905,6 @@ parameters: count: 1 path: tests/classes/Stubs/ResponseRenderer.php - - - message: "#^Call to an undefined method PhpMyAdmin\\\\DatabaseInterface\\:\\:expects\\(\\)\\.$#" - count: 1 - path: tests/classes/Table/IndexesTest.php - - - - message: "#^Cannot call method method\\(\\) on mixed\\.$#" - count: 1 - path: tests/classes/Table/IndexesTest.php - - - - message: "#^Cannot call method willReturn\\(\\) on mixed\\.$#" - count: 1 - path: tests/classes/Table/IndexesTest.php - - - - message: "#^Parameter \\#2 \\$haystack of method PHPUnit\\\\Framework\\\\Assert\\:\\:assertStringContainsString\\(\\) expects string, mixed given\\.$#" - count: 1 - path: tests/classes/Table/IndexesTest.php - - message: "#^Call to an undefined method PhpMyAdmin\\\\DatabaseInterface\\:\\:expects\\(\\)\\.$#" count: 4 @@ -17925,11 +17915,6 @@ parameters: count: 1 path: tests/classes/Table/TableTest.php - - - message: "#^Cannot access offset 'Key_name' on mixed\\.$#" - count: 1 - path: tests/classes/Table/TableTest.php - - message: "#^Cannot access offset 'SCHEMA_TABLES' on mixed\\.$#" count: 1 diff --git a/psalm-baseline.xml b/psalm-baseline.xml index c63e23a1e9..b852af6f2a 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -3568,20 +3568,15 @@ + $indexName + $indexName $oldIndexName + $indexName $oldIndexName - - - $oldIndex - - - - $oldIndex - @@ -3601,6 +3596,7 @@ + $oldIndex @@ -11466,6 +11462,11 @@ + + + getSubPart()]]> + + @@ -11736,7 +11737,6 @@ - $oldIndex @@ -11776,7 +11776,6 @@ altered]]> - getSubPart()]]> @@ -11787,9 +11786,6 @@ true|Message - - return $sqlQuery; - uiprefs =& $_SESSION['tmpval']['table_uiprefs'][$serverId][$this->dbName][$this->name]]]> @@ -15256,6 +15252,9 @@ $value + + clear + @@ -15270,22 +15269,6 @@ providerColumnMetaDefault - - - Config::getInstance() - DatabaseInterface::getInstance() - - - - - - method - willReturn - - - expects - - providerForTestFromArray @@ -15319,8 +15302,6 @@ DatabaseInterface::getInstance() DatabaseInterface::getInstance() DatabaseInterface::getInstance() - DatabaseInterface::getInstance() - DatabaseInterface::getInstance() diff --git a/resources/templates/table/index_rename_form.twig b/resources/templates/table/index_rename_form.twig index a57ecca0dc..58552cd0f1 100644 --- a/resources/templates/table/index_rename_form.twig +++ b/resources/templates/table/index_rename_form.twig @@ -15,7 +15,7 @@ checkParameters(['db', 'table'])) { - return; - } - - $GLOBALS['urlParams'] = ['db' => Current::$database, 'table' => Current::$table]; - $GLOBALS['errorUrl'] = Util::getScriptNameForOption( - Config::getInstance()->settings['DefaultTabTable'], - 'table', - ); - $GLOBALS['errorUrl'] .= Url::getCommon($GLOBALS['urlParams'], '&'); - - $databaseName = DatabaseName::tryFrom($request->getParam('db')); - if ($databaseName === null || ! $this->dbTableExists->selectDatabase($databaseName)) { - if ($request->isAjax()) { - $this->response->setRequestStatus(false); - $this->response->addJSON('message', Message::error(__('No databases selected.'))); - - return; - } - - $this->redirect('/', ['reload' => true, 'message' => __('No databases selected.')]); - - return; - } - - $tableName = TableName::tryFrom($request->getParam('table')); - if ($tableName === null || ! $this->dbTableExists->hasTable($databaseName, $tableName)) { - if ($request->isAjax()) { - $this->response->setRequestStatus(false); - $this->response->addJSON('message', Message::error(__('No table selected.'))); - - return; - } - - $this->redirect('/', ['reload' => true, 'message' => __('No table selected.')]); - - return; - } + if (! $this->checkParameters(['db', 'table'])) { + return; } - if (isset($_POST['index'])) { - if (is_array($_POST['index'])) { - // coming already from form - $oldIndex = is_array($_POST['old_index']) ? $_POST['old_index']['Key_name'] : $_POST['old_index']; - $index = clone $this->dbi->getTable(Current::$database, Current::$table)->getIndex($oldIndex); - $index->setName($_POST['index']['Key_name']); - } else { - $index = $this->dbi->getTable(Current::$database, Current::$table)->getIndex($_POST['index']); + $GLOBALS['urlParams'] = ['db' => Current::$database, 'table' => Current::$table]; + $GLOBALS['errorUrl'] = Util::getScriptNameForOption( + Config::getInstance()->settings['DefaultTabTable'], + 'table', + ); + $GLOBALS['errorUrl'] .= Url::getCommon($GLOBALS['urlParams'], '&'); + + $databaseName = DatabaseName::tryFrom($request->getParam('db')); + if ($databaseName === null || ! $this->dbTableExists->selectDatabase($databaseName)) { + if ($request->isAjax()) { + $this->response->setRequestStatus(false); + $this->response->addJSON('message', Message::error(__('No databases selected.'))); + + return; } - } else { - $index = new Index(); + + $this->redirect('/', ['reload' => true, 'message' => __('No databases selected.')]); + + return; } - if (isset($_POST['do_save_data'])) { - $oldIndexName = $request->getParsedBodyParam('old_index', ''); - $this->indexes->doSaveData( - $request, - $index, - true, - Current::$database, - Current::$table, - $request->hasBodyParam('preview_sql'), - $oldIndexName, + $tableName = TableName::tryFrom($request->getParam('table')); + if ($tableName === null || ! $this->dbTableExists->hasTable($databaseName, $tableName)) { + if ($request->isAjax()) { + $this->response->setRequestStatus(false); + $this->response->addJSON('message', Message::error(__('No table selected.'))); + + return; + } + + $this->redirect('/', ['reload' => true, 'message' => __('No table selected.')]); + + return; + } + + $oldIndexName = $request->getParsedBodyParam('old_index'); + $indexName = $request->getParsedBodyParam('index'); + if ($oldIndexName === null) { + $index = $this->dbi->getTable($databaseName->getName(), $tableName->getName())->getIndex($indexName); + + $formParams = [ + 'db' => $databaseName->getName(), + 'table' => $tableName->getName(), + 'old_index' => $index->getName(), + ]; + + $this->render('table/index_rename_form', ['index' => $index, 'form_params' => $formParams]); + + return; + } + + // coming already from form + $index = $this->dbi->getTable($databaseName->getName(), $tableName->getName())->getIndex($oldIndexName); + $index->setName($indexName); + + $previewSql = $request->hasBodyParam('preview_sql'); + + $sqlQuery = $this->indexes->getSqlQueryForRename( + $oldIndexName, + $index, + $databaseName->getName(), + $tableName->getName(), + ); + + if ($previewSql) { + $this->response->addJSON( + 'sql_data', + $this->template->render('preview_sql', ['query_data' => $sqlQuery]), ); return; } - $this->displayRenameForm($index); - } + $logicError = $this->indexes->getError(); + if ($logicError instanceof Message) { + $this->response->setRequestStatus(false); + $this->response->addJSON('message', $logicError); - /** - * Display the rename form to rename an index - * - * @param Index $index An Index instance. - */ - private function displayRenameForm(Index $index): void - { - $this->dbi->selectDb(Current::$database); - - $formParams = ['db' => Current::$database, 'table' => Current::$table]; - - if (isset($_POST['old_index'])) { - $formParams['old_index'] = $_POST['old_index']; - } elseif (isset($_POST['index'])) { - $formParams['old_index'] = $_POST['index']; + return; } - $this->render('table/index_rename_form', ['index' => $index, 'form_params' => $formParams]); + $this->dbi->query($sqlQuery); + + $message = Message::success(__('Table %1$s has been altered successfully.')); + $message->addParam($tableName->getName()); + $this->response->addJSON( + 'message', + Generator::getMessage($message, $sqlQuery, 'success'), + ); + + $indexes = Index::getFromTable($this->dbi, $tableName->getName(), $databaseName->getName()); + $indexesDuplicates = Index::findDuplicates($tableName->getName(), $databaseName->getName()); + + $this->response->addJSON( + 'index_table', + $this->template->render('indexes', [ + 'url_params' => ['db' => $databaseName->getName(), 'table' => $tableName->getName()], + 'indexes' => $indexes, + 'indexes_duplicates' => $indexesDuplicates, + ]), + ); } } diff --git a/src/Controllers/Table/IndexesController.php b/src/Controllers/Table/IndexesController.php index ced8656efb..368d4fd098 100644 --- a/src/Controllers/Table/IndexesController.php +++ b/src/Controllers/Table/IndexesController.php @@ -5,10 +5,12 @@ declare(strict_types=1); namespace PhpMyAdmin\Controllers\Table; use PhpMyAdmin\Config; +use PhpMyAdmin\Container\ContainerBuilder; use PhpMyAdmin\Controllers\AbstractController; use PhpMyAdmin\Current; use PhpMyAdmin\DatabaseInterface; use PhpMyAdmin\DbTableExists; +use PhpMyAdmin\Html\Generator; use PhpMyAdmin\Http\ServerRequest; use PhpMyAdmin\Identifiers\DatabaseName; use PhpMyAdmin\Identifiers\TableName; @@ -100,15 +102,69 @@ class IndexesController extends AbstractController } if (isset($_POST['do_save_data'])) { - $this->indexes->doSaveData( - $request, + $previewSql = $request->hasBodyParam('preview_sql'); + if (isset($_POST['old_index'])) { + $oldIndex = is_array($_POST['old_index']) ? $_POST['old_index']['Key_name'] : $_POST['old_index']; + } else { + $oldIndex = null; + } + + $sqlQuery = $this->indexes->getSqlQueryForIndexCreateOrEdit( + $oldIndex, $index, - false, Current::$database, Current::$table, - $request->hasBodyParam('preview_sql'), ); + // If there is a request for SQL previewing. + if ($previewSql) { + $this->response->addJSON( + 'sql_data', + $this->template->render('preview_sql', ['query_data' => $sqlQuery]), + ); + + return; + } + + $logicError = $this->indexes->getError(); + if ($logicError instanceof Message) { + $this->response->setRequestStatus(false); + $this->response->addJSON('message', $logicError); + + return; + } + + $this->dbi->query($sqlQuery); + + if ($request->isAjax()) { + $message = Message::success( + __('Table %1$s has been altered successfully.'), + ); + $message->addParam(Current::$table); + $this->response->addJSON( + 'message', + Generator::getMessage($message, $sqlQuery, 'success'), + ); + + $indexes = Index::getFromTable($this->dbi, Current::$table, Current::$database); + $indexesDuplicates = Index::findDuplicates(Current::$table, Current::$database); + + $this->response->addJSON( + 'index_table', + $this->template->render('indexes', [ + 'url_params' => ['db' => Current::$database, 'table' => Current::$table], + 'indexes' => $indexes, + 'indexes_duplicates' => $indexesDuplicates, + ]), + ); + + return; + } + + /** @var StructureController $controller */ + $controller = ContainerBuilder::getContainer()->get(StructureController::class); + $controller($request); + return; } diff --git a/src/Index.php b/src/Index.php index 6f31c17d8f..29116955fa 100644 --- a/src/Index.php +++ b/src/Index.php @@ -209,7 +209,7 @@ class Index $key .= $params['Seq_in_index']; } - if (strlen($key) <= 0) { + if ($key === '') { return; } diff --git a/src/Table/Indexes.php b/src/Table/Indexes.php index f882b3350d..e4489b6dee 100644 --- a/src/Table/Indexes.php +++ b/src/Table/Indexes.php @@ -4,116 +4,176 @@ declare(strict_types=1); namespace PhpMyAdmin\Table; -use PhpMyAdmin\Container\ContainerBuilder; -use PhpMyAdmin\Controllers\Table\StructureController; use PhpMyAdmin\DatabaseInterface; -use PhpMyAdmin\Html\Generator; -use PhpMyAdmin\Http\ServerRequest; use PhpMyAdmin\Identifiers\DatabaseName; use PhpMyAdmin\Index; use PhpMyAdmin\Message; use PhpMyAdmin\Query\Compatibility; use PhpMyAdmin\Query\Generator as QueryGenerator; -use PhpMyAdmin\ResponseRenderer; -use PhpMyAdmin\Template; +use PhpMyAdmin\Util; use function __; +use function implode; +use function in_array; +use function sprintf; final class Indexes { - public function __construct( - protected ResponseRenderer $response, - protected Template $template, - private DatabaseInterface $dbi, - ) { + private Message|null $error = null; + + public function __construct(private readonly DatabaseInterface $dbi) + { + } + + public function getError(): Message|null + { + return $this->error; } /** - * Process the data from the edit/create index form, - * run the query to build the new index - * and moves back to /table/sql + * Function to get the sql query for index creation or edit * - * @param Index $index An Index instance. - * @param bool $renameMode Rename the Index mode + * @param Index $index current index */ - public function doSaveData( - ServerRequest $request, + public function getSqlQueryForIndexCreateOrEdit( + string|null $oldIndexName, Index $index, - bool $renameMode, - string $db, - string $table, - bool $previewSql, - string $oldIndexName = '', - ): void { - $error = false; - if ($renameMode && Compatibility::isCompatibleRenameIndex($this->dbi->getVersion())) { + string $dbName, + string $tableName, + ): string { + // $sql_query is the one displayed in the query box + $sqlQuery = sprintf( + 'ALTER TABLE %s.%s', + Util::backquote($dbName), + Util::backquote($tableName), + ); + + // Drops the old index + if ($oldIndexName !== null) { if ($oldIndexName === 'PRIMARY') { - if ($index->getName() === '') { + $sqlQuery .= ' DROP PRIMARY KEY,'; + } else { + $sqlQuery .= sprintf( + ' DROP INDEX %s,', + Util::backquote($oldIndexName), + ); + } + } + + // Builds the new one + switch ($index->getChoice()) { + case 'PRIMARY': + if ($index->getName() == '') { $index->setName('PRIMARY'); } elseif ($index->getName() !== 'PRIMARY') { - $error = Message::error( + $this->error = Message::error( __('The name of the primary key must be "PRIMARY"!'), ); } + + $sqlQuery .= ' ADD PRIMARY KEY'; + break; + case 'FULLTEXT': + case 'UNIQUE': + case 'INDEX': + case 'SPATIAL': + if ($index->getName() === 'PRIMARY') { + $this->error = Message::error( + __('Can\'t rename index to PRIMARY!'), + ); + } + + $sqlQuery .= sprintf( + ' ADD %s', + $index->getChoice(), + ); + if ($index->getName() !== '') { + $sqlQuery .= ' ' . Util::backquote($index->getName()); + } + + break; + } + + $indexFields = []; + foreach ($index->getColumns() as $key => $column) { + $indexFields[$key] = Util::backquote($column->getName()); + if (! $column->getSubPart()) { + continue; } - $sqlQuery = QueryGenerator::getSqlQueryForIndexRename( - $db, - $table, - $oldIndexName, - $index->getName(), - ); + $indexFields[$key] .= '(' . $column->getSubPart() . ')'; + } + + if ($indexFields === []) { + $this->error = Message::error(__('No index parts defined!')); } else { - $sqlQuery = $this->dbi->getTable($db, $table) - ->getSqlQueryForIndexCreateOrEdit($index, $error); + $sqlQuery .= ' (' . implode(', ', $indexFields) . ')'; } - // If there is a request for SQL previewing. - if ($previewSql) { - $this->response->addJSON( - 'sql_data', - $this->template->render('preview_sql', ['query_data' => $sqlQuery]), - ); - - return; + $keyBlockSizes = $index->getKeyBlockSize(); + if ($keyBlockSizes !== 0) { + $sqlQuery .= ' KEY_BLOCK_SIZE = ' . $keyBlockSizes; } - if ($error) { - $this->response->setRequestStatus(false); - $this->response->addJSON('message', $error); - - return; + // specifying index type is allowed only for primary, unique and index only + // TokuDB is using Fractal Tree, Using Type is not useless + // Ref: https://mariadb.com/kb/en/storage-engine-index-types/ + $type = $index->getType(); + if ( + $index->getChoice() !== 'SPATIAL' + && $index->getChoice() !== 'FULLTEXT' + && in_array($type, Index::getIndexTypes(), true) + && ! $this->dbi->getTable($dbName, $tableName)->isEngine('TOKUDB') + ) { + $sqlQuery .= ' USING ' . $type; } - $this->dbi->query($sqlQuery); - if ($request->isAjax()) { - $message = Message::success( - __('Table %1$s has been altered successfully.'), - ); - $message->addParam($table); - $this->response->addJSON( - 'message', - Generator::getMessage($message, $sqlQuery, 'success'), - ); - - $indexes = Index::getFromTable($this->dbi, $table, $db); - $indexesDuplicates = Index::findDuplicates($table, $db); - - $this->response->addJSON( - 'index_table', - $this->template->render('indexes', [ - 'url_params' => ['db' => $db, 'table' => $table], - 'indexes' => $indexes, - 'indexes_duplicates' => $indexesDuplicates, - ]), - ); - - return; + $parser = $index->getParser(); + if ($index->getChoice() === 'FULLTEXT' && $parser !== '') { + $sqlQuery .= ' WITH PARSER ' . $parser; } - /** @var StructureController $controller */ - $controller = ContainerBuilder::getContainer()->get(StructureController::class); - $controller($request); + $comment = $index->getComment(); + if ($comment !== '') { + $sqlQuery .= sprintf( + ' COMMENT %s', + $this->dbi->quoteString($comment), + ); + } + + $sqlQuery .= ';'; + + return $sqlQuery; + } + + public function getSqlQueryForRename(string $oldIndexName, Index $index, string $db, string $table): string + { + if (! Compatibility::isCompatibleRenameIndex($this->dbi->getVersion())) { + return $this->getSqlQueryForIndexCreateOrEdit($oldIndexName, $index, $db, $table); + } + + if ($oldIndexName === 'PRIMARY') { + if ($index->getName() === '') { + $index->setName('PRIMARY'); + } elseif ($index->getName() !== 'PRIMARY') { + $this->error = Message::error( + __('The name of the primary key must be "PRIMARY"!'), + ); + } + } + + if ($index->getName() === 'PRIMARY') { + $this->error = Message::error( + __('Can\'t rename index to PRIMARY!'), + ); + } + + return QueryGenerator::getSqlQueryForIndexRename( + $db, + $table, + $oldIndexName, + $index->getName(), + ); } public function executeAddIndexSql(string|DatabaseName $db, string $sql): Message diff --git a/src/Table/Table.php b/src/Table/Table.php index b4acea2b30..71f17fff35 100644 --- a/src/Table/Table.php +++ b/src/Table/Table.php @@ -1875,120 +1875,6 @@ class Table implements Stringable return Index::singleton($this->dbi, $this->dbName, $this->name, $index); } - /** - * Function to get the sql query for index creation or edit - * - * @param Index $index current index - * @param bool $error whether error occurred or not - */ - public function getSqlQueryForIndexCreateOrEdit(Index $index, bool &$error): string - { - // $sql_query is the one displayed in the query box - $sqlQuery = sprintf( - 'ALTER TABLE %s.%s', - Util::backquote($this->dbName), - Util::backquote($this->name), - ); - - // Drops the old index - if (! empty($_POST['old_index'])) { - $oldIndex = is_array($_POST['old_index']) ? $_POST['old_index']['Key_name'] : $_POST['old_index']; - if ($oldIndex === 'PRIMARY') { - $sqlQuery .= ' DROP PRIMARY KEY,'; - } else { - $sqlQuery .= sprintf( - ' DROP INDEX %s,', - Util::backquote($oldIndex), - ); - } - } - - // Builds the new one - switch ($index->getChoice()) { - case 'PRIMARY': - if ($index->getName() == '') { - $index->setName('PRIMARY'); - } elseif ($index->getName() !== 'PRIMARY') { - $error = Message::error( - __('The name of the primary key must be "PRIMARY"!'), - ); - } - - $sqlQuery .= ' ADD PRIMARY KEY'; - break; - case 'FULLTEXT': - case 'UNIQUE': - case 'INDEX': - case 'SPATIAL': - if ($index->getName() === 'PRIMARY') { - $error = Message::error( - __('Can\'t rename index to PRIMARY!'), - ); - } - - $sqlQuery .= sprintf( - ' ADD %s', - $index->getChoice(), - ); - if ($index->getName() !== '') { - $sqlQuery .= ' ' . Util::backquote($index->getName()); - } - - break; - } - - $indexFields = []; - foreach ($index->getColumns() as $key => $column) { - $indexFields[$key] = Util::backquote($column->getName()); - if (! $column->getSubPart()) { - continue; - } - - $indexFields[$key] .= '(' . $column->getSubPart() . ')'; - } - - if ($indexFields === []) { - $error = Message::error(__('No index parts defined!')); - } else { - $sqlQuery .= ' (' . implode(', ', $indexFields) . ')'; - } - - $keyBlockSizes = $index->getKeyBlockSize(); - if ($keyBlockSizes !== 0) { - $sqlQuery .= ' KEY_BLOCK_SIZE = ' . $keyBlockSizes; - } - - // specifying index type is allowed only for primary, unique and index only - // TokuDB is using Fractal Tree, Using Type is not useless - // Ref: https://mariadb.com/kb/en/storage-engine-index-types/ - $type = $index->getType(); - if ( - $index->getChoice() !== 'SPATIAL' - && $index->getChoice() !== 'FULLTEXT' - && in_array($type, Index::getIndexTypes(), true) - && ! $this->isEngine('TOKUDB') - ) { - $sqlQuery .= ' USING ' . $type; - } - - $parser = $index->getParser(); - if ($index->getChoice() === 'FULLTEXT' && $parser !== '') { - $sqlQuery .= ' WITH PARSER ' . $parser; - } - - $comment = $index->getComment(); - if ($comment !== '') { - $sqlQuery .= sprintf( - ' COMMENT %s', - $this->dbi->quoteString($comment), - ); - } - - $sqlQuery .= ';'; - - return $sqlQuery; - } - /** * Function to handle update for display field * diff --git a/tests/classes/Controllers/Table/IndexRenameControllerTest.php b/tests/classes/Controllers/Table/IndexRenameControllerTest.php index 9248da82d7..d536c23623 100644 --- a/tests/classes/Controllers/Table/IndexRenameControllerTest.php +++ b/tests/classes/Controllers/Table/IndexRenameControllerTest.php @@ -24,31 +24,32 @@ class IndexRenameControllerTest extends AbstractTestCase public function testIndexRenameController(): void { Current::$database = 'test_db'; - Current::$table = 'test_table'; + Current::$table = 'test_table_index_rename'; $GLOBALS['lang'] = 'en'; $dummyDbi = $this->createDbiDummy(); $dummyDbi->addSelectDb('test_db'); $dummyDbi->addSelectDb('test_db'); - $dummyDbi->addResult('SELECT 1 FROM `test_db`.`test_table` LIMIT 1;', [['1']]); + $dummyDbi->addResult('SELECT 1 FROM `test_db`.`test_table_index_rename` LIMIT 1;', [['1']]); $dbi = $this->createDatabaseInterface($dummyDbi); DatabaseInterface::$instance = $dbi; $template = new Template(); $expected = $template->render('table/index_rename_form', [ - 'index' => new Index(), - 'form_params' => ['db' => 'test_db', 'table' => 'test_table'], + 'index' => new Index(['Key_name' => 'index']), + 'form_params' => ['db' => 'test_db', 'table' => 'test_table_index_rename', 'old_index' => 'index'], ]); $request = ServerRequestFactory::create()->createServerRequest('GET', 'http://example.com/') - ->withQueryParams(['db' => 'test_db', 'table' => 'test_table']); + ->withQueryParams(['db' => 'test_db', 'table' => 'test_table_index_rename']) + ->withParsedBody(['index' => 'index']); $response = new ResponseRenderer(); (new IndexRenameController( $response, $template, $dbi, - new Indexes($response, $template, $dbi), + new Indexes($dbi), new DbTableExists($dbi), ))($request); $this->assertSame($expected, $response->getHTMLResult()); @@ -62,9 +63,9 @@ class IndexRenameControllerTest extends AbstractTestCase Config::getInstance()->selectedServer['DisableIS'] = true; Current::$database = 'test_db'; - Current::$table = 'test_table'; + Current::$table = 'test_table_index_rename'; $_POST['db'] = 'test_db'; - $_POST['table'] = 'test_table'; + $_POST['table'] = 'test_table_index_rename'; $_POST['old_index'] = 'old_name'; $_POST['index'] = ['Key_name' => 'new_name']; $_POST['do_save_data'] = '1'; @@ -72,12 +73,12 @@ class IndexRenameControllerTest extends AbstractTestCase $dbiDummy = $this->createDbiDummy(); $dbiDummy->addSelectDb('test_db'); - $dbiDummy->addResult('SELECT 1 FROM `test_db`.`test_table` LIMIT 1;', [['1']], ['1']); + $dbiDummy->addResult('SELECT 1 FROM `test_db`.`test_table_index_rename` LIMIT 1;', [['1']], ['1']); $dbiDummy->addResult( - 'SHOW INDEXES FROM `test_db`.`test_table`', + 'SHOW INDEXES FROM `test_db`.`test_table_index_rename`', [ - ['test_table', '0', 'PRIMARY', 'id', 'BTREE'], - ['test_table', '1', 'old_name', 'name', 'BTREE'], + ['test_table_index_rename', '0', 'PRIMARY', 'id', 'BTREE'], + ['test_table_index_rename', '1', 'old_name', 'name', 'BTREE'], ], ['Table', 'Non_unique', 'Key_name', 'Column_name', 'Index_type'], ); @@ -89,17 +90,17 @@ class IndexRenameControllerTest extends AbstractTestCase $expected = <<<'HTML'
-ALTER TABLE `test_db`.`test_table` DROP INDEX `old_name`, ADD INDEX `new_name` (`name`) USING BTREE;
+ALTER TABLE `test_db`.`test_table_index_rename` DROP INDEX `old_name`, ADD INDEX `new_name` (`name`) USING BTREE;
 
HTML; $request = ServerRequestFactory::create()->createServerRequest('GET', 'http://example.com/') - ->withQueryParams(['db' => 'test_db', 'table' => 'test_table']) + ->withQueryParams(['db' => 'test_db', 'table' => 'test_table_index_rename']) ->withParsedBody([ 'old_index' => 'old_name', - 'index' => ['Key_name' => 'new_name'], + 'index' => 'new_name', 'do_save_data' => '1', 'preview_sql' => '1', ]); @@ -110,7 +111,7 @@ HTML; $responseRenderer, $template, $dbi, - new Indexes($responseRenderer, $template, $dbi), + new Indexes($dbi), new DbTableExists($dbi), ); $controller($request); diff --git a/tests/classes/Controllers/Table/IndexesControllerTest.php b/tests/classes/Controllers/Table/IndexesControllerTest.php index bcb3f4a02c..4c946d904f 100644 --- a/tests/classes/Controllers/Table/IndexesControllerTest.php +++ b/tests/classes/Controllers/Table/IndexesControllerTest.php @@ -96,7 +96,7 @@ class IndexesControllerTest extends AbstractTestCase $response, $template, $dbi, - new Indexes($response, $template, $dbi), + new Indexes($dbi), new DbTableExists($dbi), ); diff --git a/tests/classes/Controllers/Table/Structure/SpatialControllerTest.php b/tests/classes/Controllers/Table/Structure/SpatialControllerTest.php index d382166f2b..224ad8ac4f 100644 --- a/tests/classes/Controllers/Table/Structure/SpatialControllerTest.php +++ b/tests/classes/Controllers/Table/Structure/SpatialControllerTest.php @@ -36,7 +36,7 @@ class SpatialControllerTest extends AbstractTestCase $controllerStub = $this->createMock(StructureController::class); $controllerStub->expects($this->once())->method('__invoke')->with($request); - $indexes = new Indexes(new ResponseRenderer(), new Template(), DatabaseInterface::getInstance()); + $indexes = new Indexes(DatabaseInterface::getInstance()); $controller = new SpatialController(new ResponseRenderer(), new Template(), $controllerStub, $indexes); $controller($request); @@ -64,7 +64,7 @@ class SpatialControllerTest extends AbstractTestCase $controllerStub = $this->createMock(StructureController::class); $controllerStub->expects($this->once())->method('__invoke')->with($request); - $indexes = new Indexes(new ResponseRenderer(), new Template(), DatabaseInterface::getInstance()); + $indexes = new Indexes(DatabaseInterface::getInstance()); $controller = new SpatialController(new ResponseRenderer(), new Template(), $controllerStub, $indexes); $controller($request); @@ -90,7 +90,7 @@ class SpatialControllerTest extends AbstractTestCase $controllerStub->expects($this->never())->method('__invoke'); $response = new ResponseRenderer(); - $indexes = new Indexes(new ResponseRenderer(), new Template(), DatabaseInterface::getInstance()); + $indexes = new Indexes(DatabaseInterface::getInstance()); $controller = new SpatialController($response, new Template(), $controllerStub, $indexes); $controller($request); @@ -120,7 +120,7 @@ class SpatialControllerTest extends AbstractTestCase $controllerStub = $this->createMock(StructureController::class); $controllerStub->expects($this->once())->method('__invoke')->with($request); - $indexes = new Indexes(new ResponseRenderer(), new Template(), DatabaseInterface::getInstance()); + $indexes = new Indexes(DatabaseInterface::getInstance()); $controller = new SpatialController(new ResponseRenderer(), new Template(), $controllerStub, $indexes); $controller($request); diff --git a/tests/classes/Controllers/Table/Structure/UniqueControllerTest.php b/tests/classes/Controllers/Table/Structure/UniqueControllerTest.php index 7d8dc3b923..5ec1e554ec 100644 --- a/tests/classes/Controllers/Table/Structure/UniqueControllerTest.php +++ b/tests/classes/Controllers/Table/Structure/UniqueControllerTest.php @@ -36,7 +36,7 @@ class UniqueControllerTest extends AbstractTestCase $controllerStub = $this->createMock(StructureController::class); $controllerStub->expects($this->once())->method('__invoke')->with($request); - $indexes = new Indexes(new ResponseRenderer(), new Template(), DatabaseInterface::getInstance()); + $indexes = new Indexes(DatabaseInterface::getInstance()); $controller = new UniqueController(new ResponseRenderer(), new Template(), $controllerStub, $indexes); $controller($request); @@ -64,7 +64,7 @@ class UniqueControllerTest extends AbstractTestCase $controllerStub = $this->createMock(StructureController::class); $controllerStub->expects($this->once())->method('__invoke')->with($request); - $indexes = new Indexes(new ResponseRenderer(), new Template(), DatabaseInterface::getInstance()); + $indexes = new Indexes(DatabaseInterface::getInstance()); $controller = new UniqueController(new ResponseRenderer(), new Template(), $controllerStub, $indexes); $controller($request); @@ -90,7 +90,7 @@ class UniqueControllerTest extends AbstractTestCase $controllerStub->expects($this->never())->method('__invoke'); $response = new ResponseRenderer(); - $indexes = new Indexes(new ResponseRenderer(), new Template(), DatabaseInterface::getInstance()); + $indexes = new Indexes(DatabaseInterface::getInstance()); $controller = new UniqueController($response, new Template(), $controllerStub, $indexes); $controller($request); @@ -120,7 +120,7 @@ class UniqueControllerTest extends AbstractTestCase $controllerStub = $this->createMock(StructureController::class); $controllerStub->expects($this->once())->method('__invoke')->with($request); - $indexes = new Indexes(new ResponseRenderer(), new Template(), DatabaseInterface::getInstance()); + $indexes = new Indexes(DatabaseInterface::getInstance()); $controller = new UniqueController(new ResponseRenderer(), new Template(), $controllerStub, $indexes); $controller($request); diff --git a/tests/classes/Stubs/DbiDummy.php b/tests/classes/Stubs/DbiDummy.php index e1c9a23623..73219b23d3 100644 --- a/tests/classes/Stubs/DbiDummy.php +++ b/tests/classes/Stubs/DbiDummy.php @@ -1651,6 +1651,11 @@ class DbiDummy implements DbiExtension 'columns' => ['Name', 'Engine', 'Rows'], 'result' => [['test_table', 'InnoDB', '3']], ], + [ + 'query' => 'SHOW TABLE STATUS FROM `test_db` WHERE `Name` LIKE \'test\\\\_table\\\\_index\\\\_rename%\'', + 'columns' => ['Name', 'Engine', 'Rows'], + 'result' => [['test_table_index_rename', 'InnoDB', '3']], + ], [ 'query' => 'SHOW TABLE STATUS FROM `test_db` WHERE Name = \'test_table\'', 'columns' => ['Name', 'Engine', 'Rows'], @@ -1661,6 +1666,11 @@ class DbiDummy implements DbiExtension 'columns' => ['Table', 'Non_unique', 'Key_name', 'Column_name'], 'result' => [['test_table', '0', 'PRIMARY', 'id']], ], + [ + 'query' => 'SHOW INDEXES FROM `test_db`.`test_table_index_rename`', + 'columns' => ['Table', 'Non_unique', 'Key_name', 'Column_name'], + 'result' => [['test_table_index_rename', '0', 'PRIMARY', 'id']], + ], [ 'query' => 'SHOW INDEX FROM `test_table`;', 'columns' => ['Table', 'Non_unique', 'Key_name', 'Column_name'], diff --git a/tests/classes/Table/IndexesTest.php b/tests/classes/Table/IndexesTest.php index 5f16a220af..d55c2ad589 100644 --- a/tests/classes/Table/IndexesTest.php +++ b/tests/classes/Table/IndexesTest.php @@ -4,86 +4,86 @@ declare(strict_types=1); namespace PhpMyAdmin\Tests\Table; -use PhpMyAdmin\Config; use PhpMyAdmin\Current; use PhpMyAdmin\DatabaseInterface; -use PhpMyAdmin\Http\Factory\ServerRequestFactory; use PhpMyAdmin\Index; +use PhpMyAdmin\Message; use PhpMyAdmin\Table\Indexes; use PhpMyAdmin\Table\Table; -use PhpMyAdmin\Template; use PhpMyAdmin\Tests\AbstractTestCase; -use PhpMyAdmin\Tests\Stubs\ResponseRenderer as ResponseStub; use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\MockObject\MockObject; #[CoversClass(Indexes::class)] class IndexesTest extends AbstractTestCase { + private DatabaseInterface&MockObject $dbi; + protected function setUp(): void { parent::setUp(); - /** - * SET these to avoid undefined index error - */ Current::$database = 'db'; Current::$table = 'table'; - $config = Config::getInstance(); - $config->selectedServer['pmadb'] = ''; - $config->selectedServer['DisableIS'] = false; - $GLOBALS['urlParams'] = ['db' => 'db', 'server' => 1]; - $dbi = $this->getMockBuilder(DatabaseInterface::class) + $this->dbi = $this->getMockBuilder(DatabaseInterface::class) ->disableOriginalConstructor() ->getMock(); - - $indexs = [ - ['Schema' => 'Schema1', 'Key_name' => 'Key_name1', 'Column_name' => 'Column_name1'], - ['Schema' => 'Schema2', 'Key_name' => 'Key_name2', 'Column_name' => 'Column_name2'], - ['Schema' => 'Schema3', 'Key_name' => 'Key_name3', 'Column_name' => 'Column_name3'], - ]; - - $dbi->expects($this->any())->method('getTableIndexes') - ->willReturn($indexs); - - DatabaseInterface::$instance = $dbi; - - //$_SESSION } - public function testDoSaveData(): void + public function testGetSqlQueryForRename(): void { - $sqlQuery = 'ALTER TABLE `db`.`table` DROP PRIMARY KEY, ADD UNIQUE ;'; + $sqlQuery = 'ALTER TABLE `db`.`table` RENAME INDEX `0` TO `ABC`;'; + $this->dbi->expects($this->any())->method('getVersion') + ->willReturn(50700); + + $index = new Index(['Key_name' => 'ABC']); + + $indexes = new Indexes($this->dbi); + + $sqlResult = $indexes->getSqlQueryForRename('0', $index, Current::$database, Current::$table); + $this->assertStringContainsString($sqlQuery, $sqlResult); + + // Error message + $index->setName('NOT PRIMARY'); // Cannot rename primary so the operation should fail + $indexes->getSqlQueryForRename('PRIMARY', $index, Current::$database, Current::$table); + $error = $indexes->getError(); + $this->assertInstanceOf(Message::class, $error); + + $index->setName('PRIMARY'); // The new name cannot be PRIMARY so the operation should fail + $indexes->getSqlQueryForRename('NOT PRIMARY', $index, Current::$database, Current::$table); + $error = $indexes->getError(); + $this->assertInstanceOf(Message::class, $error); + } + + public function testGetSqlQueryForIndexCreateOrEdit(): void + { $table = $this->getMockBuilder(Table::class) ->disableOriginalConstructor() ->getMock(); - $table->expects($this->any())->method('getSqlQueryForIndexCreateOrEdit') - ->willReturn($sqlQuery); - - $dbi = DatabaseInterface::getInstance(); - $dbi->expects($this->any())->method('getTable') + $this->dbi->expects($this->any())->method('getTable') ->willReturn($table); + $indexes = new Indexes($this->dbi); - $response = new ResponseStub(); - $index = new Index(); + $db = 'pma_db'; + $table = 'pma_table'; + $index = new Index([ + 'Key_name' => 'PRIMARY', + 'columns' => [['Column_name' => 'id']], + ]); - $indexes = new Indexes($response, new Template(), $dbi); + $sqlQueryExpected = 'ALTER TABLE `pma_db`.`pma_table` DROP PRIMARY KEY, ADD PRIMARY KEY (`id`);'; - $request = ServerRequestFactory::create()->createServerRequest('GET', 'http://example.com/') - ->withQueryParams(['ajax_request' => '1']); + $_POST['old_index'] = 'PRIMARY'; + $this->assertEquals( + $sqlQueryExpected, + $indexes->getSqlQueryForIndexCreateOrEdit('PRIMARY', $index, $db, $table), + ); - // Preview SQL - $indexes->doSaveData($request, $index, false, Current::$database, Current::$table, true); - $jsonArray = $response->getJSONResult(); - $this->assertArrayHasKey('sql_data', $jsonArray); - $this->assertStringContainsString($sqlQuery, $jsonArray['sql_data']); - - // Alter success - $response->clear(); - $indexes->doSaveData($request, $index, false, Current::$database, Current::$table, false); - $jsonArray = $response->getJSONResult(); - $this->assertArrayHasKey('index_table', $jsonArray); - $this->assertArrayHasKey('message', $jsonArray); + // Error message + $index->setName('NOT PRIMARY'); // Cannot rename primary so the operation should fail + $indexes->getSqlQueryForIndexCreateOrEdit('PRIMARY', $index, $db, $table); + $this->assertInstanceOf(Message::class, $indexes->getError()); } } diff --git a/tests/classes/Table/TableTest.php b/tests/classes/Table/TableTest.php index 3d4b81969a..e00096c26f 100644 --- a/tests/classes/Table/TableTest.php +++ b/tests/classes/Table/TableTest.php @@ -9,7 +9,6 @@ use PhpMyAdmin\ConfigStorage\Relation; use PhpMyAdmin\ConfigStorage\RelationParameters; use PhpMyAdmin\DatabaseInterface; use PhpMyAdmin\Dbal\Connection; -use PhpMyAdmin\Index; use PhpMyAdmin\ListDatabase; use PhpMyAdmin\Query\Cache; use PhpMyAdmin\SqlParser\Context; @@ -1180,42 +1179,6 @@ class TableTest extends AbstractTestCase $this->assertEquals($sqlExcepted, $sql); } - /** - * Tests for getSqlQueryForIndexCreateOrEdit() method. - */ - public function testGetSqlQueryForIndexCreateOrEdit(): void - { - $db = 'pma_db'; - $table = 'pma_table'; - $index = new Index(); - $error = false; - - $_POST['old_index'] = 'PRIMARY'; - - $table = new Table($table, $db, DatabaseInterface::getInstance()); - $sql = $table->getSqlQueryForIndexCreateOrEdit($index, $error); - - $this->assertEquals('ALTER TABLE `pma_db`.`pma_table` DROP PRIMARY KEY, ADD UNIQUE;', $sql); - } - - /** - * Tests for getSqlQueryForIndexCreateOrEdit() method. - */ - public function testGetSqlQueryForIndexCreateOrEditSecondFormat(): void - { - $db = 'pma_db'; - $table = 'pma_table'; - $index = new Index(); - $error = false; - - $_POST['old_index']['Key_name'] = 'PRIMARY'; - - $table = new Table($table, $db, DatabaseInterface::getInstance()); - $sql = $table->getSqlQueryForIndexCreateOrEdit($index, $error); - - $this->assertEquals('ALTER TABLE `pma_db`.`pma_table` DROP PRIMARY KEY, ADD UNIQUE;', $sql); - } - /** * Test for getColumns */