Skip to content

Commit a561254

Browse files
foxycodedg
authored andcommitted
Table: moved caching related functionality to separate classes (#185)
1 parent 92e687d commit a561254

File tree

6 files changed

+420
-180
lines changed

6 files changed

+420
-180
lines changed

src/Database/Table/ActiveRow.php

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public function __toString()
6868

6969
public function toArray(): array
7070
{
71-
$this->accessColumn(null);
71+
$this->reloadAllColumns();
7272
return $this->data;
7373
}
7474

@@ -204,7 +204,7 @@ public function delete(): int
204204

205205
public function getIterator(): \Iterator
206206
{
207-
$this->accessColumn(null);
207+
$this->reloadAllColumns();
208208
return new \ArrayIterator($this->data);
209209
}
210210

@@ -301,14 +301,10 @@ public function __unset($key)
301301
/**
302302
* @internal
303303
*/
304-
public function accessColumn($key, bool $selectColumn = true): bool
304+
public function accessColumn(string $key, bool $selectColumn = true): bool
305305
{
306306
if ($this->table->accessColumn($key, $selectColumn) && !$this->dataRefreshed) {
307-
if (!isset($this->table[$this->getSignature()])) {
308-
throw new Nette\InvalidStateException("Database refetch failed; row with signature '{$this->getSignature()}' does not exist!");
309-
}
310-
$this->data = $this->table[$this->getSignature()]->data;
311-
$this->dataRefreshed = true;
307+
$this->refreshData();
312308
}
313309
return isset($this->data[$key]) || array_key_exists($key, $this->data);
314310
}
@@ -318,4 +314,22 @@ protected function removeAccessColumn($key): void
318314
{
319315
$this->table->removeAccessColumn($key);
320316
}
317+
318+
319+
protected function reloadAllColumns(): void
320+
{
321+
if ($this->table->reloadAllColumns() && !$this->dataRefreshed) {
322+
$this->refreshData();
323+
}
324+
}
325+
326+
327+
protected function refreshData(): void
328+
{
329+
if (!isset($this->table[$this->getSignature()])) {
330+
throw new Nette\InvalidStateException("Database refetch failed; row with signature '{$this->getSignature()}' does not exist!");
331+
}
332+
$this->data = $this->table[$this->getSignature()]->data;
333+
$this->dataRefreshed = true;
334+
}
321335
}
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
<?php
2+
3+
/**
4+
* This file is part of the Nette Framework (https://nette.org)
5+
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Nette\Database\Table;
11+
12+
use Nette\Caching\Cache;
13+
use Nette\Caching\IStorage;
14+
15+
16+
class ColumnAccessCache
17+
{
18+
/** @var Selection */
19+
protected $selection;
20+
21+
/** @var Cache */
22+
protected $cache;
23+
24+
/** @var string */
25+
protected $generalCacheKey;
26+
27+
/** @var string */
28+
protected $specificCacheKey;
29+
30+
/** @var array of touched columns */
31+
protected $accessedColumns = [];
32+
33+
/** @var array of earlier touched columns */
34+
protected $previousAccessedColumns;
35+
36+
/** @var Selection instance of observed accessed columns */
37+
protected $observeCache;
38+
39+
40+
public function __construct(Selection $selection, IStorage $cacheStorage = null)
41+
{
42+
$this->selection = $selection;
43+
$this->cache = $cacheStorage ? new Cache($cacheStorage, 'Nette.Database.' . md5($selection->getConnection()->getDsn())) : null;
44+
}
45+
46+
47+
public function getStorage(): ?IStorage
48+
{
49+
return $this->cache ? $this->cache->getStorage() : null;
50+
}
51+
52+
53+
/**
54+
* Returns general cache key independent on query parameters or sql limit
55+
* Used e.g. for previously accessed columns caching
56+
*/
57+
public function getGeneralCacheKey(): string
58+
{
59+
if ($this->generalCacheKey) {
60+
return $this->generalCacheKey;
61+
}
62+
63+
$key = [__CLASS__, $this->selection->getName(), $this->selection->getSqlBuilder()->getConditions()];
64+
$trace = [];
65+
foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) as $item) {
66+
$trace[] = isset($item['file'], $item['line']) ? $item['file'] . $item['line'] : null;
67+
}
68+
69+
$key[] = $trace;
70+
return $this->generalCacheKey = md5(serialize($key));
71+
}
72+
73+
74+
public function setGeneralCacheKey(?string $key): void
75+
{
76+
$this->generalCacheKey = $key;
77+
}
78+
79+
80+
/**
81+
* Returns object specific cache key dependent on query parameters
82+
* Used e.g. for reference memory caching
83+
*/
84+
public function getSpecificCacheKey(): string
85+
{
86+
if ($this->specificCacheKey) {
87+
return $this->specificCacheKey;
88+
}
89+
90+
return $this->specificCacheKey = $this->selection->getSqlBuilder()->getSelectQueryHash($this->getPreviousAccessedColumns());
91+
}
92+
93+
94+
public function setSpecificCacheKey(?string $key): void
95+
{
96+
$this->specificCacheKey = $key;
97+
}
98+
99+
100+
public function getAccessedColumns(): array
101+
{
102+
return $this->accessedColumns;
103+
}
104+
105+
106+
public function setAccessedColumns(array $accessedColumns): void
107+
{
108+
if ($this->cache) {
109+
$this->accessedColumns = $accessedColumns;
110+
}
111+
}
112+
113+
114+
public function setAccessedColumn(string $key, bool $value): void
115+
{
116+
if ($this->cache) {
117+
$this->accessedColumns[$key] = $value;
118+
}
119+
}
120+
121+
122+
/**
123+
* Loads cache of previous accessed columns and returns it.
124+
*/
125+
public function getPreviousAccessedColumns(): array
126+
{
127+
if ($this->cache && $this->previousAccessedColumns === null) {
128+
$this->accessedColumns = $this->previousAccessedColumns = $this->cache->load($this->getGeneralCacheKey()) ?: [];
129+
}
130+
131+
return array_keys(array_filter((array) $this->previousAccessedColumns));
132+
}
133+
134+
135+
public function setPreviousAccessedColumns(array $previousAccessedColumns): void
136+
{
137+
$this->previousAccessedColumns = $previousAccessedColumns;
138+
}
139+
140+
141+
public function clearPreviousAccessedColumns(): void
142+
{
143+
$this->previousAccessedColumns = null;
144+
}
145+
146+
147+
public function saveState(): void
148+
{
149+
if ($this->observeCache === $this->selection && $this->cache && !$this->selection->getSqlBuilder()->getSelect() && $this->accessedColumns !== $this->previousAccessedColumns) {
150+
$previousAccessed = $this->cache->load($this->getGeneralCacheKey());
151+
$accessed = $this->accessedColumns;
152+
$needSave = is_array($previousAccessed)
153+
? array_intersect_key($accessed, $previousAccessed) !== $accessed
154+
: $accessed !== $previousAccessed;
155+
156+
if ($needSave) {
157+
$save = is_array($previousAccessed) ? $previousAccessed + $accessed : $accessed;
158+
$this->cache->save($this->getGeneralCacheKey(), $save);
159+
$this->previousAccessedColumns = null;
160+
}
161+
}
162+
}
163+
164+
165+
public function &loadFromRefCache(&$referencing): string
166+
{
167+
$hash = $this->getSpecificCacheKey();
168+
$this->observeCache = &$referencing['observeCache'];
169+
$this->accessedColumns = &$referencing[$hash]['accessed'];
170+
$this->specificCacheKey = &$referencing[$hash]['specificCacheKey'];
171+
172+
if ($this->accessedColumns === null) {
173+
$this->accessedColumns = [];
174+
}
175+
176+
return $hash;
177+
}
178+
179+
180+
/**
181+
* @param Selection
182+
*/
183+
public function setObserveCache(Selection $observeCache): void
184+
{
185+
$this->observeCache = $observeCache;
186+
}
187+
188+
189+
/**
190+
* @internal
191+
*/
192+
public function setSelection(Selection $selection): void
193+
{
194+
$this->selection = $selection;
195+
}
196+
}

src/Database/Table/GroupedSelection.php

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,13 @@
2020
*/
2121
class GroupedSelection extends Selection
2222
{
23+
24+
/** @var mixed current assigned referencing array */
25+
public $refCacheCurrent;
26+
2327
/** @var Selection referenced table */
2428
protected $refTable;
2529

26-
/** @var mixed current assigned referencing array */
27-
protected $refCacheCurrent;
28-
2930
/** @var string grouping column name */
3031
protected $column;
3132

@@ -94,7 +95,8 @@ public function order(string $columns, ...$params)
9495
*/
9596
public function aggregation(string $function)
9697
{
97-
$aggregation = &$this->getRefTable($refPath)->aggregation[$refPath . $function . $this->sqlBuilder->getSelectQueryHash($this->getPreviousAccessedColumns())];
98+
$selectQueryHash = $this->sqlBuilder->getSelectQueryHash($this->cache->getPreviousAccessedColumns());
99+
$aggregation = &$this->getRefTable($refPath)->aggregation[$refPath . $function . $selectQueryHash];
98100

99101
if ($aggregation === null) {
100102
$aggregation = [];
@@ -132,16 +134,16 @@ public function count(string $column = null): int
132134
protected function execute(): void
133135
{
134136
if ($this->rows !== null) {
135-
$this->observeCache = $this;
137+
$this->cache->setObserveCache($this);
136138
return;
137139
}
138140

139-
$accessedColumns = $this->accessedColumns;
141+
$accessedColumns = $this->cache->getAccessedColumns();
140142
$this->loadRefCache();
141143

142144
if (!isset($this->refCacheCurrent['data'])) {
143145
// we have not fetched any data yet => init accessedColumns by cached accessedColumns
144-
$this->accessedColumns = $accessedColumns;
146+
$this->cache->setAccessedColumns($accessedColumns);
145147

146148
$limit = $this->sqlBuilder->getLimit();
147149
$rows = count($this->refTable->rows);
@@ -169,7 +171,7 @@ protected function execute(): void
169171
$this->data = &$this->refCacheCurrent['data'][$this->active];
170172
}
171173

172-
$this->observeCache = $this;
174+
$this->cache->setObserveCache($this);
173175
if ($this->data === null) {
174176
$this->data = [];
175177
} else {
@@ -196,12 +198,9 @@ protected function getRefTable(&$refPath): Selection
196198

197199
protected function loadRefCache(): void
198200
{
199-
$hash = $this->getSpecificCacheKey();
200-
$referencing = &$this->refCache['referencing'][$this->getGeneralCacheKey()];
201-
$this->observeCache = &$referencing['observeCache'];
201+
$referencing = &$this->refCache->getReferencing($this->cache->getGeneralCacheKey());
202+
$hash = $this->cache->loadFromRefCache($referencing);
202203
$this->refCacheCurrent = &$referencing[$hash];
203-
$this->accessedColumns = &$referencing[$hash]['accessed'];
204-
$this->specificCacheKey = &$referencing[$hash]['specificCacheKey'];
205204
$this->rows = &$referencing[$hash]['rows'];
206205

207206
if (isset($referencing[$hash]['data'][$this->active])) {

src/Database/Table/ReferenceCache.php

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
/**
4+
* This file is part of the Nette Framework (https://nette.org)
5+
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Nette\Database\Table;
11+
12+
13+
class ReferenceCache
14+
{
15+
/** @var array */
16+
protected $referencingPrototype = [];
17+
18+
/** @var array */
19+
protected $referencing = [];
20+
21+
/** @var array */
22+
protected $referenced = [];
23+
24+
25+
public function &getReferencingPrototype(string $specificCacheKey, string $table, string $column)
26+
{
27+
return $this->referencingPrototype[$specificCacheKey]["$table.$column"];
28+
}
29+
30+
31+
public function clearReferencingPrototype(): void
32+
{
33+
$this->referencingPrototype = [];
34+
}
35+
36+
37+
public function &getReferencing(string $generalCacheKey): ?array
38+
{
39+
return $this->referencing[$generalCacheKey];
40+
}
41+
42+
43+
public function unsetReferencing(string $generalCacheKey, string $specificCacheKey): void
44+
{
45+
unset($this->referencing[$generalCacheKey][$specificCacheKey]);
46+
}
47+
48+
49+
public function &getReferenced(string $specificCacheKey, string $table, string $column): ?array
50+
{
51+
return $this->referenced[$specificCacheKey]["$table.$column"];
52+
}
53+
54+
55+
public function clearReferenced(): void
56+
{
57+
$this->referenced = [];
58+
}
59+
}

0 commit comments

Comments
 (0)