Skip to content

Commit bbd6ad8

Browse files
committed
Merge pull request #137 from Unlink/columns-cache-fix
Columns cache fix
2 parents b660054 + 0e8960b commit bbd6ad8

File tree

4 files changed

+74
-24
lines changed

4 files changed

+74
-24
lines changed

src/Database/Table/ActiveRow.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ public function accessColumn($key, $selectColumn = TRUE)
323323
{
324324
if ($this->table->accessColumn($key, $selectColumn) && !$this->dataRefreshed) {
325325
if (!isset($this->table[$this->getSignature()])) {
326-
throw new Nette\InvalidStateException('Database refetch failed; row does not exist!');
326+
throw new Nette\InvalidStateException("Database refetch failed; row with signature '{$this->getSignature()}' does not exist!");
327327
}
328328
$this->data = $this->table[$this->getSignature()]->data;
329329
$this->dataRefreshed = TRUE;

src/Database/Table/Selection.php

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,6 @@ class Selection implements \Iterator, IRowContainer, \ArrayAccess, \Countable
5959
/** @var string */
6060
protected $generalCacheKey;
6161

62-
/** @var array */
63-
protected $generalCacheTraceKey;
64-
6562
/** @var string */
6663
protected $specificCacheKey;
6764

@@ -618,20 +615,20 @@ protected function query($query)
618615
}
619616

620617

621-
protected function emptyResultSet($saveCache = TRUE, $deleteRererencedCache = TRUE)
618+
protected function emptyResultSet($clearCache = TRUE, $deleteRererencedCache = TRUE)
622619
{
623-
if ($this->rows !== NULL && $saveCache) {
620+
if ($this->rows !== NULL && $clearCache) {
624621
$this->saveCacheState();
625622
}
626623

627-
if ($saveCache) {
628-
// null only if missing some column
629-
$this->generalCacheTraceKey = NULL;
624+
if ($clearCache) {
625+
// not null in case of missing some column
626+
$this->previousAccessedColumns = NULL;
627+
$this->generalCacheKey = NULL;
630628
}
631629

632630
$this->rows = NULL;
633631
$this->specificCacheKey = NULL;
634-
$this->generalCacheKey = NULL;
635632
$this->refCache['referencingPrototype'] = [];
636633
if ($deleteRererencedCache) {
637634
$this->refCache['referenced'] = [];
@@ -687,15 +684,12 @@ protected function getGeneralCacheKey()
687684
}
688685

689686
$key = [__CLASS__, $this->name, $this->sqlBuilder->getConditions()];
690-
if (!$this->generalCacheTraceKey) {
691-
$trace = [];
692-
foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) as $item) {
693-
$trace[] = isset($item['file'], $item['line']) ? $item['file'] . $item['line'] : NULL;
694-
};
695-
$this->generalCacheTraceKey = $trace;
696-
}
687+
$trace = [];
688+
foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) as $item) {
689+
$trace[] = isset($item['file'], $item['line']) ? $item['file'] . $item['line'] : NULL;
690+
};
697691

698-
$key[] = $this->generalCacheTraceKey;
692+
$key[] = $trace;
699693
return $this->generalCacheKey = md5(serialize($key));
700694
}
701695

@@ -735,8 +729,6 @@ public function accessColumn($key, $selectColumn = TRUE)
735729
}
736730

737731
if ($selectColumn && $this->previousAccessedColumns && ($key === NULL || !isset($this->previousAccessedColumns[$key])) && !$this->sqlBuilder->getSelect()) {
738-
$this->previousAccessedColumns = [];
739-
740732
if ($this->sqlBuilder->getLimit()) {
741733
$generalCacheKey = $this->generalCacheKey;
742734
$sqlBuilder = $this->sqlBuilder;
@@ -753,10 +745,12 @@ public function accessColumn($key, $selectColumn = TRUE)
753745
$this->wherePrimary($primaryValues);
754746

755747
$this->generalCacheKey = $generalCacheKey;
748+
$this->previousAccessedColumns = [];
756749
$this->execute();
757750
$this->sqlBuilder = $sqlBuilder;
758751
} else {
759752
$this->emptyResultSet(FALSE);
753+
$this->previousAccessedColumns = [];
760754
$this->execute();
761755
}
762756

@@ -1018,7 +1012,9 @@ public function key()
10181012

10191013
public function next()
10201014
{
1021-
next($this->keys);
1015+
do {
1016+
next($this->keys);
1017+
} while (($key = current($this->keys)) !== FALSE && !isset($this->data[$key]));
10221018
}
10231019

10241020

tests/Database/Table/Table.cache.phpt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,3 +185,35 @@ test(function () use ($context) { // Test saving the union of needed cols, the s
185185
['id', 'author_id', 'translator_id', 'title'],
186186
], $cols);
187187
});
188+
189+
190+
test(function () use ($context) { // Test multiple use of same selection
191+
$sql = [];
192+
$context->getConnection()->onQuery[] = function($_, $result) use (& $sql) {
193+
$sql[] = $result->getQueryString();
194+
};
195+
196+
for ($i = 0; $i < 3; $i += 1) {
197+
$bookSelection = $context->table('book');
198+
count($bookSelection);
199+
200+
foreach ($bookSelection->where('author_id = ?', 11) as $book) {
201+
$book->title;
202+
if ($i>=1) {
203+
$book->translator_id;
204+
}
205+
}
206+
$bookSelection->__destruct();
207+
}
208+
209+
Assert::same([
210+
reformat('SELECT * FROM [book]'), //First round
211+
reformat('SELECT * FROM [book] WHERE ([author_id] = 11)'),
212+
reformat('SELECT [id] FROM [book]'), //Second round
213+
reformat('SELECT [id], [title] FROM [book] WHERE ([author_id] = 11)'),
214+
reformat('SELECT * FROM [book] WHERE ([author_id] = 11)'), //Missing translator_id
215+
reformat('SELECT [id] FROM [book]'), //Third round
216+
reformat('SELECT [id], [title], [translator_id] FROM [book] WHERE ([author_id] = 11)'),
217+
218+
], $sql);
219+
});

tests/Database/Table/bugs/deleteCacheBug.phpt

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ test(function () use ($context) {
2121
$book->delete();
2222
Assert::exception(function () use ($book) {
2323
$book->toArray();
24-
}, Nette\InvalidStateException::class, 'Database refetch failed; row does not exist!');
24+
}, Nette\InvalidStateException::class, "Database refetch failed; row with signature '1' does not exist!");
2525
}
2626

2727
$booksSelection->__destruct();
@@ -42,13 +42,35 @@ test(function () use ($context) {
4242

4343
Assert::exception(function () use ($book) {
4444
$book->toArray();
45-
}, Nette\InvalidStateException::class, 'Database refetch failed; row does not exist!');
45+
}, Nette\InvalidStateException::class, "Database refetch failed; row with signature '2' does not exist!");
4646
}
4747

4848
$booksSelection->__destruct();
4949
}
5050
});
5151

52+
test(function () use ($context) {
53+
$books = [];
54+
for ($i=0; $i<2; $i++) {
55+
$booksSelection = $context->table('book')->where('id IN ?', [3,4]);
56+
foreach ($booksSelection as $book) {
57+
$books[] = $book->id;
58+
59+
if ($i === 1) {
60+
$context->query('DELETE FROM book WHERE id = 4'); //After refetch second row is skipped
61+
$book->title; // cause refetch
62+
}
63+
64+
$booksSelection->__destruct();
65+
}
66+
}
67+
Assert::same([
68+
3,
69+
4,
70+
3,
71+
], $books);
72+
});
73+
5274
test(function () use ($context) {
5375

5476
for ($i=0; $i<2; $i++) {
@@ -60,7 +82,7 @@ test(function () use ($context) {
6082
$context->query('DELETE FROM book WHERE id = 3');
6183
Assert::exception(function () use ($book) {
6284
$book->title;
63-
}, Nette\InvalidStateException::class, 'Database refetch failed; row does not exist!');
85+
}, Nette\InvalidStateException::class, "Database refetch failed; row with signature '3' does not exist!");
6486
}
6587

6688
$booksSelection->__destruct();

0 commit comments

Comments
 (0)