mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merge pull request #387 from sminnee/sapphire
--- The primary goal of this branch was to fix the sort bugs in AssetAdmin, however, it started a bit of a yak shave in that the API around SQLQuery was poor. The biggest change that this pull request makes is it changes the format of SQLQuery::$select to contain aliases as array keys (and consistently puts the "implicit alias" in there to assist with various query generation logic), but it also makes a bunch of changes to avoid direct access of that property.
This commit is contained in:
commit
2288d80c30
@ -77,7 +77,7 @@ class ManyManyComplexTableField extends HasManyComplexTableField {
|
||||
|
||||
function getQuery() {
|
||||
$query = parent::getQuery();
|
||||
$query->select[] = "CASE WHEN \"{$this->manyManyParentClass}ID\" IS NULL THEN '0' ELSE '1' END AS \"Checked\"";
|
||||
$query->selectField("CASE WHEN \"{$this->manyManyParentClass}ID\" IS NULL THEN '0' ELSE '1' END", "Checked");
|
||||
$query->groupby[] = "\"{$this->manyManyParentClass}ID\""; // necessary for Postgres
|
||||
|
||||
return $query;
|
||||
|
@ -78,7 +78,7 @@ class Aggregate extends ViewableData {
|
||||
protected function query($attr) {
|
||||
$singleton = singleton($this->type);
|
||||
$query = $singleton->buildSQL($this->filter);
|
||||
$query->select = array($attr);
|
||||
$query->select($attr);
|
||||
$query->orderby = null;
|
||||
$singleton->extend('augmentSQL', $query);
|
||||
return $query;
|
||||
@ -161,7 +161,7 @@ class Aggregate_Relationship extends Aggregate {
|
||||
$query = $this->object->getManyManyComponentsQuery($this->relationship, $this->filter);
|
||||
}
|
||||
|
||||
$query->select = array($attr);
|
||||
$query->select($attr);
|
||||
$query->groupby = array();
|
||||
|
||||
$singleton = singleton($this->type);
|
||||
|
@ -189,7 +189,13 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab
|
||||
$this->dataQuery->sort(null, null); // wipe the sort
|
||||
|
||||
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
|
||||
* the query
|
||||
* the query. Throws an InvalidArgumentException if the $field doesn't correspond to a relation
|
||||
*
|
||||
* @param string $field
|
||||
* @return string
|
||||
*/
|
||||
public function getRelationName($field) {
|
||||
if(!preg_match('/^[A-Z0-9._]+$/i', $field)) {
|
||||
throw new InvalidArgumentException("Bad field expression $field");
|
||||
}
|
||||
|
||||
if(strpos($field,'.') === false) {
|
||||
return '"'.$field.'"';
|
||||
}
|
||||
|
@ -105,7 +105,6 @@ class DataQuery {
|
||||
}
|
||||
|
||||
$baseClass = array_shift($tableClasses);
|
||||
$select = array("\"$baseClass\".*");
|
||||
|
||||
// Build our intial query
|
||||
$this->query = new SQLQuery(array());
|
||||
@ -116,7 +115,6 @@ class DataQuery {
|
||||
}
|
||||
|
||||
$this->query->from("\"$baseClass\"");
|
||||
$this->selectColumnsFromTable($this->query, $baseClass);
|
||||
|
||||
singleton($this->dataClass)->extend('augmentDataQueryCreation', $this->query, $this);
|
||||
}
|
||||
@ -155,12 +153,6 @@ class DataQuery {
|
||||
$tableNames = array_keys($tableClasses);
|
||||
$baseClass = $tableNames[0];
|
||||
|
||||
// Empty the existing select query of all non-generated selects (eg, random sorts and many-many-extrafields).
|
||||
// Maybe we should remove all fields that exist on this class instead?
|
||||
foreach ($query->select as $name => $column) {
|
||||
if (!is_numeric($name)) unset($query->select[$name]);
|
||||
}
|
||||
|
||||
// Iterate over the tables and check what we need to select from them. If any selects are made (or the table is
|
||||
// required for a select)
|
||||
foreach($tableClasses as $tableClass) {
|
||||
@ -205,8 +197,7 @@ class DataQuery {
|
||||
user_error("Bad collision item '$collision'", E_USER_WARNING);
|
||||
}
|
||||
}
|
||||
$query->select[$k] = "CASE " . implode( " ", $caseClauses) . " ELSE NULL END"
|
||||
. " AS \"$k\"";
|
||||
$query->selectField("CASE " . implode( " ", $caseClauses) . " ELSE NULL END", $k);
|
||||
}
|
||||
}
|
||||
|
||||
@ -221,8 +212,8 @@ class DataQuery {
|
||||
}
|
||||
}
|
||||
|
||||
$query->select[] = "\"$baseClass\".\"ID\"";
|
||||
$query->select[] = "CASE WHEN \"$baseClass\".\"ClassName\" IS NOT NULL THEN \"$baseClass\".\"ClassName\" ELSE '$baseClass' END AS \"RecordClassName\"";
|
||||
$query->selectField("\"$baseClass\".\"ID\"", "ID");
|
||||
$query->selectField("CASE WHEN \"$baseClass\".\"ClassName\" IS NOT NULL THEN \"$baseClass\".\"ClassName\" ELSE '$baseClass' END", "RecordClassName");
|
||||
|
||||
// TODO: Versioned, Translatable, SiteTreeSubsites, etc, could probably be better implemented as subclasses of DataQuery
|
||||
singleton($this->dataClass)->extend('augmentSQL', $query, $this);
|
||||
@ -238,7 +229,7 @@ class DataQuery {
|
||||
* @param SQLQuery $query
|
||||
* @return null
|
||||
*/
|
||||
protected function ensureSelectContainsOrderbyColumns($query) {
|
||||
protected function ensureSelectContainsOrderbyColumns($query, $originalSelect = array()) {
|
||||
$tableClasses = ClassInfo::dataClassesFor($this->dataClass);
|
||||
$baseClass = array_shift($tableClasses);
|
||||
|
||||
@ -248,8 +239,13 @@ class DataQuery {
|
||||
foreach($orderby as $k => $dir) {
|
||||
// don't touch functions in the ORDER BY or function calls
|
||||
// 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;
|
||||
}
|
||||
|
||||
$col = str_replace('"', '', trim($k));
|
||||
$parts = explode('.', $col);
|
||||
@ -274,14 +270,18 @@ class DataQuery {
|
||||
$qualCol = "\"$parts[0]\"";
|
||||
}
|
||||
|
||||
// To-do: Remove this if block once SQLQuery::$select has been refactored to store itemisedSelect()
|
||||
// format internally; then this check can be part of selectField()
|
||||
if(!isset($query->select[$col]) && !in_array($qualCol, $query->select)) {
|
||||
$query->select[] = $qualCol;
|
||||
$query->selectField($qualCol);
|
||||
}
|
||||
} else {
|
||||
$qualCol = '"' . implode('"."', $parts) . '"';
|
||||
|
||||
// To-do: Remove this if block once SQLQuery::$select has been refactored to store itemisedSelect()
|
||||
// format internally; then this check can be part of selectField()
|
||||
if(!in_array($qualCol, $query->select)) {
|
||||
$query->select[] = $qualCol;
|
||||
$query->selectField($qualCol);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -367,12 +367,12 @@ class DataQuery {
|
||||
if($databaseFields) foreach($databaseFields as $k => $v) {
|
||||
if((is_null($columns) || in_array($k, $columns)) && !isset($compositeFields[$k])) {
|
||||
// Update $collidingFields if necessary
|
||||
if(isset($query->select[$k])) {
|
||||
if(!isset($this->collidingFields[$k])) $this->collidingFields[$k] = array($query->select[$k]);
|
||||
if($expressionForField = $query->expressionForField($k)) {
|
||||
if(!isset($this->collidingFields[$k])) $this->collidingFields[$k] = array($expressionForField);
|
||||
$this->collidingFields[$k][] = "\"$tableClass\".\"$k\"";
|
||||
|
||||
} else {
|
||||
$query->select[$k] = "\"$tableClass\".\"$k\"";
|
||||
$query->selectField("\"$tableClass\".\"$k\"", $k);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -596,8 +596,12 @@ class DataQuery {
|
||||
*/
|
||||
public function subtract(DataQuery $subtractQuery, $field='ID') {
|
||||
$subSelect= $subtractQuery->getFinalisedQuery();
|
||||
$subSelect->select($this->expressionForField($field, $subSelect));
|
||||
$fieldExpression = $this->expressionForField($field, $subSelect);
|
||||
$subSelect->clearSelect();
|
||||
$subSelect->selectField($fieldExpression, $field);
|
||||
$this->where($this->expressionForField($field, $this).' NOT IN ('.$subSelect->sql().')');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -607,7 +611,9 @@ class DataQuery {
|
||||
$fieldExpressions = array_map(create_function('$item',
|
||||
"return '\"$table\".\"' . \$item . '\"';"), $fields);
|
||||
|
||||
$this->select($fieldExpressions);
|
||||
$this->query->select($fieldExpressions);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -615,8 +621,11 @@ class DataQuery {
|
||||
*/
|
||||
public function column($field = 'ID') {
|
||||
$query = $this->getFinalisedQuery(array($field));
|
||||
$query->select($this->expressionForField($field, $query));
|
||||
$this->ensureSelectContainsOrderbyColumns($query);
|
||||
$originalSelect = $query->itemisedSelect();
|
||||
$fieldExpression = $this->expressionForField($field, $query);
|
||||
$query->clearSelect();
|
||||
$query->selectField($fieldExpression, $field);
|
||||
$this->ensureSelectContainsOrderbyColumns($query, $originalSelect);
|
||||
|
||||
return $query->execute()->column($field);
|
||||
}
|
||||
@ -636,7 +645,7 @@ class DataQuery {
|
||||
* Clear the selected fields to start over
|
||||
*/
|
||||
public function clearSelect() {
|
||||
$this->query->select = array();
|
||||
$this->query->clearSelect();
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -644,8 +653,8 @@ class DataQuery {
|
||||
/**
|
||||
* Select the given field expressions. You must do your own escaping
|
||||
*/
|
||||
protected function select($fieldExpressions) {
|
||||
$this->query->select = array_merge($this->query->select, $fieldExpressions);
|
||||
protected function selectField($fieldExpression, $alias = null) {
|
||||
$this->query->selectField($fieldExpression, $alias);
|
||||
}
|
||||
|
||||
//// QUERY PARAMS
|
||||
|
@ -728,9 +728,10 @@ abstract class SS_Database {
|
||||
|
||||
if($sqlQuery->delete) {
|
||||
$text = "DELETE ";
|
||||
} else if($sqlQuery->select) {
|
||||
$text = "SELECT $distinct" . implode(", ", $sqlQuery->select);
|
||||
} else {
|
||||
$text = "SELECT $distinct" . $sqlQuery->prepareSelect();
|
||||
}
|
||||
|
||||
if($sqlQuery->from) $text .= " FROM " . implode(" ", $sqlQuery->from);
|
||||
if($sqlQuery->where) $text .= " WHERE (" . $sqlQuery->prepareWhere(). ")";
|
||||
if($sqlQuery->groupby) $text .= " GROUP BY " . $sqlQuery->prepareGroupBy();
|
||||
|
@ -133,7 +133,7 @@ class ManyManyList extends RelationList {
|
||||
function removeAll() {
|
||||
$query = $this->dataQuery()->query();
|
||||
$query->delete = true;
|
||||
$query->select = array('*');
|
||||
$query->select(array('*'));
|
||||
$query->from = array("\"$this->joinTable\"");
|
||||
$query->orderby = null;
|
||||
$query->execute();
|
||||
|
@ -844,8 +844,8 @@ class MySQLDatabase extends SS_Database {
|
||||
|
||||
// Make column selection lists
|
||||
$select = array(
|
||||
'SiteTree' => array("ClassName","$baseClasses[SiteTree].ID","ParentID","Title","MenuTitle","URLSegment","Content","LastEdited","Created","_utf8'' AS Filename", "_utf8'' AS Name", "$relevance[SiteTree] AS Relevance", "CanViewType"),
|
||||
'File' => array("ClassName","$baseClasses[File].ID","_utf8'' AS ParentID","Title","_utf8'' AS MenuTitle","_utf8'' AS URLSegment","Content","LastEdited","Created","Filename","Name","$relevance[File] AS Relevance","NULL AS CanViewType"),
|
||||
'SiteTree' => array("ClassName","$baseClasses[SiteTree].\"ID\"","ParentID","Title","MenuTitle","URLSegment","Content","LastEdited","Created","Filename" => "_utf8''", "Name" => "_utf8''", "Relevance" => $relevance['SiteTree'], "CanViewType"),
|
||||
'File' => array("ClassName","$baseClasses[File].\"ID\"","ParentID" => "_utf8''","Title","MenuTitle" => "_utf8''","URLSegment" => "_utf8''","Content","LastEdited","Created","Filename","Name", "Relevance" => $relevance['File'], "CanViewType" => "NULL"),
|
||||
);
|
||||
|
||||
// Process and combine queries
|
||||
@ -856,7 +856,7 @@ class MySQLDatabase extends SS_Database {
|
||||
|
||||
// There's no need to do all that joining
|
||||
$query->from = array(str_replace(array('"','`'),'',$baseClasses[$class]) => $baseClasses[$class]);
|
||||
$query->select = $select[$class];
|
||||
$query->select($select[$class]);
|
||||
$query->orderby = null;
|
||||
|
||||
$querySQLs[] = $query->sql();
|
||||
|
@ -13,7 +13,7 @@
|
||||
class SQLQuery {
|
||||
|
||||
/**
|
||||
* An array of fields to select.
|
||||
* An array of fields to select, keyed by an optional alias.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
@ -113,6 +113,14 @@ class SQLQuery {
|
||||
$this->limit($limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the selected fields to start over
|
||||
*/
|
||||
function clearSelect() {
|
||||
$this->select = array();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the list of columns to be selected by the query.
|
||||
*
|
||||
@ -129,10 +137,13 @@ class SQLQuery {
|
||||
*/
|
||||
public function select($fields) {
|
||||
if (func_num_args() > 1) {
|
||||
$this->select = func_get_args();
|
||||
} else {
|
||||
$this->select = is_array($fields) ? $fields : array($fields);
|
||||
$fields = func_get_args();
|
||||
} else if(!is_array($fields)) {
|
||||
$fields = array($fields);
|
||||
}
|
||||
|
||||
$this->select = array();
|
||||
$this->selectMore($fields);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -143,29 +154,43 @@ class SQLQuery {
|
||||
* @param array|string
|
||||
*/
|
||||
public function selectMore($fields) {
|
||||
if(func_num_args() > 1) $fields = func_get_args();
|
||||
|
||||
if(is_array($fields)) {
|
||||
foreach($fields as $field) {
|
||||
$this->select[] = $field;
|
||||
}
|
||||
} else {
|
||||
$this->select[] = $fields;
|
||||
if (func_num_args() > 1) {
|
||||
$fields = func_get_args();
|
||||
} else if(!is_array($fields)) {
|
||||
$fields = array($fields);
|
||||
}
|
||||
|
||||
$this->select = array();
|
||||
foreach($fields as $idx => $field) {
|
||||
if(preg_match('/^(.*) +AS +"?([^"]*)"?/i', $field, $matches)) {
|
||||
Deprecation::notice("3.0", "Use selectField() to specify column aliases");
|
||||
$this->selectField($matches[1], $matches[2]);
|
||||
} else {
|
||||
$this->selectField($field, is_numeric($idx) ? null : $idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select an additional field
|
||||
*
|
||||
* @param $field The field to select
|
||||
* @param $alias The alias of that field
|
||||
*/
|
||||
public function selectField($field, $alias = null) {
|
||||
if(!$alias) {
|
||||
if(preg_match('/"([^"]+)"$/', $field, $matches)) $alias = $matches[1];
|
||||
else $alias = $field;
|
||||
}
|
||||
$this->select[$alias] = $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SQL expression for the given field
|
||||
*
|
||||
* @todo This should be refactored after $this->select is changed to make that easier
|
||||
* Return the SQL expression for the given field alias.
|
||||
* Returns null if the given alias doesn't exist.
|
||||
*/
|
||||
public function expressionForField($field) {
|
||||
foreach($this->select as $sel) {
|
||||
if(preg_match('/AS +"?([^"]*)"?/i', $sel, $matches)) $selField = $matches[1];
|
||||
else if(preg_match('/"([^"]*)"\."([^"]*)"/', $sel, $matches)) $selField = $matches[2];
|
||||
else if(preg_match('/"?([^"]*)"?/', $sel, $matches)) $selField = $matches[2];
|
||||
if($selField == $field) return $sel;
|
||||
}
|
||||
return isset($this->select[$field]) ? $this->select[$field] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -317,13 +342,12 @@ class SQLQuery {
|
||||
else {
|
||||
$sort = explode(",", $clauses);
|
||||
}
|
||||
|
||||
|
||||
$clauses = array();
|
||||
|
||||
foreach($sort as $clause) {
|
||||
$clause = explode(" ", trim($clause));
|
||||
$dir = (isset($clause[1])) ? $clause[1] : $direction;
|
||||
$clauses[$clause[0]] = $dir;
|
||||
list($column, $direction) = $this->getDirectionFromString($clause, $direction);
|
||||
$clauses[$column] = $direction;
|
||||
}
|
||||
}
|
||||
|
||||
@ -331,16 +355,13 @@ class SQLQuery {
|
||||
foreach($clauses as $key => $value) {
|
||||
if(!is_numeric($key)) {
|
||||
$column = trim($key);
|
||||
$direction = strtoupper(trim($value));
|
||||
$columnDir = strtoupper(trim($value));
|
||||
}
|
||||
else {
|
||||
$clause = explode(" ", trim($value));
|
||||
|
||||
$column = trim($clause[0]);
|
||||
$direction = (isset($clause[1])) ? strtoupper(trim($clause[1])) : "";
|
||||
list($column, $columnDir) = $this->getDirectionFromString($value);
|
||||
}
|
||||
|
||||
$this->orderby[$column] = $direction;
|
||||
$this->orderby[$column] = $columnDir;
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -354,16 +375,16 @@ class SQLQuery {
|
||||
// directly in the ORDER BY
|
||||
if($this->orderby) {
|
||||
$i = 0;
|
||||
|
||||
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
|
||||
unset($this->orderby[$clause]);
|
||||
|
||||
$clause = trim($clause);
|
||||
$column = "_SortColumn{$i}";
|
||||
|
||||
$this->select(sprintf("%s AS \"%s\"", $clause, $column));
|
||||
$this->selectField($clause, $column);
|
||||
$this->orderby($column, $dir, false);
|
||||
$i++;
|
||||
}
|
||||
@ -373,6 +394,22 @@ class SQLQuery {
|
||||
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
|
||||
* statements which are stored as strings. Without clauses and directions,
|
||||
@ -542,6 +579,29 @@ class SQLQuery {
|
||||
Deprecation::notice('3.0', 'Please use prepareWhere() instead of getFilter()');
|
||||
return $this->prepareWhere();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an itemised select list as a map, where keys are the aliases, and values are the column sources.
|
||||
* Aliases will always be provided (if the alias is implicit, the alias value will be inferred), and won't be quoted.
|
||||
* E.g., 'Title' => '"SiteTree"."Title"'.
|
||||
*/
|
||||
public function itemisedSelect() {
|
||||
return $this->select;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the WHERE clauses ready for inserting into a query.
|
||||
* @return string
|
||||
*/
|
||||
public function prepareSelect() {
|
||||
$clauses = array();
|
||||
foreach($this->select as $alias => $field) {
|
||||
// Don't include redundant aliases.
|
||||
if($alias === $field || preg_match('/"' . preg_quote($alias) . '"$/', $field)) $clauses[] = $field;
|
||||
else $clauses[] = "$field AS \"$alias\"";
|
||||
}
|
||||
return implode(", ", $clauses);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the WHERE clauses ready for inserting into a query.
|
||||
@ -696,7 +756,7 @@ class SQLQuery {
|
||||
if($column == null) {
|
||||
if($this->groupby) {
|
||||
$countQuery = new SQLQuery();
|
||||
$countQuery->select = array("count(*)");
|
||||
$countQuery->select("count(*)");
|
||||
$countQuery->from = array('(' . $clone->sql() . ') all_distinct');
|
||||
|
||||
return $countQuery->execute()->value();
|
||||
@ -714,21 +774,11 @@ class SQLQuery {
|
||||
|
||||
/**
|
||||
* Returns true if this query can be sorted by the given field.
|
||||
* Note that the implementation of this method is a little crude at the moment, it wil return
|
||||
* "false" more often that is strictly necessary.
|
||||
*/
|
||||
function canSortBy($fieldName) {
|
||||
$fieldName = preg_replace('/(\s+?)(A|DE)SC$/', '', $fieldName);
|
||||
|
||||
$sql = $this->sql();
|
||||
|
||||
$selects = $this->select;
|
||||
foreach($selects as $i => $sel) {
|
||||
if (preg_match('/"(.*)"\."(.*)"/', $sel, $matches)) $selects[$i] = $matches[2];
|
||||
}
|
||||
|
||||
$SQL_fieldName = Convert::raw2sql($fieldName);
|
||||
return (in_array($SQL_fieldName,$selects) || stripos($sql,"AS {$SQL_fieldName}"));
|
||||
return isset($this->select[$fieldName]);
|
||||
}
|
||||
|
||||
|
||||
@ -770,22 +820,20 @@ class SQLQuery {
|
||||
|
||||
/**
|
||||
* Return a new SQLQuery that calls the given aggregate functions on this data.
|
||||
* @param $columns An aggregate expression, such as 'MAX("Balance")', or an array of them.
|
||||
* @param $column An aggregate expression, such as 'MAX("Balance")', or a set of them.
|
||||
*/
|
||||
function aggregate($columns) {
|
||||
if(!is_array($columns)) $columns = array($columns);
|
||||
|
||||
function aggregate($column) {
|
||||
if($this->groupby || $this->limit) {
|
||||
throw new Exception("SQLQuery::aggregate() doesn't work with groupby or limit, yet");
|
||||
}
|
||||
|
||||
$clone = clone $this;
|
||||
|
||||
$clone = clone $this;
|
||||
$clone->limit = null;
|
||||
$clone->orderby = null;
|
||||
$clone->groupby = null;
|
||||
$clone->select = $columns;
|
||||
$clone->select($column);
|
||||
|
||||
return $clone;
|
||||
return $clone;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -149,9 +149,9 @@ class Versioned extends DataExtension {
|
||||
|
||||
// Add all <basetable>_versions columns
|
||||
foreach(self::$db_for_versions_table as $name => $type) {
|
||||
$query->select[] = sprintf('"%s_versions"."%s"', $baseTable, $name);
|
||||
$query->selectField(sprintf('"%s_versions"."%s"', $baseTable, $name), $name);
|
||||
}
|
||||
$query->select[] = sprintf('"%s_versions"."%s" AS "ID"', $baseTable, 'RecordID');
|
||||
$query->selectField(sprintf('"%s_versions"."%s"', $baseTable, 'RecordID'), "ID");
|
||||
|
||||
if($table != $baseTable) {
|
||||
$query->from[$table] .= " AND \"{$table}_versions\".\"Version\" = \"{$baseTable}_versions\".\"Version\"";
|
||||
@ -195,9 +195,9 @@ class Versioned extends DataExtension {
|
||||
|
||||
// Add all <basetable>_versions columns
|
||||
foreach(self::$db_for_versions_table as $name => $type) {
|
||||
$query->selectMore(sprintf('"%s_versions"."%s"', $baseTable, $name));
|
||||
$query->selectField(sprintf('"%s_versions"."%s"', $baseTable, $name), $name);
|
||||
}
|
||||
$query->selectMore(sprintf('"%s_versions"."%s" AS "ID"', $baseTable, 'RecordID'));
|
||||
$query->selectField(sprintf('"%s_versions"."%s"', $baseTable, 'RecordID'), "ID");
|
||||
|
||||
// latest_version has one more step
|
||||
// Return latest version instances, regardless of whether they are on a particular stage
|
||||
@ -722,7 +722,7 @@ class Versioned extends DataExtension {
|
||||
|
||||
// Add all <basetable>_versions columns
|
||||
foreach(self::$db_for_versions_table as $name => $type) {
|
||||
$query->select[] = sprintf('"%s_versions"."%s"', $baseTable, $name);
|
||||
$query->selectField(sprintf('"%s_versions"."%s"', $baseTable, $name), $name);
|
||||
}
|
||||
|
||||
$query->where[] = "\"{$baseTable}_versions\".\"RecordID\" = '{$this->owner->ID}'";
|
||||
|
@ -37,8 +37,8 @@
|
||||
*
|
||||
* function addToQuery(&$query) {
|
||||
* parent::addToQuery($query);
|
||||
* $query->select[] = "{$this->name}Number";
|
||||
* $query->select[] = "{$this->name}Name";
|
||||
* $query->select("{$this->name}Number");
|
||||
* $query->select("{$this->name}Name");
|
||||
* }
|
||||
*
|
||||
* function setValue($value, $record = null, $markChanged=true) {
|
||||
|
@ -96,8 +96,8 @@ class Money extends DBField implements CompositeDBField {
|
||||
|
||||
function addToQuery(&$query) {
|
||||
parent::addToQuery($query);
|
||||
$query->select[] = sprintf('"%sAmount"', $this->name);
|
||||
$query->select[] = sprintf('"%sCurrency"', $this->name);
|
||||
$query->selectField(sprintf('"%sAmount"', $this->name));
|
||||
$query->selectField(sprintf('"%sCurrency"', $this->name));
|
||||
}
|
||||
|
||||
function setValue($value, $record = null, $markChanged = true) {
|
||||
|
@ -515,4 +515,18 @@ class DataListTest extends SapphireTest {
|
||||
$this->assertEquals('Bob', $list->last()->Name, 'Last comment should be from Bob');
|
||||
$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"));
|
||||
}
|
||||
}
|
||||
|
@ -91,7 +91,7 @@ class DbDatetimeTest extends FunctionalTest {
|
||||
$this->matchesRoughly($result, date('Y-m-d H:i:s', strtotime('+1 Day', $this->getDbNow())), 'tomorrow');
|
||||
|
||||
$query = new SQLQuery();
|
||||
$query->select($this->adapter->datetimeIntervalClause('"Created"', '-15 Minutes') . ' AS "test"')
|
||||
$query->select(array("test" => $this->adapter->datetimeIntervalClause('"Created"', '-15 Minutes')))
|
||||
->from('"DbDateTimeTest_Team"')
|
||||
->limit(1);
|
||||
$result = $query->execute()->value();
|
||||
@ -116,7 +116,7 @@ class DbDatetimeTest extends FunctionalTest {
|
||||
$this->matchesRoughly($result, -45 * 60, 'now - 45 minutes ahead');
|
||||
|
||||
$query = new SQLQuery();
|
||||
$query->select($this->adapter->datetimeDifferenceClause('"LastEdited"', '"Created"') . ' AS "test"')
|
||||
$query->select(array("test" => $this->adapter->datetimeDifferenceClause('"LastEdited"', '"Created"')))
|
||||
->from('"DbDateTimeTest_Team"')
|
||||
->limit(1);
|
||||
|
||||
|
@ -23,14 +23,14 @@ class SQLQueryTest extends SapphireTest {
|
||||
|
||||
function testSelectFromUserSpecifiedFields() {
|
||||
$query = new SQLQuery();
|
||||
$query->select = array("Name", "Title", "Description");
|
||||
$query->select(array("Name", "Title", "Description"));
|
||||
$query->from[] = "MyTable";
|
||||
$this->assertEquals("SELECT Name, Title, Description FROM MyTable", $query->sql());
|
||||
}
|
||||
|
||||
function testSelectWithWhereClauseFilter() {
|
||||
$query = new SQLQuery();
|
||||
$query->select = array("Name","Meta");
|
||||
$query->select(array("Name","Meta"));
|
||||
$query->from[] = "MyTable";
|
||||
$query->where[] = "Name = 'Name'";
|
||||
$query->where[] = "Meta = 'Test'";
|
||||
@ -102,7 +102,7 @@ class SQLQueryTest extends SapphireTest {
|
||||
$query = new SQLQuery();
|
||||
$query->from[] = "MyTable";
|
||||
$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->from[] = "MyTable";
|
||||
@ -117,7 +117,7 @@ class SQLQueryTest extends SapphireTest {
|
||||
$query = new SQLQuery();
|
||||
$query->from[] = "MyTable";
|
||||
$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->from[] = "MyTable";
|
||||
@ -127,23 +127,23 @@ class SQLQueryTest extends SapphireTest {
|
||||
$query = new SQLQuery();
|
||||
$query->from[] = "MyTable";
|
||||
$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->from[] = "MyTable";
|
||||
$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->from[] = "MyTable";
|
||||
$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->from[] = "MyTable";
|
||||
$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() {
|
||||
@ -174,7 +174,7 @@ class SQLQueryTest extends SapphireTest {
|
||||
$query->orderby('implode("MyName","Color") DESC');
|
||||
$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() {
|
||||
|
Loading…
Reference in New Issue
Block a user