Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions libraries/classes/Controllers/Table/Structure/ChangeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\Message;
use PhpMyAdmin\ResponseRenderer;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Statements\CreateStatement;
use PhpMyAdmin\Table\ColumnsDefinition;
use PhpMyAdmin\Template;
use PhpMyAdmin\Transformations;
use PhpMyAdmin\Url;

use function __;
use function count;
use function is_array;

final class ChangeController extends AbstractController
{
Expand Down Expand Up @@ -73,6 +76,11 @@ private function displayHtmlForColumnChange(?array $selected): void
{
global $action, $num_fields;

// Column metadata reports the effective collation, including one inherited from the table.
$inheritedCollations = self::getInheritedCollations(
$this->dbi->getTable($this->db, $this->table)->showCreate()
);

if (empty($selected)) {
$selected[] = $_REQUEST['field'];
$selected_cnt = 1;
Expand All @@ -93,6 +101,10 @@ private function displayHtmlForColumnChange(?array $selected): void
$message->addParam($selected[$i]);
$this->response->addHTML($message->getDisplay());
} else {
if (isset($inheritedCollations[$selected[$i]])) {
$value['Collation'] = null;
}

$fields_meta[] = $value;
}
}
Expand Down Expand Up @@ -122,4 +134,42 @@ private function displayHtmlForColumnChange(?array $selected): void

$this->render('columns_definitions/column_definitions_form', $templateData);
}

/** @return array<string, true> */
private static function getInheritedCollations(string $createTable): array
{
if ($createTable === '') {
return [];
}

$parser = new Parser($createTable);
if ($parser->errors !== []) {
return [];
}

$statement = $parser->statements[0] ?? null;
if (! $statement instanceof CreateStatement || ! is_array($statement->fields)) {
return [];
}

$inheritedCollations = [];
foreach ($statement->fields as $field) {
if ($field->name === null || $field->type === null) {
continue;
}

$options = $field->type->options;
if (
$options->has('COLLATE') !== false
|| $options->has('CHARACTER SET') !== false
|| $options->has('CHARSET') !== false
) {
continue;
}

$inheritedCollations[$field->name] = true;
}

return $inheritedCollations;
}
}
61 changes: 58 additions & 3 deletions test/classes/Controllers/Table/Structure/ChangeControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,26 @@
use PhpMyAdmin\Tests\AbstractTestCase;
use PhpMyAdmin\Tests\Stubs\ResponseRenderer as ResponseStub;
use PhpMyAdmin\Transformations;
use PHPUnit\Framework\Attributes\CoversClass;
use ReflectionClass;

use const PHP_VERSION_ID;

/**
* @covers \PhpMyAdmin\Controllers\Table\Structure\ChangeController
*/
#[CoversClass(ChangeController::class)]
class ChangeControllerTest extends AbstractTestCase
{
protected function setUp(): void
{
parent::setUp();
$GLOBALS['cfg']['Server']['DisableIS'] = false;
}

public function testChangeController(): void
{
$GLOBALS['server'] = 1;
$GLOBALS['text_dir'] = 'ltr';
$GLOBALS['PMA_PHP_SELF'] = 'index.php';
$GLOBALS['cfg']['Server']['DisableIS'] = false;
$GLOBALS['db'] = 'testdb';
$GLOBALS['table'] = 'mytable';
$_REQUEST['field'] = '_id';
Expand Down Expand Up @@ -61,4 +64,56 @@ public function testChangeController(): void
. ' value="_id">' . "\n", $actual);
self::assertStringContainsString('id="enumEditorModal"', $actual);
}

public function testInheritedColumnCollationIsNotSelected(): void
{
$GLOBALS['PMA_PHP_SELF'] = 'index.php';
$GLOBALS['db'] = 'testdb';
$GLOBALS['table'] = 'mytable';

$this->dummyDbi->addResult(
'SHOW CREATE TABLE `testdb`.`mytable`',
[
[
'mytable',
'CREATE TABLE `mytable` ('
. ' `inherited` varchar(20) NOT NULL,'
. ' `explicit` varchar(20) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL'
. ') DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci',
],
],
['Table', 'Create Table']
);
foreach (['inherited', 'explicit'] as $field) {
$this->dummyDbi->addResult(
'SHOW FULL COLUMNS FROM `testdb`.`mytable` LIKE \'' . $field . '\'',
[[$field, 'varchar(20)', 'latin1_swedish_ci', 'NO', '', null, '', '', '']],
['Field', 'Type', 'Collation', 'Null', 'Key', 'Default', 'Extra', 'Privileges', 'Comment']
);
}

$response = new ResponseStub();
$controller = new ChangeController(
$response,
new Template(),
'testdb',
'mytable',
new Relation($this->dbi),
new Transformations(),
$this->dbi
);
$method = (new ReflectionClass(ChangeController::class))->getMethod('displayHtmlForColumnChange');
if (PHP_VERSION_ID < 80100) {
$method->setAccessible(true);
}

$method->invokeArgs($controller, [['inherited', 'explicit']]);
$html = $response->getHTMLResult();
self::assertStringContainsString('name="field_collation_orig[0]" value=""', $html);
self::assertStringContainsString(
'name="field_collation_orig[1]" value="latin1_swedish_ci"',
$html
);
$this->assertAllQueriesConsumed();
}
}