Merge PR #19361 Column sorting does not work when names contain dots

Signed-off-by: Maurício Meneghini Fauth <mauricio@mfauth.net>
This commit is contained in:
Maurício Meneghini Fauth 2025-02-21 20:17:14 -03:00
commit 063555083e
No known key found for this signature in database
GPG Key ID: 6A16FD38AFC89CC8
5 changed files with 286 additions and 323 deletions

View File

@ -6192,18 +6192,6 @@ parameters:
count: 1
path: src/Display/Results.php
-
message: '#^Binary operation "\." between non\-empty\-string and mixed results in an error\.$#'
identifier: binaryOp.invalid
count: 1
path: src/Display/Results.php
-
message: '#^Binary operation "\." between string and \(list\<string\>\|string\) results in an error\.$#'
identifier: binaryOp.invalid
count: 1
path: src/Display/Results.php
-
message: '#^Binary operation "/" between int and mixed results in an error\.$#'
identifier: binaryOp.invalid
@ -6219,7 +6207,7 @@ parameters:
-
message: '#^Call to function array_filter\(\) requires parameter \#2 to be passed to avoid loose comparison semantics\.$#'
identifier: arrayFilter.strict
count: 3
count: 2
path: src/Display/Results.php
-
@ -6327,7 +6315,7 @@ parameters:
-
message: '#^Cannot access property \$expr on PhpMyAdmin\\SqlParser\\Components\\Expression\|null\.$#'
identifier: property.nonObject
count: 2
count: 3
path: src/Display/Results.php
-
@ -6354,6 +6342,18 @@ parameters:
count: 2
path: src/Display/Results.php
-
message: '#^Cannot access property \$type on PhpMyAdmin\\SqlParser\\Token\|null\.$#'
identifier: property.nonObject
count: 10
path: src/Display/Results.php
-
message: '#^Cannot access property \$value on PhpMyAdmin\\SqlParser\\Token\|null\.$#'
identifier: property.nonObject
count: 10
path: src/Display/Results.php
-
message: '#^Cannot cast mixed to int\.$#'
identifier: cast.int
@ -6369,7 +6369,7 @@ parameters:
-
message: '#^Construct empty\(\) is not allowed\. Use more strict comparison\.$#'
identifier: empty.notAllowed
count: 24
count: 23
path: src/Display/Results.php
-
@ -6381,7 +6381,7 @@ parameters:
-
message: '#^Loose comparison via "\=\=" is not allowed\.$#'
identifier: equal.notAllowed
count: 7
count: 3
path: src/Display/Results.php
-
@ -6480,12 +6480,6 @@ parameters:
count: 1
path: src/Display/Results.php
-
message: '#^Parameter \#1 \$haystack of function str_contains expects string, mixed given\.$#'
identifier: argument.type
count: 3
path: src/Display/Results.php
-
message: '#^Parameter \#1 \$offset of method PhpMyAdmin\\Dbal\\ResultInterface\:\:seek\(\) expects int, float\|int given\.$#'
identifier: argument.type
@ -6552,12 +6546,6 @@ parameters:
count: 1
path: src/Display/Results.php
-
message: '#^Parameter \#2 \$string of function explode expects string, mixed given\.$#'
identifier: argument.type
count: 1
path: src/Display/Results.php
-
message: '#^Parameter \#2 \$string2 of function strcasecmp expects string, mixed given\.$#'
identifier: argument.type
@ -6588,12 +6576,6 @@ parameters:
count: 1
path: src/Display/Results.php
-
message: '#^Parameter \#3 \$subject of function str_replace expects array\<string\>\|string, mixed given\.$#'
identifier: argument.type
count: 3
path: src/Display/Results.php
-
message: '#^Parameter \#4 \$database of class PhpMyAdmin\\Display\\ForeignKeyRelatedTable constructor expects string, mixed given\.$#'
identifier: argument.type
@ -6601,13 +6583,7 @@ parameters:
path: src/Display/Results.php
-
message: '#^Parameter \#4 \$sortExpressionNoDirection of method PhpMyAdmin\\Display\\Results\:\:getTableHeaders\(\) expects array\<int, string\>, list\<string\|null\> given\.$#'
identifier: argument.type
count: 1
path: src/Display/Results.php
-
message: '#^Parameter \#5 \$sortDirection of method PhpMyAdmin\\Display\\Results\:\:getSingleAndMultiSortUrls\(\) expects array\<string\>, array\<mixed\> given\.$#'
message: '#^Parameter \#6 \$colVisib of method PhpMyAdmin\\Display\\Results\:\:getOrderLinkAndSortedHeaderHtml\(\) expects array\<mixed\>\|bool, mixed given\.$#'
identifier: argument.type
count: 1
path: src/Display/Results.php
@ -6619,13 +6595,7 @@ parameters:
path: src/Display/Results.php
-
message: '#^Parameter \#8 \$colVisib of method PhpMyAdmin\\Display\\Results\:\:getOrderLinkAndSortedHeaderHtml\(\) expects array\<mixed\>\|bool, mixed given\.$#'
identifier: argument.type
count: 1
path: src/Display/Results.php
-
message: '#^Parameter \#9 \$colVisibElement of method PhpMyAdmin\\Display\\Results\:\:getOrderLinkAndSortedHeaderHtml\(\) expects int\|string\|null, mixed given\.$#'
message: '#^Parameter \#7 \$colVisibElement of method PhpMyAdmin\\Display\\Results\:\:getOrderLinkAndSortedHeaderHtml\(\) expects int\|string\|null, mixed given\.$#'
identifier: argument.type
count: 1
path: src/Display/Results.php

View File

@ -4007,39 +4007,29 @@
<InvalidArgument>
<code><![CDATA[$linkingUrlParams]]></code>
<code><![CDATA[$linkingUrlParams]]></code>
<code><![CDATA[$sortExpressionNoDirection]]></code>
</InvalidArgument>
<MixedArgument>
<code><![CDATA[$_SESSION['tmpval']['max_rows']]]></code>
<code><![CDATA[$_SESSION['tmpval']['pos'] / $_SESSION['tmpval']['max_rows']]]></code>
<code><![CDATA[$_SESSION['tmpval']['query']]]></code>
<code><![CDATA[$_SESSION['tmpval']['query']]]></code>
<code><![CDATA[$clause]]></code>
<code><![CDATA[$colOrder]]></code>
<code><![CDATA[$colVisib]]></code>
<code><![CDATA[$colVisib]]></code>
<code><![CDATA[$colVisibCurrent]]></code>
<code><![CDATA[$expr->alias]]></code>
<code><![CDATA[$field->table]]></code>
<code><![CDATA[$newSortExpressionNoDirection]]></code>
<code><![CDATA[$rel['foreign_db']]]></code>
<code><![CDATA[$rel['foreign_db']]]></code>
<code><![CDATA[$rel['foreign_field']]]></code>
<code><![CDATA[$rel['foreign_table']]]></code>
<code><![CDATA[$rel['foreign_table']]]></code>
<code><![CDATA[$sortExpressionNoDirection[$indexInExpression]]]></code>
<code><![CDATA[$sortExpressionNoDirection[$indexInExpression]]]></code>
<code><![CDATA[$sortExpressionNoDirection[$indexInExpression]]]></code>
<code><![CDATA[$statementInfo->statement->from]]></code>
<code><![CDATA[$urlParams['where_clause']]]></code>
<code><![CDATA[(int) $this->unlimNumRows / $_SESSION['tmpval']['max_rows']]]></code>
<code><![CDATA[(int) $this->unlimNumRows / $_SESSION['tmpval']['max_rows']]]></code>
<code><![CDATA[empty($field->database) ? $this->db : $field->database]]></code>
</MixedArgument>
<MixedArgumentTypeCoercion>
<code><![CDATA[$sortDirection]]></code>
<code><![CDATA[$sortExpression]]></code>
</MixedArgumentTypeCoercion>
<MixedArrayAccess>
<code><![CDATA[$_SESSION['tmpval']['display_binary']]]></code>
<code><![CDATA[$_SESSION['tmpval']['display_binary']]]></code>
@ -4156,7 +4146,6 @@
<code><![CDATA[$_SESSION['tmpval']['query'][$sqlMd5]]]></code>
<code><![CDATA[$_SESSION['tmpval']['relational_display']]]></code>
<code><![CDATA[$_SESSION['tmpval']['repeat_cells']]]></code>
<code><![CDATA[$clause]]></code>
<code><![CDATA[$colOrder]]></code>
<code><![CDATA[$colVisib]]></code>
<code><![CDATA[$colVisibCurrent]]></code>
@ -4168,7 +4157,6 @@
<code><![CDATA[$i]]></code>
<code><![CDATA[$identifier]]></code>
<code><![CDATA[$meta->name]]></code>
<code><![CDATA[$newSortExpressionNoDirection]]></code>
<code><![CDATA[$query]]></code>
<code><![CDATA[$relationalDisplay]]></code>
<code><![CDATA[$value]]></code>
@ -4178,7 +4166,6 @@
<code><![CDATA[$_SESSION['tmpval']['max_rows']]]></code>
<code><![CDATA[$_SESSION['tmpval']['pos']]]></code>
<code><![CDATA[$_SESSION['tmpval']['repeat_cells']]]></code>
<code><![CDATA[$sortExpressionNoDirection[$indexInExpression]]]></code>
</MixedOperand>
<MixedPropertyFetch>
<code><![CDATA[$expr->alias]]></code>
@ -4210,9 +4197,18 @@
</PossiblyNullArrayAccess>
<PossiblyNullOperand>
<code><![CDATA[$dispval]]></code>
<code><![CDATA[$o->expr->expr]]></code>
</PossiblyNullOperand>
<PossiblyNullPropertyFetch>
<code><![CDATA[$lexer->list[0]->type]]></code>
<code><![CDATA[$lexer->list[0]->type]]></code>
<code><![CDATA[$lexer->list[0]->value]]></code>
<code><![CDATA[$lexer->list[0]->value]]></code>
<code><![CDATA[$lexer->list[2]->type]]></code>
<code><![CDATA[$lexer->list[2]->type]]></code>
<code><![CDATA[$lexer->list[2]->value]]></code>
<code><![CDATA[$lexer->list[2]->value]]></code>
<code><![CDATA[$lexer->list[4]->type]]></code>
<code><![CDATA[$lexer->list[4]->value]]></code>
<code><![CDATA[$o->expr->expr]]></code>
<code><![CDATA[$order->expr->column]]></code>
</PossiblyNullPropertyFetch>

View File

@ -15,6 +15,7 @@ use PhpMyAdmin\Dbal\ResultInterface;
use PhpMyAdmin\FieldMetadata;
use PhpMyAdmin\Html\Generator;
use PhpMyAdmin\Index;
use PhpMyAdmin\IndexColumn;
use PhpMyAdmin\Message;
use PhpMyAdmin\MessageType;
use PhpMyAdmin\Plugins\Transformations\Output\Text_Octetstream_Sql;
@ -23,8 +24,11 @@ use PhpMyAdmin\Plugins\Transformations\Output\Text_Plain_Sql;
use PhpMyAdmin\Plugins\Transformations\Text_Plain_Link;
use PhpMyAdmin\Plugins\TransformationsInterface;
use PhpMyAdmin\ResponseRenderer;
use PhpMyAdmin\SqlParser\Components\OrderKeyword;
use PhpMyAdmin\SqlParser\Lexer;
use PhpMyAdmin\SqlParser\Parser;
use PhpMyAdmin\SqlParser\Statements\SelectStatement;
use PhpMyAdmin\SqlParser\TokenType;
use PhpMyAdmin\SqlParser\Utils\Query;
use PhpMyAdmin\SqlParser\Utils\StatementInfo;
use PhpMyAdmin\SqlParser\Utils\StatementType;
@ -40,7 +44,7 @@ use PhpMyAdmin\Utils\Gis;
use function __;
use function array_filter;
use function array_keys;
use function array_map;
use function array_merge;
use function array_shift;
use function bin2hex;
@ -54,6 +58,7 @@ use function in_array;
use function is_array;
use function is_int;
use function is_numeric;
use function is_string;
use function json_encode;
use function max;
use function mb_check_encoding;
@ -67,7 +72,6 @@ use function pack;
use function preg_match;
use function random_int;
use function str_contains;
use function str_ends_with;
use function str_replace;
use function strcasecmp;
use function strip_tags;
@ -677,21 +681,15 @@ class Results
*
* @see getTableHeaders()
*
* @param mixed[] $sortExpression sort expression
* @param array<int, string> $sortExpressionNoDirection sort expression
* without direction
* @param mixed[] $sortDirection sort direction
* @param bool $isLimitedDisplay with limited operations
* or not
* @param list<SortExpression> $sortExpressions
* @param bool $isLimitedDisplay with limited operations or not
*
* @return string html content
*/
private function getTableHeadersForColumns(
bool $hasSortLink,
StatementInfo $statementInfo,
array $sortExpression,
array $sortExpressionNoDirection,
array $sortDirection,
array $sortExpressions,
bool $isLimitedDisplay,
): string {
// required to generate sort links that will remember whether the
@ -727,12 +725,10 @@ class Results
if ($hasSortLink && ! $isLimitedDisplay) {
$sortedHeaderData = $this->getOrderLinkAndSortedHeaderHtml(
$this->fieldsMeta[$i],
$sortExpression,
$sortExpressionNoDirection,
$sortExpressions,
$statementInfo,
$sessionMaxRows,
$comments,
$sortDirection,
$colVisib,
$colVisibCurrent,
);
@ -777,10 +773,8 @@ class Results
*
* @see getTable()
*
* @param mixed[] $sortExpression sort expression
* @param array<int, string> $sortExpressionNoDirection sort expression without direction
* @param mixed[] $sortDirection sort direction
* @param bool $isLimitedDisplay with limited operations or not
* @param list<SortExpression> $sortExpressions
* @param bool $isLimitedDisplay with limited operations or not
*
* @psalm-return array{
* column_order: array,
@ -794,9 +788,7 @@ class Results
private function getTableHeaders(
DisplayParts $displayParts,
StatementInfo $statementInfo,
array $sortExpression = [],
array $sortExpressionNoDirection = [],
array $sortDirection = [],
array $sortExpressions = [],
bool $isLimitedDisplay = false,
): array {
// Output data needed for column reordering and show/hide column
@ -833,9 +825,7 @@ class Results
$tableHeadersForColumns = $this->getTableHeadersForColumns(
$displayParts->hasSortLink,
$statementInfo,
$sortExpression,
$sortExpressionNoDirection,
$sortDirection,
$sortExpressions,
$isLimitedDisplay,
);
@ -861,14 +851,14 @@ class Results
*
* @see getTableHeaders()
*
* @param mixed[]|null $sortExpression the sort expression
* @param string $unsortedSqlQuery the unsorted sql query
* @param string[] $sortExpression the sort expression
* @param string $unsortedSqlQuery the unsorted sql query
*
* @return mixed[][]
* @psalm-return array{hidden_fields?:array, options?:array}
*/
private function getSortByKeyDropDown(
array|null $sortExpression,
array $sortExpression,
string $unsortedSqlQuery,
): array {
// grab indexes data:
@ -892,17 +882,19 @@ class Results
}
$isIndexUsed = false;
$localOrder = is_array($sortExpression) ? implode(', ', $sortExpression) : '';
$localOrder = implode(', ', $sortExpression);
$options = [];
foreach ($indexes as $index) {
$ascSort = '`'
. implode('` ASC, `', array_keys($index->getColumns()))
. '` ASC';
$ascSort = implode(', ', array_map(
static fn (IndexColumn $indexName) => Util::backquote($indexName->getName()) . ' ASC',
$index->getColumns(),
));
$descSort = '`'
. implode('` DESC, `', array_keys($index->getColumns()))
. '` DESC';
$descSort = implode(', ', array_map(
static fn (IndexColumn $indexName) => Util::backquote($indexName->getName()) . ' DESC',
$index->getColumns(),
));
$isIndexUsed = $isIndexUsed
|| $localOrder === $ascSort
@ -1160,15 +1152,13 @@ class Results
*
* @see getTableHeaders()
*
* @param FieldMetadata $fieldsMeta set of field properties
* @param mixed[] $sortExpression sort expression
* @param array<int, string> $sortExpressionNoDirection sort expression without direction
* @param int $sessionMaxRows maximum rows resulted by sql
* @param string $comments comment for row
* @param mixed[] $sortDirection sort direction
* @param bool|mixed[] $colVisib column is visible(false)
* or column isn't visible(string array)
* @param int|string|null $colVisibElement element of $col_visib array
* @param FieldMetadata $fieldsMeta set of field properties
* @param list<SortExpression> $sortExpressions
* @param int $sessionMaxRows maximum rows resulted by sql
* @param string $comments comment for row
* @param bool|mixed[] $colVisib column is visible(false)
* or column isn't visible(string array)
* @param int|string|null $colVisibElement element of $col_visib array
*
* @return array{
* column_name: string,
@ -1182,12 +1172,10 @@ class Results
*/
private function getOrderLinkAndSortedHeaderHtml(
FieldMetadata $fieldsMeta,
array $sortExpression,
array $sortExpressionNoDirection,
array $sortExpressions,
StatementInfo $statementInfo,
int $sessionMaxRows,
string $comments,
array $sortDirection,
bool|array $colVisib,
int|string|null $colVisibElement,
): array {
@ -1199,17 +1187,15 @@ class Results
$sortTable = $fieldsMeta->table !== ''
&& $fieldsMeta->orgname === $fieldsMeta->name
? Util::backquote($fieldsMeta->table) . '.'
? $fieldsMeta->table
: '';
// Generates the orderby clause part of the query which is part
// of URL
[$singleSortOrder, $multiSortOrder, $orderImg] = $this->getSingleAndMultiSortUrls(
$sortExpression,
$sortExpressionNoDirection,
$sortExpressions,
$sortTable,
$fieldsMeta->name,
$sortDirection,
$fieldsMeta,
);
@ -1262,116 +1248,97 @@ class Results
/**
* Prepare parameters and html for sorted table header fields
*
* @param mixed[] $sortExpression sort expression
* @param array<int, string> $sortExpressionNoDirection sort expression without direction
* @param string $sortTable The name of the table to which
* the current column belongs to
* @param string $nameToUseInSort The current column under
* consideration
* @param string[] $sortDirection sort direction
* @param FieldMetadata $fieldsMeta set of field properties
* @param list<SortExpression> $sortExpressions
* @param string $currentTable The name of the table to which
* the current column belongs to
* @param string $currentColumn The current column under consideration
* @param FieldMetadata $fieldsMeta set of field properties
*
* @return string[] 3 element array - $single_sort_order, $sort_order, $order_img
*/
private function getSingleAndMultiSortUrls(
array $sortExpression,
array $sortExpressionNoDirection,
string $sortTable,
string $nameToUseInSort,
array $sortDirection,
array $sortExpressions,
string $currentTable,
string $currentColumn,
FieldMetadata $fieldsMeta,
): array {
// Check if the current column is in the order by clause
$isInSort = $this->isInSorted($sortExpression, $sortExpressionNoDirection, $sortTable, $nameToUseInSort);
$currentName = $nameToUseInSort;
if ($sortExpressionNoDirection[0] == '' || ! $isInSort) {
$specialIndex = $sortExpressionNoDirection[0] == ''
? 0
: count($sortExpressionNoDirection);
$sortExpressionNoDirection[$specialIndex] = Util::backquote($currentName);
// Set the direction to the config value
$sortDirection[$specialIndex] = $this->config->settings['Order'];
// Or perform SMART mode
$isInSort = $this->isInSorted($sortExpressions, $currentTable, $currentColumn);
if ($sortExpressions === [] || ! $isInSort) {
$tableName = $currentTable === '' ? '' : Util::backquote($currentTable) . '.';
$expression = $tableName . Util::backquote($currentColumn);
// Set the direction to the config value or perform SMART mode
if ($this->config->settings['Order'] === self::SMART_SORT_ORDER) {
$isTimeOrDate = $fieldsMeta->isType(FieldMetadata::TYPE_TIME)
|| $fieldsMeta->isType(FieldMetadata::TYPE_DATE)
|| $fieldsMeta->isType(FieldMetadata::TYPE_DATETIME)
|| $fieldsMeta->isType(FieldMetadata::TYPE_TIMESTAMP);
$sortDirection[$specialIndex] = $isTimeOrDate ? self::DESCENDING_SORT_DIR : self::ASCENDING_SORT_DIR;
$direction = $fieldsMeta->isDateTimeType()
? self::DESCENDING_SORT_DIR
: self::ASCENDING_SORT_DIR;
} else {
$direction = $this->config->settings['Order'];
}
$sortExpressions[] = new SortExpression(
$currentTable === '' ? null : $currentTable,
$currentColumn,
$direction,
$expression,
);
}
$sortExpressionNoDirection = array_filter($sortExpressionNoDirection);
$singleSortOrder = '';
$sortOrderColumns = [];
foreach ($sortExpressionNoDirection as $index => $expression) {
$sortOrder = '';
// check if this is the first clause,
// if it is then we have to add "order by"
$isFirstClause = $index === 0;
$nameToUseInSort = $expression;
$sortTableNew = $sortTable;
// Test to detect if the column name is a standard name
// Standard name has the table name prefixed to the column name
if (str_contains($nameToUseInSort, '.') && ! str_contains($nameToUseInSort, '(')) {
$matches = explode('.', $nameToUseInSort);
// Matches[0] has the table name
// Matches[1] has the column name
$nameToUseInSort = $matches[1];
$sortTableNew = $matches[0];
}
// $name_to_use_in_sort might contain a space due to
// formatting of function expressions like "COUNT(name )"
// so we remove the space in this situation
$nameToUseInSort = str_replace([' )', '``'], [')', '`'], $nameToUseInSort);
$nameToUseInSort = trim($nameToUseInSort, '`');
foreach ($sortExpressions as $index => $expression) {
// If this the first column name in the order by clause add
// order by clause to the column name
$sortOrder .= $isFirstClause ? "\nORDER BY " : '';
$sortOrder = $index === 0 ? 'ORDER BY ' : '';
// Again a check to see if the given column is a aggregate column
if (str_contains($nameToUseInSort, '(')) {
$sortOrder .= $nameToUseInSort;
$tableOfColumn = $currentTable;
$column = '';
// Test to detect if the column name is a standard name
// Standard name has the table name prefixed to the column name
if ($expression->columnName === null) {
$sortOrder .= $expression->expression;
} elseif ($expression->tableName !== null) {
$column = $expression->columnName;
$sortOrder .= Util::backquote($expression->tableName) . '.' . Util::backquote($column);
$tableOfColumn = $expression->tableName;
} else {
if ($sortTableNew !== '' && ! str_ends_with($sortTableNew, '.')) {
$sortTableNew .= '.';
}
$sortOrder .= $sortTableNew . Util::backquote($nameToUseInSort);
$column = $expression->columnName;
$sortOrder .= Util::backquote($column);
}
// Incase this is the current column save $single_sort_order
if ($currentName === $nameToUseInSort) {
$singleSortOrder = "\n" . 'ORDER BY ';
if (! str_contains($currentName, '(')) {
$singleSortOrder .= $sortTable;
if ($currentColumn === $column && $currentTable === $tableOfColumn) {
$singleSortOrder = 'ORDER BY ';
if ($currentTable !== '') {
$singleSortOrder .= Util::backquote($currentTable) . '.';
}
$singleSortOrder .= Util::backquote($currentName) . ' ';
$singleSortOrder .= Util::backquote($currentColumn) . ' ';
if ($isInSort) {
[$singleSortOrder, $orderImg] = $this->getSortingUrlParams(
$sortDirection[$index],
$singleSortOrder,
);
$singleSortOrder .= match ($expression->direction) {
self::ASCENDING_SORT_DIR => self::DESCENDING_SORT_DIR,
self::DESCENDING_SORT_DIR => self::ASCENDING_SORT_DIR,
};
} else {
$singleSortOrder .= strtoupper($sortDirection[$index]);
$singleSortOrder .= $expression->direction;
}
}
$sortOrder .= ' ';
if ($currentName === $nameToUseInSort && $isInSort) {
if ($currentColumn === $column && $currentTable === $tableOfColumn && $isInSort) {
// We need to generate the arrow button and related html
[$sortOrder, $orderImg] = $this->getSortingUrlParams($sortDirection[$index], $sortOrder);
$orderImg = $this->getSortingUrlParams($expression->direction);
$orderImg .= ' <small>' . ($index + 1) . '</small>';
$sortOrder .= match ($expression->direction) {
self::ASCENDING_SORT_DIR => self::DESCENDING_SORT_DIR,
self::DESCENDING_SORT_DIR => self::ASCENDING_SORT_DIR,
};
} else {
$sortOrder .= strtoupper($sortDirection[$index]);
$sortOrder .= $expression->direction;
}
// Separate columns by a comma
$sortOrderColumns[] = $sortOrder;
}
@ -1383,79 +1350,38 @@ class Results
*
* @see getTableHeaders()
*
* @param mixed[] $sortExpression sort expression
* @param mixed[] $sortExpressionNoDirection sort expression without direction
* @param string $sortTable the table name
* @param string $nameToUseInSort the sorting column name
* @param list<SortExpression> $sortExpressions
*/
private function isInSorted(
array $sortExpression,
array $sortExpressionNoDirection,
string $sortTable,
string $nameToUseInSort,
array $sortExpressions,
string $currentTable,
string $currentColumn,
): bool {
$indexInExpression = 0;
foreach ($sortExpressionNoDirection as $index => $clause) {
if (str_contains($clause, '.')) {
$fragments = explode('.', $clause);
$clause2 = $fragments[0] . '.' . str_replace('`', '', $fragments[1]);
} else {
$clause2 = $sortTable . str_replace('`', '', $clause);
foreach ($sortExpressions as $sortExpression) {
if ($sortExpression->columnName === null) {
continue;
}
if ($clause2 === $sortTable . $nameToUseInSort) {
$indexInExpression = $index;
break;
if ($sortExpression->tableName !== null) {
if ($currentTable === $sortExpression->tableName && $currentColumn === $sortExpression->columnName) {
return true;
}
} elseif ($currentColumn === $sortExpression->columnName) {
return true;
}
}
if (empty($sortExpression[$indexInExpression])) {
return false;
}
// Field name may be preceded by a space, or any number
// of characters followed by a dot (tablename.fieldname)
// so do a direct comparison for the sort expression;
// this avoids problems with queries like
// "SELECT id, count(id)..." and clicking to sort
// on id or on count(id).
// Another query to test this:
// SELECT p.*, FROM_UNIXTIME(p.temps) FROM mytable AS p
// (and try clicking on each column's header twice)
$noSortTable = $sortTable === '' || ! str_contains($sortExpressionNoDirection[$indexInExpression], $sortTable);
$noOpenParenthesis = ! str_contains($sortExpressionNoDirection[$indexInExpression], '(');
if ($sortTable !== '' && $noSortTable && $noOpenParenthesis) {
$newSortExpressionNoDirection = $sortTable
. $sortExpressionNoDirection[$indexInExpression];
} else {
$newSortExpressionNoDirection = $sortExpressionNoDirection[$indexInExpression];
}
//Back quotes are removed in next comparison, so remove them from value
//to compare.
$nameToUseInSort = str_replace('`', '', $nameToUseInSort);
$sortName = str_replace('`', '', $sortTable) . $nameToUseInSort;
return $sortName == str_replace('`', '', $newSortExpressionNoDirection)
|| $sortName == str_replace('`', '', $sortExpressionNoDirection[$indexInExpression]);
return false;
}
/**
* Get sort url parameters - sort order and order image
*
* @see getSingleAndMultiSortUrls()
*
* @param string $sortDirection the sort direction
* @param string $sortOrder the sorting order
*
* @return string[] 2 element array - $sort_order, $order_img
*/
private function getSortingUrlParams(string $sortDirection, string $sortOrder): array
private function getSortingUrlParams(string $sortDirection): string
{
if (strtoupper(trim($sortDirection)) === self::DESCENDING_SORT_DIR) {
$sortOrder .= self::ASCENDING_SORT_DIR;
if ($sortDirection === self::DESCENDING_SORT_DIR) {
$orderImg = ' ' . Generator::getImage(
's_desc',
__('Descending'),
@ -1467,7 +1393,6 @@ class Results
['class' => 'soimg hide', 'title' => ''],
);
} else {
$sortOrder .= self::DESCENDING_SORT_DIR;
$orderImg = ' ' . Generator::getImage(
's_asc',
__('Ascending'),
@ -1480,7 +1405,7 @@ class Results
);
}
return [$sortOrder, $orderImg];
return $orderImg;
}
/**
@ -3056,26 +2981,16 @@ class Results
// 1.3 Extract sorting expressions.
// we need $sort_expression and $sort_expression_nodirection
// even if there are many table references
$sortExpression = [];
$sortExpressionNoDirection = [];
$sortDirection = [];
$sortExpressions = [];
if ($statement !== null && ! empty($statement->order)) {
foreach ($statement->order as $o) {
$sortExpression[] = $o->expr->expr . ' ' . $o->type->value;
$sortExpressionNoDirection[] = $o->expr->expr;
$sortDirection[] = $o->type->value;
}
} else {
$sortExpression[] = '';
$sortExpressionNoDirection[] = '';
$sortDirection[] = '';
$sortExpressions = $this->extractSortingExpressions($statement->order);
}
// 1.4 Prepares display of first and last value of the sorted column
$sortedColumnMessage = '';
foreach ($sortExpressionNoDirection as $expression) {
$sortedColumnMessage .= $this->getSortedColumnMessage($dtResult, $expression);
foreach ($sortExpressions as $expression) {
$sortedColumnMessage .= $this->getSortedColumnMessage($dtResult, $expression->expression);
}
// 2. ----- Prepare to display the top of the page -----
@ -3121,6 +3036,11 @@ class Results
// Data is sorted by indexes only if there is only one table.
if ($this->isSelect($statementInfo)) {
$sortExpression = [];
foreach ($sortExpressions as $expression) {
$sortExpression[] = $expression->expression . ' ' . $expression->direction;
}
$sortByKeyData = $this->getSortByKeyDropDown($sortExpression, $unsortedSqlQuery);
}
}
@ -3155,14 +3075,7 @@ class Results
// end 2b
// 3. ----- Prepare the results table -----
$headers = $this->getTableHeaders(
$displayParts,
$statementInfo,
$sortExpression,
$sortExpressionNoDirection,
$sortDirection,
$isLimitedDisplay,
);
$headers = $this->getTableHeaders($displayParts, $statementInfo, $sortExpressions, $isLimitedDisplay);
$body = $this->getTableBody($dtResult, $displayParts, $map, $statementInfo, $isLimitedDisplay);
@ -3881,4 +3794,79 @@ class Results
return $str;
}
/** @return array{table:string|null, column:string|null} */
private function parseStringIntoTableAndColumn(string $nameToUseInSort): array
{
$lexer = new Lexer($nameToUseInSort);
if (
$lexer->list->count === 6
&& ($lexer->list[2]->type === TokenType::Symbol || $lexer->list[2]->type === TokenType::None)
&& is_string($lexer->list[2]->value)
&& ($lexer->list[4]->type === TokenType::Symbol || $lexer->list[4]->type === TokenType::None)
&& is_string($lexer->list[4]->value)
) {
// If a database name, table name, and column name were provided
return ['table' => $lexer->list[2]->value, 'column' => $lexer->list[4]->value];
}
if (
$lexer->list->count === 4
&& ($lexer->list[0]->type === TokenType::Symbol || $lexer->list[0]->type === TokenType::None)
&& is_string($lexer->list[0]->value)
&& ($lexer->list[2]->type === TokenType::Symbol || $lexer->list[2]->type === TokenType::None)
&& is_string($lexer->list[2]->value)
) {
// If a table name and column name were provided
return ['table' => $lexer->list[0]->value, 'column' => $lexer->list[2]->value];
}
if (
$lexer->list->count === 2
&& ($lexer->list[0]->type === TokenType::Symbol || $lexer->list[0]->type === TokenType::None)
&& is_string($lexer->list[0]->value)
) {
// If only a column name was provided
return ['table' => null, 'column' => $lexer->list[0]->value];
}
// If some other expression was provided
return ['table' => null, 'column' => null];
}
/**
* @param OrderKeyword[] $orderKeywords
*
* @return list<SortExpression>
*/
private function extractSortingExpressions(array $orderKeywords): array
{
$expressions = [];
foreach ($orderKeywords as $o) {
if ((string) (int) $o->expr->expr === $o->expr->expr) {
// If a numerical column index is used, we need to convert it to a column name
$field = $this->fieldsMeta[(int) $o->expr->expr - 1];
$normalizedExpression = '';
$tableName = null;
if ($field->table !== '') {
$tableName = $field->table;
$normalizedExpression = Util::backquote($field->table) . '.';
}
$columnName = $field->name;
$normalizedExpression .= Util::backquote($field->name);
} else {
$normalizedExpression = $o->expr->expr ?? '';
[
'table' => $tableName,
'column' => $columnName,
] = $this->parseStringIntoTableAndColumn($normalizedExpression);
}
$expressions[] = new SortExpression($tableName, $columnName, $o->type->value, $normalizedExpression);
}
return $expressions;
}
}

View File

@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace PhpMyAdmin\Display;
final readonly class SortExpression
{
/** @param 'ASC'|'DESC' $direction */
public function __construct(
public string|null $tableName,
public string|null $columnName,
public string $direction,
public string $expression = '',
) {
}
}

View File

@ -12,6 +12,7 @@ use PhpMyAdmin\Dbal\DatabaseInterface;
use PhpMyAdmin\Display\DeleteLinkEnum;
use PhpMyAdmin\Display\DisplayParts;
use PhpMyAdmin\Display\Results as DisplayResults;
use PhpMyAdmin\Display\SortExpression;
use PhpMyAdmin\FieldMetadata;
use PhpMyAdmin\Html\Generator;
use PhpMyAdmin\Message;
@ -1227,13 +1228,13 @@ class ResultsTest extends AbstractTestCase
[
'column_name' => 'id',
'order_link' => '<a href="index.php?route=/sql&db=test_db&table=test_table'
. '&sql_query=SELECT+%2A+FROM+%60test_db%60.%60test_table%60%0AORDER+BY+%60id%60+ASC'
. '&sql_signature=8e37a2f4521e45af28e26a01518e63de65737b7028e73c9536a86e83046ac1be'
. '&sql_query=SELECT+%2A+FROM+%60test_db%60.%60test_table%60+ORDER+BY+%60id%60+ASC'
. '&sql_signature=73befb6d61047cd774152967254d1efb6a8bf79e01c0cabfd7f434bb43094dc1'
. '&session_max_rows=25&is_browse_distinct=0&server=2&lang=en" class="sortlink">id'
. '<input type="hidden" value="'
. 'index.php?route=/sql&db=test_db&table=test_table'
. '&sql_query=SELECT+%2A+FROM+%60test_db%60.%60test_table%60%0AORDER+BY+%60id%60+ASC'
. '&sql_signature=8e37a2f4521e45af28e26a01518e63de65737b7028e73c9536a86e83046ac1be'
. '&sql_query=SELECT+%2A+FROM+%60test_db%60.%60test_table%60+ORDER+BY+%60id%60+ASC'
. '&sql_signature=73befb6d61047cd774152967254d1efb6a8bf79e01c0cabfd7f434bb43094dc1'
. '&session_max_rows=25&is_browse_distinct=0&server=2&lang=en"></a>'
. '<input type="hidden" name="url-remove-order" value="index.php?route=/sql&db=test_db'
. '&table=test_table&sql_query=SELECT+%2A+FROM+%60test_db%60.%60test_table%60'
@ -1242,8 +1243,8 @@ class ResultsTest extends AbstractTestCase
. '&discard_remembered_sort=1">' . "\n"
. '<input type="hidden" name="url-add-order" value="'
. 'index.php?route=/sql&db=test_db&table=test_table'
. '&sql_query=SELECT+%2A+FROM+%60test_db%60.%60test_table%60%0AORDER+BY+%60id%60+ASC'
. '&sql_signature=8e37a2f4521e45af28e26a01518e63de65737b7028e73c9536a86e83046ac1be'
. '&sql_query=SELECT+%2A+FROM+%60test_db%60.%60test_table%60+ORDER+BY+%60id%60+ASC'
. '&sql_signature=73befb6d61047cd774152967254d1efb6a8bf79e01c0cabfd7f434bb43094dc1'
. '&session_max_rows=25&is_browse_distinct=0&server=2&lang=en">',
'comments' => '',
'is_browse_pointer_enabled' => true,
@ -1254,13 +1255,13 @@ class ResultsTest extends AbstractTestCase
[
'column_name' => 'name',
'order_link' => '<a href="index.php?route=/sql&db=test_db&table=test_table'
. '&sql_query=SELECT+%2A+FROM+%60test_db%60.%60test_table%60%0AORDER+BY+%60name%60+ASC'
. '&sql_signature=aec7d5c52573ac9d51c318d643fc62a8578baab162d902ce6d10318d6c5e0674'
. '&sql_query=SELECT+%2A+FROM+%60test_db%60.%60test_table%60+ORDER+BY+%60name%60+ASC'
. '&sql_signature=848cc55530c90276732424e88b62d91144cc2a8c827e006c196cdcf744e457b7'
. '&session_max_rows=25&is_browse_distinct=0&server=2&lang=en" class="sortlink">name'
. '<input type="hidden" value="'
. 'index.php?route=/sql&db=test_db&table=test_table'
. '&sql_query=SELECT+%2A+FROM+%60test_db%60.%60test_table%60%0AORDER+BY+%60name%60+ASC'
. '&sql_signature=aec7d5c52573ac9d51c318d643fc62a8578baab162d902ce6d10318d6c5e0674'
. '&sql_query=SELECT+%2A+FROM+%60test_db%60.%60test_table%60+ORDER+BY+%60name%60+ASC'
. '&sql_signature=848cc55530c90276732424e88b62d91144cc2a8c827e006c196cdcf744e457b7'
. '&session_max_rows=25&is_browse_distinct=0&server=2&lang=en"></a>'
. '<input type="hidden" name="url-remove-order" value="index.php?route=/sql&db=test_db'
. '&table=test_table&sql_query=SELECT+%2A+FROM+%60test_db%60.%60test_table%60'
@ -1269,8 +1270,8 @@ class ResultsTest extends AbstractTestCase
. '&discard_remembered_sort=1">' . "\n"
. '<input type="hidden" name="url-add-order" value="'
. 'index.php?route=/sql&db=test_db&table=test_table'
. '&sql_query=SELECT+%2A+FROM+%60test_db%60.%60test_table%60%0AORDER+BY+%60name%60+ASC'
. '&sql_signature=aec7d5c52573ac9d51c318d643fc62a8578baab162d902ce6d10318d6c5e0674'
. '&sql_query=SELECT+%2A+FROM+%60test_db%60.%60test_table%60+ORDER+BY+%60name%60+ASC'
. '&sql_signature=848cc55530c90276732424e88b62d91144cc2a8c827e006c196cdcf744e457b7'
. '&session_max_rows=25&is_browse_distinct=0&server=2&lang=en">',
'comments' => '',
'is_browse_pointer_enabled' => true,
@ -1281,15 +1282,15 @@ class ResultsTest extends AbstractTestCase
[
'column_name' => 'datetimefield',
'order_link' => '<a href="index.php?route=/sql&db=test_db&table=test_table'
. '&sql_query=SELECT+%2A+FROM+%60test_db%60.%60test_table%60%0A'
. '&sql_query=SELECT+%2A+FROM+%60test_db%60.%60test_table%60+'
. 'ORDER+BY+%60datetimefield%60+DESC'
. '&sql_signature=6c53a00ab0e73234883b7190dab079c6e9b9b0f96eca2ee78453305599d66fc1'
. '&sql_signature=c9a3b990f85df73464dafec8ecf477df6165d4d51eba1e94ba87c484b97b175c'
. '&session_max_rows=25&is_browse_distinct=0&server=2&lang=en" class="sortlink">datetimefield'
. '<input type="hidden" value="'
. 'index.php?route=/sql&db=test_db&table=test_table'
. '&sql_query=SELECT+%2A+FROM+%60test_db%60.%60test_table%60%0A'
. '&sql_query=SELECT+%2A+FROM+%60test_db%60.%60test_table%60+'
. 'ORDER+BY+%60datetimefield%60+DESC'
. '&sql_signature=6c53a00ab0e73234883b7190dab079c6e9b9b0f96eca2ee78453305599d66fc1'
. '&sql_signature=c9a3b990f85df73464dafec8ecf477df6165d4d51eba1e94ba87c484b97b175c'
. '&session_max_rows=25&is_browse_distinct=0&server=2&lang=en"></a>'
. '<input type="hidden" name="url-remove-order" value="index.php?route=/sql&db=test_db'
. '&table=test_table&sql_query=SELECT+%2A+FROM+%60test_db%60.%60test_table%60'
@ -1298,9 +1299,9 @@ class ResultsTest extends AbstractTestCase
. '&discard_remembered_sort=1">' . "\n"
. '<input type="hidden" name="url-add-order" value="'
. 'index.php?route=/sql&db=test_db&table=test_table'
. '&sql_query=SELECT+%2A+FROM+%60test_db%60.%60test_table%60%0A'
. '&sql_query=SELECT+%2A+FROM+%60test_db%60.%60test_table%60+'
. 'ORDER+BY+%60datetimefield%60+DESC'
. '&sql_signature=6c53a00ab0e73234883b7190dab079c6e9b9b0f96eca2ee78453305599d66fc1'
. '&sql_signature=c9a3b990f85df73464dafec8ecf477df6165d4d51eba1e94ba87c484b97b175c'
. '&session_max_rows=25&is_browse_distinct=0&server=2&lang=en">',
'comments' => '',
'is_browse_pointer_enabled' => true,
@ -1520,13 +1521,13 @@ class ResultsTest extends AbstractTestCase
'column_name' => 'Rows',
'order_link' => '<a href="index.php?route=/sql&db=test_db&table=test_table'
. '&sql_query=SELECT+COUNT%28%2A%29+AS+%60Rows%60%2C+%60name%60+FROM+%60test_table'
. '%60+GROUP+BY+%60name%60%0AORDER+BY+%60Rows%60+ASC&sql_signature='
. '9711b509280946a71dcad03bf3c85b2d4b3062cab1ffd59119b299d80c07b577&session_max_rows=25'
. '%60+GROUP+BY+%60name%60+ORDER+BY+%60Rows%60+ASC&sql_signature='
. '5384a639efa206f521eeb74e4d4d9aff0b53cc19093bd1b406b088a082e9fedc&session_max_rows=25'
. '&is_browse_distinct=1&server=2&lang=en" class="sortlink">Rows<input type="hidden" value="'
. 'index.php?route=/sql&db=test_db&table=test_table&sql_query='
. 'SELECT+COUNT%28%2A%29+AS+%60Rows%60%2C+%60name%60+FROM+%60test_table%60+GROUP+BY+'
. '%60name%60%0AORDER+BY+%60name%60+ASC%2C+%60Rows%60+ASC&sql_signature='
. 'faa662252a63157194e38f4b46b9cb425807cd110147664855decc2899d4ccc0&session_max_rows=25'
. '%60name%60+ORDER+BY+%60name%60+ASC%2C+%60Rows%60+ASC&sql_signature='
. '82594c89d410923f7ded15d65c798b4f1c1325d9453cfd450e20f8c941f2a5e6&session_max_rows=25'
. '&is_browse_distinct=1&server=2&lang=en"></a><input type="hidden" name="url-remove-order"'
. ' value="index.php?route=/sql&db=test_db&table=test_table&sql_query='
. 'SELECT+COUNT%28%2A%29+AS+%60Rows%60%2C+%60name%60+FROM+%60test_table%60+GROUP+BY+%60name'
@ -1536,8 +1537,8 @@ class ResultsTest extends AbstractTestCase
. '<input type="hidden" name="url-add-order" value="'
. 'index.php?route=/sql&db=test_db&table=test_table&sql_query='
. 'SELECT+COUNT%28%2A%29+AS+%60Rows%60%2C+%60name%60+FROM+%60test_table%60+GROUP+BY+'
. '%60name%60%0AORDER+BY+%60name%60+ASC%2C+%60Rows%60+ASC&sql_signature='
. 'faa662252a63157194e38f4b46b9cb425807cd110147664855decc2899d4ccc0&session_max_rows=25'
. '%60name%60+ORDER+BY+%60name%60+ASC%2C+%60Rows%60+ASC&sql_signature='
. '82594c89d410923f7ded15d65c798b4f1c1325d9453cfd450e20f8c941f2a5e6&session_max_rows=25'
. '&is_browse_distinct=1&server=2&lang=en">',
'comments' => '',
'is_browse_pointer_enabled' => true,
@ -1549,15 +1550,15 @@ class ResultsTest extends AbstractTestCase
'column_name' => 'name',
'order_link' => '<a href="index.php?route=/sql&db=test_db&table=test_table'
. '&sql_query=SELECT+COUNT%28%2A%29+AS+%60Rows%60%2C+%60name%60+FROM+%60test_table'
. '%60+GROUP+BY+%60name%60%0AORDER+BY+%60name%60+DESC&sql_signature='
. '5a8c171115c591960b9da8efe375a2f7f4faf44030b33230ad82053648f860ae&session_max_rows=25'
. '%60+GROUP+BY+%60name%60+ORDER+BY+%60name%60+DESC&sql_signature='
. '7157bdc2027884de0d40394faa9780e4e15f4343f564869fc18e5b17206eecfd&session_max_rows=25'
. '&is_browse_distinct=1&server=2&lang=en" class="sortlink">name <img src="themes/dot.gif"'
. ' title="" alt="Ascending" class="icon ic_s_asc soimg"> <img src="themes/dot.gif" title=""'
. ' alt="Descending" class="icon ic_s_desc soimg hide"> <small>1</small><input type="hidden"'
. ' value="index.php?route=/sql&db=test_db&table=test_table&sql_query='
. 'SELECT+COUNT%28%2A%29+AS+%60Rows%60%2C+%60name%60+FROM+%60test_table%60+GROUP+BY+'
. '%60name%60%0AORDER+BY+%60name%60+DESC&sql_signature='
. '5a8c171115c591960b9da8efe375a2f7f4faf44030b33230ad82053648f860ae&session_max_rows=25'
. '%60name%60+ORDER+BY+%60name%60+DESC&sql_signature='
. '7157bdc2027884de0d40394faa9780e4e15f4343f564869fc18e5b17206eecfd&session_max_rows=25'
. '&is_browse_distinct=1&server=2&lang=en"></a><input type="hidden" name="url-remove-order"'
. ' value="index.php?route=/sql&db=test_db&table=test_table&sql_query='
. 'SELECT+COUNT%28%2A%29+AS+%60Rows%60%2C+%60name%60+FROM+%60test_table%60+GROUP+BY+'
@ -1566,8 +1567,8 @@ class ResultsTest extends AbstractTestCase
. '&discard_remembered_sort=1">' . "\n" . '<input type="hidden" name="url-add-order" value="'
. 'index.php?route=/sql&db=test_db&table=test_table&sql_query='
. 'SELECT+COUNT%28%2A%29+AS+%60Rows%60%2C+%60name%60+FROM+%60test_table%60+GROUP+BY+'
. '%60name%60%0AORDER+BY+%60name%60+DESC&sql_signature='
. '5a8c171115c591960b9da8efe375a2f7f4faf44030b33230ad82053648f860ae&session_max_rows=25'
. '%60name%60+ORDER+BY+%60name%60+DESC&sql_signature='
. '7157bdc2027884de0d40394faa9780e4e15f4343f564869fc18e5b17206eecfd&session_max_rows=25'
. '&is_browse_distinct=1&server=2&lang=en">',
'comments' => '',
'is_browse_pointer_enabled' => true,
@ -1762,18 +1763,16 @@ class ResultsTest extends AbstractTestCase
DisplayResults::class,
'getSingleAndMultiSortUrls',
[
['`Country`.`Code` ASC'], // sortExpression,
['`Country`.`Code`'], // sortExpressionNoDirection,
'`Country`.',
[new SortExpression('Country', 'Code', 'ASC', '`Country`.`Code`')],
'Country',
'FoundedIn',
['ASC'], // sortDirection,
FieldHelper::fromArray(['type' => $metaType]),
],
);
self::assertSame([
"\n" . 'ORDER BY `Country`.`FoundedIn` ' . $querySortDirection, // singleSortOrder
"\n" . 'ORDER BY `Country`.`Code` ASC, `Country`.`FoundedIn` ' . $querySortDirection, // sortOrderColumns
'ORDER BY `Country`.`FoundedIn` ' . $querySortDirection, // singleSortOrder
'ORDER BY `Country`.`Code` ASC, `Country`.`FoundedIn` ' . $querySortDirection, // sortOrderColumns
'', // orderImg
], $data);
@ -1782,18 +1781,16 @@ class ResultsTest extends AbstractTestCase
DisplayResults::class,
'getSingleAndMultiSortUrls',
[
['`Country`.`Code` ASC'], // sortExpression,
['`Country`.`Code`'], // sortExpressionNoDirection,
'`Country`.',
[new SortExpression('Country', 'Code', 'ASC', '`Country`.`Code`')],
'Country',
'Code2',
['ASC'], // sortDirection,
FieldHelper::fromArray(['type' => $metaType]),
],
);
self::assertSame([
"\n" . 'ORDER BY `Country`.`Code2` ' . $querySortDirection, // singleSortOrder
"\n" . 'ORDER BY `Country`.`Code` ASC, `Country`.`Code2` ' . $querySortDirection, // sortOrderColumns
'ORDER BY `Country`.`Code2` ' . $querySortDirection, // singleSortOrder
'ORDER BY `Country`.`Code` ASC, `Country`.`Code2` ' . $querySortDirection, // sortOrderColumns
'', // orderImg
], $data);
@ -1803,24 +1800,19 @@ class ResultsTest extends AbstractTestCase
'getSingleAndMultiSortUrls',
[
[
'`Country`.`Continent` DESC","`Country`.`Region` ASC',
'`Country`.`Population` ASC',
], // sortExpression,
[
'`Country`.`Continent`',
'`Country`.`Region`',
'`Country`.`Population`',
], // sortExpressionNoDirection,
'`Country`.',
new SortExpression('Country', 'Continent', 'DESC', '`Country`.`Continent`'),
new SortExpression('Country', 'Region', 'ASC', '`Country`.`Region`'),
new SortExpression('Country', 'Population', 'ASC', '`Country`.`Population`'),
],
'Country',
'Code2',
['DESC', 'ASC', 'ASC'], // sortDirection,
FieldHelper::fromArray(['type' => $metaType]),
],
);
self::assertSame([
"\n" . 'ORDER BY `Country`.`Code2` ' . $querySortDirection, // singleSortOrder
"\n" . 'ORDER BY `Country`.`Continent` DESC, `Country`.`Region` ASC'
'ORDER BY `Country`.`Code2` ' . $querySortDirection, // singleSortOrder
'ORDER BY `Country`.`Continent` DESC, `Country`.`Region` ASC'
. ', `Country`.`Population` ASC, `Country`.`Code2` ' . $querySortDirection, // sortOrderColumns
'', // orderImg
], $data);