Merge branch 'QA_5_2'

Signed-off-by: Maurício Meneghini Fauth <mauricio@mfauth.net>
This commit is contained in:
Maurício Meneghini Fauth 2026-02-10 22:32:28 -03:00
commit 17e34144a1
No known key found for this signature in database
GPG Key ID: 6A16FD38AFC89CC8
15 changed files with 614 additions and 521 deletions

View File

@ -4560,13 +4560,13 @@ parameters:
-
message: '#^Cannot access property \$name on PhpMyAdmin\\SqlParser\\Components\\DataType\|null\.$#'
identifier: property.nonObject
count: 2
count: 1
path: src/Database/Routines.php
-
message: '#^Cannot access property \$options on PhpMyAdmin\\SqlParser\\Components\\DataType\|null\.$#'
identifier: property.nonObject
count: 2
count: 1
path: src/Database/Routines.php
-
@ -4578,7 +4578,7 @@ parameters:
-
message: '#^Cannot access property \$parameters on PhpMyAdmin\\SqlParser\\Components\\DataType\|null\.$#'
identifier: property.nonObject
count: 3
count: 2
path: src/Database/Routines.php
-
@ -4588,9 +4588,9 @@ parameters:
path: src/Database/Routines.php
-
message: '#^Cannot call method get\(\) on PhpMyAdmin\\SqlParser\\Components\\OptionsArray\|null\.$#'
message: '#^Cannot call method has\(\) on PhpMyAdmin\\SqlParser\\Components\\OptionsArray\|null\.$#'
identifier: method.nonObject
count: 1
count: 2
path: src/Database/Routines.php
-
@ -4623,6 +4623,12 @@ parameters:
count: 4
path: src/Database/Routines.php
-
message: '#^PHPDoc tag @var with type string\|false is not subtype of type bool\.$#'
identifier: varTag.type
count: 2
path: src/Database/Routines.php
-
message: '#^Parameter \#1 \$alias of method PhpMyAdmin\\Types\:\:mapAliasToMysqlType\(\) expects string, mixed given\.$#'
identifier: argument.type
@ -4899,6 +4905,12 @@ parameters:
count: 1
path: src/Dbal/DatabaseInterface.php
-
message: '#^Parameter \#1 \$mode of static method PhpMyAdmin\\SqlParser\\Context\:\:setMode\(\) expects int\|string, string\|null given\.$#'
identifier: argument.type
count: 1
path: src/Dbal/DatabaseInterface.php
-
message: '#^Parameter \#1 \$path of function basename expects string, mixed given\.$#'
identifier: argument.type
@ -7161,7 +7173,7 @@ parameters:
-
message: '#^Cannot access offset mixed on mixed\.$#'
identifier: offsetAccess.nonOffsetAccessible
count: 1
count: 2
path: src/Indexes/Index.php
-
@ -12238,11 +12250,17 @@ parameters:
path: src/Table/Indexes.php
-
message: '#^Only booleans are allowed in a negated boolean, int\|null given\.$#'
message: '#^Only booleans are allowed in a negated boolean, string\|null given\.$#'
identifier: booleanNot.exprNotBoolean
count: 1
path: src/Table/Indexes.php
-
message: '#^Only booleans are allowed in an if condition, int\|null given\.$#'
identifier: if.condNotBoolean
count: 1
path: src/Table/Indexes.php
-
message: '#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\.$#'
identifier: foreach.nonIterable

View File

@ -3381,13 +3381,10 @@
<MixedArgumentTypeCoercion>
<code><![CDATA[$itemParamDir]]></code>
<code><![CDATA[$itemParamName]]></code>
<code><![CDATA[$options]]></code>
<code><![CDATA[$options]]></code>
<code><![CDATA[$retval['item_param_opts_num'][$idx]]]></code>
<code><![CDATA[$value]]></code>
</MixedArgumentTypeCoercion>
<MixedArrayAccess>
<code><![CDATA[$opt['value']]]></code>
<code><![CDATA[$opt['value']]]></code>
<code><![CDATA[$routine['item_param_dir'][$i]]]></code>
<code><![CDATA[$routine['item_param_dir'][$i]]]></code>
@ -3431,8 +3428,6 @@
<code><![CDATA[$itemParamName]]></code>
<code><![CDATA[$itemParamType]]></code>
<code><![CDATA[$opt]]></code>
<code><![CDATA[$opt]]></code>
<code><![CDATA[$options[]]]></code>
<code><![CDATA[$retval['item_param_opts_num'][$idx][]]]></code>
<code><![CDATA[$value]]></code>
<code><![CDATA[$value]]></code>
@ -3491,11 +3486,9 @@
<code><![CDATA[$param->type->parameters]]></code>
<code><![CDATA[$routine['SPECIFIC_NAME']]]></code>
<code><![CDATA[$routine['SPECIFIC_NAME']]]></code>
<code><![CDATA[$stmt->return->parameters]]></code>
</PossiblyNullArgument>
<PossiblyNullIterator>
<code><![CDATA[$param->type->options->options]]></code>
<code><![CDATA[$stmt->return->options->options]]></code>
</PossiblyNullIterator>
<PossiblyNullOperand>
<code><![CDATA[$_POST['item_original_type']]]></code>
@ -3505,13 +3498,9 @@
<code><![CDATA[$param->type->options]]></code>
<code><![CDATA[$param->type->options->options]]></code>
<code><![CDATA[$param->type->parameters]]></code>
<code><![CDATA[$stmt->return->name]]></code>
<code><![CDATA[$stmt->return->options]]></code>
<code><![CDATA[$stmt->return->options->options]]></code>
<code><![CDATA[$stmt->return->parameters]]></code>
</PossiblyNullPropertyFetch>
<PossiblyNullReference>
<code><![CDATA[get]]></code>
<code><![CDATA[has]]></code>
</PossiblyNullReference>
<PossiblyUndefinedArrayOffset>
<code><![CDATA[$_POST['item_original_type']]]></code>
@ -3633,6 +3622,7 @@
</PossiblyInvalidOperand>
<PossiblyNullArgument>
<code><![CDATA[$_SERVER['SCRIPT_NAME']]]></code>
<code><![CDATA[$version['@@sql_mode']]]></code>
</PossiblyNullArgument>
<PossiblyNullArrayAccess>
<code><![CDATA[$resultTarget[]]]></code>
@ -5005,9 +4995,11 @@
<code><![CDATA[$params['columns']]]></code>
</MixedArgument>
<MixedArrayOffset>
<code><![CDATA[$columns['collations'][$key]]]></code>
<code><![CDATA[$columns['sub_parts'][$key]]]></code>
</MixedArrayOffset>
<MixedAssignment>
<code><![CDATA[$collation]]></code>
<code><![CDATA[$column]]></code>
<code><![CDATA[$key]]></code>
<code><![CDATA[$name]]></code>
@ -8048,7 +8040,8 @@
<code><![CDATA[$column->getSubPart()]]></code>
</PossiblyNullOperand>
<RiskyTruthyFalsyComparison>
<code><![CDATA[! $column->getSubPart()]]></code>
<code><![CDATA[! $column->getCollation()]]></code>
<code><![CDATA[$column->getSubPart()]]></code>
</RiskyTruthyFalsyComparison>
</file>
<file src="src/Table/Search.php">

View File

@ -753,7 +753,10 @@ const makeGrid = function (t, enableResize = undefined, enableReorder = undefine
// remove decimal places if column type not supported
if ((decimals === 0) && ($thisField.attr('data-type').indexOf('time') !== -1)) {
newHtml = newHtml.substring(0, newHtml.indexOf('.'));
var index = newHtml.indexOf('.');
if (index !== -1) {
newHtml = newHtml.substring(0, index);
}
}
// remove additional decimal places

View File

@ -99,7 +99,7 @@
<option value="">{{ t('Charset') }}</option>
<option value=""></option>
{% for charset in charsets %}
<option value="{{ charset.getName() }}" title="{{ charset.getDescription() }}"{{ routine.item_returnopts_text == charset.getName() ? ' selected' }}>{{ charset.getName() }}</option>
<option value="{{ charset.getName() }}" title="{{ charset.getDescription() }}"{{ routine.item_returnopts_text|lower == charset.getName() ? ' selected' }}>{{ charset.getName() }}</option>
{% endfor %}
</select>
</div>

View File

@ -143,6 +143,9 @@
<th>
{{ t('Size') }}
</th>
<th>
{{ t('Collation') }}
</th>
</tr>
</thead>
{% set spatial_types = [
@ -190,6 +193,13 @@
value="{{ index.getChoice() != 'SPATIAL' ?
column.getSubPart() }}">
</td>
<td>
<select name="index[columns][collations][]">
<option value=""{{ column.Collation ? ' selected' }}>{{ t('None') }}</option>
<option value="A"{{ column.Collation == 'A' ? ' selected' }} title="ASC">A</option>
<option value="D"{{ column.Collation == 'D' ? ' selected' }} title="DESC">D</option>
</select>
</td>
</tr>
{% endfor %}
{% if add_fields > 0 %}
@ -223,6 +233,13 @@
name="index[columns][sub_parts][]"
value="">
</td>
<td>
<select name="index[columns][collations][]">
<option value="" selected>{{ t('None') }}</option>
<option value="A" title="ASC">A</option>
<option value="D" title="DESC">D</option>
</select>
</td>
</tr>
{% endfor %}
{% endif %}

View File

@ -62,6 +62,17 @@ final class EventsController implements InvocableController
if ($request->isAjax()) {
if (Current::$message instanceof Message && Current::$message->isSuccess()) {
$events = $this->events->getDetails(Current::$database, $_POST['item_name']);
if ($events === []) {
$this->response->setRequestStatus(false);
$this->response->addJSON(
'message',
__('The event was dropped immediately after creation.'),
);
$this->response->addJSON('tableType', 'events');
return $this->response->response();
}
$event = $events[0];
$this->response->addJSON(
'name',

View File

@ -479,19 +479,34 @@ class Routines
$retval['item_returnopts_num'] = '';
$retval['item_returnopts_text'] = '';
if (! empty($routine['DTD_IDENTIFIER'])) {
$options = [];
foreach ($stmt->return->options->options as $opt) {
$options[] = is_string($opt) ? $opt : $opt['value'];
}
if (! empty($routine['DTD_IDENTIFIER']) && $stmt->return !== null) {
$retval['item_returntype'] = $stmt->return->name;
$retval['item_returnlength'] = implode(',', $stmt->return->parameters);
$retval['item_returnopts_num'] = implode(' ', $options);
$retval['item_returnopts_text'] = implode(' ', $options);
// Extract charset (CHARSET or CHARACTER SET) separately from numeric options
/** @var string|false $charset */
$charset = $stmt->return->options->has('CHARSET');
if ($charset === false) {
/** @var string|false $charset */
$charset = $stmt->return->options->has('CHARACTER SET');
}
$retval['item_returnopts_text'] = is_string($charset) ? mb_strtolower($charset) : '';
// Extract numeric options (UNSIGNED, ZEROFILL, UNSIGNED ZEROFILL.)
$numericOpts = [];
foreach ($stmt->return->options->options as $opt) {
if (! is_string($opt) || ! in_array($opt, $this->numericOptions, true)) {
continue;
}
$numericOpts[] = $opt;
}
$retval['item_returnopts_num'] = implode(' ', $numericOpts);
}
$retval['item_definer'] = $stmt->options->get('DEFINER');
$retval['item_definer'] = $stmt->options?->has('DEFINER') ?? false;
$retval['item_definition'] = $body;
$retval['item_isdeterministic'] = '';
if ($routine['IS_DETERMINISTIC'] === 'YES') {

View File

@ -1032,10 +1032,11 @@ class DatabaseInterface
*/
public function postConnect(Server $currentServer): void
{
$version = $this->fetchSingleRow('SELECT @@version, @@version_comment');
$version = $this->fetchSingleRow('SELECT @@version, @@version_comment, @@sql_mode');
if ($version !== []) {
$this->setVersion($version);
Context::setMode($version['@@sql_mode']);
}
$this->query(

View File

@ -231,9 +231,11 @@ class Index
// coming from form
// $columns[names][]
// $columns[sub_parts][]
// $columns[collations][]
foreach ($columns['names'] as $key => $name) {
$subPart = $columns['sub_parts'][$key] ?? '';
$addedColumns[] = ['Column_name' => $name, 'Sub_part' => $subPart];
$collation = $columns['collations'][$key] ?? null;
$addedColumns[] = ['Column_name' => $name, 'Sub_part' => $subPart, 'Collation' => $collation];
}
} else {
// coming from SHOW INDEXES

View File

@ -17,7 +17,7 @@ class IndexColumn
/** @var int The column sequence number in the index, starting with 1. */
private int $seqInIndex = 1;
/** @var string|null How the column is sorted in the index. "A" (Ascending) or NULL (Not sorted) */
/** @var string|null How the column is sorted in the index. "A" (Ascending), "D" (Descending) or NULL (Not sorted) */
private string|null $collation = null;
/**

View File

@ -98,11 +98,20 @@ final class Indexes
$indexFields = [];
foreach ($index->getColumns() as $key => $column) {
$indexFields[$key] = Util::backquote($column->getName());
if (! $column->getSubPart()) {
if ($column->getSubPart()) {
$indexFields[$key] .= '(' . $column->getSubPart() . ')';
}
if (! $column->getCollation()) {
continue;
}
$indexFields[$key] .= '(' . $column->getSubPart() . ')';
$collation = $column->getCollation();
if ($collation === 'A') {
$indexFields[$key] .= ' ASC';
} elseif ($collation === 'D') {
$indexFields[$key] .= ' DESC';
}
}
if ($indexFields === []) {

View File

@ -466,16 +466,25 @@ class Table implements Stringable
// if column is virtual, check if server type is Mysql as only Mysql server
// supports extra column properties
$isVirtualColMysql = $virtuality && Compatibility::isMySqlOrPerconaDb($dbi);
// if column is virtual, check if server type is MariaDB as MariaDB server
// supports no extra virtual column properties except CHARACTER SET for text column types
$isVirtualColMariaDB = $virtuality && Compatibility::isMariaDb($dbi);
$matches = preg_match('@^(TINYTEXT|TEXT|MEDIUMTEXT|LONGTEXT|VARCHAR|CHAR|ENUM|SET)$@i', $type) === 1;
if ($collation !== '' && $collation !== 'NULL' && $matches) {
$query .= Util::getCharsetQueryPart(
$isVirtualColMariaDB ? (string) preg_replace('~_.+~s', '', $collation) : $collation,
true,
);
// if column is virtual, check if server type is MariaDB as MariaDB server
// supports no extra virtual column properties except CHARACTER SET for text column types
// https://jira.mariadb.org/browse/MDEV-12161
if ($virtuality && Compatibility::isMariaDb($dbi)) {
$version = $dbi->getVersion();
$isVirtualCollationSupported =
$version >= 100509 ||
$version >= 100418 && $version < 100500 ||
$version >= 100328 && $version < 100400 ||
$version >= 100237 && $version < 100300;
if (! $isVirtualCollationSupported) {
$collation = (string) preg_replace('~_.+~s', '', $collation);
}
}
$query .= Util::getCharsetQueryPart($collation, true);
}
if ($virtuality !== '') {

View File

@ -16,7 +16,6 @@ use PhpMyAdmin\Plugins\Export\ExportSql;
use PhpMyAdmin\Plugins\ExportType;
use PhpMyAdmin\SqlParser\Components\Expression;
use PhpMyAdmin\SqlParser\Components\OptionsArray;
use PhpMyAdmin\SqlParser\Context;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Statements\AlterStatement;
use PhpMyAdmin\SqlParser\Statements\CreateStatement;
@ -503,11 +502,6 @@ class TableMover
*/
$destination = new Expression($targetDb, $targetTable, '');
// Find server's SQL mode so the builder can generate correct
// queries.
// One of the options that alters the behaviour is `ANSI_QUOTES`.
Context::setMode((string) $this->dbi->fetchValue('SELECT @@sql_mode'));
// -----------------------------------------------------------------
// Phase 1: Dropping existent element of the same name (if exists
// and required).

View File

@ -203,6 +203,8 @@ final class DatabaseInterfaceTest extends AbstractTestCase
$versionQueryResult = [
'@@version' => '10.20.7-MariaDB-1:10.9.3+maria~ubu2204',
'@@version_comment' => 'mariadb.org binary distribution',
'@@sql_mode' => 'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,'
. 'NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION',
];
LanguageManager::getInstance()->availableLanguages();
@ -818,6 +820,8 @@ final class DatabaseInterfaceTest extends AbstractTestCase
[
'@@version' => '6.1.0',
'@@version_comment' => "Percona Server (GPL), Release '11', Revision 'c1y2gr1df4a'",
'@@sql_mode' => 'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,'
. 'NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION',
],
60100,
true,
@ -828,6 +832,8 @@ final class DatabaseInterfaceTest extends AbstractTestCase
[
'@@version' => '10.01.40-MariaDB-1:10.01.40+maria~ubu2204',
'@@version_comment' => 'mariadb.org binary distribution',
'@@sql_mode' => 'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,'
. 'NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION',
],
100140,
false,
@ -835,13 +841,29 @@ final class DatabaseInterfaceTest extends AbstractTestCase
false,
],
[
['@@version' => '7.10.3', '@@version_comment' => 'MySQL Community Server (GPL)'],
[
'@@version' => '7.10.3',
'@@version_comment' => 'MySQL Community Server (GPL)',
'@@sql_mode' => 'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,'
. 'NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION',
],
71003,
true,
false,
false,
],
[['@@version' => '5.5.0', '@@version_comment' => ''], 50500, true, false, false],
[
[
'@@version' => '5.5.0',
'@@version_comment' => '',
'@@sql_mode' => 'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,'
. 'NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION',
],
50500,
true,
false,
false,
],
];
}

939
yarn.lock

File diff suppressed because it is too large Load Diff