From 2d4db7b07512575b9ce5839b9ae2049475efa049 Mon Sep 17 00:00:00 2001 From: doctrine Date: Fri, 14 Apr 2006 22:25:02 +0000 Subject: [PATCH] --- classes/Cache/File.class.php | 4 +- classes/Cache/Sqlite.class.php | 260 ++++++++++++++++++ classes/Configurable.class.php | 2 +- classes/DataDict.class.php | 27 +- classes/Doctrine.class.php | 8 +- classes/Manager.class.php | 2 +- classes/Table.class.php | 8 +- .../drivers/datadict-sqlite.inc.php | 90 ++++++ tests/CacheSqliteTestCase.class.php | 111 ++++++++ tests/TableTestCase.class.php | 3 - tests/UnitTestCase.class.php | 3 +- tests/run.php | 15 +- 12 files changed, 498 insertions(+), 35 deletions(-) create mode 100644 classes/Cache/Sqlite.class.php create mode 100644 classes/adodb-hack/drivers/datadict-sqlite.inc.php create mode 100644 tests/CacheSqliteTestCase.class.php diff --git a/classes/Cache/File.class.php b/classes/Cache/File.class.php index f9cbd705d..0aa4dad6d 100644 --- a/classes/Cache/File.class.php +++ b/classes/Cache/File.class.php @@ -1,5 +1,5 @@ table = $table; + $dir = $this->table->getSession()->getAttribute(Doctrine::ATTR_CACHE_DIR); + + + if( ! is_dir($dir)) + mkdir($dir, 0777); + + $this->path = $dir.DIRECTORY_SEPARATOR; + + $this->dbh = new PDO("sqlite:".$this->path."data.cache"); + $this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $this->dbh->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER); + + try { + $this->dbh->query("CREATE TABLE ".$this->table->getTableName()." (id INTEGER, object TEXT)"); + } catch(PDOException $e) { + + } + /** + * create stats file + */ + if( ! file_exists($this->path.self::STATS_FILE)) + touch($this->path.self::STATS_FILE); + + } + /* + * stores a Doctrine_Record into cache + * @param Doctrine_Record $record record to be stored + * @return boolean whether or not storing was successful + */ + public function store(Doctrine_Record $record) { + if($record->getState() != Doctrine_Record::STATE_CLEAN) + return false; + + $clone = clone $record; + $id = $clone->getID(); + + $stmt = $this->dbh->query(sprintf(self::INSERT,$this->table->getTableName())); + $stmt->execute(array($id, serialize($clone))); + + return true; + } + /** + * fetches a Doctrine_Record from the cache + * @param integer $id + * @return mixed false on failure, Doctrine_Record on success + */ + public function fetch($id) { + $stmt = $this->dbh->query(sprintf(self::SELECT,$this->table->getTableName(),"= ?")); + $stmt->execute(array($id)); + $data = $stmt->fetch(PDO::FETCH_NUM); + + if($data === false) + throw new InvalidKeyException(); + + $this->fetched[] = $id; + + $record = unserialize($data[0]); + + if(is_string($record)) { + $this->delete($id); + throw new InvalidKeyException(); + } + + return $record; + } + /** + * fetches multiple records from the cache + * @param array $keys + * @return mixed false on failure, an array of Doctrine_Record objects on success + */ + public function fetchMultiple(array $keys) { + $count = (count($keys)-1); + $sql = sprintf(self::SELECT,$this->table->getTableName(),"IN (".str_repeat("?, ",$count)."?)"); + $stmt = $this->dbh->query($sql); + $stmt->execute($keys); + + while($data = $stmt->fetch(PDO::FETCH_NUM)) { + $array[] = unserialize($data[0]); + } + + $this->fetched = array_merge($this->fetched, $keys); + + if( ! isset($array)) + return false; + + return $array; + } + /** + * deletes all records from cache + * @return void + */ + public function deleteAll() { + $stmt = $this->dbh->query("DELETE FROM ".$this->table->getTableName()); + return $stmt->rowCount(); + } + /** + * @return void + */ + public function delete($id) { + $stmt = $this->dbh->query(sprintf(self::DELETE,$this->table->getTableName(),"= ?")); + $stmt->execute(array($id)); + + if($stmt->rowCount() > 0) + return true; + + return false; + } + /** + * count + * @return integer + */ + public function count() { + $stmt = $this->dbh->query("SELECT COUNT(*) FROM ".$this->table->getTableName()); + $data = $stmt->fetch(PDO::FETCH_NUM); + + // table has two columns so we have to divide the count by two + return ($data[0] / 2); + } + /** + * @param array $keys + * @return integer + */ + public function deleteMultiple(array $keys) { + if(empty($keys)) + return 0; + + $count = (count($keys)-1); + $sql = sprintf(self::DELETE,$this->table->getTableName(),"IN (".str_repeat("?, ",$count)."?)"); + $stmt = $this->dbh->query($sql); + $stmt->execute($keys); + + return $stmt->rowCount(); + } + /** + * getStats + * @return array an array of fetch statistics, keys as primary keys + * and values as fetch times + */ + public function getStats() { + $f = file_get_contents($this->path.self::STATS_FILE); + + // every cache file starts with a ":" + + $f = substr(trim($f),1); + $e = explode(":",$f); + return array_count_values($e); + } + /** + * clean + * @return void + */ + public function clean() { + $stats = $this->getStats(); + + asort($stats); + $size = $this->table->getAttribute(Doctrine::ATTR_CACHE_SIZE); + + $count = count($stats); + + if($count <= $size) + return 0; + + $e = $count - $size; + + $keys = array(); + foreach($stats as $id => $count) { + if( ! $e--) + break; + + $keys[] = $id; + } + return $this->deleteMultiple($keys); + } + /** + * saves statistics + * @return boolean + */ + public function saveStats() { + if( ! empty($this->fetched)) { + $fp = fopen($this->path.self::STATS_FILE,"a"); + fwrite($fp,":".implode(":",$this->fetched)); + fclose($fp); + $this->fetched = array(); + return true; + } + return false; + } + /** + * autoClean + * $ttl is the number of page loads between each cache cleaning + * the default is 100 page loads + * + * this means that the average number of page loads between + * each cache clean is 100 page loads (= 100 constructed Doctrine_Managers) + * @return boolean + */ + public function autoClean() { + $ttl = $this->table->getAttribute(Doctrine::ATTR_CACHE_TTL); + + $l1 = (mt_rand(1,$ttl) / $ttl); + $l2 = (1 - 1/$ttl); + + if($l1 > $l2) { + $this->clean(); + return true; + } + return false; + } + + /** + * destructor + * the purpose of this destructor is to save all the fetched + * primary keys into the cache stats and to clean cache if necessary + * + */ + public function __destruct() { + $this->saveStats(); + $this->autoClean(); + } +} +?> diff --git a/classes/Configurable.class.php b/classes/Configurable.class.php index 08643fde5..c61623bec 100644 --- a/classes/Configurable.class.php +++ b/classes/Configurable.class.php @@ -91,7 +91,7 @@ abstract class Doctrine_Configurable { break; case Doctrine::ATTR_CACHE: - if($value != Doctrine::CACHE_FILE && $value != Doctrine::CACHE_NONE) + if($value != Doctrine::CACHE_SQLITE && $value != Doctrine::CACHE_NONE) throw new Doctrine_Exception("Unknown cache container. See Doctrine::CACHE_* constants for availible containers."); break; default: diff --git a/classes/DataDict.class.php b/classes/DataDict.class.php index cd28b2d1f..38a39d1e1 100644 --- a/classes/DataDict.class.php +++ b/classes/DataDict.class.php @@ -1,36 +1,35 @@ table = $table; - $manager = $this->table->getSession()->getManager(); - + private $dbh; + public function __construct(PDO $dbh) { + $manager = Doctrine_Manager::getInstance(); require_once($manager->getRoot()."/adodb-hack/adodb.inc.php"); - $dbh = $this->table->getSession()->getDBH(); - + $this->dbh = $dbh; $this->dict = NewDataDictionary($dbh); } - public function metaColumns() { - return $this->dict->metaColumns($this->table->getTableName()); + public function metaColumns(Doctrine_Table $table) { + return $this->dict->metaColumns($table->getTableName()); } - public function createTable() { - foreach($this->table->getColumns() as $name => $args) { + public function createTable($tablename, $columns) { + foreach($columns as $name => $args) { $r[] = $name." ".$this->getADOType($args[0],$args[1])." ".$args[2]; } - $dbh = $this->table->getSession()->getDBH(); + $r = implode(", ",$r); - $a = $this->dict->createTableSQL($this->table->getTableName(),$r); + $a = $this->dict->createTableSQL($tablename,$r); $return = true; foreach($a as $sql) { try { - $dbh->query($sql); + $this->dbh->query($sql); } catch(PDOException $e) { + if($this->dbh->getAttribute(PDO::ATTR_DRIVER_NAME) == "sqlite") + throw $e; $return = false; } } diff --git a/classes/Doctrine.class.php b/classes/Doctrine.class.php index 8c134a710..9ef202612 100644 --- a/classes/Doctrine.class.php +++ b/classes/Doctrine.class.php @@ -124,9 +124,9 @@ final class Doctrine { */ /** - * file cache constant + * sqlite cache constant */ - const CACHE_FILE = 0; + const CACHE_SQLITE = 0; /** * constant for disabling the caching */ @@ -217,7 +217,7 @@ final class Doctrine { $a[] = self::$path.DIRECTORY_SEPARATOR.$entry; break; default: - if(is_file(self::$path.DIRECTORY_SEPARATOR.$entry)) { + if(is_file(self::$path.DIRECTORY_SEPARATOR.$entry) && substr($entry,-4) == ".php") { require_once($entry); } endswitch; @@ -226,7 +226,7 @@ final class Doctrine { $dir = dir($dirname); $path = $dirname.DIRECTORY_SEPARATOR; while (false !== ($entry = $dir->read())) { - if(is_file($path.$entry)) { + if(is_file($path.$entry) && substr($entry,-4) == ".php") { require_once($path.$entry); } } diff --git a/classes/Manager.class.php b/classes/Manager.class.php index 061ea2a8a..8e982b94b 100644 --- a/classes/Manager.class.php +++ b/classes/Manager.class.php @@ -45,7 +45,7 @@ class Doctrine_Manager extends Doctrine_Configurable implements Countable, Itera Doctrine::ATTR_FETCHMODE => Doctrine::FETCH_LAZY, Doctrine::ATTR_CACHE_TTL => 100, Doctrine::ATTR_CACHE_SIZE => 100, - Doctrine::ATTR_CACHE => Doctrine::CACHE_FILE, + Doctrine::ATTR_CACHE => Doctrine::CACHE_NONE, Doctrine::ATTR_BATCH_SIZE => 5, Doctrine::ATTR_LISTENER => new EmptyEventListener(), Doctrine::ATTR_PK_COLUMNS => array("id"), diff --git a/classes/Table.class.php b/classes/Table.class.php index 88bbe4316..30b76e810 100644 --- a/classes/Table.class.php +++ b/classes/Table.class.php @@ -162,8 +162,8 @@ class Doctrine_Table extends Doctrine_Configurable { endswitch; if($this->getAttribute(Doctrine::ATTR_CREATE_TABLES)) { - $dict = new Doctrine_DataDict($this); - $dict->createTable($this->columns); + $dict = new Doctrine_DataDict($this->getSession()->getDBH()); + $dict->createTable($this->tableName, $this->columns); } } @@ -192,8 +192,8 @@ class Doctrine_Table extends Doctrine_Configurable { $this->repository = new Doctrine_Repository($this); switch($this->getAttribute(Doctrine::ATTR_CACHE)): - case Doctrine::CACHE_FILE: - $this->cache = new Doctrine_Cache_File($this); + case Doctrine::CACHE_SQLITE: + $this->cache = new Doctrine_Cache_Sqlite($this); break; case Doctrine::CACHE_NONE: $this->cache = new Doctrine_Cache($this); diff --git a/classes/adodb-hack/drivers/datadict-sqlite.inc.php b/classes/adodb-hack/drivers/datadict-sqlite.inc.php new file mode 100644 index 000000000..7476e512c --- /dev/null +++ b/classes/adodb-hack/drivers/datadict-sqlite.inc.php @@ -0,0 +1,90 @@ +debug) $this->outp("AlterColumnSQL not supported"); + return array(); + } + + + function DropColumnSQL($tabname, $flds) + { + if ($this->debug) $this->outp("DropColumnSQL not supported"); + return array(); + } + +// function MetaType($t,$len=-1,$fieldobj=false) +// { +// } + +// function &MetaTables($ttype=false,$showSchema=false,$mask=false) +// { +// global $ADODB_FETCH_MODE; +// } + +// function &MetaColumns($table,$upper=true) +// { +// global $ADODB_FETCH_MODE; +// } + +// function MetaPrimaryKeys($table, $owner=false) +// { +// } + +// function &MetaIndexes($table, $primary = false, $owner = false) +// { +// } + +} + +?> \ No newline at end of file diff --git a/tests/CacheSqliteTestCase.class.php b/tests/CacheSqliteTestCase.class.php new file mode 100644 index 000000000..51d475b29 --- /dev/null +++ b/tests/CacheSqliteTestCase.class.php @@ -0,0 +1,111 @@ +manager->setAttribute(Doctrine::ATTR_CACHE,Doctrine::CACHE_NONE); + $dir = $this->session->getAttribute(Doctrine::ATTR_CACHE_DIR); + + if(file_exists($dir.DIRECTORY_SEPARATOR."stats.cache")) + unlink($dir.DIRECTORY_SEPARATOR."stats.cache"); + + $this->cache = new Doctrine_Cache_Sqlite($this->objTable); + $this->cache->deleteAll(); + } + /** + public function testStore() { + // does not store proxy objects + $this->assertFalse($this->cache->store($this->objTable->getProxy(4))); + + $this->assertTrue($this->cache->store($this->objTable->find(4))); + + $record = $this->cache->fetch(4); + $this->assertTrue($record instanceof Doctrine_Record); + + foreach($this->old as $name => $value) { + $this->assertEqual($record->get($name), $value); + } + $this->assertEqual($record->getID(), $this->old->getID()); + + } + public function testFetchMultiple() { + $this->assertFalse($this->cache->fetchMultiple(array(5,6))); + $this->cache->store($this->objTable->find(5)); + + $array = $this->cache->fetchMultiple(array(5,6)); + $this->assertEqual(gettype($array), "array"); + $this->assertEqual(count($array), 1); + $this->assertTrue($array[0] instanceof Doctrine_Record); + } + public function testDeleteMultiple() { + $this->assertEqual($this->cache->deleteMultiple(array()),0); + $this->cache->store($this->objTable->find(5)); + $this->cache->store($this->objTable->find(6)); + + $count = $this->cache->deleteMultiple(array(5,6)); + + $this->assertEqual($count,2); + $this->cache->store($this->objTable->find(6)); + $count = $this->cache->deleteMultiple(array(5,6)); + $this->assertEqual($count,1); + } + public function testDelete() { + $this->cache->store($this->objTable->find(5)); + $this->assertTrue($this->cache->fetch(5) instanceof Doctrine_Record); + + $this->assertEqual($this->cache->delete(5),true); + $this->assertFalse($this->cache->fetch(5)); + + $this->assertFalse($this->cache->delete(0)); + } + + public function testFetch() { + $this->assertFalse($this->cache->fetch(3)); + + } + public function testCount() { + $this->assertEqual($this->cache->count(), 0); + $this->cache->store($this->objTable->find(5)); + $this->assertEqual($this->cache->count(), 1); + } + public function testSaveStats() { + $this->assertFalse($this->cache->saveStats()); + $this->cache->store($this->objTable->find(5)); + $this->cache->store($this->objTable->find(6)); + $this->cache->store($this->objTable->find(7)); + $this->cache->fetchMultiple(array(5,6,7)); + + $this->assertTrue($this->cache->saveStats()); + $this->assertTrue(gettype($this->cache->getStats()), "array"); + $this->assertEqual($this->cache->getStats(),array(5 => 1, 6 => 1, 7 => 1)); + + $this->cache->fetchMultiple(array(5,6,7)); + $this->cache->fetch(5); + $this->cache->fetch(7); + $this->assertTrue($this->cache->saveStats()); + $this->assertEqual($this->cache->getStats(),array(5 => 3, 6 => 2, 7 => 3)); + } + public function testClean() { + $this->cache->store($this->objTable->find(4)); + $this->cache->store($this->objTable->find(5)); + $this->cache->store($this->objTable->find(6)); + $this->cache->store($this->objTable->find(7)); + $this->cache->store($this->objTable->find(8)); + $this->cache->store($this->objTable->find(9)); + $this->assertEqual($this->cache->count(), 6); + $this->cache->fetch(5); + $this->cache->fetch(7); + $this->cache->fetchMultiple(array(5,6,7)); + $this->cache->fetchMultiple(array(5,6,7)); + $this->cache->fetchMultiple(array(5,6,7)); + $this->cache->fetchMultiple(array(4,5,6,7,8,9)); + $this->assertTrue($this->cache->saveStats()); + + $this->manager->setAttribute(Doctrine::ATTR_CACHE_SIZE, 3); + $this->assertEqual($this->cache->clean(), 3); + + + } + */ +} +?> diff --git a/tests/TableTestCase.class.php b/tests/TableTestCase.class.php index 7a10ac289..d63c1ee42 100644 --- a/tests/TableTestCase.class.php +++ b/tests/TableTestCase.class.php @@ -33,9 +33,6 @@ class Doctrine_TableTestCase extends Doctrine_UnitTestCase { public function testGetSession() { $this->assertTrue($this->objTable->getSession() instanceof Doctrine_Session); } - public function testGetCache() { - $this->assertTrue($this->objTable->getCache() instanceof Doctrine_Cache); - } public function testGetData() { $this->assertTrue($this->objTable->getData() == array()); } diff --git a/tests/UnitTestCase.class.php b/tests/UnitTestCase.class.php index b78b1a2c8..825872128 100644 --- a/tests/UnitTestCase.class.php +++ b/tests/UnitTestCase.class.php @@ -61,13 +61,12 @@ class Doctrine_UnitTestCase extends UnitTestCase { foreach($tables as $name) { $table = $this->session->getTable($name); - $table->getCache()->deleteAll(); } $this->objTable = $this->session->getTable("User"); $this->repository = $this->objTable->getRepository(); - $this->cache = $this->objTable->getCache(); + //$this->cache = $this->objTable->getCache(); $this->prepareData(); } diff --git a/tests/run.php b/tests/run.php index 56517d6f4..72617b874 100644 --- a/tests/run.php +++ b/tests/run.php @@ -1,4 +1,5 @@ "; error_reporting(E_ALL); @@ -30,14 +35,14 @@ $test->addTestCase(new Doctrine_TableTestCase()); $test->addTestCase(new Doctrine_AccessTestCase()); $test->addTestCase(new Doctrine_ConfigurableTestCase()); - $test->addTestCase(new Doctrine_EventListenerTestCase()); $test->addTestCase(new Doctrine_DQL_ParserTestCase()); $test->addTestCase(new Doctrine_BatchIteratorTestCase()); -//$test->addTestCase(new Doctrine_Cache_FileTestCase()); - - +/** +$test->addTestCase(new Doctrine_Cache_FileTestCase()); +$test->addTestCase(new Doctrine_Cache_SqliteTestCase()); +*/ @@ -46,6 +51,7 @@ $test->addTestCase(new Doctrine_BatchIteratorTestCase()); $test->run(new HtmlReporter()); + $dbh = Doctrine_Manager::getInstance()->getCurrentSession()->getDBH(); $a = $dbh->getQueries(); @@ -55,4 +61,5 @@ foreach($a as $query) { $e = explode(" ",$query); print $query."\n"; } + ?>