mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 12:05:37 +00:00
BUGFIX: Fixed errors caused by complex raw SQL sort() calls. (#7236)
This commit is contained in:
parent
5abf8cf0f3
commit
a8e8a6060a
@ -189,7 +189,13 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab
|
|||||||
$this->dataQuery->sort(null, null); // wipe the sort
|
$this->dataQuery->sort(null, null); // wipe the sort
|
||||||
|
|
||||||
foreach(func_get_arg(0) as $col => $dir) {
|
foreach(func_get_arg(0) as $col => $dir) {
|
||||||
$this->dataQuery->sort($this->getRelationName($col), $dir, false);
|
// Convert column expressions to SQL fragment, while still allowing the passing of raw SQL fragments.
|
||||||
|
try {
|
||||||
|
$relCol = $this->getRelationName($col);
|
||||||
|
} catch(InvalidArgumentException $e) {
|
||||||
|
$relCol = $col;
|
||||||
|
}
|
||||||
|
$this->dataQuery->sort($relCol, $dir, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,12 +256,16 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Translates a Object relation name to a Database name and apply the relation join to
|
* Translates a Object relation name to a Database name and apply the relation join to
|
||||||
* the query
|
* the query. Throws an InvalidArgumentException if the $field doesn't correspond to a relation
|
||||||
*
|
*
|
||||||
* @param string $field
|
* @param string $field
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getRelationName($field) {
|
public function getRelationName($field) {
|
||||||
|
if(!preg_match('/^[A-Z0-9._]+$/i', $field)) {
|
||||||
|
throw new InvalidArgumentException("Bad field expression $field");
|
||||||
|
}
|
||||||
|
|
||||||
if(strpos($field,'.') === false) {
|
if(strpos($field,'.') === false) {
|
||||||
return '"'.$field.'"';
|
return '"'.$field.'"';
|
||||||
}
|
}
|
||||||
|
@ -229,7 +229,7 @@ class DataQuery {
|
|||||||
* @param SQLQuery $query
|
* @param SQLQuery $query
|
||||||
* @return null
|
* @return null
|
||||||
*/
|
*/
|
||||||
protected function ensureSelectContainsOrderbyColumns($query) {
|
protected function ensureSelectContainsOrderbyColumns($query, $originalSelect = array()) {
|
||||||
$tableClasses = ClassInfo::dataClassesFor($this->dataClass);
|
$tableClasses = ClassInfo::dataClassesFor($this->dataClass);
|
||||||
$baseClass = array_shift($tableClasses);
|
$baseClass = array_shift($tableClasses);
|
||||||
|
|
||||||
@ -239,8 +239,13 @@ class DataQuery {
|
|||||||
foreach($orderby as $k => $dir) {
|
foreach($orderby as $k => $dir) {
|
||||||
// don't touch functions in the ORDER BY or function calls
|
// don't touch functions in the ORDER BY or function calls
|
||||||
// selected as fields
|
// selected as fields
|
||||||
if(strpos($k, '(') !== false || preg_match('/_SortColumn/', $k))
|
if(strpos($k, '(') !== false) continue;
|
||||||
|
|
||||||
|
// Pull through SortColumn references from the originalSelect variables
|
||||||
|
if(preg_match('/_SortColumn/', $k)) {
|
||||||
|
if(isset($originalSelect[$k])) $query->selectField($originalSelect[$k], $k);
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$col = str_replace('"', '', trim($k));
|
$col = str_replace('"', '', trim($k));
|
||||||
$parts = explode('.', $col);
|
$parts = explode('.', $col);
|
||||||
@ -616,8 +621,11 @@ class DataQuery {
|
|||||||
*/
|
*/
|
||||||
public function column($field = 'ID') {
|
public function column($field = 'ID') {
|
||||||
$query = $this->getFinalisedQuery(array($field));
|
$query = $this->getFinalisedQuery(array($field));
|
||||||
$query->select($this->expressionForField($field, $query));
|
$originalSelect = $query->itemisedSelect();
|
||||||
$this->ensureSelectContainsOrderbyColumns($query);
|
$fieldExpression = $this->expressionForField($field, $query);
|
||||||
|
$query->clearSelect();
|
||||||
|
$query->selectField($fieldExpression);
|
||||||
|
$this->ensureSelectContainsOrderbyColumns($query, $originalSelect);
|
||||||
|
|
||||||
return $query->execute()->column($field);
|
return $query->execute()->column($field);
|
||||||
}
|
}
|
||||||
|
@ -347,9 +347,8 @@ class SQLQuery {
|
|||||||
$clauses = array();
|
$clauses = array();
|
||||||
|
|
||||||
foreach($sort as $clause) {
|
foreach($sort as $clause) {
|
||||||
$clause = explode(" ", trim($clause));
|
list($column, $direction) = $this->getDirectionFromString($clause, $direction);
|
||||||
$dir = (isset($clause[1])) ? $clause[1] : $direction;
|
$clauses[$column] = $direction;
|
||||||
$clauses[$clause[0]] = $dir;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -357,16 +356,13 @@ class SQLQuery {
|
|||||||
foreach($clauses as $key => $value) {
|
foreach($clauses as $key => $value) {
|
||||||
if(!is_numeric($key)) {
|
if(!is_numeric($key)) {
|
||||||
$column = trim($key);
|
$column = trim($key);
|
||||||
$direction = strtoupper(trim($value));
|
$columnDir = strtoupper(trim($value));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$clause = explode(" ", trim($value));
|
list($column, $columnDir) = $this->getDirectionFromString($value);
|
||||||
|
|
||||||
$column = trim($clause[0]);
|
|
||||||
$direction = (isset($clause[1])) ? strtoupper(trim($clause[1])) : "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->orderby[$column] = $direction;
|
$this->orderby[$column] = $columnDir;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -380,9 +376,9 @@ class SQLQuery {
|
|||||||
// directly in the ORDER BY
|
// directly in the ORDER BY
|
||||||
if($this->orderby) {
|
if($this->orderby) {
|
||||||
$i = 0;
|
$i = 0;
|
||||||
|
|
||||||
foreach($this->orderby as $clause => $dir) {
|
foreach($this->orderby as $clause => $dir) {
|
||||||
if(strpos($clause, '(') !== false) {
|
// Function calls and multi-word columns like "CASE WHEN ..."
|
||||||
|
if(strpos($clause, '(') !== false || strpos($clause, " ") !== false ) {
|
||||||
// remove the old orderby
|
// remove the old orderby
|
||||||
unset($this->orderby[$clause]);
|
unset($this->orderby[$clause]);
|
||||||
|
|
||||||
@ -399,6 +395,22 @@ class SQLQuery {
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the direction part of a single-column order by clause.
|
||||||
|
*
|
||||||
|
* Return a 2 element array: array($column, $direction)
|
||||||
|
*/
|
||||||
|
private function getDirectionFromString($value, $defaultDirection = null) {
|
||||||
|
if(preg_match('/^(.*)(asc|desc)$/i', $value, $matches)) {
|
||||||
|
$column = trim($matches[1]);
|
||||||
|
$direction = strtoupper($matches[2]);
|
||||||
|
} else {
|
||||||
|
$column = $value;
|
||||||
|
$direction = $defaultDirection ? $defaultDirection : "ASC";
|
||||||
|
}
|
||||||
|
return array($column, $direction);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current order by as array if not already. To handle legacy
|
* Returns the current order by as array if not already. To handle legacy
|
||||||
* statements which are stored as strings. Without clauses and directions,
|
* statements which are stored as strings. Without clauses and directions,
|
||||||
|
@ -515,4 +515,18 @@ class DataListTest extends SapphireTest {
|
|||||||
$this->assertEquals('Bob', $list->last()->Name, 'Last comment should be from Bob');
|
$this->assertEquals('Bob', $list->last()->Name, 'Last comment should be from Bob');
|
||||||
$this->assertEquals('Phil', $list->first()->Name, 'First comment should be from Phil');
|
$this->assertEquals('Phil', $list->first()->Name, 'First comment should be from Phil');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testSortByComplexExpression() {
|
||||||
|
// Test an expression with both spaces and commas
|
||||||
|
// This test also tests that column() can be called with a complex sort expression, so keep using column() below
|
||||||
|
$list = DataObjectTest_Team::get()->sort('CASE WHEN "DataObjectTest_Team"."ClassName" = \'DataObjectTest_SubTeam\' THEN 0 ELSE 1 END, "Title" DESC');
|
||||||
|
$this->assertEquals(array(
|
||||||
|
'Subteam 3',
|
||||||
|
'Subteam 2',
|
||||||
|
'Subteam 1',
|
||||||
|
'Team 3',
|
||||||
|
'Team 2',
|
||||||
|
'Team 1',
|
||||||
|
), $list->column("Title"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,7 +102,7 @@ class SQLQueryTest extends SapphireTest {
|
|||||||
$query = new SQLQuery();
|
$query = new SQLQuery();
|
||||||
$query->from[] = "MyTable";
|
$query->from[] = "MyTable";
|
||||||
$query->orderby('MyName');
|
$query->orderby('MyName');
|
||||||
$this->assertEquals('SELECT * FROM MyTable ORDER BY MyName', $query->sql());
|
$this->assertEquals('SELECT * FROM MyTable ORDER BY MyName ASC', $query->sql());
|
||||||
|
|
||||||
$query = new SQLQuery();
|
$query = new SQLQuery();
|
||||||
$query->from[] = "MyTable";
|
$query->from[] = "MyTable";
|
||||||
@ -117,7 +117,7 @@ class SQLQueryTest extends SapphireTest {
|
|||||||
$query = new SQLQuery();
|
$query = new SQLQuery();
|
||||||
$query->from[] = "MyTable";
|
$query->from[] = "MyTable";
|
||||||
$query->orderby('MyName ASC, Color');
|
$query->orderby('MyName ASC, Color');
|
||||||
$this->assertEquals('SELECT * FROM MyTable ORDER BY MyName ASC, Color', $query->sql());
|
$this->assertEquals('SELECT * FROM MyTable ORDER BY MyName ASC, Color ASC', $query->sql());
|
||||||
|
|
||||||
$query = new SQLQuery();
|
$query = new SQLQuery();
|
||||||
$query->from[] = "MyTable";
|
$query->from[] = "MyTable";
|
||||||
@ -127,23 +127,23 @@ class SQLQueryTest extends SapphireTest {
|
|||||||
$query = new SQLQuery();
|
$query = new SQLQuery();
|
||||||
$query->from[] = "MyTable";
|
$query->from[] = "MyTable";
|
||||||
$query->orderby(array('MyName' => 'desc', 'Color'));
|
$query->orderby(array('MyName' => 'desc', 'Color'));
|
||||||
$this->assertEquals('SELECT * FROM MyTable ORDER BY MyName DESC, Color', $query->sql());
|
$this->assertEquals('SELECT * FROM MyTable ORDER BY MyName DESC, Color ASC', $query->sql());
|
||||||
|
|
||||||
$query = new SQLQuery();
|
$query = new SQLQuery();
|
||||||
$query->from[] = "MyTable";
|
$query->from[] = "MyTable";
|
||||||
$query->orderby('implode("MyName","Color")');
|
$query->orderby('implode("MyName","Color")');
|
||||||
$this->assertEquals('SELECT implode("MyName","Color") AS "_SortColumn0" FROM MyTable ORDER BY _SortColumn0', $query->sql());
|
$this->assertEquals('SELECT *, implode("MyName","Color") AS "_SortColumn0" FROM MyTable ORDER BY _SortColumn0 ASC', $query->sql());
|
||||||
|
|
||||||
$query = new SQLQuery();
|
$query = new SQLQuery();
|
||||||
$query->from[] = "MyTable";
|
$query->from[] = "MyTable";
|
||||||
$query->orderby('implode("MyName","Color") DESC');
|
$query->orderby('implode("MyName","Color") DESC');
|
||||||
$this->assertEquals('SELECT implode("MyName","Color") AS "_SortColumn0" FROM MyTable ORDER BY _SortColumn0 DESC', $query->sql());
|
$this->assertEquals('SELECT *, implode("MyName","Color") AS "_SortColumn0" FROM MyTable ORDER BY _SortColumn0 DESC', $query->sql());
|
||||||
|
|
||||||
$query = new SQLQuery();
|
$query = new SQLQuery();
|
||||||
$query->from[] = "MyTable";
|
$query->from[] = "MyTable";
|
||||||
$query->orderby('RAND()');
|
$query->orderby('RAND()');
|
||||||
|
|
||||||
$this->assertEquals('SELECT RAND() AS "_SortColumn0" FROM MyTable ORDER BY _SortColumn0', $query->sql());
|
$this->assertEquals('SELECT *, RAND() AS "_SortColumn0" FROM MyTable ORDER BY _SortColumn0 ASC', $query->sql());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testReverseOrderBy() {
|
public function testReverseOrderBy() {
|
||||||
@ -174,7 +174,7 @@ class SQLQueryTest extends SapphireTest {
|
|||||||
$query->orderby('implode("MyName","Color") DESC');
|
$query->orderby('implode("MyName","Color") DESC');
|
||||||
$query->reverseOrderBy();
|
$query->reverseOrderBy();
|
||||||
|
|
||||||
$this->assertEquals('SELECT implode("MyName","Color") AS "_SortColumn0" FROM MyTable ORDER BY _SortColumn0 ASC',$query->sql());
|
$this->assertEquals('SELECT *, implode("MyName","Color") AS "_SortColumn0" FROM MyTable ORDER BY _SortColumn0 ASC',$query->sql());
|
||||||
}
|
}
|
||||||
|
|
||||||
function testFiltersOnID() {
|
function testFiltersOnID() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user