mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
ENHANCEMENT: Add lazy loading to DataQuery.
This commit is contained in:
parent
3ccaa1f864
commit
ff6909df97
@ -572,6 +572,17 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab
|
||||
return $clone->where("$SQL_col = '" . Convert::raw2sql($value) . "'")->First();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restrict the columns to fetch into this DataList
|
||||
*
|
||||
* @param array $queriedColumns
|
||||
* @return DataList
|
||||
*/
|
||||
public function setQueriedColumns($queriedColumns) {
|
||||
$clone = clone $this;
|
||||
$clone->dataQuery->setQueriedColumns($queriedColumns);
|
||||
return $clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter this list to only contain the given Primary IDs
|
||||
|
@ -212,7 +212,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$fields = Config::inst()->get($class, 'db', Config::UNINHERITED);
|
||||
|
||||
foreach(self::composite_fields($class, false) as $fieldName => $fieldClass) {
|
||||
// Remove the original fieldname, its not an actual database column
|
||||
// Remove the original fieldname, it's not an actual database column
|
||||
unset($fields[$fieldName]);
|
||||
|
||||
// Add all composite columns
|
||||
@ -362,6 +362,19 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
else $this->record[$k] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
// Identify fields that should be lazy loaded, but only on existing records
|
||||
if(!empty($record['ID'])) {
|
||||
$currentObj = get_class($this);
|
||||
while($currentObj != 'DataObject') {
|
||||
$fields = self::custom_database_fields($currentObj);
|
||||
foreach($fields as $field => $type) {
|
||||
if(!array_key_exists($field, $record)) $this->record[$field.'_Lazy'] = $currentObj;
|
||||
}
|
||||
$currentObj = get_parent_class($currentObj);
|
||||
}
|
||||
}
|
||||
|
||||
$this->original = $this->record;
|
||||
|
||||
// Keep track of the modification date of all the data sourced to make this page
|
||||
@ -413,7 +426,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
*/
|
||||
function duplicate($doWrite = true) {
|
||||
$className = $this->class;
|
||||
$clone = new $className( $this->record, false, $this->model );
|
||||
$clone = new $className( $this->toMap(), false, $this->model );
|
||||
$clone->ID = 0;
|
||||
|
||||
$clone->extend('onBeforeDuplicate', $this, $doWrite);
|
||||
@ -707,6 +720,13 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* @return array The data as a map.
|
||||
*/
|
||||
public function toMap() {
|
||||
foreach ($this->record as $key => $value) {
|
||||
if (strlen($key) > 5 && substr($key, -5) == '_Lazy') {
|
||||
$this->loadLazyFields($value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->record;
|
||||
}
|
||||
|
||||
@ -874,7 +894,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
foreach($fieldNames as $fieldName) {
|
||||
if(!isset($this->changed[$fieldName])) $this->changed[$fieldName] = 1;
|
||||
// Populate the null values in record so that they actually get written
|
||||
if(!isset($this->record[$fieldName])) $this->record[$fieldName] = null;
|
||||
if(!$this->$fieldName) $this->record[$fieldName] = null;
|
||||
}
|
||||
|
||||
// @todo Find better way to allow versioned to write a new version after forceChange
|
||||
@ -1509,7 +1529,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$classes = ClassInfo::ancestry($this);
|
||||
$good = false;
|
||||
$items = array();
|
||||
|
||||
|
||||
foreach($classes as $class) {
|
||||
// Wait until after we reach DataObject
|
||||
if(!$good) {
|
||||
@ -1930,7 +1950,13 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
public function getField($field) {
|
||||
// If we already have an object in $this->record, then we should just return that
|
||||
if(isset($this->record[$field]) && is_object($this->record[$field])) return $this->record[$field];
|
||||
|
||||
|
||||
// Do we have a field that needs to be lazy loaded?
|
||||
if(isset($this->record[$field.'_Lazy'])) {
|
||||
$tableClass = $this->record[$field.'_Lazy'];
|
||||
$this->loadLazyFields($tableClass);
|
||||
}
|
||||
|
||||
// Otherwise, we need to determine if this is a complex field
|
||||
if(self::is_composite_field($this->class, $field)) {
|
||||
$helper = $this->castingHelper($field);
|
||||
@ -1950,12 +1976,64 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a map of all the fields for this record.
|
||||
* Return a map of all the fields for this record
|
||||
* @deprecated 2.4 Use toMap()
|
||||
*
|
||||
* @return array A map of field names to field values.
|
||||
*/
|
||||
public function getAllFields() {
|
||||
return $this->record;
|
||||
return $this->toMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all the stub fields than an initial lazy load didn't load fully.
|
||||
*
|
||||
* @param tableClass Base table to load the values from. Others are joined as required.
|
||||
*/
|
||||
|
||||
protected function loadLazyFields($tableClass = null) {
|
||||
// Smarter way to work out the tableClass? Should the functionality in toMap and getField be moved into here?
|
||||
if (!$tableClass) $tableClass = $this->ClassName;
|
||||
|
||||
$dataQuery = new DataQuery($tableClass);
|
||||
$dataQuery->where("\"$tableClass\".\"ID\" = {$this->record['ID']}")->limit(1);
|
||||
$columns = array();
|
||||
|
||||
// Add SQL for fields, both simple & multi-value
|
||||
// TODO: This is copy & pasted from buildSQL(), it could be moved into a method
|
||||
$databaseFields = self::database_fields($tableClass);
|
||||
if($databaseFields) foreach($databaseFields as $k => $v) {
|
||||
if(!isset($this->record[$k]) || $this->record[$k] === null) {
|
||||
$columns[] = $k;
|
||||
}
|
||||
}
|
||||
|
||||
if ($columns) {
|
||||
$query = $dataQuery->query(); // eh?
|
||||
$this->extend('augmentSQL', $query, $dataQuery);
|
||||
|
||||
$dataQuery->setQueriedColumns($columns);
|
||||
$newData = $dataQuery->execute()->record();
|
||||
|
||||
// Load the data into record
|
||||
if($newData) {
|
||||
foreach($newData as $k => $v) {
|
||||
if (in_array($k, $columns)) {
|
||||
$this->record[$k] = $v;
|
||||
$this->original[$k] = $v;
|
||||
unset($this->record[$k . '_Lazy']);
|
||||
}
|
||||
}
|
||||
|
||||
// No data means that the query returned nothing; assign 'null' to all the requested fields
|
||||
} else {
|
||||
foreach($columns as $k) {
|
||||
$this->record[$k] = null;
|
||||
$this->original[$k] = null;
|
||||
unset($this->record[$k . '_Lazy']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2047,6 +2125,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
// Situation 1: Passing an DBField
|
||||
if($val instanceof DBField) {
|
||||
$val->Name = $fieldName;
|
||||
|
||||
// If we've just lazy-loaded the column, then we need to populate the $original array by
|
||||
// called getField(). Too much overhead? Could this be done by a quicker method? Maybe only
|
||||
// on a call to getChanged()?
|
||||
if (isset($this->record[$fieldName.'_Lazy'])) {
|
||||
$this->getField($fieldName);
|
||||
}
|
||||
|
||||
$this->record[$fieldName] = $val;
|
||||
// Situation 2: Passing a literal or non-DBField object
|
||||
} else {
|
||||
@ -2068,7 +2154,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$this->changed[$fieldName] = 2;
|
||||
}
|
||||
|
||||
// value is always saved back when strict check succeeds
|
||||
// If we've just lazy-loaded the column, then we need to populate the $original array by
|
||||
// called getField(). Too much overhead? Could this be done by a quicker method? Maybe only
|
||||
// on a call to getChanged()?
|
||||
if (isset($this->record[$fieldName.'_Lazy'])) {
|
||||
$this->getField($fieldName);
|
||||
}
|
||||
|
||||
// Value is always saved back when strict check succeeds.
|
||||
$this->record[$fieldName] = $val;
|
||||
}
|
||||
}
|
||||
@ -2107,8 +2200,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
*/
|
||||
public function hasField($field) {
|
||||
return (
|
||||
array_key_exists($field, $this->record)
|
||||
array_key_exists($field, $this->record)
|
||||
|| $this->db($field)
|
||||
|| (substr($field,-2) == 'ID') && $this->has_one(substr($field,0, -2))
|
||||
|| $this->hasMethod("get{$field}")
|
||||
);
|
||||
}
|
||||
@ -2391,7 +2485,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
|
||||
// Special case for has_one relationships
|
||||
} else if(preg_match('/ID$/', $fieldName) && $this->has_one(substr($fieldName,0,-2))) {
|
||||
$val = (isset($this->record[$fieldName])) ? $this->record[$fieldName] : null;
|
||||
$val = $this->$fieldName;
|
||||
return DBField::create_field('ForeignKey', $val, $fieldName, $this);
|
||||
|
||||
// Special case for ClassName
|
||||
@ -2496,7 +2590,6 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
public function buildSQL($filter = "", $sort = "", $limit = "", $join = "", $restrictClasses = true, $having = "") {
|
||||
Deprecation::notice('3.0', 'Use DataList::create and DataList to do your querying instead.');
|
||||
return $this->extendedSQL($filter, $sort, $limit, $join, $having);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -24,6 +24,8 @@ class DataQuery {
|
||||
* @var array
|
||||
*/
|
||||
protected $collidingFields = array();
|
||||
|
||||
private $queriedColumns = null;
|
||||
|
||||
/**
|
||||
* @var Boolean
|
||||
@ -86,11 +88,12 @@ class DataQuery {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the simplest intial query
|
||||
* Set up the simplest initial query
|
||||
*/
|
||||
function initialiseQuery() {
|
||||
// Get the tables to join to
|
||||
$tableClasses = ClassInfo::dataClassesFor($this->dataClass);
|
||||
// Get the tables to join to.
|
||||
// Don't get any subclass tables - let lazy loading do that.
|
||||
$tableClasses = ClassInfo::ancestry($this->dataClass, true);
|
||||
|
||||
// Error checking
|
||||
if(!$tableClasses) {
|
||||
@ -113,28 +116,78 @@ class DataQuery {
|
||||
}
|
||||
|
||||
$this->query->from("\"$baseClass\"");
|
||||
$this->selectAllFromTable($this->query, $baseClass);
|
||||
$this->selectColumnsFromTable($this->query, $baseClass);
|
||||
|
||||
singleton($this->dataClass)->extend('augmentDataQueryCreation', $this->query, $this);
|
||||
}
|
||||
|
||||
function setQueriedColumns($queriedColumns) {
|
||||
$this->queriedColumns = $queriedColumns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the query is ready to execute.
|
||||
*/
|
||||
function getFinalisedQuery() {
|
||||
$query = clone $this->query;
|
||||
|
||||
// Get the tables to join to
|
||||
$tableClasses = ClassInfo::dataClassesFor($this->dataClass);
|
||||
$baseClass = array_shift($tableClasses);
|
||||
|
||||
$collidingFields = array();
|
||||
function getFinalisedQuery($queriedColumns = null) {
|
||||
if(!$queriedColumns) $queriedColumns = $this->queriedColumns;
|
||||
if($queriedColumns) {
|
||||
$queriedColumns = array_merge($queriedColumns, array('Created', 'LastEdited', 'ClassName'));
|
||||
}
|
||||
|
||||
// Join all the tables
|
||||
if($this->querySubclasses) {
|
||||
foreach($tableClasses as $tableClass) {
|
||||
$query = clone $this->query;
|
||||
|
||||
// Generate the list of tables to iterate over and the list of columns required by any existing where clauses.
|
||||
// This second step is skipped if we're fetching the whole dataobject as any required columns will get selected
|
||||
// regardless.
|
||||
if($queriedColumns) {
|
||||
$tableClasses = ClassInfo::dataClassesFor($this->dataClass);
|
||||
|
||||
foreach ($query->where as $where) {
|
||||
// Check for just the column, in the form '"Column" = ?' and the form '"Table"."Column"' = ?
|
||||
if (preg_match('/^"([^"]+)"/', $where, $matches) ||
|
||||
preg_match('/^"([^"]+)"\."[^"]+"/', $where, $matches)) {
|
||||
if (!in_array($matches[1], $queriedColumns)) $queriedColumns[] = $matches[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
else $tableClasses = ClassInfo::ancestry($this->dataClass, true);
|
||||
|
||||
$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) {
|
||||
$joinTable = false;
|
||||
|
||||
// If queriedColumns is set, then check if any of the fields are in this table.
|
||||
if ($queriedColumns) {
|
||||
$tableFields = DataObject::database_fields($tableClass);
|
||||
$selectColumns = array();
|
||||
// Look through columns specifically requested in query (or where clause)
|
||||
foreach ($queriedColumns as $queriedColumn) {
|
||||
if (array_key_exists($queriedColumn, $tableFields)) {
|
||||
$selectColumns[] = $queriedColumn;
|
||||
}
|
||||
}
|
||||
|
||||
$this->selectColumnsFromTable($query, $tableClass, $selectColumns);
|
||||
if ($selectColumns && $tableClass != $baseClass) {
|
||||
$joinTable = true;
|
||||
}
|
||||
} else {
|
||||
$this->selectColumnsFromTable($query, $tableClass);
|
||||
if ($tableClass != $baseClass) $joinTable = true;
|
||||
}
|
||||
|
||||
if ($joinTable) {
|
||||
$query->leftJoin($tableClass, "\"$tableClass\".\"ID\" = \"$baseClass\".\"ID\"") ;
|
||||
$this->selectAllFromTable($query, $tableClass);
|
||||
}
|
||||
}
|
||||
|
||||
@ -307,28 +360,28 @@ class DataQuery {
|
||||
/**
|
||||
* Update the SELECT clause of the query with the columns from the given table
|
||||
*/
|
||||
protected function selectAllFromTable(SQLQuery &$query, $tableClass) {
|
||||
// Add SQL for multi-value fields
|
||||
$databaseFields = DataObject::database_fields($tableClass);
|
||||
$compositeFields = DataObject::composite_fields($tableClass, false);
|
||||
if($databaseFields) foreach($databaseFields as $k => $v) {
|
||||
if(!isset($compositeFields[$k])) {
|
||||
// Update $collidingFields if necessary
|
||||
if(isset($query->select[$k])) {
|
||||
if(!isset($this->collidingFields[$k])) $this->collidingFields[$k] = array($query->select[$k]);
|
||||
$this->collidingFields[$k][] = "\"$tableClass\".\"$k\"";
|
||||
protected function selectColumnsFromTable(SQLQuery &$query, $tableClass, $columns = null) {
|
||||
// Add SQL for multi-value fields
|
||||
$databaseFields = DataObject::database_fields($tableClass);
|
||||
$compositeFields = DataObject::composite_fields($tableClass, false);
|
||||
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]);
|
||||
$this->collidingFields[$k][] = "\"$tableClass\".\"$k\"";
|
||||
|
||||
} else {
|
||||
$query->select[$k] = "\"$tableClass\".\"$k\"";
|
||||
}
|
||||
}
|
||||
}
|
||||
if($compositeFields) foreach($compositeFields as $k => $v) {
|
||||
if($v) {
|
||||
$dbO = Object::create_from_string($v, $k);
|
||||
$dbO->addToQuery($query);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$query->select[$k] = "\"$tableClass\".\"$k\"";
|
||||
}
|
||||
}
|
||||
}
|
||||
if($compositeFields) foreach($compositeFields as $k => $v) {
|
||||
if((is_null($columns) || in_array($k, $columns)) && $v) {
|
||||
$dbO = Object::create_from_string($v, $k);
|
||||
$dbO->addToQuery($query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -561,7 +614,7 @@ class DataQuery {
|
||||
* Query the given field column from the database and return as an array.
|
||||
*/
|
||||
public function column($field = 'ID') {
|
||||
$query = $this->getFinalisedQuery();
|
||||
$query = $this->getFinalisedQuery(array($field));
|
||||
$query->select($this->expressionForField($field, $query));
|
||||
$this->ensureSelectContainsOrderbyColumns($query);
|
||||
|
||||
|
234
tests/model/DataObjectLazyLoadingTest.php
Normal file
234
tests/model/DataObjectLazyLoadingTest.php
Normal file
@ -0,0 +1,234 @@
|
||||
<?php
|
||||
/**
|
||||
* @package framework
|
||||
* @subpackage tests
|
||||
*/
|
||||
|
||||
class DataObjectLazyLoadingTest extends SapphireTest {
|
||||
|
||||
static $fixture_file = 'DataObjectTest.yml';
|
||||
|
||||
// These are all defined in DataObjectTest.php
|
||||
protected $extraDataObjects = array(
|
||||
'DataObjectTest_Team',
|
||||
'DataObjectTest_Fixture',
|
||||
'DataObjectTest_SubTeam',
|
||||
'OtherSubclassWithSameField',
|
||||
'DataObjectTest_FieldlessTable',
|
||||
'DataObjectTest_FieldlessSubTable',
|
||||
'DataObjectTest_ValidatedObject',
|
||||
'DataObjectTest_Player',
|
||||
'DataObjectTest_TeamComment'
|
||||
);
|
||||
|
||||
function testQueriedColumnsID() {
|
||||
$playerList = new DataList('DataObjectTest_SubTeam');
|
||||
$playerList = $playerList->setQueriedColumns(array('ID'));
|
||||
$expected = 'SELECT DISTINCT "DataObjectTest_Team"."ClassName", "DataObjectTest_Team"."Created", ' .
|
||||
'"DataObjectTest_Team"."LastEdited", "DataObjectTest_Team"."ID", CASE WHEN '.
|
||||
'"DataObjectTest_Team"."ClassName" IS NOT NULL THEN "DataObjectTest_Team"."ClassName" ELSE ' .
|
||||
'\'DataObjectTest_Team\' END AS "RecordClassName" FROM "DataObjectTest_Team" WHERE ' .
|
||||
'("DataObjectTest_Team"."ClassName" IN (\'DataObjectTest_SubTeam\'))';
|
||||
$this->assertEquals($expected, $playerList->sql());
|
||||
}
|
||||
|
||||
function testQueriedColumnsFromBaseTableAndSubTable() {
|
||||
$playerList = new DataList('DataObjectTest_SubTeam');
|
||||
$playerList = $playerList->setQueriedColumns(array('Title', 'SubclassDatabaseField'));
|
||||
$expected = 'SELECT DISTINCT "DataObjectTest_Team"."ClassName", "DataObjectTest_Team"."Created", ' .
|
||||
'"DataObjectTest_Team"."LastEdited", "DataObjectTest_Team"."Title", ' .
|
||||
'"DataObjectTest_SubTeam"."SubclassDatabaseField", "DataObjectTest_Team"."ID", CASE WHEN ' .
|
||||
'"DataObjectTest_Team"."ClassName" IS NOT NULL THEN "DataObjectTest_Team"."ClassName" ELSE ' .
|
||||
'\'DataObjectTest_Team\' END AS "RecordClassName" FROM "DataObjectTest_Team" LEFT JOIN ' .
|
||||
'"DataObjectTest_SubTeam" ON "DataObjectTest_SubTeam"."ID" = "DataObjectTest_Team"."ID" WHERE ' .
|
||||
'("DataObjectTest_Team"."ClassName" IN (\'DataObjectTest_SubTeam\'))';
|
||||
$this->assertEquals($expected, $playerList->sql());
|
||||
}
|
||||
|
||||
function testQueriedColumnsFromBaseTable() {
|
||||
$playerList = new DataList('DataObjectTest_SubTeam');
|
||||
$playerList = $playerList->setQueriedColumns(array('Title'));
|
||||
$expected = 'SELECT DISTINCT "DataObjectTest_Team"."ClassName", "DataObjectTest_Team"."Created", ' .
|
||||
'"DataObjectTest_Team"."LastEdited", "DataObjectTest_Team"."Title", "DataObjectTest_Team"."ID", ' .
|
||||
'CASE WHEN "DataObjectTest_Team"."ClassName" IS NOT NULL THEN "DataObjectTest_Team"."ClassName" ELSE ' .
|
||||
'\'DataObjectTest_Team\' END AS "RecordClassName" FROM "DataObjectTest_Team" WHERE ' .
|
||||
'("DataObjectTest_Team"."ClassName" IN (\'DataObjectTest_SubTeam\'))';
|
||||
$this->assertEquals($expected, $playerList->sql());
|
||||
}
|
||||
|
||||
function testQueriedColumnsFromSubTable() {
|
||||
$playerList = new DataList('DataObjectTest_SubTeam');
|
||||
$playerList = $playerList->setQueriedColumns(array('SubclassDatabaseField'));
|
||||
$expected = 'SELECT DISTINCT "DataObjectTest_Team"."ClassName", "DataObjectTest_Team"."Created", ' .
|
||||
'"DataObjectTest_Team"."LastEdited", "DataObjectTest_SubTeam"."SubclassDatabaseField", ' .
|
||||
'"DataObjectTest_Team"."ID", CASE WHEN "DataObjectTest_Team"."ClassName" IS NOT NULL THEN ' .
|
||||
'"DataObjectTest_Team"."ClassName" ELSE \'DataObjectTest_Team\' END AS "RecordClassName" FROM ' .
|
||||
'"DataObjectTest_Team" LEFT JOIN "DataObjectTest_SubTeam" ON "DataObjectTest_SubTeam"."ID" = ' .
|
||||
'"DataObjectTest_Team"."ID" WHERE ("DataObjectTest_Team"."ClassName" IN (\'DataObjectTest_SubTeam\'))';
|
||||
$this->assertEquals($expected, $playerList->sql());
|
||||
}
|
||||
|
||||
function testNoSpecificColumnNamesBaseDataObjectQuery() {
|
||||
// This queries all columns from base table
|
||||
$playerList = new DataList('DataObjectTest_Team');
|
||||
// Shouldn't be a left join in here.
|
||||
$this->assertEquals(0, preg_match('/SELECT DISTINCT "DataObjectTest_Team"."ID" .* LEFT JOIN .* FROM "DataObjectTest_Team"/', $playerList->sql()));
|
||||
}
|
||||
|
||||
function testNoSpecificColumnNamesSubclassDataObjectQuery() {
|
||||
// This queries all columns from base table and subtable
|
||||
$playerList = new DataList('DataObjectTest_SubTeam');
|
||||
// Should be a left join.
|
||||
$this->assertEquals(1, preg_match('/SELECT DISTINCT .* LEFT JOIN .* /', $playerList->sql()));
|
||||
}
|
||||
|
||||
function testLazyLoadedFieldsHasField() {
|
||||
$subteam1 = $this->objFromFixture('DataObjectTest_SubTeam', 'subteam1');
|
||||
$teams = DataObject::get('DataObjectTest_Team'); // query parent class
|
||||
$subteam1Lazy = $teams->find('ID', $subteam1->ID);
|
||||
|
||||
// TODO Fix hasField() to exclude *_Lazy
|
||||
// $this->assertFalse($subteam1Lazy->hasField('SubclassDatabaseField_Lazy'));
|
||||
$this->assertTrue($subteam1Lazy->hasField('SubclassDatabaseField'));
|
||||
}
|
||||
|
||||
function testLazyLoadedFieldsGetField() {
|
||||
$subteam1 = $this->objFromFixture('DataObjectTest_SubTeam', 'subteam1');
|
||||
$teams = DataObject::get('DataObjectTest_Team'); // query parent class
|
||||
$subteam1Lazy = $teams->find('ID', $subteam1->ID);
|
||||
|
||||
$this->assertEquals(
|
||||
$subteam1->getField('SubclassDatabaseField'),
|
||||
$subteam1Lazy->getField('SubclassDatabaseField')
|
||||
);
|
||||
}
|
||||
|
||||
function testLazyLoadedFieldsSetField() {
|
||||
$subteam1 = $this->objFromFixture('DataObjectTest_SubTeam', 'subteam1');
|
||||
$subteam1ID = $subteam1->ID;
|
||||
$teams = DataObject::get('DataObjectTest_Team'); // query parent class
|
||||
$subteam1Lazy = $teams->find('ID', $subteam1->ID);
|
||||
|
||||
// Updated lazyloaded field
|
||||
$subteam1Lazy->SubclassDatabaseField = 'Changed';
|
||||
$subteam1Lazy->write();
|
||||
|
||||
// Reload from database
|
||||
DataObject::flush_and_destroy_cache();
|
||||
$subteam1Reloaded = DataObject::get_by_id('DataObjectTest_SubTeam', $subteam1ID);
|
||||
|
||||
$this->assertEquals(
|
||||
'Changed',
|
||||
$subteam1Reloaded->getField('SubclassDatabaseField')
|
||||
);
|
||||
}
|
||||
|
||||
function testLazyLoadedFieldsWriteWithUnloadedFields() {
|
||||
$subteam1 = $this->objFromFixture('DataObjectTest_SubTeam', 'subteam1');
|
||||
$subteam1ID = $subteam1->ID;
|
||||
$teams = DataObject::get('DataObjectTest_Team'); // query parent class
|
||||
$subteam1Lazy = $teams->find('ID', $subteam1->ID);
|
||||
|
||||
// Updated lazyloaded field
|
||||
$subteam1Lazy->Title = 'Changed';
|
||||
$subteam1Lazy->write();
|
||||
|
||||
// Reload from database
|
||||
DataObject::flush_and_destroy_cache();
|
||||
$subteam1Reloaded = DataObject::get_by_id('DataObjectTest_SubTeam', $subteam1ID);
|
||||
|
||||
$this->assertEquals(
|
||||
'Subclassed 1',
|
||||
$subteam1Reloaded->getField('SubclassDatabaseField')
|
||||
);
|
||||
}
|
||||
|
||||
function testLazyLoadedFieldsWriteNullFields() {
|
||||
$subteam1 = $this->objFromFixture('DataObjectTest_SubTeam', 'subteam1');
|
||||
$subteam1ID = $subteam1->ID;
|
||||
$teams = DataObject::get('DataObjectTest_Team'); // query parent class
|
||||
$subteam1Lazy = $teams->find('ID', $subteam1->ID);
|
||||
|
||||
// Updated lazyloaded field
|
||||
$subteam1Lazy->SubclassDatabaseField = null;
|
||||
$subteam1Lazy->write();
|
||||
|
||||
// Reload from database
|
||||
DataObject::flush_and_destroy_cache();
|
||||
$subteam1Reloaded = DataObject::get_by_id('DataObjectTest_SubTeam', $subteam1ID);
|
||||
|
||||
$this->assertEquals(
|
||||
null,
|
||||
$subteam1Reloaded->getField('SubclassDatabaseField')
|
||||
);
|
||||
}
|
||||
|
||||
function testLazyLoadedFieldsGetChangedFields() {
|
||||
$subteam1 = $this->objFromFixture('DataObjectTest_SubTeam', 'subteam1');
|
||||
$teams = DataObject::get('DataObjectTest_Team'); // query parent class
|
||||
$subteam1Lazy = $teams->find('ID', $subteam1->ID);
|
||||
|
||||
// Updated lazyloaded field
|
||||
$subteam1Lazy->SubclassDatabaseField = 'Changed';
|
||||
$this->assertEquals(
|
||||
array('SubclassDatabaseField' => array(
|
||||
'before' => 'Subclassed 1',
|
||||
'after' => 'Changed',
|
||||
'level' => 2
|
||||
)),
|
||||
$subteam1Lazy->getChangedFields()
|
||||
);
|
||||
}
|
||||
|
||||
function testLazyLoadedFieldsHasOneRelation() {
|
||||
$subteam1 = $this->objFromFixture('DataObjectTest_SubTeam', 'subteam1');
|
||||
$parentTeam = $this->objFromFixture('DataObjectTest_Team', 'team1');
|
||||
$teams = DataObject::get('DataObjectTest_Team'); // query parent class
|
||||
$subteam1Lazy = $teams->find('ID', $subteam1->ID);
|
||||
|
||||
$parentTeamLazy = $subteam1Lazy->ParentTeam();
|
||||
$this->assertType('DataObjectTest_Team', $parentTeamLazy);
|
||||
$this->assertEquals($parentTeam->ID, $parentTeamLazy->ID);
|
||||
}
|
||||
|
||||
function testLazyLoadedFieldsToMap() {
|
||||
$subteam1 = $this->objFromFixture('DataObjectTest_SubTeam', 'subteam1');
|
||||
$parentTeam = $this->objFromFixture('DataObjectTest_Team', 'team1');
|
||||
$teams = DataObject::get('DataObjectTest_Team'); // query parent class
|
||||
$subteam1Lazy = $teams->find('ID', $subteam1->ID);
|
||||
$mapLazy = $subteam1Lazy->toMap();
|
||||
$this->assertArrayHasKey('SubclassDatabaseField', $mapLazy);
|
||||
$this->assertEquals('Subclassed 1', $mapLazy['SubclassDatabaseField']);
|
||||
}
|
||||
|
||||
function testLazyLoadedFieldsIsEmpty() {
|
||||
$subteam1 = $this->objFromFixture('DataObjectTest_SubTeam', 'subteam1');
|
||||
$parentTeam = $this->objFromFixture('DataObjectTest_Team', 'team1');
|
||||
$teams = DataObject::get('DataObjectTest_Team'); // query parent class
|
||||
$subteam1Lazy = $teams->find('ID', $subteam1->ID);
|
||||
$subteam1Lazy->Title = '';
|
||||
$subteam1Lazy->DecoratedDatabaseField = '';
|
||||
$subteam1Lazy->ParentTeamID = 0;
|
||||
// Leave $subteam1Lazy->SubclassDatabaseField intact
|
||||
$this->assertFalse($subteam1Lazy->isEmpty());
|
||||
}
|
||||
|
||||
function testLazyLoadedFieldsDuplicate() {
|
||||
$subteam1 = $this->objFromFixture('DataObjectTest_SubTeam', 'subteam1');
|
||||
$parentTeam = $this->objFromFixture('DataObjectTest_Team', 'team1');
|
||||
$teams = DataObject::get('DataObjectTest_Team'); // query parent class
|
||||
$subteam1Lazy = $teams->find('ID', $subteam1->ID);
|
||||
$subteam1LazyDup = $subteam1Lazy->duplicate();
|
||||
|
||||
$this->assertEquals('Subclassed 1', $subteam1LazyDup->SubclassDatabaseField);
|
||||
}
|
||||
|
||||
function testLazyLoadedFieldsGetAllFields() {
|
||||
$subteam1 = $this->objFromFixture('DataObjectTest_SubTeam', 'subteam1');
|
||||
$parentTeam = $this->objFromFixture('DataObjectTest_Team', 'team1');
|
||||
$teams = DataObject::get('DataObjectTest_Team'); // query parent class
|
||||
$subteam1Lazy = $teams->find('ID', $subteam1->ID);
|
||||
$this->assertArrayNotHasKey('SubclassDatabaseField_Lazy', $subteam1Lazy->getAllFields());
|
||||
$this->assertArrayHasKey('SubclassDatabaseField', $subteam1Lazy->getAllFields());
|
||||
}
|
||||
}
|
@ -367,7 +367,7 @@ class DataObjectTest extends SapphireTest {
|
||||
}
|
||||
|
||||
function testRandomSort() {
|
||||
/* If we perforn the same regularly sorted query twice, it should return the same results */
|
||||
/* If we perform the same regularly sorted query twice, it should return the same results */
|
||||
$itemsA = DataObject::get("DataObjectTest_TeamComment", "", "ID");
|
||||
foreach($itemsA as $item) $keysA[] = $item->ID;
|
||||
|
||||
@ -555,6 +555,7 @@ class DataObjectTest extends SapphireTest {
|
||||
//'Created',
|
||||
//'LastEdited',
|
||||
'SubclassDatabaseField',
|
||||
'ParentTeamID',
|
||||
'Title',
|
||||
'DatabaseField',
|
||||
'ExtendedDatabaseField',
|
||||
@ -569,6 +570,7 @@ class DataObjectTest extends SapphireTest {
|
||||
array_keys(DataObject::database_fields('DataObjectTest_SubTeam')),
|
||||
array(
|
||||
'SubclassDatabaseField',
|
||||
'ParentTeamID',
|
||||
),
|
||||
'databaseFields() on subclass contains only fields defined on instance'
|
||||
);
|
||||
@ -731,7 +733,7 @@ class DataObjectTest extends SapphireTest {
|
||||
|
||||
function testManyManyExtraFields() {
|
||||
$player = $this->objFromFixture('DataObjectTest_Player', 'player1');
|
||||
$team = $this->objFromFixture('DataObjectTest_Team', 'team1');
|
||||
$team = $this->objFromFixture('DataObjectTest_Team', 'team1');
|
||||
|
||||
// Extra fields are immediately available on the Team class (defined in $many_many_extraFields)
|
||||
$teamExtraFields = $team->many_many_extraFields('Players');
|
||||
@ -1015,7 +1017,7 @@ class DataObjectTest extends SapphireTest {
|
||||
$objEmpty->Title = '0'; //
|
||||
$this->assertFalse($objEmpty->isEmpty(), 'Zero value in attribute considered non-empty');
|
||||
}
|
||||
|
||||
|
||||
function testRelField() {
|
||||
$captain = $this->objFromFixture('DataObjectTest_Player', 'captain1');
|
||||
// Test traversal of a single has_one
|
||||
@ -1095,6 +1097,7 @@ class DataObjectTest_Team extends DataObject implements TestOnly {
|
||||
);
|
||||
|
||||
static $has_many = array(
|
||||
'SubTeams' => 'DataObjectTest_SubTeam',
|
||||
'Comments' => 'DataObjectTest_TeamComment'
|
||||
);
|
||||
|
||||
@ -1142,6 +1145,10 @@ class DataObjectTest_SubTeam extends DataObjectTest_Team implements TestOnly {
|
||||
static $db = array(
|
||||
'SubclassDatabaseField' => 'Varchar'
|
||||
);
|
||||
|
||||
static $has_one = array(
|
||||
"ParentTeam" => 'DataObjectTest_Team',
|
||||
);
|
||||
}
|
||||
class OtherSubclassWithSameField extends DataObjectTest_Team implements TestOnly {
|
||||
static $db = array(
|
||||
|
@ -26,6 +26,7 @@ DataObjectTest_SubTeam:
|
||||
Title: Subteam 1
|
||||
SubclassDatabaseField: Subclassed 1
|
||||
ExtendedDatabaseField: Extended 1
|
||||
ParentTeam: =>DataObjectTest_Team.team1
|
||||
subteam2_with_player_relation:
|
||||
Title: Subteam 2
|
||||
SubclassDatabaseField: Subclassed 2
|
||||
|
@ -240,7 +240,7 @@ class VersionedTest extends SapphireTest {
|
||||
$this->assertEquals(array(
|
||||
'VersionedTest_DataObject_Live',
|
||||
'VersionedTest_Subclass_Live',
|
||||
), DataObject::get('VersionedTest_DataObject')->dataQuery()->query()->queriedTables());
|
||||
), DataObject::get('VersionedTest_Subclass')->dataQuery()->query()->queriedTables());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -153,7 +153,7 @@ class SearchFilterApplyRelationTest_HasManyParent extends DataObject implements
|
||||
}
|
||||
|
||||
class SearchFilterApplyRelationTest_HasManyChild extends SearchFilterApplyRelationTest_HasManyParent implements TestOnly {
|
||||
// This is to create an seperate Table only.
|
||||
// This is to create an separate Table only.
|
||||
static $db = array(
|
||||
"ChildField" => "Varchar"
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user