diff --git a/src/Database/Table/ActiveRow.php b/src/Database/Table/ActiveRow.php index 9721ff3f8..d320961e1 100644 --- a/src/Database/Table/ActiveRow.php +++ b/src/Database/Table/ActiveRow.php @@ -68,7 +68,7 @@ public function __toString() public function toArray(): array { - $this->accessColumn(null); + $this->reloadAllColumns(); return $this->data; } @@ -204,7 +204,7 @@ public function delete(): int public function getIterator(): \Iterator { - $this->accessColumn(null); + $this->reloadAllColumns(); return new \ArrayIterator($this->data); } @@ -301,14 +301,10 @@ public function __unset($key) /** * @internal */ - public function accessColumn($key, bool $selectColumn = true): bool + public function accessColumn(string $key, bool $selectColumn = true): bool { if ($this->table->accessColumn($key, $selectColumn) && !$this->dataRefreshed) { - if (!isset($this->table[$this->getSignature()])) { - throw new Nette\InvalidStateException("Database refetch failed; row with signature '{$this->getSignature()}' does not exist!"); - } - $this->data = $this->table[$this->getSignature()]->data; - $this->dataRefreshed = true; + $this->refreshData(); } return isset($this->data[$key]) || array_key_exists($key, $this->data); } @@ -318,4 +314,22 @@ protected function removeAccessColumn($key): void { $this->table->removeAccessColumn($key); } + + + protected function reloadAllColumns(): void + { + if ($this->table->reloadAllColumns() && !$this->dataRefreshed) { + $this->refreshData(); + } + } + + + protected function refreshData(): void + { + if (!isset($this->table[$this->getSignature()])) { + throw new Nette\InvalidStateException("Database refetch failed; row with signature '{$this->getSignature()}' does not exist!"); + } + $this->data = $this->table[$this->getSignature()]->data; + $this->dataRefreshed = true; + } } diff --git a/src/Database/Table/ColumnAccessCache.php b/src/Database/Table/ColumnAccessCache.php new file mode 100644 index 000000000..369d4d970 --- /dev/null +++ b/src/Database/Table/ColumnAccessCache.php @@ -0,0 +1,196 @@ +selection = $selection; + $this->cache = $cacheStorage ? new Cache($cacheStorage, 'Nette.Database.' . md5($selection->getConnection()->getDsn())) : null; + } + + + public function getStorage(): ?IStorage + { + return $this->cache ? $this->cache->getStorage() : null; + } + + + /** + * Returns general cache key independent on query parameters or sql limit + * Used e.g. for previously accessed columns caching + */ + public function getGeneralCacheKey(): string + { + if ($this->generalCacheKey) { + return $this->generalCacheKey; + } + + $key = [__CLASS__, $this->selection->getName(), $this->selection->getSqlBuilder()->getConditions()]; + $trace = []; + foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) as $item) { + $trace[] = isset($item['file'], $item['line']) ? $item['file'] . $item['line'] : null; + } + + $key[] = $trace; + return $this->generalCacheKey = md5(serialize($key)); + } + + + public function setGeneralCacheKey(?string $key): void + { + $this->generalCacheKey = $key; + } + + + /** + * Returns object specific cache key dependent on query parameters + * Used e.g. for reference memory caching + */ + public function getSpecificCacheKey(): string + { + if ($this->specificCacheKey) { + return $this->specificCacheKey; + } + + return $this->specificCacheKey = $this->selection->getSqlBuilder()->getSelectQueryHash($this->getPreviousAccessedColumns()); + } + + + public function setSpecificCacheKey(?string $key): void + { + $this->specificCacheKey = $key; + } + + + public function getAccessedColumns(): array + { + return $this->accessedColumns; + } + + + public function setAccessedColumns(array $accessedColumns): void + { + if ($this->cache) { + $this->accessedColumns = $accessedColumns; + } + } + + + public function setAccessedColumn(string $key, bool $value): void + { + if ($this->cache) { + $this->accessedColumns[$key] = $value; + } + } + + + /** + * Loads cache of previous accessed columns and returns it. + */ + public function getPreviousAccessedColumns(): array + { + if ($this->cache && $this->previousAccessedColumns === null) { + $this->accessedColumns = $this->previousAccessedColumns = $this->cache->load($this->getGeneralCacheKey()) ?: []; + } + + return array_keys(array_filter((array) $this->previousAccessedColumns)); + } + + + public function setPreviousAccessedColumns(array $previousAccessedColumns): void + { + $this->previousAccessedColumns = $previousAccessedColumns; + } + + + public function clearPreviousAccessedColumns(): void + { + $this->previousAccessedColumns = null; + } + + + public function saveState(): void + { + if ($this->observeCache === $this->selection && $this->cache && !$this->selection->getSqlBuilder()->getSelect() && $this->accessedColumns !== $this->previousAccessedColumns) { + $previousAccessed = $this->cache->load($this->getGeneralCacheKey()); + $accessed = $this->accessedColumns; + $needSave = is_array($previousAccessed) + ? array_intersect_key($accessed, $previousAccessed) !== $accessed + : $accessed !== $previousAccessed; + + if ($needSave) { + $save = is_array($previousAccessed) ? $previousAccessed + $accessed : $accessed; + $this->cache->save($this->getGeneralCacheKey(), $save); + $this->previousAccessedColumns = null; + } + } + } + + + public function &loadFromRefCache(&$referencing): string + { + $hash = $this->getSpecificCacheKey(); + $this->observeCache = &$referencing['observeCache']; + $this->accessedColumns = &$referencing[$hash]['accessed']; + $this->specificCacheKey = &$referencing[$hash]['specificCacheKey']; + + if ($this->accessedColumns === null) { + $this->accessedColumns = []; + } + + return $hash; + } + + + /** + * @param Selection + */ + public function setObserveCache(Selection $observeCache): void + { + $this->observeCache = $observeCache; + } + + + /** + * @internal + */ + public function setSelection(Selection $selection): void + { + $this->selection = $selection; + } +} diff --git a/src/Database/Table/GroupedSelection.php b/src/Database/Table/GroupedSelection.php index d8946f881..1fea6472f 100644 --- a/src/Database/Table/GroupedSelection.php +++ b/src/Database/Table/GroupedSelection.php @@ -20,12 +20,13 @@ */ class GroupedSelection extends Selection { + + /** @var mixed current assigned referencing array */ + public $refCacheCurrent; + /** @var Selection referenced table */ protected $refTable; - /** @var mixed current assigned referencing array */ - protected $refCacheCurrent; - /** @var string grouping column name */ protected $column; @@ -94,7 +95,8 @@ public function order(string $columns, ...$params) */ public function aggregation(string $function) { - $aggregation = &$this->getRefTable($refPath)->aggregation[$refPath . $function . $this->sqlBuilder->getSelectQueryHash($this->getPreviousAccessedColumns())]; + $selectQueryHash = $this->sqlBuilder->getSelectQueryHash($this->cache->getPreviousAccessedColumns()); + $aggregation = &$this->getRefTable($refPath)->aggregation[$refPath . $function . $selectQueryHash]; if ($aggregation === null) { $aggregation = []; @@ -132,16 +134,16 @@ public function count(string $column = null): int protected function execute(): void { if ($this->rows !== null) { - $this->observeCache = $this; + $this->cache->setObserveCache($this); return; } - $accessedColumns = $this->accessedColumns; + $accessedColumns = $this->cache->getAccessedColumns(); $this->loadRefCache(); if (!isset($this->refCacheCurrent['data'])) { // we have not fetched any data yet => init accessedColumns by cached accessedColumns - $this->accessedColumns = $accessedColumns; + $this->cache->setAccessedColumns($accessedColumns); $limit = $this->sqlBuilder->getLimit(); $rows = count($this->refTable->rows); @@ -169,7 +171,7 @@ protected function execute(): void $this->data = &$this->refCacheCurrent['data'][$this->active]; } - $this->observeCache = $this; + $this->cache->setObserveCache($this); if ($this->data === null) { $this->data = []; } else { @@ -196,12 +198,9 @@ protected function getRefTable(&$refPath): Selection protected function loadRefCache(): void { - $hash = $this->getSpecificCacheKey(); - $referencing = &$this->refCache['referencing'][$this->getGeneralCacheKey()]; - $this->observeCache = &$referencing['observeCache']; + $referencing = &$this->refCache->getReferencing($this->cache->getGeneralCacheKey()); + $hash = $this->cache->loadFromRefCache($referencing); $this->refCacheCurrent = &$referencing[$hash]; - $this->accessedColumns = &$referencing[$hash]['accessed']; - $this->specificCacheKey = &$referencing[$hash]['specificCacheKey']; $this->rows = &$referencing[$hash]['rows']; if (isset($referencing[$hash]['data'][$this->active])) { diff --git a/src/Database/Table/ReferenceCache.php b/src/Database/Table/ReferenceCache.php new file mode 100644 index 000000000..705aba9a9 --- /dev/null +++ b/src/Database/Table/ReferenceCache.php @@ -0,0 +1,59 @@ +referencingPrototype[$specificCacheKey]["$table.$column"]; + } + + + public function clearReferencingPrototype(): void + { + $this->referencingPrototype = []; + } + + + public function &getReferencing(string $generalCacheKey): ?array + { + return $this->referencing[$generalCacheKey]; + } + + + public function unsetReferencing(string $generalCacheKey, string $specificCacheKey): void + { + unset($this->referencing[$generalCacheKey][$specificCacheKey]); + } + + + public function &getReferenced(string $specificCacheKey, string $table, string $column): ?array + { + return $this->referenced[$specificCacheKey]["$table.$column"]; + } + + + public function clearReferenced(): void + { + $this->referenced = []; + } +} diff --git a/src/Database/Table/Selection.php b/src/Database/Table/Selection.php index ddfa79796..2b7801f92 100644 --- a/src/Database/Table/Selection.php +++ b/src/Database/Table/Selection.php @@ -28,9 +28,15 @@ class Selection implements \Iterator, IRowContainer, \ArrayAccess, \Countable /** @var IConventions */ protected $conventions; - /** @var Nette\Caching\Cache */ + /** @var ColumnAccessCache */ protected $cache; + /** @var ReferenceCache */ + protected $refCache; + + /** @var array cache of Selection and GroupedSelection prototypes */ + protected $globalRefCache = []; + /** @var SqlBuilder */ protected $sqlBuilder; @@ -47,35 +53,14 @@ class Selection implements \Iterator, IRowContainer, \ArrayAccess, \Countable protected $rows; /** @var IRow[] modifiable data in [primary key => IRow] format */ - protected $data; + protected $data = []; /** @var bool */ protected $dataRefreshed = false; - /** @var mixed cache array of Selection and GroupedSelection prototypes */ - protected $globalRefCache; - - /** @var mixed */ - protected $refCache; - - /** @var string */ - protected $generalCacheKey; - - /** @var string */ - protected $specificCacheKey; - /** @var array of [conditions => [key => IRow]]; used by GroupedSelection */ protected $aggregation = []; - /** @var array of touched columns */ - protected $accessedColumns; - - /** @var array of earlier touched columns */ - protected $previousAccessedColumns; - - /** @var bool should instance observe accessed columns caching */ - protected $observeCache = false; - /** @var array of primary key values */ protected $keys = []; @@ -90,22 +75,26 @@ public function __construct(Context $context, IConventions $conventions, string $this->conventions = $conventions; $this->name = $tableName; - $this->cache = $cacheStorage ? new Nette\Caching\Cache($cacheStorage, 'Nette.Database.' . md5($context->getConnection()->getDsn())) : null; + $this->cache = new ColumnAccessCache($this, $cacheStorage); $this->primary = $conventions->getPrimary($tableName); $this->sqlBuilder = new SqlBuilder($tableName, $context); - $this->refCache = &$this->getRefTable($refPath)->globalRefCache[$refPath]; + + $this->linkRefCache(); } public function __destruct() { - $this->saveCacheState(); + $this->cache->saveState(); } public function __clone() { + $this->cache = clone $this->cache; + $this->cache->setSelection($this); $this->sqlBuilder = clone $this->sqlBuilder; + $this->linkRefCache(); } @@ -149,34 +138,43 @@ public function setPrimarySequence(string $sequence) public function getSql(): string { - return $this->sqlBuilder->buildSelectQuery($this->getPreviousAccessedColumns()); + return $this->sqlBuilder->buildSelectQuery($this->cache->getPreviousAccessedColumns()); } /** - * Loads cache of previous accessed columns and returns it. * @internal - * @return array|bool */ - public function getPreviousAccessedColumns() + public function getSqlBuilder(): SqlBuilder { - if ($this->cache && $this->previousAccessedColumns === null) { - $this->accessedColumns = $this->previousAccessedColumns = $this->cache->load($this->getGeneralCacheKey()); - if ($this->previousAccessedColumns === null) { - $this->previousAccessedColumns = []; - } - } + return $this->sqlBuilder; + } + - return array_keys(array_filter((array) $this->previousAccessedColumns)); + /** + * @internal + */ + public function getCache(): ColumnAccessCache + { + return $this->cache; } /** * @internal */ - public function getSqlBuilder(): SqlBuilder + public function &getGlobalRefCache(string $key) { - return $this->sqlBuilder; + return $this->globalRefCache[$key]; + } + + + /** + * @internal + */ + public function getConnection(): Nette\Database\Connection + { + return $this->context->getConnection(); } @@ -528,7 +526,7 @@ protected function execute(): void return; } - $this->observeCache = $this; + $this->cache->setObserveCache($this); if ($this->primary === null && $this->sqlBuilder->getSelect() === null) { throw new Nette\InvalidStateException('Table with no primary key requires an explicit select clause.'); @@ -538,13 +536,13 @@ protected function execute(): void $result = $this->query($this->getSql()); } catch (Nette\Database\DriverException $exception) { - if (!$this->sqlBuilder->getSelect() && $this->previousAccessedColumns) { - $this->previousAccessedColumns = false; - $this->accessedColumns = []; - $result = $this->query($this->getSql()); - } else { + if ($this->sqlBuilder->getSelect() || !$this->cache->getPreviousAccessedColumns()) { throw $exception; } + + $this->cache->clearPreviousAccessedColumns(); + $this->cache->setAccessedColumns([]); + $result = $this->query($this->getSql()); } $this->rows = []; @@ -557,9 +555,9 @@ protected function execute(): void } $this->data = $this->rows; - if ($usedPrimary && $this->accessedColumns !== false) { + if ($usedPrimary) { foreach ((array) $this->primary as $primary) { - $this->accessedColumns[$primary] = true; + $this->cache->setAccessedColumn($primary, true); } } } @@ -573,13 +571,13 @@ protected function createRow(array $row): ActiveRow public function createSelectionInstance($table = null): self { - return new self($this->context, $this->conventions, $table ?: $this->name, $this->cache ? $this->cache->getStorage() : null); + return new self($this->context, $this->conventions, $table ?: $this->name, $this->cache->getStorage()); } protected function createGroupedSelectionInstance($table, $column): GroupedSelection { - return new GroupedSelection($this->context, $this->conventions, $table, $column, $this, $this->cache ? $this->cache->getStorage() : null); + return new GroupedSelection($this->context, $this->conventions, $table, $column, $this, $this->cache->getStorage()); } @@ -592,38 +590,20 @@ protected function query($query): Nette\Database\ResultSet protected function emptyResultSet(bool $clearCache = true, bool $deleteRererencedCache = true): void { if ($this->rows !== null && $clearCache) { - $this->saveCacheState(); + $this->cache->saveState(); } if ($clearCache) { // NOT NULL in case of missing some column - $this->previousAccessedColumns = null; - $this->generalCacheKey = null; + $this->cache->clearPreviousAccessedColumns(); + $this->cache->setGeneralCacheKey(null); } $this->rows = null; - $this->specificCacheKey = null; - $this->refCache['referencingPrototype'] = []; + $this->cache->setSpecificCacheKey(null); + $this->refCache->clearReferencingPrototype(); if ($deleteRererencedCache) { - $this->refCache['referenced'] = []; - } - } - - - protected function saveCacheState(): void - { - if ($this->observeCache === $this && $this->cache && !$this->sqlBuilder->getSelect() && $this->accessedColumns !== $this->previousAccessedColumns) { - $previousAccessed = $this->cache->load($this->getGeneralCacheKey()); - $accessed = $this->accessedColumns; - $needSave = is_array($accessed) && is_array($previousAccessed) - ? array_intersect_key($accessed, $previousAccessed) !== $accessed - : $accessed !== $previousAccessed; - - if ($needSave) { - $save = is_array($accessed) && is_array($previousAccessed) ? $previousAccessed + $accessed : $accessed; - $this->cache->save($this->getGeneralCacheKey(), $save); - $this->previousAccessedColumns = null; - } + $this->refCache->clearReferenced(); } } @@ -647,114 +627,108 @@ protected function loadRefCache(): void /** - * Returns general cache key independent on query parameters or sql limit - * Used e.g. for previously accessed columns caching + * Link refCache references */ - protected function getGeneralCacheKey(): string + protected function linkRefCache(): void { - if ($this->generalCacheKey) { - return $this->generalCacheKey; + $refTable = $this->getRefTable($refPath); + if ($refTable === $this) { + $this->refCache = $this->globalRefCache[$refPath] = new ReferenceCache; + } else { + $this->refCache = &$refTable->getGlobalRefCache($refPath); + if ($this->refCache === null) { + $this->refCache = new ReferenceCache; + } } + } - $key = [__CLASS__, $this->name, $this->sqlBuilder->getConditions()]; - $trace = []; - foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) as $item) { - $trace[] = isset($item['file'], $item['line']) ? $item['file'] . $item['line'] : null; + + /** + * @internal + * @param string column name + * @return bool if selection requeried for more columns. + */ + public function accessColumn(string $key, bool $selectColumn = true): bool + { + if (!$this->cache->getStorage()) { + return false; } - $key[] = $trace; - return $this->generalCacheKey = md5(serialize($key)); + $this->cache->setAccessedColumn($key, $selectColumn); + + $previousAccessedColumns = $this->cache->getPreviousAccessedColumns(); + if ($selectColumn && $previousAccessedColumns && !in_array($key, $previousAccessedColumns, true) && !$this->sqlBuilder->getSelect()) { + $this->refreshData(); + } + + return $this->dataRefreshed; } /** - * Returns object specific cache key dependent on query parameters - * Used e.g. for reference memory caching + * @internal */ - protected function getSpecificCacheKey(): string + public function removeAccessColumn(string $key): void { - if ($this->specificCacheKey) { - return $this->specificCacheKey; - } - - return $this->specificCacheKey = $this->sqlBuilder->getSelectQueryHash($this->getPreviousAccessedColumns()); + $this->cache->setAccessedColumn($key, false); } /** * @internal - * @param string|null column name or null to reload all columns - * @return bool if selection requeried for more columns. + * @return bool if selection requeried for reload. */ - public function accessColumn(?string $key, bool $selectColumn = true): bool + public function reloadAllColumns(): bool { - if (!$this->cache) { + if (!$this->cache->getStorage()) { return false; } - if ($key === null) { - $this->accessedColumns = false; - $currentKey = key((array) $this->data); - } elseif ($this->accessedColumns !== false) { - $this->accessedColumns[$key] = $selectColumn; - } - - if ($selectColumn && $this->previousAccessedColumns && ($key === null || !isset($this->previousAccessedColumns[$key])) && !$this->sqlBuilder->getSelect()) { - if ($this->sqlBuilder->getLimit()) { - $generalCacheKey = $this->generalCacheKey; - $sqlBuilder = $this->sqlBuilder; - - $primaryValues = []; - foreach ((array) $this->rows as $row) { - $primary = $row->getPrimary(); - $primaryValues[] = is_array($primary) ? array_values($primary) : $primary; - } - - $this->emptyResultSet(false); - $this->sqlBuilder = clone $this->sqlBuilder; - $this->sqlBuilder->setLimit(null, null); - $this->wherePrimary($primaryValues); - - $this->generalCacheKey = $generalCacheKey; - $this->previousAccessedColumns = []; - $this->execute(); - $this->sqlBuilder = $sqlBuilder; - } else { - $this->emptyResultSet(false); - $this->previousAccessedColumns = []; - $this->execute(); - } + $this->cache->setAccessedColumns([]); + $currentKey = key($this->data); - $this->dataRefreshed = true; + $previousAccessedColumns = $this->cache->getPreviousAccessedColumns(); + if ($previousAccessedColumns && !$this->sqlBuilder->getSelect()) { + $this->refreshData(); // move iterator to specific key - if (isset($currentKey)) { - while (key($this->data) !== null && key($this->data) !== $currentKey) { - next($this->data); - } + while (key($this->data) !== null && key($this->data) !== $currentKey) { + next($this->data); } } + return $this->dataRefreshed; } - /** - * @internal - */ - public function removeAccessColumn(string $key): void + protected function refreshData(): void { - if ($this->cache && is_array($this->accessedColumns)) { - $this->accessedColumns[$key] = false; - } - } + if ($this->sqlBuilder->getLimit()) { + $generalCacheKey = $this->cache->getGeneralCacheKey(); + $sqlBuilder = $this->sqlBuilder; + $primaryValues = []; + foreach ((array) $this->rows as $row) { + $primary = $row->getPrimary(); + $primaryValues[] = is_array($primary) ? array_values($primary) : $primary; + } - /** - * Returns if selection requeried for more columns. - */ - public function getDataRefreshed(): bool - { - return $this->dataRefreshed; + $this->emptyResultSet(false); + $this->sqlBuilder = clone $this->sqlBuilder; + $this->sqlBuilder->setLimit(null, null); + $this->wherePrimary($primaryValues); + + $this->cache->setGeneralCacheKey($generalCacheKey); + $this->cache->setPreviousAccessedColumns([]); + $this->execute(); + $this->sqlBuilder = $sqlBuilder; + } else { + $this->emptyResultSet(false); + $this->cache->setPreviousAccessedColumns([]); + $this->execute(); + } + + $this->dataRefreshed = true; } @@ -781,7 +755,7 @@ public function insert($data) $this->loadRefCache(); if ($data instanceof self || $this->primary === null) { - unset($this->refCache['referencing'][$this->getGeneralCacheKey()][$this->getSpecificCacheKey()]); + $this->refCache->unsetReferencing($this->cache->getGeneralCacheKey(), $this->cache->getSpecificCacheKey()); return $return->getRowCount(); } @@ -817,7 +791,7 @@ public function insert($data) // If primaryKey cannot be prepared, return inserted rows count } else { - unset($this->refCache['referencing'][$this->getGeneralCacheKey()][$this->getSpecificCacheKey()]); + $this->refCache->unsetReferencing($this->cache->getGeneralCacheKey(), $this->cache->getSpecificCacheKey()); return $return->getRowCount(); } @@ -897,7 +871,7 @@ public function getReferencedTable(ActiveRow $row, ?string $table, string $colum $checkPrimaryKey = $row[$column]; - $referenced = &$this->refCache['referenced'][$this->getSpecificCacheKey()]["$table.$column"]; + $referenced = &$this->refCache->getReferenced($this->cache->getSpecificCacheKey(), $table, $column); $selection = &$referenced['selection']; $cacheKeys = &$referenced['cacheKeys']; if ($selection === null || ($checkPrimaryKey !== null && !isset($cacheKeys[$checkPrimaryKey]))) { @@ -940,7 +914,7 @@ public function getReferencingTable(string $table, string $column = null, int $a [$table, $column] = $hasMany; } - $prototype = &$this->refCache['referencingPrototype'][$this->getSpecificCacheKey()]["$table.$column"]; + $prototype = &$this->refCache->getReferencingPrototype($this->cache->getSpecificCacheKey(), $table, $column); if (!$prototype) { $prototype = $this->createGroupedSelectionInstance($table, $column); $prototype->where("$table.$column", array_keys((array) $this->rows)); @@ -968,9 +942,9 @@ public function current() { if (($key = current($this->keys)) !== false) { return $this->data[$key]; - } else { - return false; } + + return false; } diff --git a/tests/Database/Table/Table.cache.phpt b/tests/Database/Table/Table.cache.phpt index b6ac2393a..67f453000 100644 --- a/tests/Database/Table/Table.cache.phpt +++ b/tests/Database/Table/Table.cache.phpt @@ -117,10 +117,8 @@ test(function () use ($context) { } foreach ($relatedStack as $related) { - $property = (new ReflectionClass($related))->getProperty('accessedColumns'); - $property->setAccessible(true); // checks if instances have shared data of accessed columns - Assert::same(['id', 'author_id'], array_keys((array) $property->getValue($related))); + Assert::same(['id', 'author_id'], array_keys((array) $related->getCache()->getAccessedColumns())); } }); @@ -130,7 +128,7 @@ test(function () use ($context) { // Test saving joining keys even with 0 rows for ($i = 0; $i < 2; $i += 1) { $author = $context->table('author')->get(11); $books = $author->related('book')->where('translator_id', 99); // 0 rows - $cols[] = $books->getPreviousAccessedColumns(); + $cols[] = $books->getCache()->getPreviousAccessedColumns(); foreach ($books as $book) { } $books->__destruct(); @@ -148,7 +146,7 @@ test(function () use ($context) { // Test saving the union of needed cols, the s for ($i = 0; $i < 3; $i += 1) { $author = $context->table('author')->get(11); $books = $author->related('book'); - $cols[] = $books->getPreviousAccessedColumns(); + $cols[] = $books->getCache()->getPreviousAccessedColumns(); foreach ($books as $book) { if ($i === 0) { $book->translator_id; @@ -171,7 +169,7 @@ test(function () use ($context) { // Test saving the union of needed cols, the s for ($i = 0; $i < 3; $i += 1) { $author = $context->table('author')->get(11); $books = $author->related('book'); - $cols[] = $books->getPreviousAccessedColumns(); + $cols[] = $books->getCache()->getPreviousAccessedColumns(); foreach ($books as $book) { if ($i === 0) { $book->translator_id;