Skip to content

Commit

Permalink
Merge pull request #10862 from creative-commoners/pulls/5/eagerloadin…
Browse files Browse the repository at this point in the history
…g-new-tests

Fix various eagerloading bugs
  • Loading branch information
emteknetnz authored Jul 17, 2023
2 parents 7718443 + ed4c34b commit 10fba6e
Show file tree
Hide file tree
Showing 29 changed files with 1,222 additions and 711 deletions.
84 changes: 51 additions & 33 deletions src/ORM/DataList.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ public function __clone()
{
$this->dataQuery = clone $this->dataQuery;
$this->finalisedQuery = null;
$this->eagerLoadedData = [];
}

/**
Expand Down Expand Up @@ -831,11 +832,10 @@ public function leftJoin($table, $onClause, $alias = null, $order = 20, $paramet
*/
public function toArray()
{
$rows = $this->executeQuery();
$results = [];

foreach ($rows as $row) {
$results[] = $this->createDataObject($row);
foreach ($this as $item) {
$results[] = $item;
}

return $results;
Expand Down Expand Up @@ -1001,6 +1001,7 @@ public function getIterator(): Traversable

// Re-set the finaliseQuery so that it can be re-executed
$this->finalisedQuery = null;
$this->eagerLoadedData = [];
}

/**
Expand Down Expand Up @@ -1030,28 +1031,37 @@ private function getEagerLoadVariables(string $eagerLoadRelation): array
$hasManyIDField = null;
$manyManyLastComponent = null;
for ($i = 0; $i < count($relations) - 1; $i++) {
$hasOneComponent = $schema->hasOneComponent($dataClasses[$i], $relations[$i + 1]);
$parentDataClass = $dataClasses[$i];
$relationName = $relations[$i + 1];
$hasOneComponent = $schema->hasOneComponent($parentDataClass, $relationName);
if ($hasOneComponent) {
$dataClasses[] = $hasOneComponent;
$hasOneIDField = $relations[$i + 1] . 'ID';
continue;
}
$belongsToComponent = $schema->belongsToComponent($dataClasses[$i], $relations[$i + 1]);
$belongsToComponent = $schema->belongsToComponent($parentDataClass, $relationName);
if ($belongsToComponent) {
$dataClasses[] = $belongsToComponent;
$belongsToIDField = $schema->getRemoteJoinField($dataClasses[$i], $relations[$i + 1], 'belongs_to');
$belongsToIDField = $schema->getRemoteJoinField($parentDataClass, $relationName, 'belongs_to');
continue;
}
$hasManyComponent = $schema->hasManyComponent($dataClasses[$i], $relations[$i + 1]);
$hasManyComponent = $schema->hasManyComponent($parentDataClass, $relationName);
if ($hasManyComponent) {
$dataClasses[] = $hasManyComponent;
$hasManyIDField = $schema->getRemoteJoinField($dataClasses[$i], $relations[$i + 1], 'has_many');
$hasManyIDField = $schema->getRemoteJoinField($parentDataClass, $relationName, 'has_many');
continue;
}
// this works for both many_many and belongs_many_many
$manyManyComponent = $schema->manyManyComponent($dataClasses[$i], $relations[$i + 1]);
$manyManyComponent = $schema->manyManyComponent($parentDataClass, $relationName);
if ($manyManyComponent) {
$dataClasses[] = $manyManyComponent['childClass'];
$manyManyComponent['extraFields'] = $schema->manyManyExtraFieldsForComponent($parentDataClass, $relationName) ?: [];
if (is_a($manyManyComponent['relationClass'], ManyManyThroughList::class, true)) {
$manyManyComponent['joinClass'] = $manyManyComponent['join'];
$manyManyComponent['join'] = $schema->baseDataTable($manyManyComponent['joinClass']);
} else {
$manyManyComponent['joinClass'] = null;
}
$manyManyLastComponent = $manyManyComponent;
continue;
}
Expand Down Expand Up @@ -1130,7 +1140,8 @@ private function fetchEagerLoadRelations(Query $query): void
$parentIDs,
$relationDataClass,
$eagerLoadRelation,
$relationName
$relationName,
$parentDataClass
);
} else {
throw new LogicException('Something went wrong with the eager loading');
Expand Down Expand Up @@ -1221,7 +1232,7 @@ private function fetchEagerLoadHasMany(
$eagerLoadID = $relationItem->$hasManyIDField;
if (!isset($this->eagerLoadedData[$eagerLoadRelation][$eagerLoadID][$relationName])) {
$arrayList = ArrayList::create();
$arrayList->setDataClass($relationItem->dataClass);
$arrayList->setDataClass($relationDataClass);
$this->eagerLoadedData[$eagerLoadRelation][$eagerLoadID][$relationName] = $arrayList;
}
$this->eagerLoadedData[$eagerLoadRelation][$eagerLoadID][$relationName]->push($relationItem);
Expand All @@ -1235,39 +1246,46 @@ private function fetchEagerLoadManyMany(
array $parentIDs,
string $relationDataClass,
string $eagerLoadRelation,
string $relationName
string $relationName,
string $parentDataClass
): array {
$parentIDField = $manyManyLastComponent['parentField'];
$childIDField = $manyManyLastComponent['childField'];
// $join will either be:
// - the join table name for many-many
// - the join data class for many-many-through
$join = $manyManyLastComponent['join'];
$joinTable = $manyManyLastComponent['join'];
$extraFields = $manyManyLastComponent['extraFields'];
$joinClass = $manyManyLastComponent['joinClass'];

// Get the join records so we can correctly identify which children belong to which parents
$joinRows = DB::query('SELECT * FROM "' . $joinTable . '" WHERE "' . $parentIDField . '" IN (' . implode(',', $parentIDs) . ')');

// many_many_through
if (is_a($manyManyLastComponent['relationClass'], ManyManyThroughList::class, true)) {
$joinThroughObjs = DataObject::get($join)->filter([$parentIDField => $parentIDs]);
$relationItemIDs = [];
$joinRows = [];
foreach ($joinThroughObjs as $joinThroughObj) {
$joinRows[] = [
$parentIDField => $joinThroughObj->$parentIDField,
$childIDField => $joinThroughObj->$childIDField
];
$relationItemIDs[] = $joinThroughObj->$childIDField;
}
if ($joinClass !== null) {
$relationList = ManyManyThroughList::create(
$relationDataClass,
$joinClass,
$childIDField,
$parentIDField,
$extraFields,
$relationDataClass,
$parentDataClass
)->forForeignID($parentIDs);
// many_many + belongs_many_many
} else {
$joinTableQuery = DB::query('SELECT * FROM "' . $join . '" WHERE "' . $parentIDField . '" IN (' . implode(',', $parentIDs) . ')');
$relationItemIDs = $joinTableQuery->column($childIDField);
$joinRows = $joinTableQuery;
$relationList = ManyManyList::create(
$relationDataClass,
$joinTable,
$childIDField,
$parentIDField,
$extraFields
)->forForeignID($parentIDs);
}

// Get ALL of the items for this relation up front, for ALL of the parents
// Use a real RelationList here so that the extraFields and join record are correctly set for all relations
// Fetched as a map so we can get the ID for all records up front (instead of in another nested loop)
// Fetched after that as an array because for some reason that performs better in the loop
// Note that "Me" is a method on ViewableData that returns $this - i.e. that is the actual DataObject record
$relationArray = DataObject::get($relationDataClass)->byIDs($relationItemIDs)->map('ID', 'Me')->toArray();
$relationArray = $relationList->map('ID', 'Me')->toArray();

// Store the children in an ArrayList against the correct parent
foreach ($joinRows as $row) {
Expand All @@ -1277,13 +1295,13 @@ private function fetchEagerLoadManyMany(

if (!isset($this->eagerLoadedData[$eagerLoadRelation][$parentID][$relationName])) {
$arrayList = ArrayList::create();
$arrayList->setDataClass($relationItem->dataClass);
$arrayList->setDataClass($relationDataClass);
$this->eagerLoadedData[$eagerLoadRelation][$parentID][$relationName] = $arrayList;
}
$this->eagerLoadedData[$eagerLoadRelation][$parentID][$relationName]->push($relationItem);
}

return [$relationArray, $relationItemIDs];
return [$relationArray, array_keys($relationArray)];
}

/**
Expand Down
27 changes: 15 additions & 12 deletions src/ORM/DataObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -1954,12 +1954,6 @@ public function setEagerLoadedData(string $eagerLoadRelation, mixed $eagerLoaded
*/
public function getComponents($componentName, $id = null)
{
if (isset($this->eagerLoadedData[$componentName])) {
return $this->eagerLoadedData[$componentName];
}
if (!isset($id)) {
$id = $this->ID;
}
$result = null;

$schema = $this->getSchema();
Expand All @@ -1972,7 +1966,14 @@ public function getComponents($componentName, $id = null)
));
}

if (isset($this->eagerLoadedData[$componentName])) {
return $this->eagerLoadedData[$componentName];
}

// If we haven't been written yet, we can't save these relations, so use a list that handles this case
if (!isset($id)) {
$id = $this->ID;
}
if (!$id) {
if (!isset($this->unsavedRelations[$componentName])) {
$this->unsavedRelations[$componentName] =
Expand Down Expand Up @@ -2174,12 +2175,6 @@ public function inferReciprocalComponent($remoteClass, $remoteRelation)
*/
public function getManyManyComponents($componentName, $id = null)
{
if (isset($this->eagerLoadedData[$componentName])) {
return $this->eagerLoadedData[$componentName];
}
if (!isset($id)) {
$id = $this->ID;
}
$schema = static::getSchema();
$manyManyComponent = $schema->manyManyComponent(static::class, $componentName);
if (!$manyManyComponent) {
Expand All @@ -2190,7 +2185,14 @@ public function getManyManyComponents($componentName, $id = null)
));
}

if (isset($this->eagerLoadedData[$componentName])) {
return $this->eagerLoadedData[$componentName];
}

// If we haven't been written yet, we can't save these relations, so use a list that handles this case
if (!isset($id)) {
$id = $this->ID;
}
if (!$id) {
if (!isset($this->unsavedRelations[$componentName])) {
$this->unsavedRelations[$componentName] = new UnsavedRelationList(
Expand Down Expand Up @@ -3447,6 +3449,7 @@ public function flushCache($persistent = true)
$this->extend('flushCache');

$this->components = [];
$this->eagerLoadedData = [];
return $this;
}

Expand Down
Loading

0 comments on commit 10fba6e

Please sign in to comment.