Skip to content

Selection: moved caching related functionality to separate classes #185

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 11, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 22 additions & 8 deletions src/Database/Table/ActiveRow.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public function __toString()

public function toArray(): array
{
$this->accessColumn(null);
$this->reloadAllColumns();
return $this->data;
}

Expand Down Expand Up @@ -204,7 +204,7 @@ public function delete(): int

public function getIterator(): \Iterator
{
$this->accessColumn(null);
$this->reloadAllColumns();
return new \ArrayIterator($this->data);
}

Expand Down Expand Up @@ -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);
}
Expand All @@ -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;
}
}
196 changes: 196 additions & 0 deletions src/Database/Table/ColumnAccessCache.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
<?php

/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/

declare(strict_types=1);

namespace Nette\Database\Table;

use Nette\Caching\Cache;
use Nette\Caching\IStorage;


class ColumnAccessCache
{
/** @var Selection */
protected $selection;

/** @var Cache */
protected $cache;

/** @var string */
protected $generalCacheKey;

/** @var string */
protected $specificCacheKey;

/** @var array of touched columns */
protected $accessedColumns = [];

/** @var array of earlier touched columns */
protected $previousAccessedColumns;

/** @var Selection instance of observed accessed columns */
protected $observeCache;


public function __construct(Selection $selection, IStorage $cacheStorage = null)
{
$this->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;
}
}
25 changes: 12 additions & 13 deletions src/Database/Table/GroupedSelection.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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 = [];
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 {
Expand All @@ -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])) {
Expand Down
59 changes: 59 additions & 0 deletions src/Database/Table/ReferenceCache.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/

declare(strict_types=1);

namespace Nette\Database\Table;


class ReferenceCache
{
/** @var array */
protected $referencingPrototype = [];

/** @var array */
protected $referencing = [];

/** @var array */
protected $referenced = [];


public function &getReferencingPrototype(string $specificCacheKey, string $table, string $column)
{
return $this->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 = [];
}
}
Loading