Skip to content

Commit

Permalink
Selection: previously accessed columns are cached by stack trace EXPE…
Browse files Browse the repository at this point in the history
…RIMENTAL! [closes #6][closes #15]
  • Loading branch information
hrach committed Jul 10, 2014
1 parent 1943f3c commit 0152fc7
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 114 deletions.
22 changes: 20 additions & 2 deletions src/Database/Table/Selection.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ class Selection extends Nette\Object implements \Iterator, IRowContainer, \Array
/** @var string */
protected $generalCacheKey;

/** @var array */
protected $generalCacheTraceKey;

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

Expand Down Expand Up @@ -536,6 +539,11 @@ protected function emptyResultSet($saveCache = TRUE)
$this->saveCacheState();
}

if ($saveCache) {
// null only if missing some column
$this->generalCacheTraceKey = NULL;
}

$this->rows = NULL;
$this->specificCacheKey = NULL;
$this->generalCacheKey = NULL;
Expand Down Expand Up @@ -590,7 +598,17 @@ protected function getGeneralCacheKey()
return $this->generalCacheKey;
}

return $this->generalCacheKey = md5(serialize(array(__CLASS__, $this->name, $this->sqlBuilder->getConditions())));
$key = array(__CLASS__, $this->name, $this->sqlBuilder->getConditions());
if (!$this->generalCacheTraceKey) {
$trace = array();
foreach (debug_backtrace(PHP_VERSION_ID >= 50306 ? DEBUG_BACKTRACE_IGNORE_ARGS : FALSE) as $item) {
$trace[] = isset($item['file'], $item['line']) ? $item['file'] . $item['line'] : NULL;
};
$this->generalCacheTraceKey = $trace;
}

$key[] = $this->generalCacheTraceKey;
return $this->generalCacheKey = md5(serialize($key));
}


Expand Down Expand Up @@ -714,7 +732,7 @@ public function insert($data)

$primarySequenceName = $this->getPrimarySequence();
$primaryKey = $this->context->getInsertId(
!empty($primarySequenceName)
!empty($primarySequenceName)
? $this->context->getConnection()->getSupplementalDriver()->delimite($primarySequenceName)
: $primarySequenceName
);
Expand Down
41 changes: 5 additions & 36 deletions tests/Database/Table/Table.cache.observer.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -13,44 +13,13 @@ require __DIR__ . '/../connect.inc.php'; // create $connection
Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql");


class CacheMock implements Nette\Caching\IStorage
{
public $writes = 0;
private $defaultBook = array('id' => TRUE, 'author_id' => TRUE);
$cacheStorage = Mockery::mock('Nette\Caching\Istorage');
$cacheStorage->shouldReceive('read')->withAnyArgs()->once()->andReturn(array('id' => TRUE));
$cacheStorage->shouldReceive('read')->withAnyArgs()->times(4)->andReturn(array('id' => TRUE, 'author_id' => TRUE));
$cacheStorage->shouldReceive('write')->with(Mockery::any(), array('id' => TRUE, 'author_id' => TRUE, 'title' => TRUE), array());

function read($key)
{
$key = substr($key, strpos($key, "\x00") + 1);
switch ($key) {
case "aad5184d8c52b773bd73b5c7c5c819c9": // authors
return array('id' => TRUE);
case "d7dc896279409ab73e6742c667cf8dc1": // book
return $this->defaultBook;
}
}

function write($key, $data, array $dependencies)
{
$key = substr($key, strpos($key, "\x00") + 1);
$this->writes++;
switch ($key) {
case "aad5184d8c52b773bd73b5c7c5c819c9":
return;
case "d7dc896279409ab73e6742c667cf8dc1":
$this->defaultBook = $data;
return;
}
}

function lock($key) {}
function remove($key) {}
function clean(array $conditions) {}
}

$cacheStorage = new CacheMock;
$context = new Nette\Database\Context($connection, $structure, $conventions, $cacheStorage);


$queries = 0;
$connection->onQuery[] = function($dao, ResultSet $result) use (& $queries) {
if (!preg_match('#SHOW|CONSTRAINT_NAME|pg_catalog|sys\.|SET|PRAGMA|FROM sqlite_#i', $result->queryString)) {
Expand All @@ -70,5 +39,5 @@ unset($book, $author);
foreach ($stack as $selection) $selection->__destruct();
$authors->__destruct();

Assert::same(1, $cacheStorage->writes);
Assert::same(3, $queries);
Mockery::close();
31 changes: 15 additions & 16 deletions tests/Database/Table/Table.cache.observer2.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,22 @@ class CacheMock extends MemoryStorage
$cacheStorage = new CacheMock;
$context = new Nette\Database\Context($connection, $structure, $conventions, $cacheStorage);

for ($i = 0; $i < 2; $i += 1) {
$authors = $context->table('author');
foreach ($authors as $author) {
$author->name;
}

$authors = $context->table('author');
foreach ($authors as $author) {
$author->name;
}


$authors->where('web IS NOT NULL');
foreach ($authors as $author) {
$author->web;
if ($i === 0) {
$authors->where('web IS NOT NULL');
foreach ($authors as $author) {
$author->web;
}
$authors->__destruct();
} else {
$sql = $authors->getSql();
}
}

$authors->__destruct();


$authors = $context->table('author');
Assert::equal(reformat('SELECT [id], [name] FROM [author]'), $authors->getSql());


Assert::equal(reformat('SELECT [id], [name] FROM [author]'), $sql);
Assert::same(2, $cacheStorage->writes);
140 changes: 80 additions & 60 deletions tests/Database/Table/Table.cache.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,41 @@ Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverN


test(function() use ($context) { // Testing Selection caching
$bookSelection = $context->table('book')->wherePrimary(2);
Assert::same(reformat('SELECT * FROM [book] WHERE ([book].[id] = ?)'), $bookSelection->getSql());

$sql = array();
for ($i = 0; $i < 4; $i += 1) {
if ($i !== 2) {
$bookSelection = $context->table('book')->wherePrimary(2);
}

$book = $bookSelection->fetch();
$book->title;
$book->translator;
$bookSelection->__destruct();
$bookSelection = $context->table('book')->wherePrimary(2);
Assert::same(reformat('SELECT [id], [title], [translator_id] FROM [book] WHERE ([book].[id] = ?)'), $bookSelection->getSql());
$sql[] = $bookSelection->getSql();

if ($i !== 2) {
$book = $bookSelection->fetch();
$book->title;
$book->translator;
if ($i === 1) {
$book->author;
} else {
$bookSelection->__destruct();
}
} else {
$bookSelection->__destruct();
}
}

$book = $bookSelection->fetch();
$book->author_id;
Assert::same(reformat('SELECT * FROM [book] WHERE ([book].[id] = ?)'), $bookSelection->getSql());
/*
* schedule:
* - fetch all columns / cycle 1
* - fetch used columns, require another and fetch all again / cycle 2, 3
* - fetch used column with new used column / cycle 4
*/

$bookSelection->__destruct();
$bookSelection = $context->table('book')->wherePrimary(2);
Assert::same(reformat('SELECT [id], [title], [translator_id], [author_id] FROM [book] WHERE ([book].[id] = ?)'), $bookSelection->getSql());
Assert::same(array(
reformat('SELECT * FROM [book] WHERE ([book].[id] = ?)'),
reformat('SELECT [id], [title], [translator_id] FROM [book] WHERE ([book].[id] = ?)'),
reformat('SELECT * FROM [book] WHERE ([book].[id] = ?)'),
reformat('SELECT [id], [title], [translator_id], [author_id] FROM [book] WHERE ([book].[id] = ?)')
), $sql);
});


Expand Down Expand Up @@ -107,61 +123,65 @@ test(function() use ($context) {
});


test(function() use ($context) {
$author = $context->table('author')->get(11);
$books = $author->related('book')->where('translator_id', 99); // 0 rows
foreach ($books as $book) {}
$books->__destruct();
unset($author);

$author = $context->table('author')->get(11);
$books = $author->related('book')->where('translator_id', 11);
Assert::same(array('id', 'author_id'), $books->getPreviousAccessedColumns());
test(function() use ($context) { // Test saving joining keys even with 0 rows
$cols = array();
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();
foreach ($books as $book) {}
$books->__destruct();
}

Assert::same(array(
array(),
array('id', 'author_id'),
), $cols);
});


test(function() use ($context) { // Test saving the union of needed cols, the second call is subset
$author = $context->table('author')->get(11);
$books = $author->related('book');
foreach ($books as $book) {
$book->translator_id;
$book->title;
}
$books->__destruct();

$author = $context->table('author')->get(11);
$books = $author->related('book');
foreach ($books as $book) {
$book->title;
$cols = array();
for ($i = 0; $i < 3; $i += 1) {
$author = $context->table('author')->get(11);
$books = $author->related('book');
$cols[] = $books->getPreviousAccessedColumns();
foreach ($books as $book) {
if ($i === 0) {
$book->translator_id;
}
$book->title;
}
$books->__destruct();
}
$books->__destruct();

$author = $context->table('author')->get(11);
Assert::same(
reformat('SELECT [id], [author_id], [translator_id], [title] FROM [book] WHERE ([book].[author_id] IN (?))'),
$author->related('book')->getSql()
);
Assert::same(array(
array(),
array('id', 'author_id', 'translator_id', 'title'),
array('id', 'author_id', 'translator_id', 'title'),
), $cols);
});


test(function() use ($context) { // Test saving the union of needed cols, the second call is not subset
$author = $context->table('author')->get(11);
$books = $author->related('book');
foreach ($books as $book) {
$book->translator_id;
}
$books->__destruct();

$author = $context->table('author')->get(11);
$books = $author->related('book');
foreach ($books as $book) {
$book->title;
$cols = array();
for ($i = 0; $i < 3; $i += 1) {
$author = $context->table('author')->get(11);
$books = $author->related('book');
$cols[] = $books->getPreviousAccessedColumns();
foreach ($books as $book) {
if ($i === 0) {
$book->translator_id;
} else {
$book->title;
}
}
$books->__destruct();
}
$books->__destruct();

$author = $context->table('author')->get(11);
Assert::same(
reformat('SELECT [id], [author_id], [translator_id], [title] FROM [book] WHERE ([book].[author_id] IN (?))'),
$author->related('book')->getSql()
);
Assert::same(array(
array(),
array('id', 'author_id', 'translator_id'),
array('id', 'author_id', 'translator_id', 'title'),
), $cols);
});

0 comments on commit 0152fc7

Please sign in to comment.