mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merge branch '4.4' into 4.5
This commit is contained in:
commit
9d5c3ef20e
@ -501,6 +501,16 @@ class DataList extends ViewableData implements SS_List, Filterable, Sortable, Li
|
|||||||
* Unlike getRelationName, this is immutable and will fallback to the quoted field
|
* Unlike getRelationName, this is immutable and will fallback to the quoted field
|
||||||
* name if not a relation.
|
* name if not a relation.
|
||||||
*
|
*
|
||||||
|
* Example use (simple WHERE condition on data sitting in a related table):
|
||||||
|
*
|
||||||
|
* <code>
|
||||||
|
* $columnName = null;
|
||||||
|
* $list = Page::get()
|
||||||
|
* ->applyRelation('TaxonomyTerms.ID', $columnName)
|
||||||
|
* ->where([$columnName => 'my value']);
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
*
|
||||||
* @param string $field Name of field or relation to apply
|
* @param string $field Name of field or relation to apply
|
||||||
* @param string &$columnName Quoted column name
|
* @param string &$columnName Quoted column name
|
||||||
* @param bool $linearOnly Set to true to restrict to linear relations only. Set this
|
* @param bool $linearOnly Set to true to restrict to linear relations only. Set this
|
||||||
|
@ -4,6 +4,7 @@ namespace SilverStripe\ORM;
|
|||||||
|
|
||||||
use SilverStripe\Core\ClassInfo;
|
use SilverStripe\Core\ClassInfo;
|
||||||
use SilverStripe\Core\Convert;
|
use SilverStripe\Core\Convert;
|
||||||
|
use SilverStripe\Core\Extensible;
|
||||||
use SilverStripe\Core\Injector\Injector;
|
use SilverStripe\Core\Injector\Injector;
|
||||||
use SilverStripe\ORM\Connect\Query;
|
use SilverStripe\ORM\Connect\Query;
|
||||||
use SilverStripe\ORM\Queries\SQLConditionGroup;
|
use SilverStripe\ORM\Queries\SQLConditionGroup;
|
||||||
@ -21,6 +22,8 @@ use InvalidArgumentException;
|
|||||||
class DataQuery
|
class DataQuery
|
||||||
{
|
{
|
||||||
|
|
||||||
|
use Extensible;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
@ -930,7 +933,7 @@ class DataQuery
|
|||||||
$joinExpression = "{$foreignKeyIDColumn} = {$localIDColumn}";
|
$joinExpression = "{$foreignKeyIDColumn} = {$localIDColumn}";
|
||||||
}
|
}
|
||||||
$this->query->addLeftJoin(
|
$this->query->addLeftJoin(
|
||||||
$foreignTable,
|
$this->getJoinTableName($foreignClass, $foreignTable),
|
||||||
$joinExpression,
|
$joinExpression,
|
||||||
$foreignTableAliased
|
$foreignTableAliased
|
||||||
);
|
);
|
||||||
@ -944,7 +947,7 @@ class DataQuery
|
|||||||
if ($ancestorTable !== $foreignTable) {
|
if ($ancestorTable !== $foreignTable) {
|
||||||
$ancestorTableAliased = $foreignPrefix . $ancestorTable;
|
$ancestorTableAliased = $foreignPrefix . $ancestorTable;
|
||||||
$this->query->addLeftJoin(
|
$this->query->addLeftJoin(
|
||||||
$ancestorTable,
|
$this->getJoinTableName($ancestor, $ancestorTable),
|
||||||
"\"{$foreignTableAliased}\".\"ID\" = \"{$ancestorTableAliased}\".\"ID\"",
|
"\"{$foreignTableAliased}\".\"ID\" = \"{$ancestorTableAliased}\".\"ID\"",
|
||||||
$ancestorTableAliased
|
$ancestorTableAliased
|
||||||
);
|
);
|
||||||
@ -990,7 +993,7 @@ class DataQuery
|
|||||||
$foreignIDColumn = $schema->sqlColumnForField($foreignBaseClass, 'ID', $foreignPrefix);
|
$foreignIDColumn = $schema->sqlColumnForField($foreignBaseClass, 'ID', $foreignPrefix);
|
||||||
$localColumn = $schema->sqlColumnForField($localClass, "{$localField}ID", $localPrefix);
|
$localColumn = $schema->sqlColumnForField($localClass, "{$localField}ID", $localPrefix);
|
||||||
$this->query->addLeftJoin(
|
$this->query->addLeftJoin(
|
||||||
$foreignBaseTable,
|
$this->getJoinTableName($foreignClass, $foreignBaseTable),
|
||||||
"{$foreignIDColumn} = {$localColumn}",
|
"{$foreignIDColumn} = {$localColumn}",
|
||||||
$foreignPrefix . $foreignBaseTable
|
$foreignPrefix . $foreignBaseTable
|
||||||
);
|
);
|
||||||
@ -1005,7 +1008,7 @@ class DataQuery
|
|||||||
if ($ancestorTable !== $foreignBaseTable) {
|
if ($ancestorTable !== $foreignBaseTable) {
|
||||||
$ancestorTableAliased = $foreignPrefix . $ancestorTable;
|
$ancestorTableAliased = $foreignPrefix . $ancestorTable;
|
||||||
$this->query->addLeftJoin(
|
$this->query->addLeftJoin(
|
||||||
$ancestorTable,
|
$this->getJoinTableName($ancestor, $ancestorTable),
|
||||||
"{$foreignIDColumn} = \"{$ancestorTableAliased}\".\"ID\"",
|
"{$foreignIDColumn} = \"{$ancestorTableAliased}\".\"ID\"",
|
||||||
$ancestorTableAliased
|
$ancestorTableAliased
|
||||||
);
|
);
|
||||||
@ -1039,7 +1042,13 @@ class DataQuery
|
|||||||
$schema = DataObject::getSchema();
|
$schema = DataObject::getSchema();
|
||||||
|
|
||||||
if (class_exists($relationClassOrTable)) {
|
if (class_exists($relationClassOrTable)) {
|
||||||
$relationClassOrTable = $schema->tableName($relationClassOrTable);
|
// class is provided
|
||||||
|
$relationTable = $schema->tableName($relationClassOrTable);
|
||||||
|
$relationTableUpdated = $this->getJoinTableName($relationClassOrTable, $relationTable);
|
||||||
|
} else {
|
||||||
|
// table is provided
|
||||||
|
$relationTable = $relationClassOrTable;
|
||||||
|
$relationTableUpdated = $relationClassOrTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if already joined to component alias (skip join table for the check)
|
// Check if already joined to component alias (skip join table for the check)
|
||||||
@ -1051,10 +1060,10 @@ class DataQuery
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Join parent class to join table
|
// Join parent class to join table
|
||||||
$relationAliasedTable = $componentPrefix . $relationClassOrTable;
|
$relationAliasedTable = $componentPrefix . $relationTable;
|
||||||
$parentIDColumn = $schema->sqlColumnForField($parentClass, 'ID', $parentPrefix);
|
$parentIDColumn = $schema->sqlColumnForField($parentClass, 'ID', $parentPrefix);
|
||||||
$this->query->addLeftJoin(
|
$this->query->addLeftJoin(
|
||||||
$relationClassOrTable,
|
$relationTableUpdated,
|
||||||
"\"{$relationAliasedTable}\".\"{$parentField}\" = {$parentIDColumn}",
|
"\"{$relationAliasedTable}\".\"{$parentField}\" = {$parentIDColumn}",
|
||||||
$relationAliasedTable
|
$relationAliasedTable
|
||||||
);
|
);
|
||||||
@ -1062,7 +1071,7 @@ class DataQuery
|
|||||||
// Join on base table of component class
|
// Join on base table of component class
|
||||||
$componentIDColumn = $schema->sqlColumnForField($componentBaseClass, 'ID', $componentPrefix);
|
$componentIDColumn = $schema->sqlColumnForField($componentBaseClass, 'ID', $componentPrefix);
|
||||||
$this->query->addLeftJoin(
|
$this->query->addLeftJoin(
|
||||||
$componentBaseTable,
|
$this->getJoinTableName($componentBaseClass, $componentBaseTable),
|
||||||
"\"{$relationAliasedTable}\".\"{$componentField}\" = {$componentIDColumn}",
|
"\"{$relationAliasedTable}\".\"{$componentField}\" = {$componentIDColumn}",
|
||||||
$componentAliasedTable
|
$componentAliasedTable
|
||||||
);
|
);
|
||||||
@ -1076,7 +1085,7 @@ class DataQuery
|
|||||||
if ($ancestorTable !== $componentBaseTable) {
|
if ($ancestorTable !== $componentBaseTable) {
|
||||||
$ancestorTableAliased = $componentPrefix . $ancestorTable;
|
$ancestorTableAliased = $componentPrefix . $ancestorTable;
|
||||||
$this->query->addLeftJoin(
|
$this->query->addLeftJoin(
|
||||||
$ancestorTable,
|
$this->getJoinTableName($ancestor, $ancestorTable),
|
||||||
"{$componentIDColumn} = \"{$ancestorTableAliased}\".\"ID\"",
|
"{$componentIDColumn} = \"{$ancestorTableAliased}\".\"ID\"",
|
||||||
$ancestorTableAliased
|
$ancestorTableAliased
|
||||||
);
|
);
|
||||||
@ -1142,16 +1151,47 @@ class DataQuery
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Query the given field column from the database and return as an array.
|
* Query the given field column from the database and return as an array.
|
||||||
|
* querying DB columns of related tables is supported but you need to make sure that the related table
|
||||||
|
* is already available in join
|
||||||
|
*
|
||||||
|
* @see DataList::applyRelation()
|
||||||
|
*
|
||||||
|
* example use:
|
||||||
|
*
|
||||||
|
* <code>
|
||||||
|
* column("MyTable"."Title")
|
||||||
|
*
|
||||||
|
* or
|
||||||
|
*
|
||||||
|
* $columnName = null;
|
||||||
|
* Category::get()
|
||||||
|
* ->applyRelation('Products.Title', $columnName)
|
||||||
|
* ->column($columnName);
|
||||||
|
* </code>
|
||||||
*
|
*
|
||||||
* @param string $field See {@link expressionForField()}.
|
* @param string $field See {@link expressionForField()}.
|
||||||
* @return array List of column values for the specified column
|
* @return array List of column values for the specified column
|
||||||
|
* @throws InvalidArgumentException
|
||||||
*/
|
*/
|
||||||
public function column($field = 'ID')
|
public function column($field = 'ID')
|
||||||
{
|
{
|
||||||
$fieldExpression = $this->expressionForField($field);
|
$fieldExpression = $this->expressionForField($field);
|
||||||
$query = $this->getFinalisedQuery(array($field));
|
$query = $this->getFinalisedQuery([$field]);
|
||||||
$originalSelect = $query->getSelect();
|
$originalSelect = $query->getSelect();
|
||||||
$query->setSelect(array());
|
$query->setSelect([]);
|
||||||
|
|
||||||
|
// field wasn't recognised as a valid field from the table class hierarchy
|
||||||
|
// check if the field is in format "<table_name>"."<column_name>"
|
||||||
|
// if that's the case we may want to query related table
|
||||||
|
if (!$fieldExpression) {
|
||||||
|
if (!$this->validateColumnField($field, $query)) {
|
||||||
|
throw new InvalidArgumentException('Invalid column name ' . $field);
|
||||||
|
}
|
||||||
|
|
||||||
|
$fieldExpression = $field;
|
||||||
|
$field = null;
|
||||||
|
}
|
||||||
|
|
||||||
$query->selectField($fieldExpression, $field);
|
$query->selectField($fieldExpression, $field);
|
||||||
$this->ensureSelectContainsOrderbyColumns($query, $originalSelect);
|
$this->ensureSelectContainsOrderbyColumns($query, $originalSelect);
|
||||||
|
|
||||||
@ -1257,4 +1297,34 @@ class DataQuery
|
|||||||
$this->dataQueryManipulators[] = $manipulator;
|
$this->dataQueryManipulators[] = $manipulator;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function validateColumnField($field, SQLSelect $query)
|
||||||
|
{
|
||||||
|
// standard column - nothing to process here
|
||||||
|
if (strpos($field, '.') === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fieldData = explode('.', $field);
|
||||||
|
$tablePrefix = str_replace('"', '', $fieldData[0]);
|
||||||
|
|
||||||
|
// check if related table is available
|
||||||
|
return $query->isJoinedTo($tablePrefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this extension point to alter the table name
|
||||||
|
* useful for versioning for example
|
||||||
|
*
|
||||||
|
* @param $class
|
||||||
|
* @param $table
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
private function getJoinTableName($class, $table)
|
||||||
|
{
|
||||||
|
$updated = $table;
|
||||||
|
$this->invokeWithExtensions('updateJoinTableName', $class, $table, $updated);
|
||||||
|
|
||||||
|
return $updated;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,24 +4,24 @@ namespace SilverStripe\ORM\Tests;
|
|||||||
|
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use SilverStripe\Core\Convert;
|
use SilverStripe\Core\Convert;
|
||||||
use SilverStripe\Core\Injector\InjectorNotFoundException;
|
use SilverStripe\Dev\SapphireTest;
|
||||||
use SilverStripe\ORM\DataList;
|
use SilverStripe\ORM\DataList;
|
||||||
use SilverStripe\ORM\DataQuery;
|
use SilverStripe\ORM\DataQuery;
|
||||||
use SilverStripe\ORM\DB;
|
use SilverStripe\ORM\DB;
|
||||||
use SilverStripe\ORM\Filterable;
|
use SilverStripe\ORM\Filterable;
|
||||||
use SilverStripe\ORM\Filters\ExactMatchFilter;
|
use SilverStripe\ORM\Filters\ExactMatchFilter;
|
||||||
use SilverStripe\Dev\SapphireTest;
|
|
||||||
use SilverStripe\ORM\Tests\DataObjectTest\Fixture;
|
|
||||||
use SilverStripe\ORM\Tests\DataObjectTest\Bracket;
|
use SilverStripe\ORM\Tests\DataObjectTest\Bracket;
|
||||||
use SilverStripe\ORM\Tests\DataObjectTest\EquipmentCompany;
|
use SilverStripe\ORM\Tests\DataObjectTest\EquipmentCompany;
|
||||||
use SilverStripe\ORM\Tests\DataObjectTest\Fan;
|
use SilverStripe\ORM\Tests\DataObjectTest\Fan;
|
||||||
|
use SilverStripe\ORM\Tests\DataObjectTest\Fixture;
|
||||||
use SilverStripe\ORM\Tests\DataObjectTest\Player;
|
use SilverStripe\ORM\Tests\DataObjectTest\Player;
|
||||||
use SilverStripe\ORM\Tests\DataObjectTest\Sortable;
|
use SilverStripe\ORM\Tests\DataObjectTest\Sortable;
|
||||||
|
use SilverStripe\ORM\Tests\DataObjectTest\Staff;
|
||||||
use SilverStripe\ORM\Tests\DataObjectTest\SubTeam;
|
use SilverStripe\ORM\Tests\DataObjectTest\SubTeam;
|
||||||
use SilverStripe\ORM\Tests\DataObjectTest\Team;
|
use SilverStripe\ORM\Tests\DataObjectTest\Team;
|
||||||
use SilverStripe\ORM\Tests\DataObjectTest\TeamComment;
|
use SilverStripe\ORM\Tests\DataObjectTest\TeamComment;
|
||||||
use SilverStripe\ORM\Tests\DataObjectTest\ValidatedObject;
|
use SilverStripe\ORM\Tests\DataObjectTest\ValidatedObject;
|
||||||
use SilverStripe\ORM\Tests\DataObjectTest\Staff;
|
use SilverStripe\ORM\Tests\ManyManyListTest\Category;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @skipUpgrade
|
* @skipUpgrade
|
||||||
@ -1842,4 +1842,37 @@ class DataListTest extends SapphireTest
|
|||||||
|
|
||||||
$this->assertSQLContains(DB::get_conn()->random() . ' AS "_SortColumn', $list->dataQuery()->sql());
|
$this->assertSQLContains(DB::get_conn()->random() . ' AS "_SortColumn', $list->dataQuery()->sql());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testColumnFailureInvalidColumn()
|
||||||
|
{
|
||||||
|
$this->expectException(InvalidArgumentException::class);
|
||||||
|
|
||||||
|
Category::get()->column('ObviouslyInvalidColumn');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testColumnFailureInvalidTable()
|
||||||
|
{
|
||||||
|
$this->expectException(InvalidArgumentException::class);
|
||||||
|
|
||||||
|
$columnName = null;
|
||||||
|
Category::get()
|
||||||
|
->applyRelation('Products.ID', $columnName)
|
||||||
|
->column('"ObviouslyInvalidTable"."ID"');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testColumnFromRelatedTable()
|
||||||
|
{
|
||||||
|
$columnName = null;
|
||||||
|
$productTitles = Category::get()
|
||||||
|
->applyRelation('Products.Title', $columnName)
|
||||||
|
->column($columnName);
|
||||||
|
|
||||||
|
$productTitles = array_diff($productTitles, [null]);
|
||||||
|
sort($productTitles);
|
||||||
|
|
||||||
|
$this->assertEquals([
|
||||||
|
'Product A',
|
||||||
|
'Product B',
|
||||||
|
], $productTitles);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user