From 571cb467264aa61f6b4f877b038fc8a9abc0856d Mon Sep 17 00:00:00 2001 From: doctrine Date: Thu, 13 Apr 2006 20:37:28 +0000 Subject: [PATCH] --- classes/Access.class.php | 76 ++ classes/Association.class.php | 33 + classes/BatchIterator.class.php | 74 ++ classes/Cache.class.php | 57 + classes/Cache/File.class.php | 261 ++++ classes/Collection.class.php | 289 +++++ classes/Collection/Batch.class.php | 182 +++ classes/Collection/Immediate.class.php | 28 + classes/Collection/Lazy.class.php | 18 + classes/Configurable.class.php | 150 +++ classes/ConfigurableComponent.class.php | 64 + classes/DB.class.php | 121 ++ classes/DQL/Parser.class.php | 779 ++++++++++++ classes/DataDict.class.php | 96 ++ classes/Debugger.class.php | 151 +++ classes/Doctrine.class.php | 268 +++++ classes/EventListener.class.php | 124 ++ classes/Exception.class.php | 6 + classes/Exception/Find.class.php | 10 + classes/Exception/Mapping.class.php | 10 + classes/Exception/Naming.class.php | 11 + classes/Exception/PrimaryKey.class.php | 10 + classes/Exception/Refresh.class.php | 11 + classes/Exception/Session.class.php | 12 + classes/Exception/Table.class.php | 13 + classes/Exception/Validator.class.php | 11 + classes/ForeignKey.class.php | 6 + classes/FormBuilder.class.php | 22 + classes/Lib.php | 140 +++ classes/LocalKey.class.php | 6 + classes/Manager.class.php | 207 ++++ classes/Record.class.php | 999 +++++++++++++++ classes/Relation.class.php | 83 ++ classes/Repository.class.php | 114 ++ classes/Session.class.php | 736 +++++++++++ classes/Session/Common.class.php | 10 + classes/Session/Firebird.class.php | 20 + classes/Session/Informix.class.php | 6 + classes/Session/Mssql.class.php | 18 + classes/Session/Mysql.class.php | 38 + classes/Session/Oracle.class.php | 25 + classes/Session/Pgsql.class.php | 17 + classes/Session/Sqlite.class.php | 6 + classes/Table.class.php | 617 ++++++++++ classes/Validator.class.php | 111 ++ classes/Validator/Blank.class.php | 18 + classes/Validator/Country.class.php | 264 ++++ classes/Validator/Date.class.php | 13 + classes/Validator/Email.class.php | 46 + classes/Validator/Htmlcolor.class.php | 16 + classes/Validator/Ip.class.php | 21 + classes/Validator/Nospace.class.php | 16 + classes/Validator/Notnull.class.php | 13 + classes/Validator/Range.class.php | 31 + classes/Validator/Regexp.class.php | 13 + classes/Validator/Required.class.php | 13 + classes/Validator/Unique.class.php | 17 + classes/Validator/Usstate.class.php | 71 ++ classes/adodb-hack/adodb-datadict.inc.php | 1072 +++++++++++++++++ classes/adodb-hack/adodb.inc.php | 50 + .../drivers/datadict-access.inc.php | 95 ++ .../adodb-hack/drivers/datadict-db2.inc.php | 143 +++ .../drivers/datadict-firebird.inc.php | 151 +++ .../drivers/datadict-generic.inc.php | 125 ++ .../adodb-hack/drivers/datadict-ibase.inc.php | 67 ++ .../drivers/datadict-informix.inc.php | 80 ++ .../adodb-hack/drivers/datadict-mssql.inc.php | 282 +++++ .../adodb-hack/drivers/datadict-mysql.inc.php | 182 +++ .../adodb-hack/drivers/datadict-oci8.inc.php | 282 +++++ .../adodb-hack/drivers/datadict-pgsql.inc.php | 371 ++++++ .../adodb-hack/drivers/datadict-sapdb.inc.php | 121 ++ .../drivers/datadict-sybase.inc.php | 228 ++++ .../adodb-hack/drivers/odbc/odbc_datadict.inc | 89 ++ .../drivers/sqlite/sqlite_datadict.inc | 90 ++ tests/AccessTestCase.class.php | 39 + tests/BatchIteratorTestCase.class.php | 15 + tests/CacheFileTestCase.class.php | 56 + tests/CollectionTestCase.class.php | 7 + tests/CompositePrimaryKeyTestCase.class.php | 3 + tests/ConfigurableTestCase.class.php | 74 ++ tests/DQLParserTestCase.class.php | 194 +++ tests/EventListenerTestCase.class.php | 13 + tests/FormBuilderTestCase.class.php | 7 + tests/ManagerTestCase.class.php | 23 + tests/RecordTestCase.class.php | 440 +++++++ tests/RepositoryTestCase.class.php | 7 + tests/SessionTestCase.class.php | 263 ++++ tests/TableTestCase.class.php | 94 ++ tests/UnitTestCase.class.php | 146 +++ tests/ValidatorTestCase.class.php | 73 ++ tests/classes.php | 103 ++ tests/run.php | 55 + 92 files changed, 11638 insertions(+) create mode 100644 classes/Access.class.php create mode 100644 classes/Association.class.php create mode 100644 classes/BatchIterator.class.php create mode 100644 classes/Cache.class.php create mode 100644 classes/Cache/File.class.php create mode 100644 classes/Collection.class.php create mode 100644 classes/Collection/Batch.class.php create mode 100644 classes/Collection/Immediate.class.php create mode 100644 classes/Collection/Lazy.class.php create mode 100644 classes/Configurable.class.php create mode 100644 classes/ConfigurableComponent.class.php create mode 100644 classes/DB.class.php create mode 100644 classes/DQL/Parser.class.php create mode 100644 classes/DataDict.class.php create mode 100644 classes/Debugger.class.php create mode 100644 classes/Doctrine.class.php create mode 100644 classes/EventListener.class.php create mode 100644 classes/Exception.class.php create mode 100644 classes/Exception/Find.class.php create mode 100644 classes/Exception/Mapping.class.php create mode 100644 classes/Exception/Naming.class.php create mode 100644 classes/Exception/PrimaryKey.class.php create mode 100644 classes/Exception/Refresh.class.php create mode 100644 classes/Exception/Session.class.php create mode 100644 classes/Exception/Table.class.php create mode 100644 classes/Exception/Validator.class.php create mode 100644 classes/ForeignKey.class.php create mode 100644 classes/FormBuilder.class.php create mode 100644 classes/Lib.php create mode 100644 classes/LocalKey.class.php create mode 100644 classes/Manager.class.php create mode 100644 classes/Record.class.php create mode 100644 classes/Relation.class.php create mode 100644 classes/Repository.class.php create mode 100644 classes/Session.class.php create mode 100644 classes/Session/Common.class.php create mode 100644 classes/Session/Firebird.class.php create mode 100644 classes/Session/Informix.class.php create mode 100644 classes/Session/Mssql.class.php create mode 100644 classes/Session/Mysql.class.php create mode 100644 classes/Session/Oracle.class.php create mode 100644 classes/Session/Pgsql.class.php create mode 100644 classes/Session/Sqlite.class.php create mode 100644 classes/Table.class.php create mode 100644 classes/Validator.class.php create mode 100644 classes/Validator/Blank.class.php create mode 100644 classes/Validator/Country.class.php create mode 100644 classes/Validator/Date.class.php create mode 100644 classes/Validator/Email.class.php create mode 100644 classes/Validator/Htmlcolor.class.php create mode 100644 classes/Validator/Ip.class.php create mode 100644 classes/Validator/Nospace.class.php create mode 100644 classes/Validator/Notnull.class.php create mode 100644 classes/Validator/Range.class.php create mode 100644 classes/Validator/Regexp.class.php create mode 100644 classes/Validator/Required.class.php create mode 100644 classes/Validator/Unique.class.php create mode 100644 classes/Validator/Usstate.class.php create mode 100644 classes/adodb-hack/adodb-datadict.inc.php create mode 100644 classes/adodb-hack/adodb.inc.php create mode 100644 classes/adodb-hack/drivers/datadict-access.inc.php create mode 100644 classes/adodb-hack/drivers/datadict-db2.inc.php create mode 100644 classes/adodb-hack/drivers/datadict-firebird.inc.php create mode 100644 classes/adodb-hack/drivers/datadict-generic.inc.php create mode 100644 classes/adodb-hack/drivers/datadict-ibase.inc.php create mode 100644 classes/adodb-hack/drivers/datadict-informix.inc.php create mode 100644 classes/adodb-hack/drivers/datadict-mssql.inc.php create mode 100644 classes/adodb-hack/drivers/datadict-mysql.inc.php create mode 100644 classes/adodb-hack/drivers/datadict-oci8.inc.php create mode 100644 classes/adodb-hack/drivers/datadict-pgsql.inc.php create mode 100644 classes/adodb-hack/drivers/datadict-sapdb.inc.php create mode 100644 classes/adodb-hack/drivers/datadict-sybase.inc.php create mode 100644 classes/adodb-hack/drivers/odbc/odbc_datadict.inc create mode 100644 classes/adodb-hack/drivers/sqlite/sqlite_datadict.inc create mode 100644 tests/AccessTestCase.class.php create mode 100644 tests/BatchIteratorTestCase.class.php create mode 100644 tests/CacheFileTestCase.class.php create mode 100644 tests/CollectionTestCase.class.php create mode 100644 tests/CompositePrimaryKeyTestCase.class.php create mode 100644 tests/ConfigurableTestCase.class.php create mode 100644 tests/DQLParserTestCase.class.php create mode 100644 tests/EventListenerTestCase.class.php create mode 100644 tests/FormBuilderTestCase.class.php create mode 100644 tests/ManagerTestCase.class.php create mode 100644 tests/RecordTestCase.class.php create mode 100644 tests/RepositoryTestCase.class.php create mode 100644 tests/SessionTestCase.class.php create mode 100644 tests/TableTestCase.class.php create mode 100644 tests/UnitTestCase.class.php create mode 100644 tests/ValidatorTestCase.class.php create mode 100644 tests/classes.php create mode 100644 tests/run.php diff --git a/classes/Access.class.php b/classes/Access.class.php new file mode 100644 index 000000000..594801697 --- /dev/null +++ b/classes/Access.class.php @@ -0,0 +1,76 @@ + value pairs + */ + public function setArray(array $array) { + foreach($array as $k=>$v): + $this->set($k,$v); + endforeach; + } + /** + * __set -- an alias of set() + * @see set, offsetSet + * @param $name + * @param $value + */ + public function __set($name,$value) { + $this->set($name,$value); + } + /** + * __get -- an alias of get() + * @see get, offsetGet + * @param mixed $name + * @return mixed + */ + public function __get($name) { + return $this->get($name); + } + /** + * @return boolean -- whether or not the data has a field $offset + */ + public function offsetExists($offset) { + return (bool) isset($this->data[$offset]); + } + /** + * offsetGet -- an alias of get() + * @see get, __get + * @param mixed $offset + * @return mixed + */ + public function offsetGet($offset) { + return $this->get($offset); + } + /** + * sets $offset to $value + * @see set, __set + * @param mixed $offset + * @param mixed $value + * @return void + */ + public function offsetSet($offset, $value) { + if( ! isset($offset)) { + $this->add($value); + } else + $this->set($offset,$value); + } + /** + * unset a given offset + * @see set, offsetSet, __set + * @param mixed $offset + */ + public function offsetUnset($offset) { + if($this instanceof Doctrine_Collection) { + return $this->remove($offset); + } else { + $this->set($offset,null); + } + } +} +?> diff --git a/classes/Association.class.php b/classes/Association.class.php new file mode 100644 index 000000000..07ca5484e --- /dev/null +++ b/classes/Association.class.php @@ -0,0 +1,33 @@ +associationTable = $associationTable; + } + /** + * @return Doctrine_Table + */ + public function getAssociationFactory() { + return $this->associationTable; + } +} +?> diff --git a/classes/BatchIterator.class.php b/classes/BatchIterator.class.php new file mode 100644 index 000000000..2543b8ef9 --- /dev/null +++ b/classes/BatchIterator.class.php @@ -0,0 +1,74 @@ +collection = $collection; + $this->keys = $this->collection->getKeys(); + $this->count = $this->collection->count(); + } + /** + * @return void + */ + public function rewind() { + $this->index = 0; + $i = $this->index; + if(isset($this->keys[$i])) + $this->key = $this->keys[$i]; + } + /** + * @return boolean whether or not the iteration will continue + */ + public function valid() { + return $this->index < $this->count; + } + /** + * @return integer the current key + */ + public function key() { + return $this->key; + } + /** + * @return Doctrine_Record the current DAO + */ + public function current() { + return $this->collection->get($this->key); + } + /** + * @return void + */ + public function next() { + $this->index++; + $i = $this->index; + if(isset($this->keys[$i])) + $this->key = $this->keys[$i]; + } +} +?> diff --git a/classes/Cache.class.php b/classes/Cache.class.php new file mode 100644 index 000000000..2d354c3f7 --- /dev/null +++ b/classes/Cache.class.php @@ -0,0 +1,57 @@ + diff --git a/classes/Cache/File.class.php b/classes/Cache/File.class.php new file mode 100644 index 000000000..f9cbd705d --- /dev/null +++ b/classes/Cache/File.class.php @@ -0,0 +1,261 @@ +objTable = $objTable; + + $name = $this->getTable()->getTableName(); + + $manager = Doctrine_Manager::getInstance(); + + $dir = $manager->getAttribute(Doctrine::ATTR_CACHE_DIR); + + if( ! is_dir($dir)) + mkdir($dir, 0777); + + if( ! is_dir($dir.DIRECTORY_SEPARATOR.$name)) + mkdir($dir.DIRECTORY_SEPARATOR.$name, 0777); + + $this->path = $dir.DIRECTORY_SEPARATOR.$name.DIRECTORY_SEPARATOR; + + /** + * create stats file + */ + if( ! file_exists($this->path.self::STATS_FILE)) + touch($this->path.self::STATS_FILE); + + + } + /** + * @return Doctrine_Table + */ + public function getTable() { + return $this->objTable; + } + /** + * @return integer number of cache files + */ + public function count() { + $c = -1; + foreach(glob($this->path."*.cache") as $file) { + $c++; + } + return $c; + } + /** + * 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); + } + /** + * store store a Doctrine_Record into file cache + * @param Doctrine_Record $record data access object 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; + + + $file = $this->path.$record->getID().".cache"; + + if(file_exists($file)) + return false; + + $clone = clone $record; + $id = $clone->getID(); + + $fp = fopen($file,"w+"); + fwrite($fp,serialize($clone)); + fclose($fp); + + + + $this->fetched[] = $id; + + return true; + } + /** + * clean + * @return void + */ + public function clean() { + $stats = $this->getStats(); + + arsort($stats); + $size = $this->objTable->getAttribute(Doctrine::ATTR_CACHE_SIZE); + + $count = count($stats); + $i = 1; + + $preserve = array(); + foreach($stats as $id => $count) { + if($i > $size) + break; + + $preserve[$id] = true; + $i++; + } + + foreach(glob($this->path."*.cache") as $file) { + $e = explode(".",basename($file)); + $c = count($e); + $id = $e[($c - 2)]; + + if( ! isset($preserve[$id])) + @unlink($this->path.$id.".cache"); + } + + $fp = fopen($this->path.self::STATS_FILE,"w+"); + fwrite($fp,""); + fclose($fp); + } + /** + * @param integer $id primary key of the DAO + * @return string filename and path + */ + public function getFileName($id) { + return $this->path.$id.".cache"; + } + /** + * @return array an array of fetched primary keys + */ + public function getFetched() { + return $this->fetched; + } + /** + * fetch fetch a Doctrine_Record from the file cache + * @param integer $id + */ + public function fetch($id) { + $name = $this->getTable()->getComponentName(); + $file = $this->path.$id.".cache"; + + if( ! file_exists($file)) + throw new InvalidKeyException(); + + $data = file_get_contents($file); + + $record = unserialize($data); + + if( ! ($record instanceof Doctrine_Record)) { + // broken file, delete silently + $this->delete($id); + throw new InvalidKeyException(); + } + + $this->fetched[] = $id; + + return $record; + } + /** + * exists check the existence of a cache file + * @param integer $id primary key of the cached DAO + * @return boolean whether or not a cache file exists + */ + public function exists($id) { + $name = $this->getTable()->getComponentName(); + $file = $this->path.$id.".cache"; + return file_exists($file); + } + /** + * deleteAll + * @return void + */ + public function deleteAll() { + foreach(glob($this->path."*.cache") as $file) { + @unlink($file); + } + $fp = fopen($this->path.self::STATS_FILE,"w+"); + fwrite($fp,""); + fclose($fp); + } + /** + * delete delete a cache file + * @param integer $id primary key of the cached DAO + */ + public function delete($id) { + $file = $this->path.$id.".cache"; + + if( ! file_exists($file)) + return false; + + @unlink($file); + return true; + } + /** + * deleteMultiple delete multiple cache files + * @param array $ids an array containing cache file ids + * @return integer the number of files deleted + */ + public function deleteMultiple(array $ids) { + $deleted = 0; + foreach($ids as $id) { + if($this->delete($id)) $deleted++; + } + return $deleted; + } + /** + * destructor + * the purpose of this destructor is to save all the fetched + * primary keys into the cache stats + */ + public function __destruct() { + if( ! empty($this->fetched)) { + $fp = fopen($this->path.self::STATS_FILE,"a"); + fwrite($fp,":".implode(":",$this->fetched)); + fclose($fp); + } + /** + * + * cache auto-cleaning algorithm + * $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) + * + */ + $ttl = $this->objTable->getAttribute(Doctrine::ATTR_CACHE_TTL); + $l1 = (mt_rand(1,$ttl) / $ttl); + $l2 = (1 - 1/$ttl); + + if($l1 > $l2) + $this->clean(); + + } +} +?> diff --git a/classes/Collection.class.php b/classes/Collection.class.php new file mode 100644 index 000000000..8167bc229 --- /dev/null +++ b/classes/Collection.class.php @@ -0,0 +1,289 @@ +table = $table; + } + /** + * @return object Doctrine_Table + */ + public function getTable() { + return $this->table; + } + /** + * @param array $data + */ + public function addData(array $data) { + $this->data[] = $data; + } + /** + * @return mixed + */ + public function getLast() { + return end($this->data); + } + /** + * @return void + */ + public function setReference(Doctrine_Record $record,Doctrine_Relation $relation) { + $this->reference = $record; + $this->relation = $relation; + + if($relation instanceof Doctrine_ForeignKey || + $relation instanceof Doctrine_LocalKey) { + $this->reference_field = $relation->getForeign(); + + $value = $record->get($relation->getLocal()); + + foreach($this as $record) { + if($value !== null) { + $record->set($this->reference_field, $value); + } else { + $record->set($this->reference_field, $this->reference); + } + } + } + } + /** + * @return mixed + */ + public function getReference() { + return $this->reference; + } + /** + * @return boolean + */ + public function expand($i = null) { + if( ! isset($this->reference)) + return false; + + $id = $this->reference->getID(); + + if(empty($id)) + return false; + + foreach($this->data as $v) { + switch(gettype($v)): + case "array": + $ids[] = $v['id']; + break; + case "object": + $id = $v->getID(); + if( ! empty($id)) + $ids[] = $id; + break; + endswitch; + } + + if($this instanceof Doctrine_Collection_Immediate) { + $fields = implode(", ",$this->table->getColumnNames()); + } else { + $fields = implode(", ",$this->table->getPrimaryKeys()); + } + + if($this->relation instanceof Doctrine_ForeignKey) { + $str = ""; + $params = array($this->reference->getID()); + + if( ! empty($ids)) { + $str = " && id NOT IN (".substr(str_repeat("?, ",count($ids)),0,-2).")"; + $params = array_merge($params,$ids); + } + $str = " WHERE ".$this->reference_field." = ?".$str; + $query = "SELECT ".$fields." FROM ".$this->table->getTableName().$str; + $coll = $this->table->execute($query,$params); + + } elseif($this->relation instanceof Doctrine_Association) { + + $asf = $fk->getAssociationFactory(); + $query = "SELECT ".$foreign." FROM ".$asf->getTableName()." WHERE ".$local."=".$this->getID(); + + $table = $fk->getTable(); + $graph = new Doctrine_DQL_Parser($table->getSession()); + + $q = "FROM ".$table->getComponentName()." WHERE ".$table->getComponentName().".id IN ($query)"; + } + + foreach($coll as $record) { + if(isset($this->reference_field)) + $record->rawSet($this->reference_field, $this->reference); + + $this->reference->addReference($record); + } + + return true; + } + + /** + * @return boolean + */ + public function remove($key) { + if( ! isset($this->data[$key])) + throw new InvalidKeyException(); + + $removed = $this->data[$key]; + + unset($this->data[$key]); + return $removed; + } + /** + * @param mixed $key + * @return boolean + */ + public function contains($key) { + return isset($this->data[$key]); + } + /** + * @param mixed $key + * @return object Doctrine_Record return a specified dao + */ + public function get($key) { + if( ! isset($this->data[$key])) { + $this->expand(); + + if( ! isset($this->data[$key])) + $this->data[$key] = $this->table->create(); + + if(isset($this->reference_field)) { + $value = $this->reference->get($this->relation->getLocal()); + + if($value !== null) { + $this->data[$key]->set($this->reference_field, $value); + } else { + $this->data[$key]->set($this->reference_field, $this->reference); + } + } + } + + return $this->data[$key]; + } + + /** + * @return array an array containing all primary keys + */ + public function getPrimaryKeys() { + $list = array(); + foreach($this->data[$key] as $record): + $list[] = $record->getID(); + endforeach; + return $list; + } + /** + * returns all keys + * @return array + */ + public function getKeys() { + return array_keys($this->data); + } + /** + * count + * this class implements interface countable + * @return integer number of data access objects in this collection + */ + public function count() { + return count($this->data); + } + /** + * set + * @param integer $key + * @param Doctrine_Record $record + * @return void + */ + public function set($key,Doctrine_Record $record) { + if(isset($this->reference_field)) + $record->set($this->reference_field,$this->reference); + + $this->data[$key] = $record; + } + /** + * add + * adds a dao instance to this collection + * @param Doctrine_Record $record data access object to be added + * @param string $key optional key for the DAO + * @return boolean + */ + public function add(Doctrine_Record $record,$key = null) { + if(isset($this->reference_field)) + $record->rawSet($this->reference_field,$this->reference); + + if(isset($key)) { + + if(isset($this->data[$key])) + return false; + + $this->data[$key] = $record; + } + + $this->data[] = $record; + return true; + + } + /** + * save + * saves all data access objects + */ + public function save() { + $this->table->getSession()->saveCollection($this); + } + /** + * single shot delete + * deletes all dao instances from this collection + * uses only one database query to perform this operation + * @return boolean + */ + public function delete() { + $ids = $this->table->getSession()->deleteCollection($this); + $this->data = array(); + } + /** + * getIterator + * @return object ArrayIterator + */ + public function getIterator() { + $data = $this->data; + return new ArrayIterator($data); + } + /** + * returns a string representation of this object + */ + public function __toString() { + return Doctrine_Lib::getCollectionAsString($this); + } +} +?> diff --git a/classes/Collection/Batch.class.php b/classes/Collection/Batch.class.php new file mode 100644 index 000000000..134c0e05f --- /dev/null +++ b/classes/Collection/Batch.class.php @@ -0,0 +1,182 @@ +getTable($key)); + $this->data = $graph->getData($key); + if( ! is_array($this->data)) + $this->data = array(); + + $this->batchSize = $this->getTable()->getAttribute(Doctrine::ATTR_BATCH_SIZE); + } + + /** + * @param integer $batchSize batch size + */ + public function setBatchSize($batchSize) { + $batchSize = (int) $batchSize; + if($batchSize <= 0) + return false; + + $this->batchSize = $batchSize; + return true; + } + /** + * @return integer + */ + public function getBatchSize() { + return $this->batchSize; + } + /** + * load load a specified element, by loading the batch the element is part of + * @param Doctrine_Record $record data access object + * @return boolean whether or not the load operation was successful + */ + public function load(Doctrine_Record $record) { + if(empty($this->data)) + return false; + + $id = $record->getID(); + foreach($this->data as $key => $v) { + if(is_object($v)) { + if($v->getID() == $id) + break; + + } elseif(is_array($v["id"])) { + if($v["id"] == $id) + break; + } + } + $x = floor($key / $this->batchSize); + + if( ! isset($this->loaded[$x])) { + + $e = $x * $this->batchSize; + $e2 = ($x + 1)* $this->batchSize; + + $a = array(); + $proxies = array(); + + for($i = $e; $i < $e2 && $i < $this->count(); $i++): + if(is_object($this->data[$i])) + $id = $this->data[$i]->getID(); + elseif(is_array($this->data[$i])) + $id = $this->data[$i]["id"]; + + $load = false; + + // check the cache + // no need of fetching the same data twice + try { + $record = $this->table->getCache()->fetch($id); + } catch(InvalidKeyException $ex) { + $load = true; + } + + if($load) + $a[] = $id; + endfor; + + $c = count($a); + + $query = $this->table->getQuery()." WHERE "; + $query .= ($c > 1)?"id IN (":"id = "; + $query .= substr(str_repeat("?, ",count($a)),0,-2); + $query .= ($c > 1)?")":""; + + $stmt = $this->table->getSession()->execute($query,$a); + + while($row = $stmt->fetch(PDO::FETCH_ASSOC)): + $this->table->setData($row); + + if(is_object($this->data[$e])) { + $this->data[$e]->factoryRefresh($this->table); + } else { + $this->data[$e] = $this->table->getRecord(); + } + + $e++; + endwhile; + $this->loaded[$x] = true; + return true; + } else { + return false; + } + } + /** + * get + * @param mixed $key the key of the data access object + * @return object Doctrine_Record data access object + */ + public function get($key) { + if(isset($this->data[$key])) { + switch(gettype($this->data[$key])): + case "array": + try { + + // try to fetch the Doctrine_Record from cache + if( ! isset($this->data[$key]["id"])) + throw new InvalidKeyException(); + + $record = $this->table->getCache()->fetch($this->data[$key]["id"]); + + } catch(InvalidKeyException $e) { + + // Doctrine_Record didn't exist in cache + $this->table->setData($this->data[$key]); + $proxy = $this->table->getProxy(); + $record = $proxy; + } + + $record->addCollection($this); + break; + case "object": + $record = $this->data[$key]; + break; + endswitch; + } else { + + $this->expand(); + + if(isset($this->data[$key])) { + $record = $this->data[$key]; + } else { + $record = $this->table->create(); + } + } + + + if(isset($this->reference_field)) + $record->set($this->reference_field,$this->reference); + + $this->data[$key] = $record; + return $this->data[$key]; + } + /** + * @return Doctrine_BatchIterator + */ + public function getIterator() { + return new Doctrine_BatchIterator($this); + } +} +?> diff --git a/classes/Collection/Immediate.class.php b/classes/Collection/Immediate.class.php new file mode 100644 index 000000000..cea26206a --- /dev/null +++ b/classes/Collection/Immediate.class.php @@ -0,0 +1,28 @@ +getTable($key)); + + $name = $this->table->getComponentName(); + $data = $graph->getData($name); + if(is_array($data)) { + foreach($data as $k=>$v): + $this->table->setData($v); + $this->add($this->table->getRecord()); + endforeach; + } + } +} +?> diff --git a/classes/Collection/Lazy.class.php b/classes/Collection/Lazy.class.php new file mode 100644 index 000000000..f4e8efaae --- /dev/null +++ b/classes/Collection/Lazy.class.php @@ -0,0 +1,18 @@ + diff --git a/classes/Configurable.class.php b/classes/Configurable.class.php new file mode 100644 index 000000000..08643fde5 --- /dev/null +++ b/classes/Configurable.class.php @@ -0,0 +1,150 @@ + 1) + throw new Doctrine_Exception("Cache slam defense should be a floating point number between 0 and 1"); + break; + case Doctrine::ATTR_FETCHMODE: + if($value < 0) + throw new Doctrine_Exception("Unknown fetchmode. Fetchmode should be an integer between 0 and 2. See Doctrine::FETCH_* constants."); + break; + case Doctrine::ATTR_LISTENER: + $this->setEventListener($value); + break; + case Doctrine::ATTR_PK_COLUMNS: + if( ! is_array($value)) + throw new Doctrine_Exception("The value of Doctrine::ATTR_PK_COLUMNS attribute must be an array"); + break; + case Doctrine::ATTR_PK_TYPE: + if($value != Doctrine::INCREMENT_KEY && $value != Doctrine::UNIQUE_KEY) + throw new Doctrine_Exception("The value of Doctrine::ATTR_PK_TYPE attribute must be either Doctrine::INCREMENT_KEY or Doctrine::UNIQUE_KEY"); + + break; + case Doctrine::ATTR_LOCKMODE: + if($this instanceof Doctrine_Session) { + if($this->getState() != Doctrine_Session::STATE_OPEN) + throw new Doctrine_Exception("Couldn't set lockmode. There are transactions open."); + + } elseif($this instanceof Doctrine_Manager) { + foreach($this as $session) { + if($session->getState() != Doctrine_Session::STATE_OPEN) + throw new Doctrine_Exception("Couldn't set lockmode. There are transactions open."); + } + } else { + throw new Doctrine_Exception("Lockmode attribute can only be set at the global or session level."); + } + break; + case Doctrine::ATTR_CREATE_TABLES: + $value = (bool) $value; + break; + case Doctrine::ATTR_VLD: + + break; + case Doctrine::ATTR_CACHE: + if($value != Doctrine::CACHE_FILE && $value != Doctrine::CACHE_NONE) + throw new Doctrine_Exception("Unknown cache container. See Doctrine::CACHE_* constants for availible containers."); + break; + default: + throw new Exception("Unknown attribute."); + endswitch; + + $this->attributes[$attribute] = $value; + + } + /** + * @param Doctrine_EventListener $listener + * @return void + */ + final public function setEventListener(Doctrine_EventListener $listener) { + $i = Doctrine::ATTR_LISTENER; + $this->attributes[$i] = $listener; + } + /** + * @return mixed the value of the attribute + */ + final public function getAttribute($attribute) { + $attribute = (int) $attribute; + + if($attribute < 1 || $attribute > 14) + throw new InvalidKeyException(); + + if( ! isset($this->attributes[$attribute])) { + if(isset($this->parent)) + return $this->parent->getAttribute($attribute); + + return null; + } + return $this->attributes[$attribute]; + } + /** + * getAttributes + * @return array + */ + final public function getAttributes() { + return $this->attributes; + } + /** + * @param Doctrine_Configurable $component + * @return void + */ + final public function setParent(Doctrine_Configurable $component) { + $this->parent = $component; + } + /** + * getParent + */ + final public function getParent() { + return $this->parent; + } +} +?> diff --git a/classes/ConfigurableComponent.class.php b/classes/ConfigurableComponent.class.php new file mode 100644 index 000000000..ae9ce292b --- /dev/null +++ b/classes/ConfigurableComponent.class.php @@ -0,0 +1,64 @@ +getComponent()->setTableName($name); + } + /** + * setInheritanceMap + * @param array $inheritanceMap + * @return void + */ + final public function setInheritanceMap(array $inheritanceMap) { + $this->getComponent()->setInheritanceMap($inheritanceMap); + } + /** + * setAttribute + * @param integer $attribute + * @param mixed $value + * @see Doctrine::ATTR_* constants + * @return void + */ + final public function setAttribute($attribute,$value) { + $this->getComponent()->setAttribute($attribute,$value); + } + /** + * @param string $objTableName + * @param string $fkField + * @return void + */ + final public function ownsOne($componentName,$foreignKey) { + $this->getComponent()->bind($componentName,$foreignKey,Doctrine_Table::ONE_COMPOSITE); + } + /** + * @param string $objTableName + * @param string $fkField + * @return void + */ + final public function ownsMany($componentName,$foreignKey) { + $this->getComponent()->bind($componentName,$foreignKey,Doctrine_Table::MANY_COMPOSITE); + } + /** + * @param string $objTableName + * @param string $fkField + * @return void + */ + final public function hasOne($componentName,$foreignKey) { + $this->getComponent()->bind($componentName,$foreignKey,Doctrine_Table::ONE_AGGREGATE); + } + /** + * @param string $objTableName + * @param string $fkField + * @return void + */ + final public function hasMany($componentName,$foreignKey) { + $this->getComponent()->bind($componentName,$foreignKey,Doctrine_Table::MANY_AGGREGATE); + } + + abstract public function getComponent(); +} +?> diff --git a/classes/DB.class.php b/classes/DB.class.php new file mode 100644 index 000000000..1f6846f44 --- /dev/null +++ b/classes/DB.class.php @@ -0,0 +1,121 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array("Doctrine_DBStatement",array($this))); + } + /** + * @param string $dsn PEAR::DB like DSN + * format: schema://user:password@address/dbname + */ + public static function getConnection($dsn = null) { + static $instance; + if( ! isset($instance)) { + if( ! isset($dsn)) { + $a = parse_url(self::DSN); + } else { + $a = parse_url($dsn); + } + $e = array(); + $e[0] = $a["scheme"].":host=".$a["host"].";dbname=".substr($a["path"],1); + $e[1] = $a["user"]; + $e[2] = $a["pass"]; + + $instance = new Doctrine_DB($e[0],$e[1],$e[2]); + } + return $instance; + } + /** + * @param string $query query to be executed + */ + public function query($query) { + $this->queries[] = $query; + $time = microtime(); + + $stmt = parent::query($query); + $this->exectimes[] = (microtime() - $time); + return $stmt; + } + /** + * @param string $query query to be prepared + */ + public function prepare($query) { + $this->queries[] = $query; + return parent::prepare($query); + } + /** + * @param string $time exectime of the last executed query + * @return void + */ + public function addExecTime($time) { + $this->exectimes[] = $time; + } + + public function getExecTimes() { + return $this->exectimes; + } + /** + * @return array an array of executed queries + */ + public function getQueries() { + return $this->queries; + } + /** + * @return ArrayIterator + */ + public function getIterator() { + return new ArrayIterator($this->queries); + } + /** + * returns the number of executed queries + * @return integer + */ + public function count() { + return count($this->queries); + } + +} +class Doctrine_DBStatement extends PDOStatement { + /** + * @param Doctrine_DB $dbh Doctrine Database Handler + */ + private $dbh; + /** + * @param Doctrine_DB $dbh + */ + private function __construct(Doctrine_DB $dbh) { + $this->dbh = $dbh; + } + /** + * @param array $params + */ + public function execute($params) { + $time = microtime(); + $result = parent::execute($params); + + $exectime = (microtime() - $time); + $this->dbh->addExecTime($exectime); + return $result; + } +} +?> diff --git a/classes/DQL/Parser.class.php b/classes/DQL/Parser.class.php new file mode 100644 index 000000000..a422cfe79 --- /dev/null +++ b/classes/DQL/Parser.class.php @@ -0,0 +1,779 @@ +session = $session; + } + /** + * clear + * resets all the variables + */ + private function clear() { + $this->fetchModes = array(); + $this->fields = array(); + $this->tnames = array(); + + $this->from = array(); + $this->join = array(); + $this->where = array(); + $this->orderby = array(); + $this->inheritanceApplied = false; + $this->aggregate = false; + $this->data = array(); + $this->connectors = array(); + } + /** + * loadFields -- this method loads fields for a given factory and + * constructs a little bit of sql for every field + * + * fields of the factories become: [tablename].[fieldname] as [tablename]__[fieldname] + * + * @access private + * @param object Doctrine_Table $table a Doctrine_Table object + * @param integer $fetchmode fetchmode the table is using eg. Doctrine::FETCH_LAZY + * @return void + */ + private function loadFields(Doctrine_Table $table,$fetchmode) { + switch($fetchmode): + case Doctrine::FETCH_IMMEDIATE: + $names = $table->getColumnNames(); + break; + case Doctrine::FETCH_LAZY: + case Doctrine::FETCH_BATCH: + $names = $table->getPrimaryKeys(); + break; + default: + throw new InvalidFetchModeException(); + endswitch; + $cname = $table->getComponentName(); + $this->fetchModes[$cname] = $fetchmode; + $tablename = $table->getTableName(); + + $count = count($this->tnames); + foreach($names as $name) { + if($count == 0) { + $this->fields[] = $tablename.".".$name; + } else { + $this->fields[] = $tablename.".".$name." AS ".$cname."__".$name; + } + } + } + /** + * @return string the built sql query + */ + final public function getQuery() { + if(empty($this->fields) || empty($this->from)) + return false; + + // build the basic query + $q = "SELECT ".implode(", ",$this->fields). + " FROM "; + foreach($this->from as $tname => $bool) { + $str = $tname; + if(isset($this->join[$tname])) + $str .= " ".$this->join[$tname]; + + $a[] = $str; + } + $q .= implode(", ",$a); + $this->applyInheritance(); + if( ! empty($this->where)) + $q .= " WHERE ".implode(" && ",$this->where); + + if( ! empty($this->orderby)) + $q .= " ORDER BY ".implode(", ",$this->orderby); + + return $q; + } + /** + * sql delete for mysql + */ + final public function buildDelete() { + if(empty($this->fields) || empty($this->from)) + return false; + + $a = array_merge(array_keys($this->from),$this->joined); + $q = "DELETE ".implode(", ",$a)." FROM "; + $a = array(); + + foreach($this->from as $tname => $bool) { + $str = $tname; + if(isset($this->join[$tname])) + $str .= " ".$this->join[$tname]; + + $a[] = $str; + } + + $q .= implode(", ",$a); + $this->applyInheritance(); + if( ! empty($this->where)) + $q .= " WHERE ".implode(" && ",$this->where); + + if( ! empty($this->orderby)) + $q .= " ORDER BY ".implode(", ",$this->orderby); + + if( ! empty($this->limit) && ! empty($this->offset)) + $q = $this->session->modifyLimitQuery($q,$this->limit,$this->offset); + + return $q; + } + /** + * applyInheritance + * applies column aggregation inheritance to DQL query + * @return boolean + */ + final public function applyInheritance() { + if($this->inheritanceApplied) + return false; + + // get the inheritance maps + $array = array(); + + foreach($this->tnames as $objTable): + $tname = $objTable->getTableName(); + $array[$tname][] = $objTable->getInheritanceMap(); + endforeach; + + // apply inheritance maps + $str = ""; + $c = array(); + + foreach($array as $tname => $maps) { + $a = array(); + foreach($maps as $map) { + $b = array(); + foreach($map as $field=>$value) { + $b[] = $tname.".$field = $value"; + } + if( ! empty($b)) $a[] = implode(" && ",$b); + } + if( ! empty($a)) $c[] = implode(" || ",$a); + } + + $str .= implode(" || ",$c); + + $this->addWhere($str); + $this->inheritanceApplied = true; + return true; + } + /** + * @param string $where + * @return boolean + */ + final public function addWhere($where) { + if(empty($where)) + return false; + + $this->where[] = "(".$where.")"; + return true; + } + /** + * @param string $from from part of the query + */ + final public function addFrom($from) { + $this->from[] = $from; + } + /** + * getData + * @param $key the factory name + * @return array the data row for the specified factory + */ + final public function getData($key) { + if(isset($this->data[$key])) + return $this->data[$key]; + + return array(); + } + /** + * execute + * executes the datagraph and populates Doctrine_Collections + * @param string $params + * @return Doctrine_Collection the root collection + */ + private function execute($params = array()) { + + switch(count($this->tnames)): + case 0: + throw new DQLException(); + break; + case 1: + $query = $this->getQuery(); + + $keys = array_keys($this->tnames); + + $name = $this->tnames[$keys[0]]->getComponentName(); + $stmt = $this->session->execute($query,$params); + + while($data = $stmt->fetch(PDO::FETCH_ASSOC)): + foreach($data as $key => $value): + $e = explode("__",$key); + if(count($e) > 1) { + $data[$e[1]] = $value; + } else { + $data[$e[0]] = $value; + } + unset($data[$key]); + endforeach; + $this->data[$name][] = $data; + endwhile; + + return $this->getCollection($keys[0]); + break; + default: + $query = $this->getQuery(); + + $keys = array_keys($this->tnames); + $root = $keys[0]; + $stmt = $this->session->execute($query,$params); + + $previd = array(); + + $coll = $this->getCollection($root); + + $array = $this->parseData($stmt); + + foreach($array as $data): + + /** + * remove duplicated data rows and map data into objects + */ + foreach($data as $key => $row): + if(empty($row) || empty($row['id'])) + continue; + + $key = ucwords($key); + $name = $this->tnames[$key]->getComponentName(); + + if( ! isset($previd[$name])) + $previd[$name] = array(); + + + if($previd[$name] !== $row) { + $this->tnames[$name]->setData($row); + $record = $this->tnames[$name]->getRecord(); + + if($name == $root) { + $this->tnames[$name]->setData($row); + $record = $this->tnames[$name]->getRecord(); + $coll->add($record); + } else { + $last = $coll->getLast(); + + if( ! $last->hasReference($name)) { + $last->initReference($this->getCollection($name),$this->connectors[$name]); + } + $last->addReference($record); + } + } + + $previd[$name] = $row; + endforeach; + endforeach; + + return $coll; + endswitch; + } + /** + * parseData + * @return array + */ + public function parseData(PDOStatement $stmt) { + $array = array(); + while($data = $stmt->fetch(PDO::FETCH_ASSOC)): + /** + * parse the data into two-dimensional array + */ + foreach($data as $key => $value): + $e = explode("__",$key); + + if(count($e) > 1) { + $data[$e[0]][$e[1]] = $value; + } else { + $data[0][$e[0]] = $value; + } + unset($data[$key]); + endforeach; + $array[] = $data; + endwhile; + $stmt->closeCursor(); + return $array; + } + /** + * @return Doctrine_Table + */ + public function getTable($name) { + return $this->tnames[$name]; + } + /** + * getCollection + * @param integer $index + */ + private function getCollection($name) { + switch($this->fetchModes[$name]): + case 0: + $coll = new Doctrine_Collection_Immediate($this,$name); + break; + case 1: + $coll = new Doctrine_Collection_Batch($this,$name); + break; + case 2: + $coll = new Doctrine_Collection_Lazy($this,$name); + break; + default: + throw new Exception("Unknown fetchmode"); + endswitch; + + return $coll; + } + /** + * query the database with DQL (Doctrine Query Language) + * + * @param string $query DQL query + * @param array $params parameters + */ + public function query($query,$params = array()) { + $this->parseQuery($query); + + if($this->aggregate) { + $keys = array_keys($this->tnames); + $query = $this->getQuery(); + $stmt = $this->tnames[$keys[0]]->getSession()->select($query); + $data = $stmt->fetch(PDO::FETCH_ASSOC); + if(count($data) == 1) { + return current($data); + } else { + return $data; + } + } else { + return $this->execute($params); + } + } + /** + * DQL PARSER + */ + final public function parseQuery($query) { + $this->clear(); + $e = self::bracketExplode($query," ","(",")"); + + $parts = array(); + foreach($e as $k=>$part): + switch(strtolower($part)): + case "select": + case "from": + case "where": + case "limit": + case "offset": + $p = $part; + $parts[$part] = array(); + break; + case "order": + $p = $part; + $i = $k+1; + if(isset($e[$i]) && strtolower($e[$i]) == "by") { + $parts[$part] = array(); + } + break; + case "by": + continue; + default: + $parts[$p][] = $part; + endswitch; + endforeach; + + foreach($parts as $k => $part) { + $part = implode(" ",$part); + switch($k): + case "SELECT": + $this->parseSelect($part); + break; + case "FROM": + $this->parseFrom($part); + break; + case "WHERE": + $this->addWhere($this->parseWhere($part)); + break; + case "ORDER": + $this->parseOrderBy($part); + break; + case "LIMIT": + $this->limit = trim($part); + break; + case "OFFSET": + $this->offset = trim($part); + break; + endswitch; + } + } + /** + * DQL SELECT PARSER + * parses the select part of the query string + * @param string $str + * @return void + */ + private function parseSelect($str) { + $this->aggregate = true; + foreach(explode(",",trim($str)) as $reference) { + + $e = explode(" AS ",trim($reference)); + + $f = explode("(",$e[0]); + $a = explode(".",$f[1]); + $field = substr(array_pop($a),0,-1); + + $reference = trim(implode(".",$a)); + + $objTable = $this->load($reference); + if(isset($e[1])) + $s = " AS $e[1]"; + + $this->fields[]= $f[0]."(".$objTable->getTableName().".$field)$s"; + + } + } + /** + * DQL FROM PARSER + * parses the from part of the query string + + * @param string $str + * @return void + */ + private function parseFrom($str) { + foreach(explode(",",trim($str)) as $reference) { + $reference = trim($reference); + $e = explode("-",$reference); + $reference = $e[0]; + $table = $this->load($reference); + + if(isset($e[1])) { + switch(strtolower($e[1])): + case "i": + case "immediate": + $fetchmode = Doctrine::FETCH_IMMEDIATE; + break; + case "b": + case "batch": + $fetchmode = Doctrine::FETCH_BATCH; + break; + case "l": + case "lazy": + $fetchmode = Doctrine::FETCH_LAZY; + break; + default: + throw new DQLException("Unknown fetchmode '$e[1]'. The availible fetchmodes are 'i', 'b' and 'l'."); + endswitch; + } else + $fetchmode = $table->getAttribute(Doctrine::ATTR_FETCHMODE); + + if( ! $this->aggregate) { + $this->loadFields($table,$fetchmode); + } + } + } + /** + * DQL ORDER BY PARSER + * parses the order by part of the query string + * + * @param string $str + * @return void + */ + private function parseOrderBy($str) { + foreach(explode(",",trim($str)) as $r) { + $r = trim($r); + $e = explode(" ",$r); + $a = explode(".",$e[0]); + + if(count($a) > 1) { + $field = array_pop($a); + $reference = implode(".",$a); + $name = end($a); + $this->load($reference); + $tname = $this->tnames[$name]->getTableName(); + + $r = $tname.".".$field; + if(isset($e[1])) $r .= " ".$e[1]; + $this->orderby[] = $r; + } else { + $this->orderby[] = $r; + } + } + } + /** + * DQL WHERE PARSER + * parses the where part of the query string + * + * + * @param string $str + * @return string + */ + private function parseWhere($str) { + $tmp = trim($str); + $str = self::bracketTrim($tmp,"(",")"); + + $brackets = false; + while($tmp != $str) { + $brackets = true; + $tmp = $str; + $str = self::bracketTrim($str,"(",")"); + } + + $parts = self::bracketExplode($str," && ","(",")"); + if(count($parts) > 1) { + $ret = array(); + foreach($parts as $part) { + $ret[] = $this->parseWhere($part); + } + $r = implode(" && ",$ret); + } else { + $parts = self::bracketExplode($str," || ","(",")"); + if(count($parts) > 1) { + $ret = array(); + foreach($parts as $part) { + $ret[] = $this->parseWhere($part); + } + $r = implode(" || ",$ret); + } else { + return $this->loadWhere($parts[0]); + } + } + if($brackets) + return "(".$r.")"; + else + return $r; + } + /** + * trims brackets + * + * @param string $str + * @param string $e1 the first bracket, usually '(' + * @param string $e2 the second bracket, usually ')' + */ + public static function bracketTrim($str,$e1,$e2) { + if(substr($str,0,1) == $e1 && substr($str,-1) == $e2) + return substr($str,1,-1); + else + return $str; + } + /** + * bracketExplode + * usage: + * $str = (age < 20 && age > 18) && email LIKE 'John@example.com' + * now exploding $str with parameters $d = ' && ', $e1 = '(' and $e2 = ')' + * would return an array: + * array("(age < 20 && age > 18)", "email LIKE 'John@example.com'") + * + * @param string $str + * @param string $d the delimeter which explodes the string + * @param string $e1 the first bracket, usually '(' + * @param string $e2 the second bracket, usually ')' + * + */ + public static function bracketExplode($str,$d,$e1,$e2) { + $str = explode("$d",$str); + $i = 0; + $term = array(); + foreach($str as $key=>$val) { + if (empty($term[$i])) { + $term[$i] = trim($val); + $s1 = substr_count($term[$i],"$e1"); + $s2 = substr_count($term[$i],"$e2"); + if($s1 == $s2) $i++; + } else { + $term[$i] .= "$d".trim($val); + $c1 = substr_count($term[$i],"$e1"); + $c2 = substr_count($term[$i],"$e2"); + if($c1 == $c2) $i++; + } + } + return $term; + } + /** + * loadWhere + * + */ + private function loadWhere($where) { + $e = explode(" ",$where); + $r = array_shift($e); + $a = explode(".",$r); + + if(count($a) > 1) { + $field = array_pop($a); + $operator = array_shift($e); + $value = implode(" ",$e); + $reference = implode(".",$a); + $objTable = $this->session->getTable(end($a)); + $where = $objTable->getTableName().".".$field." ".$operator." ".$value; + if(count($a) > 1 && isset($a[1])) { + $root = $a[0]; + $fk = $this->tnames[$root]->getForeignKey($a[1]); + if($fk instanceof Doctrine_Association) { + $asf = $fk->getAssociationFactory(); + switch($fk->getType()): + case Doctrine_Table::ONE_AGGREGATE: + case Doctrine_Table::ONE_COMPOSITE: + + break; + case Doctrine_Table::MANY_AGGREGATE: + case Doctrine_Table::MANY_COMPOSITE: + $b = array_shift($a); + $b = array_shift($a); + $graph = new Doctrine_DQL_Parser($this->session); + $graph->parseQuery("FROM $b WHERE $where"); + $where = $this->tnames[$root]->getTableName().".id IN (SELECT ".$fk->getLocal()." FROM ".$asf->getTableName()." WHERE ".$fk->getForeign()." IN (".$graph->getQuery()."))"; + break; + endswitch; + } else + $this->load($reference); + + } else + $this->load($reference); + } + return $where; + } + /** + * @param string $path the path of the loadable component + * @param integer $fetchmode optional fetchmode, if not set the components default fetchmode will be used + * @throws DQLException + */ + final public function load($path, $fetchmode = Doctrine::FETCH_LAZY) { + $e = explode(".",$path); + foreach($e as $key => $name) { + $low = strtolower($name); + $name = ucwords($low); + + try { + if($key == 0) { + + $objTable = $this->session->getTable($name); + if(count($e) == 1) { + $tname = $objTable->getTableName(); + $this->from[$tname] = true; + } + } else { + $fk = $objTable->getForeignKey($name); + $tname = $objTable->getTableName(); + $next = $fk->getTable(); + $tname2 = $next->getTableName(); + + $this->connectors[$name] = $fk; + + if($fk instanceof Doctrine_ForeignKey || + $fk instanceof Doctrine_LocalKey) { + switch($fk->getType()): + case Doctrine_Table::ONE_AGGREGATE: + case Doctrine_Table::ONE_COMPOSITE: + + $this->where[] = "(".$tname.".".$fk->getLocal()." = ".$tname2.".".$fk->getForeign().")"; + $this->from[$tname] = true; + $this->from[$tname2] = true; + break; + case Doctrine_Table::MANY_AGGREGATE: + case Doctrine_Table::MANY_COMPOSITE: + $this->join[$tname] = "LEFT JOIN ".$tname2." ON ".$tname.".".$fk->getLocal()." = ".$tname2.".".$fk->getForeign(); + $this->joined[] = $tname2; + $this->from[$tname] = true; + break; + endswitch; + } elseif($fk instanceof Doctrine_Association) { + $asf = $fk->getAssociationFactory(); + + switch($fk->getType()): + case Doctrine_Table::ONE_AGGREGATE: + case Doctrine_Table::ONE_COMPOSITE: + + break; + case Doctrine_Table::MANY_AGGREGATE: + case Doctrine_Table::MANY_COMPOSITE: + + //$this->addWhere("SELECT ".$fk->getLocal()." FROM ".$asf->getTableName()." WHERE ".$fk->getForeign()." IN (SELECT ".$fk->getTable()->getComponentName().")"); + $this->from[$tname] = true; + break; + endswitch; + } + + $objTable = $next; + } + if( ! isset($this->tnames[$name])) { + $this->tnames[$name] = $objTable; + } + + } catch(Doctrine_Exception $e) { + throw new DQLException(); + } catch(InvalidKeyException $e) { + throw new DQLException(); + } + } + return $objTable; + } +} + +?> diff --git a/classes/DataDict.class.php b/classes/DataDict.class.php new file mode 100644 index 000000000..cd28b2d1f --- /dev/null +++ b/classes/DataDict.class.php @@ -0,0 +1,96 @@ +table = $table; + $manager = $this->table->getSession()->getManager(); + + require_once($manager->getRoot()."/adodb-hack/adodb.inc.php"); + + $dbh = $this->table->getSession()->getDBH(); + + $this->dict = NewDataDictionary($dbh); + } + + public function metaColumns() { + return $this->dict->metaColumns($this->table->getTableName()); + } + + public function createTable() { + foreach($this->table->getColumns() 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); + + $return = true; + foreach($a as $sql) { + try { + $dbh->query($sql); + } catch(PDOException $e) { + $return = false; + } + } + + return $return; + } + /** + * converts doctrine type to adodb type + * + * @param string $type column type + * @param integer $length column length + */ + public function getADOType($type,$length) { + switch($type): + case "string": + case "s": + if($length < 255) + return "C($length)"; + elseif($length < 4000) + return "X"; + break; + case "mbstring": + if($length < 255) + return "C2($length)"; + + return "X2"; + case "clob": + return "XL"; + break; + case "float": + case "f": + case "double": + return "F"; + break; + case "timestamp": + case "t": + return "T"; + break; + case "boolean": + case "bool": + return "L"; + break; + case "integer": + case "int": + case "i": + if(empty($length)) + return "I8"; + elseif($length < 4) + return "I1"; + elseif($length < 6) + return "I2"; + elseif($length < 10) + return "I4"; + elseif($length <= 20) + return "I8"; + else + throw new Doctrine_Exception("Too long integer (max length is 20)."); + + break; + endswitch; + } +} +?> diff --git a/classes/Debugger.class.php b/classes/Debugger.class.php new file mode 100644 index 000000000..c9abeb3c3 --- /dev/null +++ b/classes/Debugger.class.php @@ -0,0 +1,151 @@ +object = $object; + $this->code = $code; + } + final public function getCode() { + return $this->code; + } + final public function getObject() { + return $this->object; + } +} +class Doctrine_Debugger extends Doctrine_EventListener { + const EVENT_LOAD = 1; + const EVENT_PRELOAD = 2; + const EVENT_SLEEP = 3; + const EVENT_WAKEUP = 4; + const EVENT_UPDATE = 5; + const EVENT_PREUPDATE = 6; + const EVENT_CREATE = 7; + const EVENT_PRECREATE = 8; + + const EVENT_SAVE = 9; + const EVENT_PRESAVE = 10; + const EVENT_INSERT = 11; + const EVENT_PREINSERT = 12; + const EVENT_DELETE = 13; + const EVENT_PREDELETE = 14; + const EVENT_EVICT = 15; + const EVENT_PREEVICT = 16; + const EVENT_CLOSE = 17; + const EVENT_PRECLOSE = 18; + + const EVENT_OPEN = 19; + const EVENT_COMMIT = 20; + const EVENT_PRECOMMIT = 21; + const EVENT_ROLLBACK = 22; + const EVENT_PREROLLBACK = 23; + const EVENT_BEGIN = 24; + const EVENT_PREBEGIN = 25; + const EVENT_COLLDELETE = 26; + const EVENT_PRECOLLDELETE = 27; + private $debug; + + public function getMessages() { + return $this->debug; + } + + + public function onLoad(Doctrine_Record $record) { + $this->debug[] = new Doctrine_DebugMessage($record,self::EVENT_LOAD); + } + public function onPreLoad(Doctrine_Record $record) { + $this->debug[] = new Doctrine_DebugMessage($record,self::EVENT_PRELOAD); + } + + public function onSleep(Doctrine_Record $record) { + $this->debug[] = new Doctrine_DebugMessage($record,self::EVENT_SLEEP); + } + + public function onWakeUp(Doctrine_Record $record) { + $this->debug[] = new Doctrine_DebugMessage($record,self::EVENT_WAKEUP); + } + + public function onUpdate(Doctrine_Record $record) { + $this->debug[] = new Doctrine_DebugMessage($record,self::EVENT_UPDATE); + } + public function onPreUpdate(Doctrine_Record $record) { + $this->debug[] = new Doctrine_DebugMessage($record,self::EVENT_PREUPDATE); + } + + public function onCreate(Doctrine_Record $record) { + $this->debug[] = new Doctrine_DebugMessage($record,self::EVENT_CREATE); + } + public function onPreCreate(Doctrine_Record $record) { + $this->debug[] = new Doctrine_DebugMessage($record,self::EVENT_PRECREATE); + } + + public function onSave(Doctrine_Record $record) { + $this->debug[] = new Doctrine_DebugMessage($record,self::EVENT_SAVE); + } + public function onPreSave(Doctrine_Record $record) { + $this->debug[] = new Doctrine_DebugMessage($record,self::EVENT_PRESAVE); + } + + public function onInsert(Doctrine_Record $record) { + $this->debug[] = new Doctrine_DebugMessage($record,self::EVENT_INSERT); + } + public function onPreInsert(Doctrine_Record $record) { + $this->debug[] = new Doctrine_DebugMessage($record,self::EVENT_PREINSERT); + } + + public function onDelete(Doctrine_Record $record) { + $this->debug[] = new Doctrine_DebugMessage($record,self::EVENT_DELETE); + } + public function onPreDelete(Doctrine_Record $record) { + $this->debug[] = new Doctrine_DebugMessage($record,self::EVENT_PREDELETE); + } + + public function onEvict(Doctrine_Record $record) { + $this->debug[] = new Doctrine_DebugMessage($record,self::EVENT_EVICT); + } + public function onPreEvict(Doctrine_Record $record) { + $this->debug[] = new Doctrine_DebugMessage($record,self::EVENT_PREEVICT); + } + + public function onClose(Doctrine_Session $session) { + $this->debug[] = new Doctrine_DebugMessage($session,self::EVENT_CLOSE); + } + public function onPreClose(Doctrine_Session $session) { + $this->debug[] = new Doctrine_DebugMessage($session,self::EVENT_PRECLOSE); + } + + public function onOpen(Doctrine_Session $session) { + $this->debug[] = new Doctrine_DebugMessage($session,self::EVENT_OPEN); + } + + public function onTransactionCommit(Doctrine_Session $session) { + $this->debug[] = new Doctrine_DebugMessage($session,self::EVENT_COMMIT); + } + public function onPreTransactionCommit(Doctrine_Session $session) { + $this->debug[] = new Doctrine_DebugMessage($session,self::EVENT_PRECOMMIT); + } + + public function onTransactionRollback(Doctrine_Session $session) { + $this->debug[] = new Doctrine_DebugMessage($session,self::EVENT_ROLLBACK); + } + public function onPreTransactionRollback(Doctrine_Session $session) { + $this->debug[] = new Doctrine_DebugMessage($session,self::EVENT_PREROLLBACK); + } + + public function onTransactionBegin(Doctrine_Session $session) { + $this->debug[] = new Doctrine_DebugMessage($session,self::EVENT_BEGIN); + } + public function onPreTransactionBegin(Doctrine_Session $session) { + $this->debug[] = new Doctrine_DebugMessage($session,self::EVENT_PREBEGIN); + } + + public function onCollectionDelete(Doctrine_Collection $collection) { + $this->debug[] = new Doctrine_DebugMessage($collection,self::EVENT_COLLDELETE); + } + public function onPreCollectionDelete(Doctrine_Collection $collection) { + $this->debug[] = new Doctrine_DebugMessage($collection,self::EVENT_PRECOLLDELETE); + } +} +?> diff --git a/classes/Doctrine.class.php b/classes/Doctrine.class.php new file mode 100644 index 000000000..8c134a710 --- /dev/null +++ b/classes/Doctrine.class.php @@ -0,0 +1,268 @@ +read())) { + switch($entry): + case ".": + case "..": + break; + case "Cache": + case "Record": + case "Collection": + case "Table": + case "Validator": + case "Exception": + case "Session": + case "DQL": + $a[] = self::$path.DIRECTORY_SEPARATOR.$entry; + break; + default: + if(is_file(self::$path.DIRECTORY_SEPARATOR.$entry)) { + require_once($entry); + } + endswitch; + } + foreach($a as $dirname) { + $dir = dir($dirname); + $path = $dirname.DIRECTORY_SEPARATOR; + while (false !== ($entry = $dir->read())) { + if(is_file($path.$entry)) { + require_once($path.$entry); + } + } + } + } + /** + * simple autoload function + */ + public static function autoload($classname) { + if(! self::$path) + self::$path = dirname(__FILE__); + + $e = explode("_",$classname); + + if($e[0] != "Doctrine") + return false; + + if(end($e) != "Exception") { + if(count($e) > 2) { + array_shift($e); + $dir = array_shift($e); + $class = self::$path.DIRECTORY_SEPARATOR.$dir.DIRECTORY_SEPARATOR.implode('',$e).".class.php"; + } elseif(count($e) > 1) { + $class = self::$path.DIRECTORY_SEPARATOR.$e[1].".class.php"; + } else + return false; + } else { + $class = self::$path.DIRECTORY_SEPARATOR."Exception".DIRECTORY_SEPARATOR.$e[1].".class.php"; + } + + if( ! file_exists($class)) { + return false; + } + + require_once($class); + return true; + } +} +?> diff --git a/classes/EventListener.class.php b/classes/EventListener.class.php new file mode 100644 index 000000000..24bb1ecd1 --- /dev/null +++ b/classes/EventListener.class.php @@ -0,0 +1,124 @@ + diff --git a/classes/Exception.class.php b/classes/Exception.class.php new file mode 100644 index 000000000..7e8461591 --- /dev/null +++ b/classes/Exception.class.php @@ -0,0 +1,6 @@ + diff --git a/classes/Exception/Find.class.php b/classes/Exception/Find.class.php new file mode 100644 index 000000000..7226e4251 --- /dev/null +++ b/classes/Exception/Find.class.php @@ -0,0 +1,10 @@ + diff --git a/classes/Exception/Mapping.class.php b/classes/Exception/Mapping.class.php new file mode 100644 index 000000000..01b8ed51e --- /dev/null +++ b/classes/Exception/Mapping.class.php @@ -0,0 +1,10 @@ + diff --git a/classes/Exception/Naming.class.php b/classes/Exception/Naming.class.php new file mode 100644 index 000000000..bcb5dfa5d --- /dev/null +++ b/classes/Exception/Naming.class.php @@ -0,0 +1,11 @@ + diff --git a/classes/Exception/PrimaryKey.class.php b/classes/Exception/PrimaryKey.class.php new file mode 100644 index 000000000..8b69b34cf --- /dev/null +++ b/classes/Exception/PrimaryKey.class.php @@ -0,0 +1,10 @@ + diff --git a/classes/Exception/Refresh.class.php b/classes/Exception/Refresh.class.php new file mode 100644 index 000000000..20083b94f --- /dev/null +++ b/classes/Exception/Refresh.class.php @@ -0,0 +1,11 @@ + diff --git a/classes/Exception/Session.class.php b/classes/Exception/Session.class.php new file mode 100644 index 000000000..851c22730 --- /dev/null +++ b/classes/Exception/Session.class.php @@ -0,0 +1,12 @@ +openSession() to open a new session.",Doctrine::ERR_NO_SESSIONS); + } +} +?> diff --git a/classes/Exception/Table.class.php b/classes/Exception/Table.class.php new file mode 100644 index 000000000..824b26957 --- /dev/null +++ b/classes/Exception/Table.class.php @@ -0,0 +1,13 @@ + diff --git a/classes/Exception/Validator.class.php b/classes/Exception/Validator.class.php new file mode 100644 index 000000000..8a2e25015 --- /dev/null +++ b/classes/Exception/Validator.class.php @@ -0,0 +1,11 @@ +validator = $validator; + } + public function getErrorStack() { + return $this->validator->getErrorStack(); + } +} +?> diff --git a/classes/ForeignKey.class.php b/classes/ForeignKey.class.php new file mode 100644 index 000000000..45da235ae --- /dev/null +++ b/classes/ForeignKey.class.php @@ -0,0 +1,6 @@ + diff --git a/classes/FormBuilder.class.php b/classes/FormBuilder.class.php new file mode 100644 index 000000000..bcc6700e3 --- /dev/null +++ b/classes/FormBuilder.class.php @@ -0,0 +1,22 @@ +name.">".""; + } +} +class InputElement { + private $attributes = array(); + +} +?> diff --git a/classes/Lib.php b/classes/Lib.php new file mode 100644 index 000000000..060246bd5 --- /dev/null +++ b/classes/Lib.php @@ -0,0 +1,140 @@ +"; + $r[] = "Component : ".$record->getTable()->getComponentName(); + $r[] = "ID : ".$record->getID(); + $r[] = "References : ".count($record->getReferences()); + $r[] = "State : ".Doctrine_Lib::getRecordStateAsString($record->getState()); + $r[] = "OID : ".$record->getOID(); + $r[] = ""; + return implode("\n",$r)."
"; + } + /** + * getStateAsString + * returns a given session state as string + * @param integer $state session state + */ + public static function getSessionStateAsString($state) { + switch($state): + case Doctrine_Session::STATE_OPEN: + return "open"; + break; + case Doctrine_Session::STATE_CLOSED: + return "closed"; + break; + case Doctrine_Session::STATE_BUSY: + return "busy"; + break; + case Doctrine_Session::STATE_ACTIVE: + return "active"; + break; + endswitch; + } + /** + * returns a string representation of Doctrine_Session object + * @param Doctrine_Session $session + * @return string + */ + public function getSessionAsString(Doctrine_Session $session) { + $r[] = "
";
+        $r[] = "Doctrine_Session object";
+        $r[] = "State               : ".Doctrine_Session::getStateAsString($session->getState());
+        $r[] = "Open Transactions   : ".$session->getTransactionLevel();
+        $r[] = "Open Factories      : ".$session->count();
+        $sum = 0;
+        $rsum = 0;
+        $csum = 0;
+        foreach($session->getTables() as $objTable) {
+            if($objTable->getCache() instanceof Doctrine_Cache_File) {
+                $sum += array_sum($objTable->getCache()->getStats());
+                $rsum += $objTable->getRepository()->count();
+                $csum += $objTable->getCache()->count();
+            }
+        }
+        $r[] = "Cache Hits          : ".$sum." hits ";
+        $r[] = "Cache               : ".$csum." objects ";
+
+        $r[] = "Repositories        : ".$rsum." objects ";
+        $queries = false;
+        if($session->getDBH() instanceof Doctrine_DB) {
+            $handler = "Doctrine Database Handler";
+            $queries = count($this->dbh->getQueries());
+            $sum     = array_sum($this->dbh->getExecTimes());
+        } elseif($this->dbh instanceof PDO) {
+            $handler = "PHP Native PDO Driver";
+        } else
+            $handler = "Unknown Database Handler";
+
+        $r[] = "DB Handler          : ".$handler;
+        if($queries) {
+            $r[] = "Executed Queries    : ".$queries;
+            $r[] = "Sum of Exec Times   : ".$sum;
+        }
+
+        $r[] = "
"; + return implode("\n",$r)."
"; + } + /** + * returns a string representation of Doctrine_Table object + * @param Doctrine_Table $table + * @return string + */ + public function getTableAsString(Doctrine_Table $table) { + $r[] = "
";
+        $r[] = "Component   : ".$this->getComponentName();
+        $r[] = "Table       : ".$this->getTableName();
+        $r[] = "Repository  : ".$this->getRepository()->count()." objects";
+        if($this->cache instanceof Doctrine_Cache_File) {
+            $r[] = "Cache       : ".$this->getCache()->count()." objects";
+            $r[] = "Cache hits  : ".array_sum($this->getCache()->getStats())." hits";
+        }
+        $r[] = "
"; + return implode("\n",$r)."
"; + } + /** + * returns a string representation of Doctrine_Collection object + * @param Doctrine_Collection $collection + * @return string + */ + public function getCollectionAsString(Doctrine_Collection $collection) { + $r[] = "
";
+        $r[] = get_class($collection);
+
+        foreach($collection as $key => $record) {
+            $r[] = "Key : ".$key." ID : ".$record->getID();
+        }
+        $r[] = "
"; + return implode("\n",$r); + } +} +?> diff --git a/classes/LocalKey.class.php b/classes/LocalKey.class.php new file mode 100644 index 000000000..e9ef704f5 --- /dev/null +++ b/classes/LocalKey.class.php @@ -0,0 +1,6 @@ + diff --git a/classes/Manager.class.php b/classes/Manager.class.php new file mode 100644 index 000000000..061ea2a8a --- /dev/null +++ b/classes/Manager.class.php @@ -0,0 +1,207 @@ +root = dirname(__FILE__); + } + /** + * setDefaultAttributes + */ + final public function setDefaultAttributes() { + static $init = false; + if( ! $init) { + $init = true; + $attributes = array( + Doctrine::ATTR_CACHE_DIR => "%ROOT%".DIRECTORY_SEPARATOR."cachedir", + Doctrine::ATTR_FETCHMODE => Doctrine::FETCH_LAZY, + Doctrine::ATTR_CACHE_TTL => 100, + Doctrine::ATTR_CACHE_SIZE => 100, + Doctrine::ATTR_CACHE => Doctrine::CACHE_FILE, + Doctrine::ATTR_BATCH_SIZE => 5, + Doctrine::ATTR_LISTENER => new EmptyEventListener(), + Doctrine::ATTR_PK_COLUMNS => array("id"), + Doctrine::ATTR_PK_TYPE => Doctrine::INCREMENT_KEY, + Doctrine::ATTR_LOCKMODE => 1, + Doctrine::ATTR_VLD => false, + Doctrine::ATTR_CREATE_TABLES => true + ); + foreach($attributes as $attribute => $value) { + $old = $this->getAttribute($attribute); + if($old === null) + $this->setAttribute($attribute,$value); + } + } + } + /** + * @return string the root directory of Doctrine + */ + final public function getRoot() { + return $this->root; + } + /** + * getInstance this class uses the singleton pattern + */ + final public static function getInstance() { + static $instance; + if( ! isset($instance)) + $instance = new Doctrine_Manager(); + + return $instance; + } + + /** + * openSession open a new session and save it to Doctrine_Manager->sessions + * @param PDO $pdo PDO database driver + * @param string $name name of the session, if empty numeric key is used + * @return Doctrine_Session the opened session object + */ + final public function openSession(PDO $pdo, $name = null) { + // initialize the default attributes + $this->setDefaultAttributes(); + + if($name !== null) { + $name = (string) $name; + if(isset($this->sessions[$name])) + throw new InvalidKeyException(); + + } else { + $name = $this->index; + $this->index++; + } + switch($pdo->getAttribute(PDO::ATTR_DRIVER_NAME)): + case "mysql": + $this->sessions[$name] = new Doctrine_Session_Mysql($this,$pdo); + break; + case "sqlite": + $this->sessions[$name] = new Doctrine_Session_Sqlite($this,$pdo); + break; + case "pgsql": + $this->sessions[$name] = new Doctrine_Session_Pgsql($this,$pdo); + break; + case "oci": + $this->sessions[$name] = new Doctrine_Session_Oracle($this,$pdo); + break; + case "mssql": + $this->sessions[$name] = new Doctrine_Session_Mssql($this,$pdo); + break; + case "firebird": + $this->sessions[$name] = new Doctrine_Session_Firebird($this,$pdo); + break; + case "informix": + $this->sessions[$name] = new Doctrine_Session_Informix($this,$pdo); + break; + endswitch; + + + $this->currIndex = $name; + return $this->sessions[$name]; + } + /** + * getSession + * @param integer $index + * @return object Doctrine_Session + * @throws InvalidKeyException + */ + final public function getSession($index) { + if( ! isset($this->sessions[$index])) + throw new InvalidKeyException(); + + $this->currIndex = $index; + return $this->sessions[$index]; + } + /** + * closes the session + * + * @param Doctrine_Session $session + * @return void + */ + final public function closeSession(Doctrine_Session $session) { + $session->close(); + unset($session); + } + /** + * getSessions + * @return array + */ + final public function getSessions() { + return $this->sessions; + } + /** + * setCurrentSession + * @param mixed $key the session key + * @throws InvalidKeyException + * @return void + */ + final public function setCurrentSession($key) { + $key = (string) $key; + if( ! isset($this->sessions[$key])) + throw new InvalidKeyException(); + + $this->currIndex = $key; + } + /** + * count + * @return integer the number of open sessions + */ + public function count() { + return count($this->sessions); + } + /** + * getIterator + * @return ArrayIterator + */ + public function getIterator() { + return new ArrayIterator($this->sessions); + } + /** + * getCurrentSession + * @return object Doctrine_Session + */ + final public function getCurrentSession() { + $i = $this->currIndex; + if( ! isset($this->sessions[$i])) + throw new Doctrine_Session_Exception(); + + return $this->sessions[$i]; + } + /** + * __toString + */ + public function __toString() { + $r[] = "
";
+        $r[] = "Doctrine_Manager";
+        $r[] = "Sessions : ".count($this->sessions);
+        $r[] = "
"; + return implode("\n",$r); + } +} +?> diff --git a/classes/Record.class.php b/classes/Record.class.php new file mode 100644 index 000000000..bd50378d1 --- /dev/null +++ b/classes/Record.class.php @@ -0,0 +1,999 @@ +table = $table; + $exists = ( ! $this->table->isNewEntry()); + } else { + $this->table = Doctrine_Manager::getInstance()->getCurrentSession()->getTable(get_class($this)); + $exists = false; + } + + // Check if the current session has the records table in its registry + // If not this is record is only used for creating table definition and setting up + // relations. + + if($this->table->getSession()->hasTable($this->table->getComponentName())) { + + $this->oid = self::$index; + + self::$index++; + + + if( ! $exists) { + // listen the onPreCreate event + $this->table->getAttribute(Doctrine::ATTR_LISTENER)->onPreCreate($this); + } else { + // listen the onPreLoad event + $this->table->getAttribute(Doctrine::ATTR_LISTENER)->onPreLoad($this); + } + // get the data array + $this->data = $this->table->getData(); + + // clean data array + $cols = $this->cleanData(); + + if( ! $exists) { + + + if($cols > 0) + $this->state = Doctrine_Record::STATE_TDIRTY; + else + $this->state = Doctrine_Record::STATE_TCLEAN; + + + + // listen the onCreate event + $this->table->getAttribute(Doctrine::ATTR_LISTENER)->onCreate($this); + } else { + $this->state = Doctrine_Record::STATE_CLEAN; + + if($cols <= 1) + $this->state = Doctrine_Record::STATE_PROXY; + else + $this->loaded = true; + + // id property is protected + $keys = $this->table->getPrimaryKeys(); + if(count($keys) == 1) { + $this->id = $this->data[$keys[0]]; + } else { + /** + $this->id = array(); + foreach($keys as $key) { + $this->id[] = $this->data[$key]; + } + */ + } + + // listen the onLoad event + $this->table->getAttribute(Doctrine::ATTR_LISTENER)->onLoad($this); + } + // add data access object to registry + $this->table->getRepository()->add($this); + + unset($this->data['id']); + + $this->table->setData(array()); + + $this->table->getCache()->store($this); + } + } + /** + * setUp + * implemented by child classes + */ + public function setUp() { } + /** + * return the object identifier + * @return integer + */ + public function getOID() { + return $this->oid; + } + /** + * isLoaded + */ + public function isLoaded() { + return $this->loaded; + } + /** + * cleanData + * modifies data array + * example: + * + * $data = array("name"=>"John","lastname"=> null,"id"=>1,"unknown"=>"unknown"); + * $names = array("name","lastname","id"); + * $data after operation: + * $data = array("name"=>"John","lastname" => array(),"id"=>1); + */ + private function cleanData() { + $cols = 0; + $tmp = $this->data; + + $this->data = array(); + + foreach($this->table->getColumnNames() as $name) { + if( ! isset($tmp[$name])) { + $this->data[$name] = array(); + + } else { + $cols++; + $this->data[$name] = $tmp[$name]; + } + } + + return $cols; + } + /** + * this method is automatically called when this Doctrine_Record is serialized + */ + public function __sleep() { + $this->table->getAttribute(Doctrine::ATTR_LISTENER)->onSleep($this); + + $this->table = $this->table->getComponentName(); + // unset all vars that won't need to be serialized + + unset($this->modified); + unset($this->associations); + unset($this->state); + unset($this->collections); + unset($this->references); + unset($this->originals); + unset($this->oid); + unset($this->loaded); + + foreach($this->data as $k=>$v) { + if($v instanceof Doctrine_Record) + $this->data[$k] = array(); + } + return array_keys(get_object_vars($this)); + } + /** + * __wakeup + * this method is automatically called everytime a Doctrine_Record object is unserialized + */ + public function __wakeup() { + $this->modified = array(); + $this->state = Doctrine_Record::STATE_CLEAN; + + $name = $this->table; + + $manager = Doctrine_Manager::getInstance(); + $sess = $manager->getCurrentSession(); + + $this->oid = self::$index; + self::$index++; + + $this->table = $sess->getTable($name); + + $this->table->getRepository()->add($this); + + $this->loaded = true; + + $this->cleanData(); + + + unset($this->data['id']); + + $this->table->getAttribute(Doctrine::ATTR_LISTENER)->onWakeUp($this); + + } + /** + * addCollection + * @param Doctrine_Collection $collection + * @param mixed $key + */ + final public function addCollection(Doctrine_Collection $collection,$key = null) { + if($key !== null) { + if(isset($this->collections[$key])) + throw InvalidKeyException(); + + $this->collections[$key] = $collection; + } else { + $this->collections[] = $collection; + } + } + /** + * getCollection + * @param integer $key + * @return Doctrine_Collection + */ + final public function getCollection($key) { + return $this->collections[$key]; + } + /** + * hasCollections + * @return boolean whether or not this dao is part of a collection + */ + final public function hasCollections() { + return (! empty($this->collections)); + } + /** + * getState + * @see Doctrine_Record::STATE_* constants + * @return integer the current state + */ + final public function getState() { + return $this->state; + } + /** + * refresh refresh internal data from the database + * @return boolean + */ + final public function refresh() { + if($this->getID() == null) return false; + + $query = $this->table->getQuery()." WHERE ".implode(" = ? && ",$this->table->getPrimaryKeys())." = ?"; + $this->data = $this->table->getSession()->execute($query,array($this->getID()))->fetch(PDO::FETCH_ASSOC); + unset($this->data["id"]); + $this->modified = array(); + $this->cleanData(); + $this->state = Doctrine_Record::STATE_CLEAN; + + $this->getTable()->getCache()->store($this); + return true; + } + /** + * factoryRefresh + * @throws Doctrine_Exception + * @return void + */ + final public function factoryRefresh() { + $data = $this->table->getData(); + + if($this->id != $data["id"]) + throw new Doctrine_Refresh_Exception(); + + $this->data = $data; + $this->cleanData(); + unset($this->data["id"]); + $this->state = Doctrine_Record::STATE_CLEAN; + $this->modified = array(); + + $this->getTable()->getCache()->store($this); + } + /** + * return the factory that created this data access object + * @return object Doctrine_Table a Doctrine_Table object + */ + final public function getTable() { + return $this->table; + } + /** + * return all the internal data + * @return array an array containing all the properties + */ + final public function getData() { + return $this->data; + } + /** + * get + * returns a value of a property or related component + * + * @param $name name of the property or related component + * @throws InvalidKeyException + * @return mixed + */ + public function get($name) { + if(isset($this->data[$name])) { + + // check if the property is not loaded (= it is an empty array) + if(is_array($this->data[$name]) && ! $this->loaded) { + + // no use trying to load the data from database if the Doctrine_Record is new or clean + if($this->state != Doctrine_Record::STATE_TDIRTY && + $this->state != Doctrine_Record::STATE_TCLEAN && + $this->state != Doctrine_Record::STATE_CLEAN) { + + $this->loaded = true; + + if( ! empty($this->collections)) { + foreach($this->collections as $collection) { + $collection->load($this); + } + } else { + $this->refresh(); + } + $this->state = Doctrine_Record::STATE_CLEAN; + } + + if(is_array($this->data[$name])) + return null; + + return $this->data[$name]; + } + return $this->data[$name]; + } + if($name == "id") + return $this->id; + + if( ! isset($this->references[$name])) + $this->loadReference($name); + + + return $this->references[$name]; + } + /** + * rawSet + * doctrine uses this function internally, not recommended for developers + * + * @param mixed $name name of the property or reference + * @param mixed $value value of the property or reference + */ + final public function rawSet($name,$value) { + if($value instanceof Doctrine_Record) + $id = $value->getID(); + + if( ! empty($id)) + $value = $id; + + $this->data[$name] = $value; + } + /** + * set + * method for altering properties and Doctrine_Record references + * + * @param mixed $name name of the property or reference + * @param mixed $value value of the property or reference + * @throws InvalidKeyException + * @throws InvalidTypeException + * @return void + */ + public function set($name,$value) { + if(isset($this->data[$name])) { + $old = $this->get($name); + + if($value instanceof Doctrine_Record) { + $id = $value->getID(); + + if( ! empty($id)) + $value = $value->getID(); + } + + if($old !== $value) { + $this->data[$name] = $value; + + $this->modified[] = $name; + + switch($this->state): + case Doctrine_Record::STATE_CLEAN: + case Doctrine_Record::STATE_PROXY: + $this->state = Doctrine_Record::STATE_DIRTY; + break; + case Doctrine_Record::STATE_TCLEAN: + $this->state = Doctrine_Record::STATE_TDIRTY; + break; + endswitch; + } + } else { + // if not found, throws InvalidKeyException + + $fk = $this->table->getForeignKey($name); + + if($value->getTable()->getComponentName() != $name) + throw new InvalidKeyException(); + + // one-to-many or one-to-one relation + if($fk instanceof Doctrine_ForeignKey || + $fk instanceof Doctrine_LocalKey) { + switch($fk->getType()): + case Doctrine_Table::MANY_COMPOSITE: + case Doctrine_Table::MANY_AGGREGATE: + // one-to-many relation found + if( ! ($value instanceof Doctrine_Collection)) + throw new InvalidTypeException("Couldn't call Doctrine::set(), second argument should be an instance of Doctrine_Collection when setting one-to-many references."); + + $value->setReference($this,$fk); + break; + case Doctrine_Table::ONE_COMPOSITE: + case Doctrine_Table::ONE_AGGREGATE: + // one-to-one relation found + if( ! ($value instanceof Doctrine_Record)) + throw new InvalidTypeException("Couldn't call Doctrine::set(), second argument should be an instance of Doctrine_Record when setting one-to-one references."); + + if($fk->getLocal() == "id") { + $this->references[$name]->set($fk->getForeign(),$this); + } else { + $this->set($fk->getLocal(),$value); + } + break; + endswitch; + + } elseif($fk instanceof Doctrine_Association) { + // many-to-many relation found + if( ! ($value instanceof Doctrine_Collection)) + throw new InvalidTypeException("Couldn't call Doctrine::set(), second argument should be an instance of Doctrine_Collection when setting one-to-many references."); + } + + $this->references[$name] = $value; + } + } + /** + * applies the changes made to this object into database + * this method is smart enough to know if any changes are made + * and whether to use INSERT or UPDATE statement + * + * this method also saves the related composites + * + * @return void + */ + final public function save() { + $this->table->getSession()->beginTransaction(); + + // listen the onPreSave event + $this->table->getAttribute(Doctrine::ATTR_LISTENER)->onPreSave($this); + + + + $saveLater = $this->table->getSession()->saveRelated($this); + + $this->table->getSession()->save($this); + + foreach($saveLater as $fk) { + $table = $fk->getTable(); + $foreign = $fk->getForeign(); + $local = $fk->getLocal(); + + $name = $table->getComponentName(); + if(isset($this->references[$name])) { + $obj = $this->references[$name]; + $obj->save(); + } + } + + // save the MANY-TO-MANY associations + + $this->saveAssociations(); + + $this->table->getSession()->commit(); + } + /** + * returns an array of modified fields and associated values + * @return array + */ + final public function getModified() { + $a = array(); + + foreach($this->modified as $k=>$v) { + $a[$v] = $this->data[$v]; + } + return $a; + } + /** + * this class implements countable interface + * @return integer the number of columns + */ + public function count() { + return count($this->data); + } + /** + * getIterator + * @return ArrayIterator an ArrayIterator that iterates through the data + */ + public function getIterator() { + return new ArrayIterator($this->data); + } + /** + * saveAssociations + * save the associations of many-to-many relations + * this method also deletes associations that do not exist anymore + * @return void + */ + final public function saveAssociations() { + foreach($this->table->getForeignKeys() as $fk): + $table = $fk->getTable(); + $name = $table->getComponentName(); + + + if($fk instanceof Doctrine_Association) { + switch($fk->getType()): + case Doctrine_Table::MANY_COMPOSITE: + + break; + case Doctrine_Table::MANY_AGGREGATE: + $asf = $fk->getAssociationFactory(); + if(isset($this->references[$name])) { + + $new = $this->references[$name]; + + if( ! isset($this->originals[$name])) { + $this->loadReference($name); + } + + $r = $this->getRelationOperations($name,$new); + + foreach($r["delete"] as $record) { + $query = "DELETE FROM ".$asf->getTableName()." WHERE ".$fk->getForeign()." = ?" + ." && ".$fk->getLocal()." = ?"; + $this->table->getSession()->execute($query, array($record->getID(),$this->getID())); + } + foreach($r["add"] as $record) { + $reldao = $asf->create(); + $reldao->set($fk->getForeign(),$record); + $reldao->set($fk->getLocal(),$this); + $reldao->save(); + } + $this->originals[$name] = clone $this->references[$name]; + } + break; + endswitch; + } elseif($fk instanceof Doctrine_ForeignKey || + $fk instanceof Doctrine_LocalKey) { + + switch($fk->getType()): + case Doctrine_Table::ONE_COMPOSITE: + if(isset($this->originals[$name]) && $this->originals[$name]->getID() != $this->references[$name]->getID()) + $this->originals[$name]->delete(); + + break; + case Doctrine_Table::MANY_COMPOSITE: + if(isset($this->references[$name])) { + $new = $this->references[$name]; + + if( ! isset($this->originals[$name])) + $this->loadReference($name); + + $r = $this->getRelationOperations($name,$new); + + foreach($r["delete"] as $record) { + $record->delete(); + } + + $this->originals[$name] = clone $this->references[$name]; + } + break; + endswitch; + } + endforeach; + } + /** + * get the records that need to be added + * and/or deleted in order to change the old collection + * to the new one + * + * The algorithm here is very simple and definitely not + * the fastest one, since we have to iterate through the collections twice. + * the complexity of this algorithm is O(2*n^2) + * + * First we iterate through the new collection and get the + * records that do not exist in the old collection (Doctrine_Records that need to be added). + * + * Then we iterate through the old collection and get the records + * that do not exists in the new collection (Doctrine_Records that need to be deleted). + */ + final public function getRelationOperations($name, Doctrine_Collection $new) { + $r["add"] = array(); + $r["delete"] = array(); + + + + foreach($new as $k=>$record) { + + $found = false; + + if($record->getID() !== null) { + foreach($this->originals[$name] as $k2 => $record2) { + if($record2->getID() == $record->getID()) { + $found = true; + break; + } + } + } + if( ! $found) { + $this->originals[$name][] = $record; + $r["add"][] = $record; + } + } + + foreach($this->originals[$name] as $k => $record) { + if($record->getID() === null) + continue; + + $found = false; + foreach($new as $k2=>$record2) { + if($record2->getID() == $record->getID()) { + $found = true; + break; + } + } + + if( ! $found) { + $r["delete"][] = $record; + unset($this->originals[$name][$k]); + } + } + + return $r; + } + /** + * getOriginals + */ + final public function getOriginals($name) { + if( ! isset($this->originals[$name])) + throw new InvalidKeyException(); + + return $this->originals[$name]; + } + /** + * deletes this data access object and all the related composites + * this operation is isolated by a transaction + * + * this event can be listened by the onPreDelete and onDelete listeners + * + * @return boolean true on success, false on failure + */ + final public function delete() { + $this->table->getSession()->delete($this); + } + /** + * returns a copy of this object + * @return DAO + */ + final public function copy() { + return $this->table->create($this->data); + } + /** + * @param integer $id + * @return void + */ + final public function setID($id = null) { + if($id === null) { + $this->id = null; + $this->cleanData(); + $this->state = Doctrine_Record::STATE_TCLEAN; + $this->modified = array(); + } else { + $this->id = $id; + $this->state = Doctrine_Record::STATE_CLEAN; + $this->modified = array(); + } + } + /** + * return the primary key this object is pointing at + * @return int id + */ + final public function getID() { + return $this->id; + } + /** + * hasRefence + * @param string $name + */ + public function hasReference($name) { + return isset($this->references[$name]); + } + /** + * @param Doctrine_Collection $coll + * @param string $connectorField + */ + public function initReference(Doctrine_Collection $coll, Doctrine_Relation $connector) { + $name = $coll->getTable()->getComponentName(); + $coll->setReference($this, $connector); + $this->references[$name] = $coll; + $this->originals[$name] = clone $coll; + } + /** + * addReference + */ + public function addReference(Doctrine_Record $record) { + $name = $record->getTable()->getComponentName(); + $this->references[$name]->add($record); + $this->originals[$name]->add($record); + } + /** + * getReferences + * @return array all references + */ + public function getReferences() { + return $this->references; + } + + /** + * @throws InvalidKeyException + * @param name + * @return void + */ + final public function loadReference($name) { + $fk = $this->table->getForeignKey($name); + $table = $fk->getTable(); + $name = $table->getComponentName(); + + + switch($this->getState()): + case Doctrine_Record::STATE_TDIRTY: + case Doctrine_Record::STATE_TCLEAN: + + if($fk->getType() == Doctrine_Table::ONE_COMPOSITE || $fk->getType() == Doctrine_Table::ONE_AGGREGATE) { + // ONE-TO-ONE + $this->references[$name] = $table->create(); + + if($fk instanceof Doctrine_ForeignKey) { + $this->references[$name]->set($fk->getForeign(),$this); + } else { + $this->set($fk->getLocal(),$this->references[$name]); + } + } else { + + $this->references[$name] = new Doctrine_Collection($table); + if($fk instanceof Doctrine_ForeignKey) { + // ONE-TO-MANY + $this->references[$name]->setReference($this,$fk); + } + $this->originals[$name] = new Doctrine_Collection($table); + } + break; + case Doctrine_Record::STATE_DIRTY: + case Doctrine_Record::STATE_CLEAN: + case Doctrine_Record::STATE_PROXY: + + $local = $fk->getLocal(); + + $coll = false; + + if($fk instanceof Doctrine_ForeignKey || + $fk instanceof Doctrine_LocalKey) { + + $graph = $table->getDQLParser(); + + // get the local identifier + $id = $this->get($local); + + if(empty($id)) { + + $this->references[$name] = $table->create(); + + if($this->table->hasPrimaryKey($fk->getLocal())) { + $this->references[$name]->set($fk->getForeign(),$this); + } else { + $this->set($fk->getLocal(),$this->references[$name]); + } + + } else { + + if($this->table->hasPrimaryKey($fk->getForeign())) { + try { + $coll = new Doctrine_Collection($table); + + $coll[0] = $table->getCache()->fetch($id); + + } catch(InvalidKeyException $e) { + $coll = false; + } + } + + if( ! $coll) { + $query = "FROM ".$name." WHERE ".$name.".".$fk->getForeign()." = ?"; + $coll = $graph->query($query,array($id)); + } + + if($fk->getType() == Doctrine_Table::ONE_COMPOSITE || + $fk->getType() == Doctrine_Table::ONE_AGGREGATE) { + + if($coll->contains(0)) { + $this->references[$name] = $coll[0]; + $this->originals[$name] = clone $coll[0]; + + } else { + $this->references[$name] = $table->create(); + if($this->table->hasPrimaryKey($fk->getLocal())) { + + $this->references[$name]->set($fk->getForeign(),$this); + } else { + $this->set($fk->getLocal(),$this->references[$name]); + } + } + } else { + $this->references[$name] = $coll; + $this->references[$name]->setReference($this,$fk); + + + $this->originals[$name] = clone $coll; + + } + } + } elseif($fk instanceof Doctrine_Association) { + + $foreign = $fk->getForeign(); + + $asf = $fk->getAssociationFactory(); + $query = "SELECT ".$foreign." FROM ".$asf->getTableName()." WHERE ".$local." = ?"; + + $table = $fk->getTable(); + $graph = new Doctrine_DQL_Parser($table->getSession()); + + $q = "FROM ".$table->getComponentName()." WHERE ".$table->getComponentName().".id IN ($query)"; + + + $coll = $graph->query($q, array($this->getID())); + + $this->references[$name] = $coll; + $this->originals[$name] = clone $coll; + + } + break; + endswitch; + } + + /** + * @param string $objTableName + * @param string $fkField + * @return void + */ + final public function ownsOne($componentName,$foreignKey, $localKey = "id") { + $this->table->bind($componentName,$foreignKey,Doctrine_Table::ONE_COMPOSITE, $localKey); + } + /** + * @param string $objTableName + * @param string $fkField + * @return void + */ + final public function ownsMany($componentName,$foreignKey, $localKey = "id") { + $this->table->bind($componentName,$foreignKey,Doctrine_Table::MANY_COMPOSITE, $localKey); + } + /** + * @param string $objTableName + * @param string $fkField + * @return void + */ + final public function hasOne($componentName,$foreignKey, $localKey = "id") { + $this->table->bind($componentName,$foreignKey,Doctrine_Table::ONE_AGGREGATE, $localKey); + } + /** + * @param string $objTableName + * @param string $fkField + * @return void + */ + final public function hasMany($componentName,$foreignKey, $localKey = "id") { + $this->table->bind($componentName,$foreignKey,Doctrine_Table::MANY_AGGREGATE, $localKey); + } + /** + * setInheritanceMap + * @param array $inheritanceMap + * @return void + */ + final public function setInheritanceMap(array $inheritanceMap) { + $this->table->setInheritanceMap($inheritanceMap); + } + /** + * setPrimaryKey + * @param string $key + */ + final public function setPrimaryKey($key) { + $this->table->setPrimaryKey($key); + } + /** + * setTableName + * @param string $name table name + * @return void + */ + final public function setTableName($name) { + $this->table->setTableName($name); + } + /** + * setAttribute + * @param integer $attribute + * @param mixed $value + * @see Doctrine::ATTR_* constants + * @return void + */ + final public function setAttribute($attribute, $value) { + $this->table->setAttribute($attribute,$value); + } + /** + * hasColumn + * @param string $name + * @param string $type + * @param integer $length + * @param mixed $options + * @return void + */ + final public function hasColumn($name, $type, $length = 20, $options = "") { + $this->table->hasColumn($name, $type, $length, $options); + } + /** + * returns a string representation of this object + */ + public function __toString() { + return Doctrine_Lib::getRecordAsString($this); + } +} +?> diff --git a/classes/Relation.class.php b/classes/Relation.class.php new file mode 100644 index 000000000..e43c87910 --- /dev/null +++ b/classes/Relation.class.php @@ -0,0 +1,83 @@ +table = $table; + $this->local = $local; + $this->foreign = $foreign; + $this->type = $type; + } + /** + * @see Doctrine_Table::BIND_ONE, Doctrine_Table::BIND_MANY + * @return integer bind type 1 or 0 + */ + public function getType() { + return $this->type; + } + /** + * @return object Doctrine_Table foreign factory object + */ + public function getTable() { + return $this->table; + } + /** + * @return string the name of the local column + */ + public function getLocal() { + return $this->local; + } + /** + * @return string the name of the foreign column where + * the local column is pointing at + */ + public function getForeign() { + return $this->foreign; + } + /** + * __toString + */ + public function __toString() { + $r[] = "
";
+        $r[] = "Class       : ".get_class($this);
+        $r[] = "Component   : ".$this->table->getComponentName();
+        $r[] = "Table       : ".$this->table->getTableName();
+        $r[] = "Local key   : ".$this->local;
+        $r[] = "Foreign key : ".$this->foreign;
+        $r[] = "Type        : ".$this->type;
+        $r[] = "
"; + return implode("\n", $r); + } +} + +?> diff --git a/classes/Repository.class.php b/classes/Repository.class.php new file mode 100644 index 000000000..d3952d659 --- /dev/null +++ b/classes/Repository.class.php @@ -0,0 +1,114 @@ +table = $table; + } + /** + * @return object Doctrine_Table + */ + public function getTable() { + return $this->table; + } + /** + * add + * @param Doctrine_Record $record record to be added into registry + */ + public function add(Doctrine_Record $record) { + $oid = $record->getOID(); + + if(isset($this->registry[$oid])) + return false; + + $this->registry[$oid] = $record; + + return true; + } + /** + * get + * @param integer $oid + * @throws InvalidKeyException + */ + public function get($oid) { + if( ! isset($this->registry[$oid])) + throw new InvalidKeyException(); + + return $this->registry[$oid]; + } + /** + * count + * Doctrine_Registry implements interface Countable + * @return integer the number of records this registry has + */ + public function count() { + return count($this->registry); + } + /** + * @param integer $oid object identifier + * @return boolean whether ot not the operation was successful + */ + public function evict($oid) { + if( ! isset($this->registry[$oid])) + return false; + + unset($this->registry[$oid]); + return true; + } + /** + * @return integer number of records evicted + */ + public function evictAll() { + $evicted = 0; + foreach($this->registry as $oid=>$record) { + if($this->evict($oid)) + $evicted++; + } + return $evicted; + } + /** + * getIterator + * @return ArrayIterator + */ + public function getIterator() { + return new ArrayIterator($this->registry); + } + /** + * contains + * @param integer $oid object identifier + */ + public function contains($oid) { + return isset($this->registry[$oid]); + } + /** + * loadAll + * @return void + */ + public function loadAll() { + $this->table->findAll(); + } +} +?> diff --git a/classes/Session.class.php b/classes/Session.class.php new file mode 100644 index 000000000..f5b2a0c9f --- /dev/null +++ b/classes/Session.class.php @@ -0,0 +1,736 @@ +dbh = $pdo; + + $this->setParent($manager); + + $this->state = Doctrine_Session::STATE_OPEN; + + $this->dbh->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER); + $this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + $this->getAttribute(Doctrine::ATTR_LISTENER)->onOpen($this); + } + /** + * @return integer the session state + */ + public function getState() { + return $this->state; + } + /** + * @return Doctrine_Manager + */ + public function getManager() { + return $this->getParent(); + } + /** + * @return object PDO the database handle + */ + public function getDBH() { + return $this->dbh; + } + /** + * query + * queries the database with Doctrine Query Language + */ + final public function query($query,array $params = array()) { + $parser = new Doctrine_DQL_Parser($this); + + return $parser->query($query, $params); + } + /** + * queries the database with limit and offset + * added to the query and returns a PDOStatement object + * + * @param string $query + * @param integer $limit + * @param integer $offset + * @return PDOStatement + */ + public function select($query,$limit = 0,$offset = 0) { + if($limit > 0 || $offset > 0) + $query = $this->modifyLimitQuery($query,$limit,$offset); + + return $this->dbh->query($query); + } + /** + * @return object PDOStatement -- the PDOStatement object + */ + public function execute($query, array $params = array()) { + if( ! empty($params)) { + $stmt = $this->dbh->prepare($query); + $stmt->execute($params); + return $stmt; + } else { + return $this->dbh->query($query); + } + } + /** + * @param $mixed -- Doctrine_Table name + * @return boolean + */ + public function hasTable($name) { + return isset($this->tables[$name]); + } + /** + * @param string $name component name + * @throws Doctrine_Table_Exception + * @return object Doctrine_Table + */ + public function getTable($name) { + $name = ucwords(strtolower($name)); + + if(isset($this->tables[$name])) + return $this->tables[$name]; + + $class = $name."Table"; + + if(class_exists($class) && in_array("Doctrine_Table", class_parents($class))) + return new $class($name); + else + return new Doctrine_Table($name); + } + /** + * @return array -- an array of all initialized factories + */ + public function getTables() { + return $this->tables; + } + /** + * @return ArrayIterator + */ + public function getIterator() { + return new ArrayIterator($this->tables); + } + /** + * @return integer + */ + public function count() { + return count($this->tables); + } + /** + * @param $objTable -- a Doctrine_Table object to be added into factory registry + * @return boolean + */ + public function addTable(Doctrine_Table $objTable) { + $name = $objTable->getComponentName(); + + if(isset($this->tables[$name])) + return false; + + $this->tables[$name] = $objTable; + return true; + } + /** + * create creates a record + * @param string $name component name + * @return Doctrine_Record Doctrine_Record object + */ + public function create($name) { + return $this->getTable($name)->create(); + } + /** + * buildFlushTree + * @return array + */ + public function buildFlushTree() { + $tree = array(); + foreach($this->tables as $k => $table) { + $tmp = array(); + $tmp[] = $table->getComponentName(); + $pos = 0; + foreach($table->getForeignKeys() as $fk) { + if($fk instanceof Doctrine_ForeignKey || + $fk instanceof Doctrine_LocalKey) { + + $name = $fk->getTable()->getComponentName(); + $index = array_search($name,$tree); + + if(isset($locked[$name])) { + $pos = $index; + continue; + } + + if($index !== false) + unset($tree[$index]); + + switch($fk->getType()): + case Doctrine_Table::ONE_COMPOSITE: + case Doctrine_Table::ONE_AGGREGATE: + array_unshift($tmp,$name); + break; + case Doctrine_Table::MANY_COMPOSITE: + case Doctrine_Table::MANY_AGGREGATE: + $tmp[] = $name; + break; + endswitch; + $locked[$name] = true; + } + } + $index = array_search($k,$tree); + + if($index === false) { + if($pos != 0) { + $first = array_splice($tree,0,$pos); + $tree = array_merge($first, $tmp, $tree); + } else { + $tree = array_merge($tree,$tmp); + } + } else { + $first = array_splice($tree,0,$index); + array_splice($tree, 0, ($index + 1)); + $tree = array_merge($first, $tmp, $tree); + } + } + return $tree; + } + + /** + * flush saves all the records from all tables + * this operation is isolated using a transaction + * @return void + */ + public function flush() { + $this->beginTransaction(); + $this->saveAll(); + $this->commit(); + } + /** + * saveAll save all the records from all tables + */ + private function saveAll() { + $tree = $this->buildFlushTree(); + + foreach($tree as $name) { + $table = $this->tables[$name]; + + foreach($table->getRepository() as $record) { + $this->save($record); + } + } + foreach($tree as $name) { + $table = $this->tables[$name]; + foreach($table->getRepository() as $record) { + $record->saveAssociations(); + } + } + } + /** + * clear -- clears the whole registry + * @return void + */ + public function clear() { + foreach($this->tables as $k => $objTable) { + $objTable->getRepository()->evictAll(); + } + $this->tables = array(); + } + /** + * close + * closes the session + * @return void + */ + public function close() { + $this->getAttribute(Doctrine::ATTR_LISTENER)->onPreClose($this); + + $this->clear(); + $this->state = Doctrine_Session::STATE_CLOSED; + + $this->getAttribute(Doctrine::ATTR_LISTENER)->onClose($this); + } + /** + * get the current transaction nesting level + * @return integer transaction nesting level + */ + public function getTransactionLevel() { + return $this->transaction_level; + } + /** + * beginTransaction + * starts a new transaction + * @return void + */ + public function beginTransaction() { + if($this->transaction_level == 0) { + + if($this->getAttribute(Doctrine::ATTR_LOCKMODE) == Doctrine::LOCK_PESSIMISTIC) { + $this->getAttribute(Doctrine::ATTR_LISTENER)->onPreTransactionBegin($this); + $this->dbh->beginTransaction(); + $this->getAttribute(Doctrine::ATTR_LISTENER)->onTransactionBegin($this); + } + $this->state = Doctrine_Session::STATE_ACTIVE; + } else { + $this->state = Doctrine_Session::STATE_BUSY; + } + $this->transaction_level++; + } + /** + * commits the current transaction + * if lockmode is optimistic this method starts a transaction + * and commits it instantly + * @return void + */ + public function commit() { + $this->transaction_level--; + + if($this->transaction_level == 0) { + + + if($this->getAttribute(Doctrine::ATTR_LOCKMODE) == Doctrine::LOCK_OPTIMISTIC) { + $this->getAttribute(Doctrine::ATTR_LISTENER)->onPreTransactionBegin($this); + + $this->dbh->beginTransaction(); + + $this->getAttribute(Doctrine::ATTR_LISTENER)->onTransactionBegin($this); + } + + if($this->getAttribute(Doctrine::ATTR_VLD)) { + $this->validator = new Doctrine_Validator(); + } + + $this->bulkInsert(); + $this->bulkUpdate(); + $this->bulkDelete(); + + + if(isset($this->validator) && $this->validator->hasErrors()) { + $this->rollback(); + throw new Doctrine_Validator_Exception($this->validator); + } + + $this->dbh->commit(); + $this->getAttribute(Doctrine::ATTR_LISTENER)->onTransactionCommit($this); + + $this->delete = array(array()); + $this->state = Doctrine_Session::STATE_OPEN; + + $this->validator = null; + + } elseif($this->transaction_level == 1) + $this->state = Doctrine_Session::STATE_ACTIVE; + } + /** + * rollback + * rolls back all the transactions + * @return void + */ + public function rollback() { + $this->getAttribute(Doctrine::ATTR_LISTENER)->onPreTransactionRollback($this); + + $this->transaction_level = 0; + $this->dbh->rollback(); + $this->state = Doctrine_Session::STATE_OPEN; + + $this->getAttribute(Doctrine::ATTR_LISTENER)->onTransactionRollback($this); + } + /** + * bulkInsert + * inserts all the objects in the pending insert list into database + * @return void + */ + public function bulkInsert() { + foreach($this->insert as $name => $inserts) { + if( ! isset($inserts[0])) + continue; + + $record = $inserts[0]; + $table = $record->getTable(); + $seq = $table->getSequenceName(); + $increment = false; + $id = null; + $keys = $table->getPrimaryKeys(); + if(count($keys) == 1 && $keys[0] == "id") { + + // record uses auto_increment column + + $sql = "SELECT MAX(id) FROM ".$record->getTable()->getTableName(); + $stmt = $this->dbh->query($sql); + $data = $stmt->fetch(PDO::FETCH_NUM); + $id = $data[0]; + $stmt->closeCursor(); + $increment = true; + } + + + foreach($inserts as $k => $record) { + $record->getTable()->getAttribute(Doctrine::ATTR_LISTENER)->onPreSave($record); + // listen the onPreInsert event + $record->getTable()->getAttribute(Doctrine::ATTR_LISTENER)->onPreInsert($record); + + if($increment) { + // record uses auto_increment column + $id++; + } + + $this->insert($record,$id); + // listen the onInsert event + $record->getTable()->getAttribute(Doctrine::ATTR_LISTENER)->onInsert($record); + + $record->getTable()->getAttribute(Doctrine::ATTR_LISTENER)->onSave($record); + + $record->getTable()->getCache()->store($record); + } + } + $this->insert = array(array()); + } + /** + * bulkUpdate + * updates all objects in the pending update list + * @return void + */ + public function bulkUpdate() { + foreach($this->update as $name => $updates) { + foreach($updates as $k => $record) { + $record->getTable()->getAttribute(Doctrine::ATTR_LISTENER)->onPreSave($record); + // listen the onPreUpdate event + $record->getTable()->getAttribute(Doctrine::ATTR_LISTENER)->onPreUpdate($record); + + $this->update($record); + // listen the onUpdate event + $record->getTable()->getAttribute(Doctrine::ATTR_LISTENER)->onUpdate($record); + + $record->getTable()->getAttribute(Doctrine::ATTR_LISTENER)->onSave($record); + } + } + $this->update = array(array()); + } + /** + * bulkDelete + * @return void + */ + public function bulkDelete() { + foreach($this->delete as $name => $deletes) { + $record = false; + $ids = array(); + foreach($deletes as $k => $record) { + $ids[] = $record->getID(); + $record->setID(null); + } + if($record instanceof Doctrine_Record) { + $params = substr(str_repeat("?, ",count($ids)),0,-2); + $query = "DELETE FROM ".$record->getTable()->getTableName()." WHERE id IN(".$params.")"; + $this->execute($query,$ids); + + $record->getTable()->getCache()->deleteMultiple($ids); + } + } + $this->delete = array(array()); + } + + + + /** + * @param Doctrine_Collection $coll + * @return void + */ + public function saveCollection(Doctrine_Collection $coll) { + $this->beginTransaction(); + + foreach($coll as $key=>$record): + $record->save(); + endforeach; + + $this->commit(); + } + /** + * @param Doctrine_Collection $coll + * @return void + */ + public function deleteCollection(Doctrine_Collection $coll) { + $this->beginTransaction(); + foreach($coll as $k=>$record) { + $record->delete(); + } + $this->commit(); + } + /** + * @param Doctrine_Record $record + * @return void + */ + + public function save(Doctrine_Record $record) { + + switch($record->getState()): + case Doctrine_Record::STATE_TDIRTY: + $this->addInsert($record); + break; + case Doctrine_Record::STATE_DIRTY: + case Doctrine_Record::STATE_PROXY: + $this->addUpdate($record); + break; + case Doctrine_Record::STATE_CLEAN: + case Doctrine_Record::STATE_TCLEAN: + // do nothing + break; + endswitch; + } + /** + * @param Doctrine_Record $record + */ + final public function saveRelated(Doctrine_Record $record) { + $saveLater = array(); + foreach($record->getReferences() as $k=>$v) { + $fk = $record->getTable()->getForeignKey($k); + if($fk instanceof Doctrine_ForeignKey || + $fk instanceof Doctrine_LocalKey) { + switch($fk->getType()): + case Doctrine_Table::ONE_COMPOSITE: + case Doctrine_Table::MANY_COMPOSITE: + $local = $fk->getLocal(); + $foreign = $fk->getForeign(); + + if($record->getTable()->hasPrimaryKey($fk->getLocal())) { + switch($record->getState()): + case Doctrine_Record::STATE_TDIRTY: + case Doctrine_Record::STATE_TCLEAN: + $saveLater[$k] = $fk; + break; + case Doctrine_Record::STATE_CLEAN: + case Doctrine_Record::STATE_DIRTY: + $v->save(); + break; + endswitch; + } else { + // ONE-TO-ONE relationship + $obj = $record->get($fk->getTable()->getComponentName()); + + if($obj->getState() != Doctrine_Record::STATE_TCLEAN) + $obj->save(); + + } + break; + endswitch; + } elseif($fk instanceof Doctrine_Association) { + $v->save(); + } + } + return $saveLater; + } + /** + * @param Doctrine_Record $record + * @return boolean + */ + private function update(Doctrine_Record $record) { + $array = $record->getModified(); + if(empty($array)) + return false; + + + + $set = array(); + foreach($array as $name => $value): + $set[] = $name." = ?"; + + if($value instanceof Doctrine_Record) { + $array[$name] = $value->getID(); + $record->set($name, $value->getID()); + } + endforeach; + + if(isset($this->validator)) { + if( ! $this->validator->validateRecord($record)) { + return false; + } + } + + $params = array_values($array); + $params[] = $record->getID(); + + + $sql = "UPDATE ".$record->getTable()->getTableName()." SET ".implode(", ",$set)." WHERE ".implode(" = ? && ",$record->getTable()->getPrimaryKeys())." = ?"; + $stmt = $this->dbh->prepare($sql); + $stmt->execute($params); + + $record->setID($record->getID()); + + $record->getTable()->getCache()->delete($record->getID()); + return true; + } + /** + * @param Doctrine_Record $record + * @return boolean + */ + private function insert(Doctrine_Record $record,$id = null) { + $array = $record->getModified(); + if(empty($array)) + return false; + + + foreach($record->getTable()->getInheritanceMap() as $k=>$v): + $array[$k] = $v; + endforeach; + + $seq = $record->getTable()->getSequenceName(); + if( ! empty($seq)) { + $id = $this->getNextID($seq); + $array["id"] = $id; + } + + foreach($array as $k => $value) { + if($value instanceof Doctrine_Record) { + $array[$k] = $value->getID(); + $record->set($k,$value->getID()); + } + } + + if(isset($this->validator)) { + if( ! $this->validator->validateRecord($record)) { + return false; + } + } + + $strfields = join(", ", array_keys($array)); + $strvalues = substr(str_repeat("?, ",count($array)),0,-2); + + $sql = "INSERT INTO ".$record->getTable()->getTableName()." (".$strfields.") VALUES (".$strvalues.")"; + + $stmt = $this->dbh->prepare($sql); + + $stmt->execute(array_values($array)); + + $record->setID($id); + return true; + } + /** + * deletes all related composites + * this method is always called internally when this data access object is deleted + * + * @return void + */ + final public function deleteComposites(Doctrine_Record $record) { + foreach($record->getTable()->getForeignKeys() as $fk) { + switch($fk->getType()): + case Doctrine_Table::ONE_COMPOSITE: + case Doctrine_Table::MANY_COMPOSITE: + $obj = $record->get($fk->getTable()->getComponentName()); + $obj->delete(); + break; + endswitch; + } + } + /** + * deletes this data access object and all the related composites + * this operation is isolated by a transaction + * + * this event can be listened by the onPreDelete and onDelete listeners + * + * @return boolean true on success, false on failure + */ + final public function delete(Doctrine_Record $record) { + switch($record->getState()): + case Doctrine_Record::STATE_PROXY: + case Doctrine_Record::STATE_CLEAN: + case Doctrine_Record::STATE_DIRTY: + $this->beginTransaction(); + + $this->deleteComposites($record); + $this->addDelete($record); + + $this->commit(); + return true; + break; + default: + return false; + endswitch; + } + /** + * adds data access object into pending insert list + * @param Doctrine_Record $record + */ + public function addInsert(Doctrine_Record $record) { + $name = $record->getTable()->getComponentName(); + $this->insert[$name][] = $record; + } + /** + * adds data access object into penging update list + * @param Doctrine_Record $record + */ + public function addUpdate(Doctrine_Record $record) { + $name = $record->getTable()->getComponentName(); + $this->update[$name][] = $record; + } + /** + * adds data access object into pending delete list + * @param Doctrine_Record $record + */ + public function addDelete(Doctrine_Record $record) { + $name = $record->getTable()->getComponentName(); + $this->delete[$name][] = $record; + } + /** + * returns a string representation of this object + * @return string + */ + public function __toString() { + return Doctrine_Lib::getSessionAsString($this); + } +} +?> diff --git a/classes/Session/Common.class.php b/classes/Session/Common.class.php new file mode 100644 index 000000000..4cd8d0bd0 --- /dev/null +++ b/classes/Session/Common.class.php @@ -0,0 +1,10 @@ + diff --git a/classes/Session/Firebird.class.php b/classes/Session/Firebird.class.php new file mode 100644 index 000000000..709dd3e7d --- /dev/null +++ b/classes/Session/Firebird.class.php @@ -0,0 +1,20 @@ +query("SELECT UNIQUE FROM ".$sequence); + $data = $stmt->fetch(PDO::FETCH_NUM); + return $data[0]; + } +} +?> diff --git a/classes/Session/Informix.class.php b/classes/Session/Informix.class.php new file mode 100644 index 000000000..5465f89df --- /dev/null +++ b/classes/Session/Informix.class.php @@ -0,0 +1,6 @@ + diff --git a/classes/Session/Mssql.class.php b/classes/Session/Mssql.class.php new file mode 100644 index 000000000..e372eab88 --- /dev/null +++ b/classes/Session/Mssql.class.php @@ -0,0 +1,18 @@ +query("INSERT INTO $sequence (vapor) VALUES (0)"); + $stmt = $this->query("SELECT @@IDENTITY FROM $sequence"); + $data = $stmt->fetch(PDO::FETCH_NUM); + return $data[0]; + } +} +?> diff --git a/classes/Session/Mysql.class.php b/classes/Session/Mysql.class.php new file mode 100644 index 000000000..bb00bd03a --- /dev/null +++ b/classes/Session/Mysql.class.php @@ -0,0 +1,38 @@ +getTable()->getCompositePaths(); + $a = array_merge(array($coll->getTable()->getComponentName()),$a); + + $graph = new Doctrine_DQL_Parser($this); + foreach($coll as $k=>$record) { + switch($record->getState()): + case Doctrine_Record::STATE_DIRTY: + case Doctrine_Record::STATE_CLEAN: + $ids[] = $record->getID(); + break; + endswitch; + } + if(empty($ids)) + return array(); + + $graph->parseQuery("FROM ".implode(", ",$a)." WHERE ".$coll->getTable()->getTableName().".id IN(".implode(", ",$ids).")"); + + $query = $graph->buildDelete(); + + $this->getDBH()->query($query); + return $ids; + } + */ +} +?> diff --git a/classes/Session/Oracle.class.php b/classes/Session/Oracle.class.php new file mode 100644 index 000000000..f597071c7 --- /dev/null +++ b/classes/Session/Oracle.class.php @@ -0,0 +1,25 @@ += ".++$offset; + return $query; + } + /** + * returns the next value in the given sequence + * @param string $sequence + * @return integer + */ + public function getNextID($sequence) { + $stmt = $this->query("SELECT $sequence.nextval FROM dual"); + $data = $stmt->fetch(PDO::FETCH_NUM); + return $data[0]; + } +} +?> diff --git a/classes/Session/Pgsql.class.php b/classes/Session/Pgsql.class.php new file mode 100644 index 000000000..538c7b20f --- /dev/null +++ b/classes/Session/Pgsql.class.php @@ -0,0 +1,17 @@ +query("SELECT NEXTVAL('$sequence')"); + $data = $stmt->fetch(PDO::FETCH_NUM); + return $data[0]; + } +} +?> diff --git a/classes/Session/Sqlite.class.php b/classes/Session/Sqlite.class.php new file mode 100644 index 000000000..160951c08 --- /dev/null +++ b/classes/Session/Sqlite.class.php @@ -0,0 +1,6 @@ + diff --git a/classes/Table.class.php b/classes/Table.class.php new file mode 100644 index 000000000..38a6dc671 --- /dev/null +++ b/classes/Table.class.php @@ -0,0 +1,617 @@ +session = Doctrine_Manager::getInstance()->getCurrentSession(); + + $this->setParent($this->session); + + $name = ucwords(strtolower($name)); + + $this->name = $name; + + if( ! class_exists($name) || empty($name)) + throw new Doctrine_Exception("Couldn't find class $name"); + + $record = new $name($this); + $record->setUp(); + + $names = array(); + + $class = $name; + + + + // get parent classes + + do { + if($class == "Doctrine_Record") break; + + $name = ucwords(strtolower($class)); + $names[] = $name; + } while($class = get_parent_class($class)); + + // reverse names + $names = array_reverse($names); + + + // create database table + if(method_exists($record,"setTableDefinition")) { + $record->setTableDefinition(); + + if(isset($this->columns)) { + $method = new ReflectionMethod($name,"setTableDefinition"); + $class = $method->getDeclaringClass(); + + if( ! isset($this->tableName)) + $this->tableName = strtolower($class->getName()); + + switch(count($this->primaryKeys)): + case 0: + $this->columns = array_merge(array("id" => array("integer",11,"AUTOINCREMENT PRIMARY")), $this->columns); + $this->primaryKeys[] = "id"; + break; + case 1: + + break; + default: + + endswitch; + + if($this->getAttribute(Doctrine::ATTR_CREATE_TABLES)) { + $dict = new Doctrine_DataDict($this); + $dict->createTable($this->columns); + } + + } + } else { + throw new Doctrine_Exception("Class '$name' has no table definition."); + } + + // save parents + array_pop($names); + $this->parents = $names; + + $this->query = "SELECT ".implode(", ",array_keys($this->columns))." FROM ".$this->getTableName(); + + // check if an instance of this table is already initialized + if( ! $this->session->addTable($this)) + throw new Doctrine_Table_Exception(); + + $this->initComponents(); + } + /** + * initializes components this table uses + * + * @return void + */ + final public function initComponents() { + $this->repository = new Doctrine_Repository($this); + + switch($this->getAttribute(Doctrine::ATTR_CACHE)): + case Doctrine::CACHE_FILE: + $this->cache = new Doctrine_Cache_File($this); + break; + case Doctrine::CACHE_NONE: + $this->cache = new Doctrine_Cache($this); + break; + endswitch; + } + /** + * hasColumn + * @param string $name + * @param string $type + * @param integer $length + * @param mixed $options + * @return void + */ + final public function hasColumn($name, $type, $length, $options = "") { + $this->columns[$name] = array($type,$length,$options); + + $e = explode("|",$options); + if(in_array("primary",$e)) { + $this->primaryKeys[] = $name; + } + } + /** + * returns all primary keys + * @return array + */ + final public function getPrimaryKeys() { + return $this->primaryKeys; + } + /** + * @return boolean + */ + final public function hasPrimaryKey($key) { + return in_array($key,$this->primaryKeys); + } + /** + * @param $sequence + * @return void + */ + final public function setSequenceName($sequence) { + $this->sequenceName = $sequence; + } + /** + * @return string sequence name + */ + final public function getSequenceName() { + return $this->sequenceName; + } + /** + * setInheritanceMap + * @param array $inheritanceMap + * @return void + */ + final public function setInheritanceMap(array $inheritanceMap) { + $this->inheritanceMap = $inheritanceMap; + } + /** + * @return array inheritance map (array keys as fields) + */ + final public function getInheritanceMap() { + return $this->inheritanceMap; + } + /** + * return all composite paths in the form [component1].[component2]. . .[componentN] + * @return array + */ + final public function getCompositePaths() { + $array = array(); + $name = $this->getComponentName(); + foreach($this->bound as $k=>$a) { + try { + $fk = $this->getForeignKey($k); + switch($fk->getType()): + case Doctrine_Table::ONE_COMPOSITE: + case Doctrine_Table::MANY_COMPOSITE: + $n = $fk->getTable()->getComponentName(); + $array[] = $name.".".$n; + $e = $fk->getTable()->getCompositePaths(); + if( ! empty($e)) { + foreach($e as $name) { + $array[] = $name.".".$n.".".$name; + } + } + break; + endswitch; + } catch(InvalidKeyException $e) { + + } + } + return $array; + } + /** + * @var array bound relations + */ + final public function getBounds() { + return $this->bound; + } + /** + * @param string $name + */ + final public function getBound($name) { + if( ! isset($this->bound[$name])) + throw new InvalidKeyException(); + + return $this->bound[$name]; + } + /** + * @param string $objTableName + * @param string $fkField + * @return void + */ + final public function bind($objTableName,$fkField,$type,$localKey) { + $name = (string) $objTableName; + $field = (string) $fkField; + + if(isset($this->foreignKeys[$name])) + throw new InvalidKeyException(); + + $e = explode(".", $field); + + // is reference table used? + if($e[0] != $name && $e[0] == $this->name) + $this->bound[$name] = array($field,Doctrine_Table::MANY_COMPOSITE); + + $this->bound[$name] = array($field,$type,$localKey); + } + /** + * getComponentName + * @return string the component name + */ + final public function getComponentName() { + return $this->name; + } + /** + * @return Doctrine_Session + */ + final public function getSession() { + return $this->session; + } + /** + * @return Doctrine_Cache + */ + final public function getCache() { + return $this->cache; + } + /** + * @return Doctrine_Repository + */ + final public function getRepository() { + return $this->repository; + } + /** + * @param string $name component name of which a foreign key object is bound + * @return Doctrine_ForeignKey + */ + final public function getForeignKey($name) { + if(isset($this->foreignKeys[$name])) + return $this->foreignKeys[$name]; + + if(isset($this->bound[$name])) { + $type = $this->bound[$name][1]; + $field = $this->bound[$name][0]; + $e = explode(".",$field); + $objTable = $this->session->getTable($name); + + switch($e[0]): + case $name: + // ONE-TO-MANY or ONE-TO-ONE + $foreignKey = new Doctrine_ForeignKey($objTable,$this->bound[$name][2],$e[1],$type); + break; + case $this->name: + // ONE-TO-ONE + + if($type <= Doctrine_Table::ONE_COMPOSITE) + $foreignKey = new Doctrine_LocalKey($objTable,$e[1],$this->bound[$name][2],$type); + else + throw new Doctrine_Mapping_Exception(); + break; + default: + if(in_array($e[0], $this->parents)) { + // ONE-TO-ONE + if($type <= Doctrine_Table::ONE_COMPOSITE) + $foreignKey = new Doctrine_LocalKey($objTable,$e[1],$this->bound[$name][2],$type); + else + throw new Doctrine_Mapping_Exception(); + } else { + // POSSIBLY MANY-TO-MANY + $bound = $objTable->getBound($this->name); + $e2 = explode(".",$bound[0]); + + if($e2[0] != $e[0]) + throw new Doctrine_Mapping_Exception(); + + $associationTable = $this->session->getTable($e2[0]); + + $this->foreignKeys[$e2[0]] = new Doctrine_ForeignKey($associationTable,$this->bound[$name][2],$e2[1],Doctrine_Table::MANY_COMPOSITE); + + $foreignKey = new Doctrine_Association($objTable,$associationTable,$e2[1],$e[1],$type); + } + endswitch; + $this->foreignKeys[$name] = $foreignKey; + return $this->foreignKeys[$name]; + } else { + throw new InvalidKeyException(); + } + } + /** + * @return array an array containing all foreign key objects + */ + final public function getForeignKeys() { + $a = array(); + foreach($this->bound as $k=>$v) { + $a[$k] = $this->getForeignKey($k); + } + return $a; + } + /** + * @return void + */ + final public function setTableName($name) { + $this->tableName = $name; + } + + /** + * @return string database table name this class represents + */ + final public function getTableName() { + return $this->tableName; + } + /** + * createDAO + * @param $array an array where keys are field names and values representing field values + * @return Doctrine_Record A new Data Access Object. Uses an sql insert statement when saved + */ + final public function create(array $array = array()) { + $this->data = $array; + $this->isNewEntry = true; + $record = $this->getRecord(); + $this->isNewEntry = false; + return $record; + } + /** + * @param $id database row id + * @throws Doctrine_Find_Exception + * @return DAOProxy a proxy for given database row, if the id is not set method + * uses internal factory data (= data that was fetched by datagraph or collection) + */ + final public function find($id = null) { + if($id !== null) { + try { + // try to get from cache + $record = $this->cache->fetch($id); + return $record; + } catch(InvalidKeyException $e) { + // do nothing + } + + $query = $this->query." WHERE ".implode(" = ? && ",$this->primaryKeys)." = ?"; + $query = $this->applyInheritance($query); + + $params = array_merge(array($id), array_values($this->inheritanceMap)); + + $this->data = $this->session->execute($query,$params)->fetch(PDO::FETCH_ASSOC); + + if($this->data === false) + throw new Doctrine_Find_Exception(); + } + return $this->getRecord(); + } + /** + * applyInheritance + * @param $where query where part to be modified + * @return query where part with column aggregation inheritance added + */ + final public function applyInheritance($where) { + if( ! empty($this->inheritanceMap)) { + $a = array(); + foreach($this->inheritanceMap as $field => $value) { + $a[] = $field." = ?"; + } + $i = implode(" && ",$a); + $where .= " && $i"; + } + return $where; + } + /** + * findAll + * @return Doctrine_Collection a collection of all data access objects + */ + final public function findAll() { + $graph = new Doctrine_DQL_Parser($this->session); + $users = $graph->query("FROM ".$this->name); + return $users; + } + /** + * findBySql + * @return Doctrine_Collection a collection of data access objects + */ + final public function findBySql($sql) { + $graph = new Doctrine_DQL_Parser($this->session); + $users = $graph->query("FROM ".$this->name." WHERE ".$sql); + return $users; + } + /** + * getRecord + * @return Doctrine_Record + */ + public function getRecord() { + return new $this->name($this); + } + /** + * @param $id database row id + * @throws Doctrine_Find_Exception + * @return DAOProxy a proxy for given database row, if the id is not set method + * uses internal factory data (= data that was fetched by datagraph or collection) + */ + final public function getProxy($id = null) { + if($id !== null) { + $id = (int) $id; + try { + // try to get from cache + $record = $this->cache->fetch($id); + return $record; + } catch(InvalidKeyException $e) { + // do nothing + } + $query = "SELECT ".implode(", ",$this->primaryKeys)." FROM ".$this->getTableName()." WHERE ".implode(" = ? && ",$this->primaryKeys)." = ?"; + $query = $this->applyInheritance($query); + + $params = array_merge(array($id), array_values($this->inheritanceMap)); + + $this->data = $this->session->execute($query,$params)->fetch(PDO::FETCH_ASSOC); + + if($this->data === false) + throw new Doctrine_Find_Exception(); + } + return $this->getRecord(); + } + /** + * getTableDescription + * @return Doctrine_Table_Description the columns object for this factory + */ + final public function getTableDescription() { + return $this->columns; + } + /** + * @param integer $fetchMode + * @return Doctrine_DQL_Parser a Doctrine_DQL_Parser object + */ + public function getDQLParser() { + $graph = new Doctrine_DQL_Parser($this->getSession()); + $graph->load($this->getComponentName()); + return $graph; + } + /** + * execute + */ + public function execute($query, array $array = array()) { + $coll = new Doctrine_Collection($this); + $stmt = $this->session->getDBH()->prepare($query); + $stmt->execute($array); + $data = $stmt->fetchAll(PDO::FETCH_ASSOC); + $stmt->closeCursor(); + + foreach($data as $row) { + $this->data = $row; + try { + $record = $this->getCache()->fetch($this->data["id"]); + } catch(InvalidKeyException $e) { + $record = $this->getRecord(); + } + $coll->add($record); + } + return $coll; + } + /** + * @return array + */ + final public function getColumns() { + return $this->columns; + } + /** + * @return array an array containing all the column names + */ + public function getColumnNames() { + return array_keys($this->columns); + } + /** + * setData + * @param array $data internal data, users are strongly discouraged to use this function + * @return void + */ + public function setData(array $data) { + $this->data = $data; + } + /** + * @return boolean whether or not a newly created object is new or not + */ + final public function isNewEntry() { + return $this->isNewEntry; + } + /** + * @return string simple cached query + */ + final public function getQuery() { + return $this->query; + } + /** + * @return array internal data, used by Doctrine_Record instances when retrieving data from database + */ + final public function getData() { + return $this->data; + } + /** + * @return string string representation of this object + */ + public function __toString() { + return Doctrine_Lib::getTableAsString($this); + } +} +?> diff --git a/classes/Validator.class.php b/classes/Validator.class.php new file mode 100644 index 000000000..3646d59bc --- /dev/null +++ b/classes/Validator.class.php @@ -0,0 +1,111 @@ +getModified(); + $columns = $record->getTable()->getColumns(); + $name = $record->getTable()->getComponentName(); + + $err = array(); + foreach($modified as $key => $value) { + $column = $columns[$key]; + + if(strlen($value) > $column[1]) { + $err[$key] = Doctrine_Validator::ERR_LENGTH; + continue; + } + + if(self::gettype($value) !== $column[0]) { + $err[$key] = Doctrine_Validator::ERR_TYPE; + continue; + } + + $e = explode("|",$column[2]); + + foreach($e as $k => $arg) { + if(empty($arg) || $arg == "primary") + continue; + + $validator = self::getValidator($arg); + if( ! $validator->validate($record,$key,$value)) { + switch(strtolower($arg)): + case "unique": + $err[$key] = Doctrine_Validator::ERR_UNIQUE; + break; + default: + $err[$key] = Doctrine_Validator::ERR_VALID; + break; + endswitch; + } + if(isset($err[$key])) + break; + } + } + + if( ! empty($err)) { + $this->stack[$name][] = $err; + return false; + } + + return true; + } + /** + * @return boolean + */ + public function hasErrors() { + return (count($this->stack) > 0); + } + /** + * @return array + */ + public function getErrorStack() { + return $this->stack; + } + /** + * @param mixed $var + */ + public static function gettype($var) { + $type = gettype($var); + switch($type): + case "string": + if(preg_match("/^[0-9]*$/",$var)) return "integer"; + elseif(is_numeric($var)) return "float"; + else return $type; + break; + default: + return $type; + endswitch; + } +} +?> diff --git a/classes/Validator/Blank.class.php b/classes/Validator/Blank.class.php new file mode 100644 index 000000000..d3fcafb47 --- /dev/null +++ b/classes/Validator/Blank.class.php @@ -0,0 +1,18 @@ + diff --git a/classes/Validator/Country.class.php b/classes/Validator/Country.class.php new file mode 100644 index 000000000..049345900 --- /dev/null +++ b/classes/Validator/Country.class.php @@ -0,0 +1,264 @@ + "Andorra", + "ae" => "United Arab Emirates", + "af" => "Afghanistan", + "ag" => "Antigua and Barbuda", + "ai" => "Anguilla", + "al" => "Albania", + "am" => "Armenia", + "an" => "Netherlands Antilles", + "ao" => "Angola", + "aq" => "Antarctica", + "ar" => "Argentina", + "as" => "American Samoa", + "at" => "Austria", + "au" => "Australia", + "aw" => "Aruba", + "az" => "Azerbaijan", + "ba" => "Bosnia Hercegovina", + "bb" => "Barbados", + "bd" => "Bangladesh", + "be" => "Belgium", + "bf" => "Burkina Faso", + "bg" => "Bulgaria", + "bh" => "Bahrain", + "bi" => "Burundi", + "bj" => "Benin", + "bm" => "Bermuda", + "bn" => "Brunei Darussalam", + "bo" => "Bolivia", + "br" => "Brazil", + "bs" => "Bahamas", + "bt" => "Bhutan", + "bv" => "Bouvet Island", + "bw" => "Botswana", + "by" => "Belarus (Byelorussia)", + "bz" => "Belize", + "ca" => "Canada", + "cc" => "Cocos Islands", + "cd" => 'Congo, The Democratic Republic of the', + "cf" => "Central African Republic", + "cg" => "Congo", + "ch" => "Switzerland", + "ci" => "Ivory Coast", + "ck" => "Cook Islands", + "cl" => "Chile", + "cm" => "Cameroon", + "cn" => "China", + "co" => "Colombia", + "cr" => "Costa Rica", + "cs" => "Czechoslovakia", + "cu" => "Cuba", + "cv" => "Cape Verde", + "cx" => "Christmas Island", + "cy" => "Cyprus", + "cz" => 'Czech Republic', + "de" => "Germany", + "dj" => "Djibouti", + "dk" => 'Denmark', + "dm" => "Dominica", + "do" => "Dominican Republic", + "dz" => "Algeria", + "ec" => "Ecuador", + "ee" => "Estonia", + "eg" => "Egypt", + "eh" => "Western Sahara", + "er" => 'Eritrea', + "es" => "Spain", + "et" => "Ethiopia", + "fi" => "Finland", + "fj" => "Fiji", + "fk" => "Falkland Islands", + "fm" => "Micronesia", + "fo" => "Faroe Islands", + "fr" => "France", + "fx" => 'France, Metropolitan FX', + "ga" => "Gabon", + "gb" => 'United Kingdom (Great Britain)', + "gd" => "Grenada", + "ge" => "Georgia", + "gf" => "French Guiana", + "gh" => "Ghana", + "gi" => "Gibraltar", + "gl" => "Greenland", + "gm" => "Gambia", + "gn" => "Guinea", + "gp" => "Guadeloupe", + "gq" => "Equatorial Guinea", + "gr" => "Greece", + "gs" => 'South Georgia and the South Sandwich Islands', + "gt" => "Guatemala", + "gu" => "Guam", + "gw" => "Guinea-bissau", + "gy" => "Guyana", + "hk" => "Hong Kong", + "hm" => "Heard and McDonald Islands", + "hn" => "Honduras", + "hr" => "Croatia", + "ht" => "Haiti", + "hu" => "Hungary", + "id" => "Indonesia", + "ie" => "Ireland", + "il" => "Israel", + "in" => "India", + "io" => "British Indian Ocean Territory", + "iq" => "Iraq", + "ir" => "Iran", + "is" => "Iceland", + "it" => "Italy", + "jm" => "Jamaica", + "jo" => "Jordan", + "jp" => "Japan", + "ke" => "Kenya", + "kg" => "Kyrgyzstan", + "kh" => "Cambodia", + "ki" => "Kiribati", + "km" => "Comoros", + "kn" => "Saint Kitts and Nevis", + "kp" => "North Korea", + "kr" => "South Korea", + "kw" => "Kuwait", + "ky" => "Cayman Islands", + "kz" => "Kazakhstan", + "la" => "Laos", + "lb" => "Lebanon", + "lc" => "Saint Lucia", + "li" => "Lichtenstein", + "lk" => "Sri Lanka", + "lr" => "Liberia", + "ls" => "Lesotho", + "lt" => "Lithuania", + "lu" => "Luxembourg", + "lv" => "Latvia", + "ly" => "Libya", + "ma" => "Morocco", + "mc" => "Monaco", + "md" => "Moldova Republic", + "mg" => "Madagascar", + "mh" => "Marshall Islands", + "mk" => 'Macedonia, The Former Yugoslav Republic of', + "ml" => "Mali", + "mm" => "Myanmar", + "mn" => "Mongolia", + "mo" => "Macau", + "mp" => "Northern Mariana Islands", + "mq" => "Martinique", + "mr" => "Mauritania", + "ms" => "Montserrat", + "mt" => "Malta", + "mu" => "Mauritius", + "mv" => "Maldives", + "mw" => "Malawi", + "mx" => "Mexico", + "my" => "Malaysia", + "mz" => "Mozambique", + "na" => "Namibia", + "nc" => "New Caledonia", + "ne" => "Niger", + "nf" => "Norfolk Island", + "ng" => "Nigeria", + "ni" => "Nicaragua", + "nl" => "Netherlands", + "no" => "Norway", + "np" => "Nepal", + "nr" => "Nauru", + "nt" => "Neutral Zone", + "nu" => "Niue", + "nz" => "New Zealand", + "om" => "Oman", + "pa" => "Panama", + "pe" => "Peru", + "pf" => "French Polynesia", + "pg" => "Papua New Guinea", + "ph" => "Philippines", + "pk" => "Pakistan", + "pl" => "Poland", + "pm" => "St. Pierre and Miquelon", + "pn" => "Pitcairn", + "pr" => "Puerto Rico", + "pt" => "Portugal", + "pw" => "Palau", + "py" => "Paraguay", + "qa" => 'Qatar', + "re" => "Reunion", + "ro" => "Romania", + "ru" => "Russia", + "rw" => "Rwanda", + "sa" => "Saudi Arabia", + "sb" => "Solomon Islands", + "sc" => "Seychelles", + "sd" => "Sudan", + "se" => "Sweden", + "sg" => "Singapore", + "sh" => "St. Helena", + "si" => "Slovenia", + "sj" => "Svalbard and Jan Mayen Islands", + "sk" => 'Slovakia (Slovak Republic)', + "sl" => "Sierra Leone", + "sm" => "San Marino", + "sn" => "Senegal", + "so" => "Somalia", + "sr" => "Suriname", + "st" => "Sao Tome and Principe", + "sv" => "El Salvador", + "sy" => "Syria", + "sz" => "Swaziland", + "tc" => "Turks and Caicos Islands", + "td" => "Chad", + "tf" => "French Southern Territories", + "tg" => "Togo", + "th" => "Thailand", + "tj" => "Tajikistan", + "tk" => "Tokelau", + "tm" => "Turkmenistan", + "tn" => "Tunisia", + "to" => "Tonga", + "tp" => "East Timor", + "tr" => "Turkey", + "tt" => "Trinidad, Tobago", + "tv" => "Tuvalu", + "tw" => "Taiwan", + "tz" => "Tanzania", + "ua" => "Ukraine", + "ug" => "Uganda", + "uk" => "United Kingdom", + "um" => "United States Minor Islands", + "us" => "United States of America", + "uy" => "Uruguay", + "uz" => "Uzbekistan", + "va" => "Vatican City", + "vc" => "Saint Vincent, Grenadines", + "ve" => "Venezuela", + "vg" => "Virgin Islands (British)", + "vi" => "Virgin Islands (USA)", + "vn" => "Viet Nam", + "vu" => "Vanuatu", + "wf" => 'Wallis and Futuna Islands', + "ws" => "Samoa", + "ye" => 'Yemen', + "yt" => 'Mayotte', + "yu" => "Yugoslavia", + "za" => "South Africa", + "zm" => "Zambia", + "zr" => "Zaire", + "zw" => "Zimbabwe"); + /** + * @return array + */ + public static function getCountries() { + return self::$countries; + } + /** + * @param Doctrine_Record $record + * @param string $key + * @param mixed $value + * @return boolean + */ + public function validate(Doctrine_Record $record, $key, $value) { + return isset(self::$countries[$value]); + } + +} +?> diff --git a/classes/Validator/Date.class.php b/classes/Validator/Date.class.php new file mode 100644 index 000000000..c3bd188a0 --- /dev/null +++ b/classes/Validator/Date.class.php @@ -0,0 +1,13 @@ + diff --git a/classes/Validator/Email.class.php b/classes/Validator/Email.class.php new file mode 100644 index 000000000..5ab1e928d --- /dev/null +++ b/classes/Validator/Email.class.php @@ -0,0 +1,46 @@ + 64) + return false; + + if(strlen($parts[1]) < 1 || strlen($parts[1]) > 255) + return false; + + $local_array = explode(".", $parts[0]); + for ($i = 0; $i < sizeof($local_array); $i++) { + if (!ereg("^(([A-Za-z0-9!#$%&'*+/=?^_`{|}~-][A-Za-z0-9!#$%&'*+/=?^_`{|}~\.-]{0,63})|(\"[^(\\|\")]{0,62}\"))$", $parts[$i])) { + return false; + } + } + if (!ereg("^\[?[0-9\.]+\]?$", $parts[1])) { // Check if domain is IP. If not, it should be valid domain name + $domain_array = explode(".", $parts[1]); + if (count($domain_array) < 2) { + return false; // Not enough parts to domain + } + for ($i = 0; $i < sizeof($domain_array); $i++) { + if (!ereg("^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]+))$", $domain_array[$i])) { + return false; + } + } + } + if(function_exists("checkdnsrr")) { + if( ! checkdnsrr($parts[1], "MX")) + return false; + } + + return true; + } +} +?> diff --git a/classes/Validator/Htmlcolor.class.php b/classes/Validator/Htmlcolor.class.php new file mode 100644 index 000000000..77bb3cec8 --- /dev/null +++ b/classes/Validator/Htmlcolor.class.php @@ -0,0 +1,16 @@ + diff --git a/classes/Validator/Ip.class.php b/classes/Validator/Ip.class.php new file mode 100644 index 000000000..4767aaad2 --- /dev/null +++ b/classes/Validator/Ip.class.php @@ -0,0 +1,21 @@ +$v): + if(! is_numeric($v)) return false; + $v = (int) $v; + if($v < 0 || $v > 255) return false; + endforeach; + return true; + } +} +?> diff --git a/classes/Validator/Nospace.class.php b/classes/Validator/Nospace.class.php new file mode 100644 index 000000000..4142a0a41 --- /dev/null +++ b/classes/Validator/Nospace.class.php @@ -0,0 +1,16 @@ + diff --git a/classes/Validator/Notnull.class.php b/classes/Validator/Notnull.class.php new file mode 100644 index 000000000..93514cd6f --- /dev/null +++ b/classes/Validator/Notnull.class.php @@ -0,0 +1,13 @@ + diff --git a/classes/Validator/Range.class.php b/classes/Validator/Range.class.php new file mode 100644 index 000000000..14669b145 --- /dev/null +++ b/classes/Validator/Range.class.php @@ -0,0 +1,31 @@ +min = $min; + } + /** + * @param integer $max + */ + public function setMax($max) { + $this->max = $max; + } + /** + * @param Doctrine_Record $record + * @param string $key + * @param mixed $value + * @return boolean + */ + public function validate(Doctrine_Record $record, $key, $value) { + if($var < $this->min) + return false; + + if($var > $this->max) + return false; + + return true; + } +} +?> diff --git a/classes/Validator/Regexp.class.php b/classes/Validator/Regexp.class.php new file mode 100644 index 000000000..b2660192d --- /dev/null +++ b/classes/Validator/Regexp.class.php @@ -0,0 +1,13 @@ + diff --git a/classes/Validator/Required.class.php b/classes/Validator/Required.class.php new file mode 100644 index 000000000..9419924aa --- /dev/null +++ b/classes/Validator/Required.class.php @@ -0,0 +1,13 @@ + diff --git a/classes/Validator/Unique.class.php b/classes/Validator/Unique.class.php new file mode 100644 index 000000000..4dfb21402 --- /dev/null +++ b/classes/Validator/Unique.class.php @@ -0,0 +1,17 @@ +getTable(); + $sql = "SELECT id FROM ".$table->getTableName()." WHERE ".$key." = ?"; + $stmt = $table->getSession()->getDBH()->prepare($sql); + $stmt->execute(array($value)); + return ( ! is_array($stmt->fetch())); + } +} +?> diff --git a/classes/Validator/Usstate.class.php b/classes/Validator/Usstate.class.php new file mode 100644 index 000000000..ba068e85c --- /dev/null +++ b/classes/Validator/Usstate.class.php @@ -0,0 +1,71 @@ + true, + "AL" => true, + "AR" => true, + "AZ" => true, + "CA" => true, + "CO" => true, + "CT" => true, + "DC" => true, + "DE" => true, + "FL" => true, + "GA" => true, + "HI" => true, + "IA" => true, + "ID" => true, + "IL" => true, + "IN" => true, + "KS" => true, + "KY" => true, + "LA" => true, + "MA" => true, + "MD" => true, + "ME" => true, + "MI" => true, + "MN" => true, + "MO" => true, + "MS" => true, + "MT" => true, + "NC" => true, + "ND" => true, + "NE" => true, + "NH" => true, + "NJ" => true, + "NM" => true, + "NV" => true, + "NY" => true, + "OH" => true, + "OK" => true, + "OR" => true, + "PA" => true, + "PR" => true, + "RI" => true, + "SC" => true, + "SD" => true, + "TN" => true, + "TX" => true, + "UT" => true, + "VA" => true, + "VI" => true, + "VT" => true, + "WA" => true, + "WI" => true, + "WV" => true, + "WY" => true + ); + public function getStates() { + return self::$states; + } + /** + * @param Doctrine_Record $record + * @param string $key + * @param mixed $value + * @return boolean + */ + public function validate(Doctrine_Record $record, $key, $value) { + return isset(self::$states[$value]); + } +} +?> diff --git a/classes/adodb-hack/adodb-datadict.inc.php b/classes/adodb-hack/adodb-datadict.inc.php new file mode 100644 index 000000000..eadd3dc8b --- /dev/null +++ b/classes/adodb-hack/adodb-datadict.inc.php @@ -0,0 +1,1072 @@ +$value) + $new_array[strtoupper($key)] = $value; + + return $new_array; + } + + return $an_array; +} + +/** + Parse arguments, treat "text" (text) and 'text' as quotation marks. + To escape, use "" or '' or )) + + Will read in "abc def" sans quotes, as: abc def + Same with 'abc def'. + However if `abc def`, then will read in as `abc def` + + @param endstmtchar Character that indicates end of statement + @param tokenchars Include the following characters in tokens apart from A-Z and 0-9 + @returns 2 dimensional array containing parsed tokens. +*/ +function Lens_ParseArgs($args,$endstmtchar=',',$tokenchars='_.-') +{ + $pos = 0; + $intoken = false; + $stmtno = 0; + $endquote = false; + $tokens = array(); + $tokens[$stmtno] = array(); + $max = strlen($args); + $quoted = false; + + while ($pos < $max) { + $ch = substr($args,$pos,1); + switch($ch) { + case ' ': + case "\t": + case "\n": + case "\r": + if (!$quoted) { + if ($intoken) { + $intoken = false; + $tokens[$stmtno][] = implode('',$tokarr); + } + break; + } + $tokarr[] = $ch; + break; + case '`': + if ($intoken) $tokarr[] = $ch; + case '(': + case ')': + case '"': + case "'": + if ($intoken) { + if (empty($endquote)) { + $tokens[$stmtno][] = implode('',$tokarr); + if ($ch == '(') $endquote = ')'; + else $endquote = $ch; + $quoted = true; + $intoken = true; + $tokarr = array(); + } else if ($endquote == $ch) { + $ch2 = substr($args,$pos+1,1); + if ($ch2 == $endquote) { + $pos += 1; + $tokarr[] = $ch2; + } else { + $quoted = false; + $intoken = false; + $tokens[$stmtno][] = implode('',$tokarr); + $endquote = ''; + } + } else + $tokarr[] = $ch; + }else { + if ($ch == '(') $endquote = ')'; + else $endquote = $ch; + $quoted = true; + $intoken = true; + $tokarr = array(); + if ($ch == '`') $tokarr[] = '`'; + } + break; + default: + if (!$intoken) { + if ($ch == $endstmtchar) { + $stmtno += 1; + $tokens[$stmtno] = array(); + break; + } + $intoken = true; + $quoted = false; + $endquote = false; + $tokarr = array(); + } + if ($quoted) $tokarr[] = $ch; + else if (ctype_alnum($ch) || strpos($tokenchars,$ch) !== false) $tokarr[] = $ch; + else { + if ($ch == $endstmtchar) { + $tokens[$stmtno][] = implode('',$tokarr); + $stmtno += 1; + $tokens[$stmtno] = array(); + $intoken = false; + $tokarr = array(); + break; + } + $tokens[$stmtno][] = implode('',$tokarr); + $tokens[$stmtno][] = $ch; + $intoken = false; + } + } + $pos += 1; + } + if ($intoken) $tokens[$stmtno][] = implode('',$tokarr); + + return $tokens; +} + + +class ADODB_DataDict { + var $connection; + var $debug = false; + var $dropTable = 'DROP TABLE %s'; + var $renameTable = 'RENAME TABLE %s TO %s'; + var $dropIndex = 'DROP INDEX %s'; + var $addCol = ' ADD'; + var $alterCol = ' ALTER COLUMN'; + var $dropCol = ' DROP COLUMN'; + var $renameColumn = 'ALTER TABLE %s RENAME COLUMN %s TO %s'; // table, old-column, new-column, column-definitions (not used by default) + var $nameRegex = '\w'; + var $nameRegexBrackets = 'a-zA-Z0-9_\(\)'; + var $schema = false; + var $serverInfo = array(); + var $autoIncrement = false; + var $invalidResizeTypes4 = array('CLOB','BLOB','TEXT','DATE','TIME'); // for changetablesql + var $blobSize = 100; /// any varchar/char field this size or greater is treated as a blob + /// in other words, we use a text area for editting. + var $metaTablesSQL; + var $metaColumnsSQL; + var $debug_echo = true; + var $fetchMode; + var $raiseErrorFn; + + function SetFetchMode($mode) + { + GLOBAL $ADODB_FETCH_MODE; + $old = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = $mode; + return $old; + } + + function outp($text) + { + $this->debug_output = "
\n(" . $this->dbtype . "): ".htmlspecialchars($text)."
\n"; + if($this->debug_echo) + echo $this->debug_output; + } + + function GetCommentSQL($table,$col) + { + return false; + } + + function SetCommentSQL($table,$col,$cmt) + { + return false; + } + + /** + * @param ttype can either be 'VIEW' or 'TABLE' or false. + * If false, both views and tables are returned. + * "VIEW" returns only views + * "TABLE" returns only tables + * @param showSchema returns the schema/user with the table name, eg. USER.TABLE + * @param mask is the input mask - only supported by oci8 and postgresql + * + * @return array of tables for current database. + */ + + function MetaTables() + { + global $ADODB_FETCH_MODE; + + $false = false; + if ($mask) { + return $false; + } + if ($this->metaTablesSQL) { + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + + if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false); + + $rs = $this->Execute($this->metaTablesSQL); + if (isset($savem)) $this->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + + if ($rs === false) return $false; + $arr =& $rs->GetArray(); + $arr2 = array(); + + if ($hast = ($ttype && isset($arr[0][1]))) { + $showt = strncmp($ttype,'T',1); + } + + for ($i=0; $i < sizeof($arr); $i++) { + if ($hast) { + if ($showt == 0) { + if (strncmp($arr[$i][1],'T',1) == 0) $arr2[] = trim($arr[$i][0]); + } else { + if (strncmp($arr[$i][1],'V',1) == 0) $arr2[] = trim($arr[$i][0]); + } + } else + $arr2[] = trim($arr[$i][0]); + } + $rs->Close(); + return $arr2; + } + return $false; + } + + /** + * List columns in a database as an array of ADOFieldObjects. + * See top of file for definition of object. + * + * @param table table name to query + * @param upper uppercase table name (required by some databases) + * @schema is optional database schema to use - not supported by all databases. + * + * @return array of ADOFieldObjects for current table. + */ + + function MetaColumns($table, $upper=true, $schema=false) + { + global $ADODB_FETCH_MODE; + + $false = false; + + if (!empty($this->metaColumnsSQL)) { + $schema = false; + $this->_findschema($table,$schema); + + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + + if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false); + $stmt = $this->connection->query(sprintf($this->metaColumnsSQL,($upper)?strtoupper($table):$table)); + if (isset($savem)) $this->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + + if ($stmt === false) + return $false; + + $retarr = array(); + while ($rs = $stmt->fetch(PDO::FETCH_NUM)) { + $fld = new ADOFieldObject(); + $fld->name = $rs[0]; + $fld->type = $rs[1]; + if (isset($rs[3]) && $rs[3]) { + if ($rs[3]>0) $fld->max_length = $rs[3]; + $fld->scale = $rs[4]; + if ($fld->scale>0) $fld->max_length += 1; + } else + $fld->max_length = $rs[2]; + + if ($ADODB_FETCH_MODE == ADODB_FETCH_NUM) $retarr[] = $fld; + else $retarr[strtoupper($fld->name)] = $fld; + } + $stmt->closeCursor(); + return $retarr; + } + return $false; + } + + function _findschema(&$table,&$schema) + { + if (!$schema && ($at = strpos($table,'.')) !== false) { + $schema = substr($table,0,$at); + $table = substr($table,$at+1); + } + } + + /** + * @returns an array with the primary key columns in it. + */ + + function MetaPrimaryKeys($tab,$owner=false,$intkey=false) + { + // owner not used in base class - see oci8 + $p = array(); + $objs =& $this->MetaColumns($table); + if ($objs) { + foreach($objs as $v) { + if (!empty($v->primary_key)) + $p[] = $v->name; + } + } + if (sizeof($p)) return $p; + if (function_exists('ADODB_VIEW_PRIMARYKEYS')) + return ADODB_VIEW_PRIMARYKEYS($this->databaseType, $this->database, $table, $owner); + return false; + } + + /** + * List indexes on a table as an array. + * @param table table name to query + * @param primary true to only show primary keys. Not actually used for most databases + * + * @return array of indexes on current table. Each element represents an index, and is itself an associative array. + + Array ( + [name_of_index] => Array + ( + [unique] => true or false + [columns] => Array + ( + [0] => firstname + [1] => lastname + ) + ) + */ + + function MetaIndexes($table, $primary = false, $owner = false) + { + $false = false; + return $false; + } + + function MetaType($t,$len=-1,$fieldobj=false) + { + if (is_object($t)) { + $fieldobj = $t; + $t = $fieldobj->type; + $len = $fieldobj->max_length; + } + // changed in 2.32 to hashing instead of switch stmt for speed... + static $typeMap = array( + 'VARCHAR' => 'C', + 'VARCHAR2' => 'C', + 'CHAR' => 'C', + 'C' => 'C', + 'STRING' => 'C', + 'NCHAR' => 'C', + 'NVARCHAR' => 'C', + 'VARYING' => 'C', + 'BPCHAR' => 'C', + 'CHARACTER' => 'C', + 'INTERVAL' => 'C', # Postgres + ## + 'LONGCHAR' => 'X', + 'TEXT' => 'X', + 'NTEXT' => 'X', + 'M' => 'X', + 'X' => 'X', + 'CLOB' => 'X', + 'NCLOB' => 'X', + 'LVARCHAR' => 'X', + ## + 'BLOB' => 'B', + 'IMAGE' => 'B', + 'BINARY' => 'B', + 'VARBINARY' => 'B', + 'LONGBINARY' => 'B', + 'B' => 'B', + ## + 'YEAR' => 'D', // mysql + 'DATE' => 'D', + 'D' => 'D', + ## + 'TIME' => 'T', + 'TIMESTAMP' => 'T', + 'DATETIME' => 'T', + 'TIMESTAMPTZ' => 'T', + 'T' => 'T', + ## + 'BOOL' => 'L', + 'BOOLEAN' => 'L', + 'BIT' => 'L', + 'L' => 'L', + ## + 'COUNTER' => 'R', + 'R' => 'R', + 'SERIAL' => 'R', // ifx + 'INT IDENTITY' => 'R', + ## + 'INT' => 'I', + 'INT2' => 'I', + 'INT4' => 'I', + 'INT8' => 'I', + 'INTEGER' => 'I', + 'INTEGER UNSIGNED' => 'I', + 'SHORT' => 'I', + 'TINYINT' => 'I', + 'SMALLINT' => 'I', + 'I' => 'I', + ## + 'LONG' => 'N', // interbase is numeric, oci8 is blob + 'BIGINT' => 'N', // this is bigger than PHP 32-bit integers + 'DECIMAL' => 'N', + 'DEC' => 'N', + 'REAL' => 'N', + 'DOUBLE' => 'N', + 'DOUBLE PRECISION' => 'N', + 'SMALLFLOAT' => 'N', + 'FLOAT' => 'N', + 'NUMBER' => 'N', + 'NUM' => 'N', + 'NUMERIC' => 'N', + 'MONEY' => 'N', + + ## informix 9.2 + 'SQLINT' => 'I', + 'SQLSERIAL' => 'I', + 'SQLSMINT' => 'I', + 'SQLSMFLOAT' => 'N', + 'SQLFLOAT' => 'N', + 'SQLMONEY' => 'N', + 'SQLDECIMAL' => 'N', + 'SQLDATE' => 'D', + 'SQLVCHAR' => 'C', + 'SQLCHAR' => 'C', + 'SQLDTIME' => 'T', + 'SQLINTERVAL' => 'N', + 'SQLBYTES' => 'B', + 'SQLTEXT' => 'X' + ); + + $tmap = false; + $t = strtoupper($t); + $tmap = (isset($typeMap[$t])) ? $typeMap[$t] : 'N'; + switch ($tmap) { + case 'C': + // is the char field is too long, return as text field... + if ($this->blobSize >= 0) { + if ($len > $this->blobSize) return 'X'; + } else if ($len > 250) { + return 'X'; + } + return 'C'; + case 'I': + if (!empty($fieldobj->primary_key)) return 'R'; + return 'I'; + case false: + return 'N'; + case 'B': + if (isset($fieldobj->binary)) + return ($fieldobj->binary) ? 'B' : 'X'; + return 'B'; + case 'D': + if (!empty($this->datetime)) return 'T'; + return 'D'; + default: + if ($t == 'LONG' && $this->dataProvider == 'oci8') return 'B'; + return $tmap; + } + } + + function NameQuote($name = NULL,$allowBrackets=false) + { + if (!is_string($name)) { + return FALSE; + } + + $name = trim($name); + + if ( !is_object($this->connection) ) { + return $name; + } + + + $quote = "'"; + + // if name is of the form `name`, quote it + if ( preg_match('/^`(.+)`$/', $name, $matches) ) { + return $quote . $matches[1] . $quote; + } + + // if name contains special characters, quote it + $regex = ($allowBrackets) ? $this->nameRegexBrackets : $this->nameRegex; + + if ( !preg_match('/^[' . $regex . ']+$/', $name) ) { + return $quote . $name . $quote; + } + + return $name; + } + + function TableName($name) + { + if ( $this->schema ) { + return $this->NameQuote($this->schema) .'.'. $this->NameQuote($name); + } + return $this->NameQuote($name); + } + + // Executes the sql array returned by GetTableSQL and GetIndexSQL + function ExecuteSQLArray($sql, $continueOnError = true) + { + $rez = 2; + $conn = &$this->connection; + $saved = $conn->debug; + foreach($sql as $line) { + if ($this->debug) $conn->debug = true; + $ok = $conn->Execute($line); + $conn->debug = $saved; + if (!$ok) { + if ($this->debug) $this->outp($conn->ErrorMsg()); + if (!$continueOnError) return 0; + $rez = 1; + } + } + return $rez; + } + + /* + Returns the actual type given a character code. + + C: varchar + X: CLOB (character large object) or largest varchar size if CLOB is not supported + C2: Multibyte varchar + X2: Multibyte CLOB + + B: BLOB (binary large object) + + D: Date + T: Date-time + L: Integer field suitable for storing booleans (0 or 1) + I: Integer + F: Floating point number + N: Numeric or decimal number + */ + + function ActualType($meta) + { + return $meta; + } + + function CreateDatabase($dbname,$options=false) + { + $options = $this->_Options($options); + $sql = array(); + + $s = 'CREATE DATABASE ' . $this->NameQuote($dbname); + if (isset($options[$this->upperName])) + $s .= ' '.$options[$this->upperName]; + + $sql[] = $s; + return $sql; + } + + /* + Generates the SQL to create index. Returns an array of sql strings. + */ + + function CreateIndexSQL($idxname, $tabname, $flds, $idxoptions = false) + { + if (!is_array($flds)) { + $flds = explode(',',$flds); + } + foreach($flds as $key => $fld) { + # some indexes can use partial fields, eg. index first 32 chars of "name" with NAME(32) + $flds[$key] = $this->NameQuote($fld,$allowBrackets=true); + } + return $this->_IndexSQL($this->NameQuote($idxname), $this->TableName($tabname), $flds, $this->_Options($idxoptions)); + } + + function DropIndexSQL ($idxname, $tabname = NULL) + { + return array(sprintf($this->dropIndex, $this->NameQuote($idxname), $this->TableName($tabname))); + } + + function SetSchema($schema) + { + $this->schema = $schema; + } + + function AddColumnSQL($tabname, $flds) + { + $tabname = $this->TableName ($tabname); + $sql = array(); + list($lines,$pkey) = $this->_GenFields($flds); + $alter = 'ALTER TABLE ' . $tabname . $this->addCol . ' '; + foreach($lines as $v) { + $sql[] = $alter . $v; + } + return $sql; + } + + /** + * Change the definition of one column + * + * As some DBM's can't do that on there own, you need to supply the complete defintion of the new table, + * to allow, recreating the table and copying the content over to the new table + * @param string $tabname table-name + * @param string $flds column-name and type for the changed column + * @param string $tableflds='' complete defintion of the new table, eg. for postgres, default '' + * @param array/string $tableoptions='' options for the new table see CreateTableSQL, default '' + * @return array with SQL strings + */ + + function AlterColumnSQL($tabname, $flds, $tableflds='',$tableoptions='') + { + $tabname = $this->TableName ($tabname); + $sql = array(); + list($lines,$pkey) = $this->_GenFields($flds); + $alter = 'ALTER TABLE ' . $tabname . $this->alterCol . ' '; + foreach($lines as $v) { + $sql[] = $alter . $v; + } + return $sql; + } + + /** + * Rename one column + * + * Some DBM's can only do this together with changeing the type of the column (even if that stays the same, eg. mysql) + * @param string $tabname table-name + * @param string $oldcolumn column-name to be renamed + * @param string $newcolumn new column-name + * @param string $flds='' complete column-defintion-string like for AddColumnSQL, only used by mysql atm., default='' + * @return array with SQL strings + */ + + function RenameColumnSQL($tabname,$oldcolumn,$newcolumn,$flds='') + { + $tabname = $this->TableName ($tabname); + if ($flds) { + list($lines,$pkey) = $this->_GenFields($flds); + list(,$first) = each($lines); + list(,$column_def) = split("[\t ]+",$first,2); + } + return array(sprintf($this->renameColumn,$tabname,$this->NameQuote($oldcolumn),$this->NameQuote($newcolumn),$column_def)); + } + + /** + * Drop one column + * + * Some DBM's can't do that on there own, you need to supply the complete defintion of the new table, + * to allow, recreating the table and copying the content over to the new table + * @param string $tabname table-name + * @param string $flds column-name and type for the changed column + * @param string $tableflds='' complete defintion of the new table, eg. for postgres, default '' + * @param array/string $tableoptions='' options for the new table see CreateTableSQL, default '' + * @return array with SQL strings + */ + + function DropColumnSQL($tabname, $flds, $tableflds='',$tableoptions='') + { + $tabname = $this->TableName ($tabname); + if (!is_array($flds)) $flds = explode(',',$flds); + $sql = array(); + $alter = 'ALTER TABLE ' . $tabname . $this->dropCol . ' '; + foreach($flds as $v) { + $sql[] = $alter . $this->NameQuote($v); + } + return $sql; + } + + function DropTableSQL($tabname) + { + return array (sprintf($this->dropTable, $this->TableName($tabname))); + } + + function RenameTableSQL($tabname,$newname) + { + return array (sprintf($this->renameTable, $this->TableName($tabname),$this->TableName($newname))); + } + + /* + Generate the SQL to create table. Returns an array of sql strings. + */ + + function CreateTableSQL($tabname, $flds, $tableoptions=false) + { + if (!$tableoptions) $tableoptions = array(); + + list($lines,$pkey) = $this->_GenFields($flds, true); + + $taboptions = $this->_Options($tableoptions); + $tabname = $this->TableName ($tabname); + $sql = $this->_TableSQL($tabname,$lines,$pkey,$taboptions); + $tsql = $this->_Triggers($tabname,$taboptions); + foreach($tsql as $s) $sql[] = $s; + + return $sql; + } + + function _GenFields($flds,$widespacing=false) + { + if (is_string($flds)) { + $padding = ' '; + $txt = $flds.$padding; + $flds = array(); + $flds0 = Lens_ParseArgs($txt,','); + $hasparam = false; + foreach($flds0 as $f0) { + $f1 = array(); + foreach($f0 as $token) { + switch (strtoupper($token)) { + case 'CONSTRAINT': + case 'DEFAULT': + $hasparam = $token; + break; + default: + if ($hasparam) $f1[$hasparam] = $token; + else $f1[] = $token; + $hasparam = false; + break; + } + } + $flds[] = $f1; + + } + } + $this->autoIncrement = false; + $lines = array(); + $pkey = array(); + foreach($flds as $fld) { + $fld = _array_change_key_case($fld); + $fname = false; + $fdefault = false; + $fautoinc = false; + $ftype = false; + $fsize = false; + $fprec = false; + $fprimary = false; + $fnoquote = false; + $fdefts = false; + $fdefdate = false; + $fconstraint = false; + $fnotnull = false; + $funsigned = false; + + //----------------- + // Parse attributes + foreach($fld as $attr => $v) { + if ($attr == 2 && is_numeric($v)) $attr = 'SIZE'; + else if (is_numeric($attr) && $attr > 1 && !is_numeric($v)) $attr = strtoupper($v); + switch($attr) { + case '0': + case 'NAME': + $fname = $v; + break; + case '1': + case 'TYPE': + $ty = $v; $ftype = $this->ActualType(strtoupper($v)); + break; + case 'SIZE': + $dotat = strpos($v,'.'); + if ($dotat === false) $dotat = strpos($v,','); + if ($dotat === false) $fsize = $v; + else { + $fsize = substr($v,0,$dotat); + $fprec = substr($v,$dotat+1); + } + break; + case 'UNSIGNED': + $funsigned = true; + break; + case 'AUTOINCREMENT': + case 'AUTO': + $fautoinc = true; + $fnotnull = true; + break; + case 'KEY': + case 'PRIMARY': + $fprimary = $v; + $fnotnull = true; + break; + case 'DEF': + case 'DEFAULT': + $fdefault = $v; + break; + case 'NOTNULL': + $fnotnull = $v; + break; + case 'NOQUOTE': + $fnoquote = $v; + break; + case 'DEFDATE': + $fdefdate = $v; + break; + case 'DEFTIMESTAMP': + $fdefts = $v; + break; + case 'CONSTRAINT': + $fconstraint = $v; + break; + } + } + + //-------------------- + // VALIDATE FIELD INFO + if (!strlen($fname)) { + if ($this->debug) $this->outp("Undefined NAME"); + return false; + } + + $fid = strtoupper(preg_replace('/^`(.+)`$/', '$1', $fname)); + $fname = $this->NameQuote($fname); + + if (!strlen($ftype)) { + if ($this->debug) $this->outp("Undefined TYPE for field '$fname'"); + return false; + } else { + $ftype = strtoupper($ftype); + } + + $ftype = $this->_GetSize($ftype, $ty, $fsize, $fprec); + + if ($ty == 'X' || $ty == 'X2' || $ty == 'B') $fnotnull = false; // some blob types do not accept nulls + + if ($fprimary) $pkey[] = $fname; + + // some databases do not allow blobs to have defaults + if ($ty == 'X') $fdefault = false; + + //-------------------- + // CONSTRUCT FIELD SQL + if ($fdefts) { + if (substr($this->dbtype,0,5) == 'mysql') { + $ftype = 'TIMESTAMP'; + } else { + $fdefault = $this->connection->sysTimeStamp; + } + } else if ($fdefdate) { + if (substr($this->dbtype,0,5) == 'mysql') { + $ftype = 'TIMESTAMP'; + } else { + $fdefault = $this->connection->sysDate; + } + } else if ($fdefault !== false && !$fnoquote) + if ($ty == 'C' or $ty == 'X' or + ( substr($fdefault,0,1) != "'" && !is_numeric($fdefault))) + if (strlen($fdefault) != 1 && substr($fdefault,0,1) == ' ' && substr($fdefault,strlen($fdefault)-1) == ' ') + $fdefault = trim($fdefault); + else if (strtolower($fdefault) != 'null') + $fdefault = $this->connection->qstr($fdefault); + $suffix = $this->_CreateSuffix($fname,$ftype,$fnotnull,$fdefault,$fautoinc,$fconstraint,$funsigned); + + if ($widespacing) $fname = str_pad($fname,24); + $lines[$fid] = $fname.' '.$ftype.$suffix; + + if ($fautoinc) $this->autoIncrement = true; + } // foreach $flds + return array($lines,$pkey); + } + + /* + GENERATE THE SIZE PART OF THE DATATYPE + $ftype is the actual type + $ty is the type defined originally in the DDL + */ + + function _GetSize($ftype, $ty, $fsize, $fprec) + { + if (strlen($fsize) && $ty != 'X' && $ty != 'B' && strpos($ftype,'(') === false) { + $ftype .= "(".$fsize; + if (strlen($fprec)) $ftype .= ",".$fprec; + $ftype .= ')'; + } + return $ftype; + } + + // return string must begin with space + function _CreateSuffix($fname,$ftype,$fnotnull,$fdefault,$fautoinc,$fconstraint) + { + $suffix = ''; + if (strlen($fdefault)) $suffix .= " DEFAULT $fdefault"; + if ($fnotnull) $suffix .= ' NOT NULL'; + if ($fconstraint) $suffix .= ' '.$fconstraint; + return $suffix; + } + + function _IndexSQL($idxname, $tabname, $flds, $idxoptions) + { + $sql = array(); + + if ( isset($idxoptions['REPLACE']) || isset($idxoptions['DROP']) ) { + $sql[] = sprintf ($this->dropIndex, $idxname); + if ( isset($idxoptions['DROP']) ) + return $sql; + } + + if ( empty ($flds) ) { + return $sql; + } + + $unique = isset($idxoptions['UNIQUE']) ? ' UNIQUE' : ''; + + $s = 'CREATE' . $unique . ' INDEX ' . $idxname . ' ON ' . $tabname . ' '; + + if ( isset($idxoptions[$this->upperName]) ) + $s .= $idxoptions[$this->upperName]; + + if ( is_array($flds) ) + $flds = implode(', ',$flds); + $s .= '(' . $flds . ')'; + $sql[] = $s; + + return $sql; + } + + function _DropAutoIncrement($tabname) + { + return false; + } + + function _TableSQL($tabname,$lines,$pkey,$tableoptions) + { + $sql = array(); + + if (isset($tableoptions['REPLACE']) || isset ($tableoptions['DROP'])) { + $sql[] = sprintf($this->dropTable,$tabname); + if ($this->autoIncrement) { + $sInc = $this->_DropAutoIncrement($tabname); + if ($sInc) $sql[] = $sInc; + } + if ( isset ($tableoptions['DROP']) ) { + return $sql; + } + } + $s = "CREATE TABLE $tabname (\n"; + $s .= implode(",\n", $lines); + if (sizeof($pkey)>0) { + $s .= ",\n PRIMARY KEY ("; + $s .= implode(", ",$pkey).")"; + } + if (isset($tableoptions['CONSTRAINTS'])) + $s .= "\n".$tableoptions['CONSTRAINTS']; + + if (isset($tableoptions[$this->upperName.'_CONSTRAINTS'])) + $s .= "\n".$tableoptions[$this->upperName.'_CONSTRAINTS']; + + $s .= "\n)"; + if (isset($tableoptions[$this->upperName])) $s .= $tableoptions[$this->upperName]; + $sql[] = $s; + + return $sql; + } + + /* + GENERATE TRIGGERS IF NEEDED + used when table has auto-incrementing field that is emulated using triggers + */ + + function _Triggers($tabname,$taboptions) + { + return array(); + } + + /* + Sanitize options, so that array elements with no keys are promoted to keys + */ + + function _Options($opts) + { + if (!is_array($opts)) return array(); + $newopts = array(); + foreach($opts as $k => $v) { + if (is_numeric($k)) $newopts[strtoupper($v)] = $v; + else $newopts[strtoupper($k)] = $v; + } + return $newopts; + } + + /* + "Florian Buzin [ easywe ]" + + This function changes/adds new fields to your table. You don't + have to know if the col is new or not. It will check on its own. + */ + + function ChangeTableSQL($tablename, $flds, $tableoptions = false) + { + global $ADODB_FETCH_MODE; + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC; + if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false); + + // check table exists + $save_handler = $this->raiseErrorFn; + $this->raiseErrorFn = ''; + $cols = $this->MetaColumns($tablename); + $this->raiseErrorFn = $save_handler; + + if (isset($savem)) $this->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + + if ( empty($cols)) { + return $this->CreateTableSQL($tablename, $flds, $tableoptions); + } + + if (is_array($flds)) { + // Cycle through the update fields, comparing + // existing fields to fields to update. + // if the Metatype and size is exactly the + // same, ignore - by Mark Newham + $holdflds = array(); + foreach($flds as $k=>$v) { + if ( isset($cols[$k]) && is_object($cols[$k]) ) { + $c = $cols[$k]; + $ml = $c->max_length; + $mt = &$this->MetaType($c->type,$ml); + if ($ml == -1) $ml = ''; + if ($mt == 'X') $ml = $v['SIZE']; + if (($mt != $v['TYPE']) || $ml != $v['SIZE']) { + $holdflds[$k] = $v; + } + } else { + $holdflds[$k] = $v; + } + } + $flds = $holdflds; + } + + // already exists, alter table instead + list($lines,$pkey) = $this->_GenFields($flds); + $alter = 'ALTER TABLE ' . $this->TableName($tablename); + $sql = array(); + + foreach ( $lines as $id => $v ) { + if ( isset($cols[$id]) && is_object($cols[$id]) ) { + $flds = Lens_ParseArgs($v,','); + // We are trying to change the size of the field, if not allowed, simply ignore the request. + if ($flds && in_array(strtoupper(substr($flds[0][1],0,4)),$this->invalidResizeTypes4)) continue; + + $sql[] = $alter . $this->alterCol . ' ' . $v; + } else { + $sql[] = $alter . $this->addCol . ' ' . $v; + } + } + return $sql; + } +} + +?> diff --git a/classes/adodb-hack/adodb.inc.php b/classes/adodb-hack/adodb.inc.php new file mode 100644 index 000000000..39122fc70 --- /dev/null +++ b/classes/adodb-hack/adodb.inc.php @@ -0,0 +1,50 @@ +getAttribute(PDO::ATTR_DRIVER_NAME); + + include_once ADODB_DIR . '/adodb-datadict.inc.php'; + include_once ADODB_DIR . '/drivers/datadict-' . $dbtype . '.inc.php'; + + $class = "ADODB2_$dbtype"; + $dict = new $class(); + $dict->connection = $conn; + $dict->upperName = strtoupper($dbtype); + //$dict->quote = $conn->nameQuote; + //$dict->debug_echo = $conn->debug_echo; + + return $dict; +} +class ADOFieldObject { + var $name = ''; + var $max_length=0; + var $type=""; +} + +?> diff --git a/classes/adodb-hack/drivers/datadict-access.inc.php b/classes/adodb-hack/drivers/datadict-access.inc.php new file mode 100644 index 000000000..dce322320 --- /dev/null +++ b/classes/adodb-hack/drivers/datadict-access.inc.php @@ -0,0 +1,95 @@ +debug) ADOConnection::outp("Warning: Access does not supported DEFAULT values (field $fname)"); + } + if ($fnotnull) $suffix .= ' NOT NULL'; + if ($fconstraint) $suffix .= ' '.$fconstraint; + return $suffix; + } + + function CreateDatabase($dbname,$options=false) + { + return array(); + } + + + function SetSchema($schema) + { + } + + function AlterColumnSQL($tabname, $flds) + { + if ($this->debug) ADOConnection::outp("AlterColumnSQL not supported"); + return array(); + } + + + function DropColumnSQL($tabname, $flds) + { + if ($this->debug) ADOConnection::outp("DropColumnSQL not supported"); + return array(); + } + +} + + +?> \ No newline at end of file diff --git a/classes/adodb-hack/drivers/datadict-db2.inc.php b/classes/adodb-hack/drivers/datadict-db2.inc.php new file mode 100644 index 000000000..3d8f4497f --- /dev/null +++ b/classes/adodb-hack/drivers/datadict-db2.inc.php @@ -0,0 +1,143 @@ +debug) ADOConnection::outp("AlterColumnSQL not supported"); + return array(); + } + + + function DropColumnSQL($tabname, $flds) + { + if ($this->debug) ADOConnection::outp("DropColumnSQL not supported"); + return array(); + } + + + function ChangeTableSQL($tablename, $flds, $tableoptions = false) + { + + /** + Allow basic table changes to DB2 databases + DB2 will fatally reject changes to non character columns + + */ + + $validTypes = array("CHAR","VARC"); + $invalidTypes = array("BIGI","BLOB","CLOB","DATE", "DECI","DOUB", "INTE", "REAL","SMAL", "TIME"); + // check table exists + $cols = &$this->MetaColumns($tablename); + if ( empty($cols)) { + return $this->CreateTableSQL($tablename, $flds, $tableoptions); + } + + // already exists, alter table instead + list($lines,$pkey) = $this->_GenFields($flds); + $alter = 'ALTER TABLE ' . $this->TableName($tablename); + $sql = array(); + + foreach ( $lines as $id => $v ) { + if ( isset($cols[$id]) && is_object($cols[$id]) ) { + /** + If the first field of $v is the fieldname, and + the second is the field type/size, we assume its an + attempt to modify the column size, so check that it is allowed + $v can have an indeterminate number of blanks between the + fields, so account for that too + */ + $vargs = explode(' ' , $v); + // assume that $vargs[0] is the field name. + $i=0; + // Find the next non-blank value; + for ($i=1;$ialterCol . ' ' . $v; + } else { + $sql[] = $alter . $this->addCol . ' ' . $v; + } + } + + return $sql; + } + +} + + +?> \ No newline at end of file diff --git a/classes/adodb-hack/drivers/datadict-firebird.inc.php b/classes/adodb-hack/drivers/datadict-firebird.inc.php new file mode 100644 index 000000000..52baae8df --- /dev/null +++ b/classes/adodb-hack/drivers/datadict-firebird.inc.php @@ -0,0 +1,151 @@ +connection) ) { + return $name; + } + + $quote = $this->connection->nameQuote; + + // if name is of the form `name`, quote it + if ( preg_match('/^`(.+)`$/', $name, $matches) ) { + return $quote . $matches[1] . $quote; + } + + // if name contains special characters, quote it + if ( !preg_match('/^[' . $this->nameRegex . ']+$/', $name) ) { + return $quote . $name . $quote; + } + + return $quote . $name . $quote; + } + + function CreateDatabase($dbname, $options=false) + { + $options = $this->_Options($options); + $sql = array(); + + $sql[] = "DECLARE EXTERNAL FUNCTION LOWER CSTRING(80) RETURNS CSTRING(80) FREE_IT ENTRY_POINT 'IB_UDF_lower' MODULE_NAME 'ib_udf'"; + + return $sql; + } + + function _DropAutoIncrement($t) + { + if (strpos($t,'.') !== false) { + $tarr = explode('.',$t); + return 'DROP GENERATOR '.$tarr[0].'."gen_'.$tarr[1].'"'; + } + return 'DROP GENERATOR "GEN_'.$t; + } + + + function _CreateSuffix($fname,$ftype,$fnotnull,$fdefault,$fautoinc,$fconstraint,$funsigned) + { + $suffix = ''; + + if (strlen($fdefault)) $suffix .= " DEFAULT $fdefault"; + if ($fnotnull) $suffix .= ' NOT NULL'; + if ($fautoinc) $this->seqField = $fname; + if ($fconstraint) $suffix .= ' '.$fconstraint; + + return $suffix; + } + +/* +CREATE or replace TRIGGER jaddress_insert +before insert on jaddress +for each row +begin +IF ( NEW."seqField" IS NULL OR NEW."seqField" = 0 ) THEN + NEW."seqField" = GEN_ID("GEN_tabname", 1); +end; +*/ + function _Triggers($tabname,$tableoptions) + { + if (!$this->seqField) return array(); + + $tab1 = preg_replace( '/"/', '', $tabname ); + if ($this->schema) { + $t = strpos($tab1,'.'); + if ($t !== false) $tab = substr($tab1,$t+1); + else $tab = $tab1; + $seqField = $this->seqField; + $seqname = $this->schema.'.'.$this->seqPrefix.$tab; + $trigname = $this->schema.'.trig_'.$this->seqPrefix.$tab; + } else { + $seqField = $this->seqField; + $seqname = $this->seqPrefix.$tab1; + $trigname = 'trig_'.$seqname; + } + if (isset($tableoptions['REPLACE'])) + { $sql[] = "DROP GENERATOR \"$seqname\""; + $sql[] = "CREATE GENERATOR \"$seqname\""; + $sql[] = "ALTER TRIGGER \"$trigname\" BEFORE INSERT OR UPDATE AS BEGIN IF ( NEW.$seqField IS NULL OR NEW.$seqField = 0 ) THEN NEW.$seqField = GEN_ID(\"$seqname\", 1); END"; + } + else + { $sql[] = "CREATE GENERATOR \"$seqname\""; + $sql[] = "CREATE TRIGGER \"$trigname\" FOR $tabname BEFORE INSERT OR UPDATE AS BEGIN IF ( NEW.$seqField IS NULL OR NEW.$seqField = 0 ) THEN NEW.$seqField = GEN_ID(\"$seqname\", 1); END"; + } + + $this->seqField = false; + return $sql; + } + +} + + +?> \ No newline at end of file diff --git a/classes/adodb-hack/drivers/datadict-generic.inc.php b/classes/adodb-hack/drivers/datadict-generic.inc.php new file mode 100644 index 000000000..054e1e829 --- /dev/null +++ b/classes/adodb-hack/drivers/datadict-generic.inc.php @@ -0,0 +1,125 @@ +debug) ADOConnection::outp("AlterColumnSQL not supported"); + return array(); + } + + + function DropColumnSQL($tabname, $flds) + { + if ($this->debug) ADOConnection::outp("DropColumnSQL not supported"); + return array(); + } + +} + +/* +//db2 + function ActualType($meta) + { + switch($meta) { + case 'C': return 'VARCHAR'; + case 'X': return 'VARCHAR'; + + case 'C2': return 'VARCHAR'; // up to 32K + case 'X2': return 'VARCHAR'; + + case 'B': return 'BLOB'; + + case 'D': return 'DATE'; + case 'T': return 'TIMESTAMP'; + + case 'L': return 'SMALLINT'; + case 'I': return 'INTEGER'; + case 'I1': return 'SMALLINT'; + case 'I2': return 'SMALLINT'; + case 'I4': return 'INTEGER'; + case 'I8': return 'BIGINT'; + + case 'F': return 'DOUBLE'; + case 'N': return 'DECIMAL'; + default: + return $meta; + } + } + +// ifx +function ActualType($meta) + { + switch($meta) { + case 'C': return 'VARCHAR';// 255 + case 'X': return 'TEXT'; + + case 'C2': return 'NVARCHAR'; + case 'X2': return 'TEXT'; + + case 'B': return 'BLOB'; + + case 'D': return 'DATE'; + case 'T': return 'DATETIME'; + + case 'L': return 'SMALLINT'; + case 'I': return 'INTEGER'; + case 'I1': return 'SMALLINT'; + case 'I2': return 'SMALLINT'; + case 'I4': return 'INTEGER'; + case 'I8': return 'DECIMAL(20)'; + + case 'F': return 'FLOAT'; + case 'N': return 'DECIMAL'; + default: + return $meta; + } + } +*/ +?> \ No newline at end of file diff --git a/classes/adodb-hack/drivers/datadict-ibase.inc.php b/classes/adodb-hack/drivers/datadict-ibase.inc.php new file mode 100644 index 000000000..0665c96e3 --- /dev/null +++ b/classes/adodb-hack/drivers/datadict-ibase.inc.php @@ -0,0 +1,67 @@ +debug) ADOConnection::outp("AlterColumnSQL not supported"); + return array(); + } + + + function DropColumnSQL($tabname, $flds) + { + if ($this->debug) ADOConnection::outp("DropColumnSQL not supported"); + return array(); + } + +} + + +?> \ No newline at end of file diff --git a/classes/adodb-hack/drivers/datadict-informix.inc.php b/classes/adodb-hack/drivers/datadict-informix.inc.php new file mode 100644 index 000000000..684cf7777 --- /dev/null +++ b/classes/adodb-hack/drivers/datadict-informix.inc.php @@ -0,0 +1,80 @@ +debug) ADOConnection::outp("AlterColumnSQL not supported"); + return array(); + } + + + function DropColumnSQL($tabname, $flds) + { + if ($this->debug) ADOConnection::outp("DropColumnSQL not supported"); + return array(); + } + + // return string must begin with space + function _CreateSuffix($fname, &$ftype, $fnotnull,$fdefault,$fautoinc,$fconstraint) + { + if ($fautoinc) { + $ftype = 'SERIAL'; + return ''; + } + $suffix = ''; + if (strlen($fdefault)) $suffix .= " DEFAULT $fdefault"; + if ($fnotnull) $suffix .= ' NOT NULL'; + if ($fconstraint) $suffix .= ' '.$fconstraint; + return $suffix; + } + +} + +?> \ No newline at end of file diff --git a/classes/adodb-hack/drivers/datadict-mssql.inc.php b/classes/adodb-hack/drivers/datadict-mssql.inc.php new file mode 100644 index 000000000..0b7991887 --- /dev/null +++ b/classes/adodb-hack/drivers/datadict-mssql.inc.php @@ -0,0 +1,282 @@ +type; + $len = $fieldobj->max_length; + } + + $len = -1; // mysql max_length is not accurate + switch (strtoupper($t)) { + case 'R': + case 'INT': + case 'INTEGER': return 'I'; + case 'BIT': + case 'TINYINT': return 'I1'; + case 'SMALLINT': return 'I2'; + case 'BIGINT': return 'I8'; + + case 'REAL': + case 'FLOAT': return 'F'; + default: return parent::MetaType($t,$len,$fieldobj); + } + } + + function ActualType($meta) + { + switch(strtoupper($meta)) { + + case 'C': return 'VARCHAR'; + case 'XL': return (isset($this)) ? $this->typeXL : 'TEXT'; + case 'X': return (isset($this)) ? $this->typeX : 'TEXT'; ## could be varchar(8000), but we want compat with oracle + case 'C2': return 'NVARCHAR'; + case 'X2': return 'NTEXT'; + + case 'B': return 'IMAGE'; + + case 'D': return 'DATETIME'; + case 'T': return 'DATETIME'; + case 'L': return 'BIT'; + + case 'R': + case 'I': return 'INT'; + case 'I1': return 'TINYINT'; + case 'I2': return 'SMALLINT'; + case 'I4': return 'INT'; + case 'I8': return 'BIGINT'; + + case 'F': return 'REAL'; + case 'N': return 'NUMERIC'; + default: + return $meta; + } + } + + + function AddColumnSQL($tabname, $flds) + { + $tabname = $this->TableName ($tabname); + $f = array(); + list($lines,$pkey) = $this->_GenFields($flds); + $s = "ALTER TABLE $tabname $this->addCol"; + foreach($lines as $v) { + $f[] = "\n $v"; + } + $s .= implode(', ',$f); + $sql[] = $s; + return $sql; + } + + /* + function AlterColumnSQL($tabname, $flds) + { + $tabname = $this->TableName ($tabname); + $sql = array(); + list($lines,$pkey) = $this->_GenFields($flds); + foreach($lines as $v) { + $sql[] = "ALTER TABLE $tabname $this->alterCol $v"; + } + + return $sql; + } + */ + + function DropColumnSQL($tabname, $flds) + { + $tabname = $this->TableName ($tabname); + if (!is_array($flds)) + $flds = explode(',',$flds); + $f = array(); + $s = 'ALTER TABLE ' . $tabname; + foreach($flds as $v) { + $f[] = "\n$this->dropCol ".$this->NameQuote($v); + } + $s .= implode(', ',$f); + $sql[] = $s; + return $sql; + } + + // return string must begin with space + function _CreateSuffix($fname,$ftype,$fnotnull,$fdefault,$fautoinc,$fconstraint) + { + $suffix = ''; + if (strlen($fdefault)) $suffix .= " DEFAULT $fdefault"; + if ($fautoinc) $suffix .= ' IDENTITY(1,1)'; + if ($fnotnull) $suffix .= ' NOT NULL'; + else if ($suffix == '') $suffix .= ' NULL'; + if ($fconstraint) $suffix .= ' '.$fconstraint; + return $suffix; + } + + /* +CREATE TABLE + [ database_name.[ owner ] . | owner. ] table_name + ( { < column_definition > + | column_name AS computed_column_expression + | < table_constraint > ::= [ CONSTRAINT constraint_name ] } + + | [ { PRIMARY KEY | UNIQUE } [ ,...n ] + ) + +[ ON { filegroup | DEFAULT } ] +[ TEXTIMAGE_ON { filegroup | DEFAULT } ] + +< column_definition > ::= { column_name data_type } + [ COLLATE < collation_name > ] + [ [ DEFAULT constant_expression ] + | [ IDENTITY [ ( seed , increment ) [ NOT FOR REPLICATION ] ] ] + ] + [ ROWGUIDCOL] + [ < column_constraint > ] [ ...n ] + +< column_constraint > ::= [ CONSTRAINT constraint_name ] + { [ NULL | NOT NULL ] + | [ { PRIMARY KEY | UNIQUE } + [ CLUSTERED | NONCLUSTERED ] + [ WITH FILLFACTOR = fillfactor ] + [ON {filegroup | DEFAULT} ] ] + ] + | [ [ FOREIGN KEY ] + REFERENCES ref_table [ ( ref_column ) ] + [ ON DELETE { CASCADE | NO ACTION } ] + [ ON UPDATE { CASCADE | NO ACTION } ] + [ NOT FOR REPLICATION ] + ] + | CHECK [ NOT FOR REPLICATION ] + ( logical_expression ) + } + +< table_constraint > ::= [ CONSTRAINT constraint_name ] + { [ { PRIMARY KEY | UNIQUE } + [ CLUSTERED | NONCLUSTERED ] + { ( column [ ASC | DESC ] [ ,...n ] ) } + [ WITH FILLFACTOR = fillfactor ] + [ ON { filegroup | DEFAULT } ] + ] + | FOREIGN KEY + [ ( column [ ,...n ] ) ] + REFERENCES ref_table [ ( ref_column [ ,...n ] ) ] + [ ON DELETE { CASCADE | NO ACTION } ] + [ ON UPDATE { CASCADE | NO ACTION } ] + [ NOT FOR REPLICATION ] + | CHECK [ NOT FOR REPLICATION ] + ( search_conditions ) + } + + + */ + + /* + CREATE [ UNIQUE ] [ CLUSTERED | NONCLUSTERED ] INDEX index_name + ON { table | view } ( column [ ASC | DESC ] [ ,...n ] ) + [ WITH < index_option > [ ,...n] ] + [ ON filegroup ] + < index_option > :: = + { PAD_INDEX | + FILLFACTOR = fillfactor | + IGNORE_DUP_KEY | + DROP_EXISTING | + STATISTICS_NORECOMPUTE | + SORT_IN_TEMPDB + } +*/ + function _IndexSQL($idxname, $tabname, $flds, $idxoptions) + { + $sql = array(); + + if ( isset($idxoptions['REPLACE']) || isset($idxoptions['DROP']) ) { + $sql[] = sprintf ($this->dropIndex, $idxname, $tabname); + if ( isset($idxoptions['DROP']) ) + return $sql; + } + + if ( empty ($flds) ) { + return $sql; + } + + $unique = isset($idxoptions['UNIQUE']) ? ' UNIQUE' : ''; + $clustered = isset($idxoptions['CLUSTERED']) ? ' CLUSTERED' : ''; + + if ( is_array($flds) ) + $flds = implode(', ',$flds); + $s = 'CREATE' . $unique . $clustered . ' INDEX ' . $idxname . ' ON ' . $tabname . ' (' . $flds . ')'; + + if ( isset($idxoptions[$this->upperName]) ) + $s .= $idxoptions[$this->upperName]; + + + $sql[] = $s; + + return $sql; + } + + + function _GetSize($ftype, $ty, $fsize, $fprec) + { + switch ($ftype) { + case 'INT': + case 'SMALLINT': + case 'TINYINT': + case 'BIGINT': + return $ftype; + } + if ($ty == 'T') return $ftype; + return parent::_GetSize($ftype, $ty, $fsize, $fprec); + + } +} +?> \ No newline at end of file diff --git a/classes/adodb-hack/drivers/datadict-mysql.inc.php b/classes/adodb-hack/drivers/datadict-mysql.inc.php new file mode 100644 index 000000000..8848021b0 --- /dev/null +++ b/classes/adodb-hack/drivers/datadict-mysql.inc.php @@ -0,0 +1,182 @@ +type; + $len = $fieldobj->max_length; + } + $is_serial = is_object($fieldobj) && $fieldobj->primary_key && $fieldobj->auto_increment; + + $len = -1; // mysql max_length is not accurate + switch (strtoupper($t)) { + case 'STRING': + case 'CHAR': + case 'VARCHAR': + case 'TINYBLOB': + case 'TINYTEXT': + case 'ENUM': + case 'SET': + if ($len <= $this->blobSize) return 'C'; + + case 'TEXT': + case 'LONGTEXT': + case 'MEDIUMTEXT': + return 'X'; + + // php_mysql extension always returns 'blob' even if 'text' + // so we have to check whether binary... + case 'IMAGE': + case 'LONGBLOB': + case 'BLOB': + case 'MEDIUMBLOB': + return !empty($fieldobj->binary) ? 'B' : 'X'; + + case 'YEAR': + case 'DATE': return 'D'; + + case 'TIME': + case 'DATETIME': + case 'TIMESTAMP': return 'T'; + + case 'FLOAT': + case 'DOUBLE': + return 'F'; + + case 'INT': + case 'INTEGER': return $is_serial ? 'R' : 'I'; + case 'TINYINT': return $is_serial ? 'R' : 'I1'; + case 'SMALLINT': return $is_serial ? 'R' : 'I2'; + case 'MEDIUMINT': return $is_serial ? 'R' : 'I4'; + case 'BIGINT': return $is_serial ? 'R' : 'I8'; + default: return 'N'; + } + } + + function ActualType($meta) + { + switch(strtoupper($meta)) { + case 'C': return 'VARCHAR'; + case 'XL':return 'LONGTEXT'; + case 'X': return 'TEXT'; + + case 'C2': return 'VARCHAR'; + case 'X2': return 'LONGTEXT'; + + case 'B': return 'LONGBLOB'; + + case 'D': return 'DATE'; + case 'T': return 'DATETIME'; + case 'L': return 'TINYINT'; + + case 'R': + case 'I4': + case 'I': return 'INTEGER'; + case 'I1': return 'TINYINT'; + case 'I2': return 'SMALLINT'; + case 'I8': return 'BIGINT'; + + case 'F': return 'DOUBLE'; + case 'N': return 'NUMERIC'; + default: + return $meta; + } + } + + // return string must begin with space + function _CreateSuffix($fname,$ftype,$fnotnull,$fdefault,$fautoinc,$fconstraint,$funsigned) + { + $suffix = ''; + if ($funsigned) $suffix .= ' UNSIGNED'; + if ($fnotnull) $suffix .= ' NOT NULL'; + if (strlen($fdefault)) $suffix .= " DEFAULT $fdefault"; + if ($fautoinc) $suffix .= ' AUTO_INCREMENT'; + if ($fconstraint) $suffix .= ' '.$fconstraint; + return $suffix; + } + + /* + CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name [(create_definition,...)] + [table_options] [select_statement] + create_definition: + col_name type [NOT NULL | NULL] [DEFAULT default_value] [AUTO_INCREMENT] + [PRIMARY KEY] [reference_definition] + or PRIMARY KEY (index_col_name,...) + or KEY [index_name] (index_col_name,...) + or INDEX [index_name] (index_col_name,...) + or UNIQUE [INDEX] [index_name] (index_col_name,...) + or FULLTEXT [INDEX] [index_name] (index_col_name,...) + or [CONSTRAINT symbol] FOREIGN KEY [index_name] (index_col_name,...) + [reference_definition] + or CHECK (expr) + */ + + /* + CREATE [UNIQUE|FULLTEXT] INDEX index_name + ON tbl_name (col_name[(length)],... ) + */ + + function _IndexSQL($idxname, $tabname, $flds, $idxoptions) + { + $sql = array(); + + if ( isset($idxoptions['REPLACE']) || isset($idxoptions['DROP']) ) { + if ($this->alterTableAddIndex) $sql[] = "ALTER TABLE $tabname DROP INDEX $idxname"; + else $sql[] = sprintf($this->dropIndex, $idxname, $tabname); + + if ( isset($idxoptions['DROP']) ) + return $sql; + } + + if ( empty ($flds) ) { + return $sql; + } + + if (isset($idxoptions['FULLTEXT'])) { + $unique = ' FULLTEXT'; + } elseif (isset($idxoptions['UNIQUE'])) { + $unique = ' UNIQUE'; + } else { + $unique = ''; + } + + if ( is_array($flds) ) $flds = implode(', ',$flds); + + if ($this->alterTableAddIndex) $s = "ALTER TABLE $tabname ADD $unique INDEX $idxname "; + else $s = 'CREATE' . $unique . ' INDEX ' . $idxname . ' ON ' . $tabname; + + $s .= ' (' . $flds . ')'; + + if ( isset($idxoptions[$this->upperName]) ) + $s .= $idxoptions[$this->upperName]; + + $sql[] = $s; + + return $sql; + } +} +?> diff --git a/classes/adodb-hack/drivers/datadict-oci8.inc.php b/classes/adodb-hack/drivers/datadict-oci8.inc.php new file mode 100644 index 000000000..6d0d313bf --- /dev/null +++ b/classes/adodb-hack/drivers/datadict-oci8.inc.php @@ -0,0 +1,282 @@ +type; + $len = $fieldobj->max_length; + } + switch (strtoupper($t)) { + case 'VARCHAR': + case 'VARCHAR2': + case 'CHAR': + case 'VARBINARY': + case 'BINARY': + if (isset($this) && $len <= $this->blobSize) return 'C'; + return 'X'; + + case 'NCHAR': + case 'NVARCHAR2': + case 'NVARCHAR': + if (isset($this) && $len <= $this->blobSize) return 'C2'; + return 'X2'; + + case 'NCLOB': + case 'CLOB': + return 'XL'; + + case 'LONG RAW': + case 'LONG VARBINARY': + case 'BLOB': + return 'B'; + + case 'DATE': + return 'T'; + + case 'INT': + case 'SMALLINT': + case 'INTEGER': + return 'I'; + + default: + return 'N'; + } + } + + function ActualType($meta) + { + switch($meta) { + case 'C': return 'VARCHAR'; + case 'X': return $this->typeX; + case 'XL': return $this->typeXL; + + case 'C2': return 'NVARCHAR'; + case 'X2': return 'NVARCHAR(2000)'; + + case 'B': return 'BLOB'; + + case 'D': + case 'T': return 'DATE'; + case 'L': return 'DECIMAL(1)'; + case 'I1': return 'DECIMAL(3)'; + case 'I2': return 'DECIMAL(5)'; + case 'I': + case 'I4': return 'DECIMAL(10)'; + + case 'I8': return 'DECIMAL(20)'; + case 'F': return 'DECIMAL'; + case 'N': return 'DECIMAL'; + default: + return $meta; + } + } + + function CreateDatabase($dbname, $options=false) + { + $options = $this->_Options($options); + $password = isset($options['PASSWORD']) ? $options['PASSWORD'] : 'tiger'; + $tablespace = isset($options["TABLESPACE"]) ? " DEFAULT TABLESPACE ".$options["TABLESPACE"] : ''; + $sql[] = "CREATE USER ".$dbname." IDENTIFIED BY ".$password.$tablespace; + $sql[] = "GRANT CREATE SESSION, CREATE TABLE,UNLIMITED TABLESPACE,CREATE SEQUENCE TO $dbname"; + + return $sql; + } + + function AddColumnSQL($tabname, $flds) + { + $f = array(); + list($lines,$pkey) = $this->_GenFields($flds); + $s = "ALTER TABLE $tabname ADD ("; + foreach($lines as $v) { + $f[] = "\n $v"; + } + + $s .= implode(', ',$f).')'; + $sql[] = $s; + return $sql; + } + + function AlterColumnSQL($tabname, $flds) + { + $f = array(); + list($lines,$pkey) = $this->_GenFields($flds); + $s = "ALTER TABLE $tabname MODIFY("; + foreach($lines as $v) { + $f[] = "\n $v"; + } + $s .= implode(', ',$f).')'; + $sql[] = $s; + return $sql; + } + + function DropColumnSQL($tabname, $flds) + { + if (!is_array($flds)) $flds = explode(',',$flds); + foreach ($flds as $k => $v) $flds[$k] = $this->NameQuote($v); + + $sql = array(); + $s = "ALTER TABLE $tabname DROP("; + $s .= implode(', ',$flds).') CASCADE CONSTRAINTS'; + $sql[] = $s; + return $sql; + } + + function _DropAutoIncrement($t) + { + if (strpos($t,'.') !== false) { + $tarr = explode('.',$t); + return "drop sequence ".$tarr[0].".seq_".$tarr[1]; + } + return "drop sequence seq_".$t; + } + + // return string must begin with space + function _CreateSuffix($fname,$ftype,$fnotnull,$fdefault,$fautoinc,$fconstraint,$funsigned) + { + $suffix = ''; + + if ($fdefault == "''" && $fnotnull) {// this is null in oracle + $fnotnull = false; + if ($this->debug) ADOConnection::outp("NOT NULL and DEFAULT='' illegal in Oracle"); + } + + if (strlen($fdefault)) $suffix .= " DEFAULT $fdefault"; + if ($fnotnull) $suffix .= ' NOT NULL'; + + if ($fautoinc) $this->seqField = $fname; + if ($fconstraint) $suffix .= ' '.$fconstraint; + + return $suffix; + } + +/* +CREATE or replace TRIGGER jaddress_insert +before insert on jaddress +for each row +begin +select seqaddress.nextval into :new.A_ID from dual; +end; +*/ + function _Triggers($tabname,$tableoptions) + { + if (!$this->seqField) return array(); + + if ($this->schema) { + $t = strpos($tabname,'.'); + if ($t !== false) $tab = substr($tabname,$t+1); + else $tab = $tabname; + $seqname = $this->schema.'.'.$this->seqPrefix.$tab; + $trigname = $this->schema.'.'.$this->trigPrefix.$this->seqPrefix.$tab; + } else { + $seqname = $this->seqPrefix.$tabname; + $trigname = $this->trigPrefix.$seqname; + } + if (isset($tableoptions['REPLACE'])) $sql[] = "DROP SEQUENCE $seqname"; + $seqCache = ''; + if (isset($tableoptions['SEQUENCE_CACHE'])){$seqCache = $tableoptions['SEQUENCE_CACHE'];} + $seqIncr = ''; + if (isset($tableoptions['SEQUENCE_INCREMENT'])){$seqIncr = ' INCREMENT BY '.$tableoptions['SEQUENCE_INCREMENT'];} + $seqStart = ''; + if (isset($tableoptions['SEQUENCE_START'])){$seqIncr = ' START WITH '.$tableoptions['SEQUENCE_START'];} + $sql[] = "CREATE SEQUENCE $seqname $seqStart $seqIncr $seqCache"; + $sql[] = "CREATE OR REPLACE TRIGGER $trigname BEFORE insert ON $tabname FOR EACH ROW WHEN (NEW.$this->seqField IS NULL OR NEW.$this->seqField = 0) BEGIN select $seqname.nextval into :new.$this->seqField from dual; END;"; + + $this->seqField = false; + return $sql; + } + + /* + CREATE [TEMPORARY] TABLE [IF NOT EXISTS] tbl_name [(create_definition,...)] + [table_options] [select_statement] + create_definition: + col_name type [NOT NULL | NULL] [DEFAULT default_value] [AUTO_INCREMENT] + [PRIMARY KEY] [reference_definition] + or PRIMARY KEY (index_col_name,...) + or KEY [index_name] (index_col_name,...) + or INDEX [index_name] (index_col_name,...) + or UNIQUE [INDEX] [index_name] (index_col_name,...) + or FULLTEXT [INDEX] [index_name] (index_col_name,...) + or [CONSTRAINT symbol] FOREIGN KEY [index_name] (index_col_name,...) + [reference_definition] + or CHECK (expr) + */ + + + + function _IndexSQL($idxname, $tabname, $flds,$idxoptions) + { + $sql = array(); + + if ( isset($idxoptions['REPLACE']) || isset($idxoptions['DROP']) ) { + $sql[] = sprintf ($this->dropIndex, $idxname, $tabname); + if ( isset($idxoptions['DROP']) ) + return $sql; + } + + if ( empty ($flds) ) { + return $sql; + } + + if (isset($idxoptions['BITMAP'])) { + $unique = ' BITMAP'; + } elseif (isset($idxoptions['UNIQUE'])) { + $unique = ' UNIQUE'; + } else { + $unique = ''; + } + + if ( is_array($flds) ) + $flds = implode(', ',$flds); + $s = 'CREATE' . $unique . ' INDEX ' . $idxname . ' ON ' . $tabname . ' (' . $flds . ')'; + + if ( isset($idxoptions[$this->upperName]) ) + $s .= $idxoptions[$this->upperName]; + + if (isset($idxoptions['oci8'])) + $s .= $idxoptions['oci8']; + + + $sql[] = $s; + + return $sql; + } + + function GetCommentSQL($table,$col) + { + $table = $this->connection->qstr($table); + $col = $this->connection->qstr($col); + return "select comments from USER_COL_COMMENTS where TABLE_NAME=$table and COLUMN_NAME=$col"; + } + + function SetCommentSQL($table,$col,$cmt) + { + $cmt = $this->connection->qstr($cmt); + return "COMMENT ON COLUMN $table.$col IS $cmt"; + } +} +?> \ No newline at end of file diff --git a/classes/adodb-hack/drivers/datadict-pgsql.inc.php b/classes/adodb-hack/drivers/datadict-pgsql.inc.php new file mode 100644 index 000000000..94249f885 --- /dev/null +++ b/classes/adodb-hack/drivers/datadict-pgsql.inc.php @@ -0,0 +1,371 @@ +type; + $len = $fieldobj->max_length; + } + $is_serial = is_object($fieldobj) && $fieldobj->primary_key && $fieldobj->unique && + $fieldobj->has_default && substr($fieldobj->default_value,0,8) == 'nextval('; + + switch (strtoupper($t)) { + case 'INTERVAL': + case 'CHAR': + case 'CHARACTER': + case 'VARCHAR': + case 'NAME': + case 'BPCHAR': + if ($len <= $this->blobSize) return 'C'; + + case 'TEXT': + return 'X'; + + case 'IMAGE': // user defined type + case 'BLOB': // user defined type + case 'BIT': // This is a bit string, not a single bit, so don't return 'L' + case 'VARBIT': + case 'BYTEA': + return 'B'; + + case 'BOOL': + case 'BOOLEAN': + return 'L'; + + case 'DATE': + return 'D'; + + case 'TIME': + case 'DATETIME': + case 'TIMESTAMP': + case 'TIMESTAMPTZ': + return 'T'; + + case 'INTEGER': return !$is_serial ? 'I' : 'R'; + case 'SMALLINT': + case 'INT2': return !$is_serial ? 'I2' : 'R'; + case 'INT4': return !$is_serial ? 'I4' : 'R'; + case 'BIGINT': + case 'INT8': return !$is_serial ? 'I8' : 'R'; + + case 'OID': + case 'SERIAL': + return 'R'; + + case 'FLOAT4': + case 'FLOAT8': + case 'DOUBLE PRECISION': + case 'REAL': + return 'F'; + + default: + return 'N'; + } + } + + function ActualType($meta) + { + switch($meta) { + case 'C': return 'VARCHAR'; + case 'XL': + case 'X': return 'TEXT'; + + case 'C2': return 'VARCHAR'; + case 'X2': return 'TEXT'; + + case 'B': return 'BYTEA'; + + case 'D': return 'DATE'; + case 'T': return 'TIMESTAMP'; + + case 'L': return 'BOOLEAN'; + case 'I': return 'INTEGER'; + case 'I1': return 'SMALLINT'; + case 'I2': return 'INT2'; + case 'I4': return 'INT4'; + case 'I8': return 'INT8'; + + case 'F': return 'FLOAT8'; + case 'N': return 'NUMERIC'; + default: + return $meta; + } + } + + /** + * Adding a new Column + * + * reimplementation of the default function as postgres does NOT allow to set the default in the same statement + * + * @param string $tabname table-name + * @param string $flds column-names and types for the changed columns + * @return array with SQL strings + */ + function AddColumnSQL($tabname, $flds) + { + $tabname = $this->TableName ($tabname); + $sql = array(); + list($lines,$pkey) = $this->_GenFields($flds); + $alter = 'ALTER TABLE ' . $tabname . $this->addCol . ' '; + foreach($lines as $v) { + if (($not_null = preg_match('/NOT NULL/i',$v))) { + $v = preg_replace('/NOT NULL/i','',$v); + } + if (preg_match('/^([^ ]+) .*DEFAULT ([^ ]+)/',$v,$matches)) { + list(,$colname,$default) = $matches; + $sql[] = $alter . str_replace('DEFAULT '.$default,'',$v); + $sql[] = 'UPDATE '.$tabname.' SET '.$colname.'='.$default; + $sql[] = 'ALTER TABLE '.$tabname.' ALTER COLUMN '.$colname.' SET DEFAULT ' . $default; + } else { + $sql[] = $alter . $v; + } + if ($not_null) { + list($colname) = explode(' ',$v); + $sql[] = 'ALTER TABLE '.$tabname.' ALTER COLUMN '.$colname.' SET NOT NULL'; + } + } + return $sql; + } + + /** + * Change the definition of one column + * + * Postgres can't do that on it's own, you need to supply the complete defintion of the new table, + * to allow, recreating the table and copying the content over to the new table + * @param string $tabname table-name + * @param string $flds column-name and type for the changed column + * @param string $tableflds complete defintion of the new table, eg. for postgres, default '' + * @param array/ $tableoptions options for the new table see CreateTableSQL, default '' + * @return array with SQL strings + */ + function AlterColumnSQL($tabname, $flds, $tableflds='',$tableoptions='') + { + if (!$tableflds) { + if ($this->debug) ADOConnection::outp("AlterColumnSQL needs a complete table-definiton for PostgreSQL"); + return array(); + } + return $this->_recreate_copy_table($tabname,False,$tableflds,$tableoptions); + } + + /** + * Drop one column + * + * Postgres < 7.3 can't do that on it's own, you need to supply the complete defintion of the new table, + * to allow, recreating the table and copying the content over to the new table + * @param string $tabname table-name + * @param string $flds column-name and type for the changed column + * @param string $tableflds complete defintion of the new table, eg. for postgres, default '' + * @param array/ $tableoptions options for the new table see CreateTableSQL, default '' + * @return array with SQL strings + */ + function DropColumnSQL($tabname, $flds, $tableflds='',$tableoptions='') + { + $has_drop_column = 7.3 <= (float) @$this->serverInfo['version']; + if (!$has_drop_column && !$tableflds) { + if ($this->debug) ADOConnection::outp("DropColumnSQL needs complete table-definiton for PostgreSQL < 7.3"); + return array(); + } + if ($has_drop_column) { + return ADODB_DataDict::DropColumnSQL($tabname, $flds); + } + return $this->_recreate_copy_table($tabname,$flds,$tableflds,$tableoptions); + } + + /** + * Save the content into a temp. table, drop and recreate the original table and copy the content back in + * + * We also take care to set the values of the sequenz and recreate the indexes. + * All this is done in a transaction, to not loose the content of the table, if something went wrong! + * @internal + * @param string $tabname table-name + * @param string $dropflds column-names to drop + * @param string $tableflds complete defintion of the new table, eg. for postgres + * @param array/string $tableoptions options for the new table see CreateTableSQL, default '' + * @return array with SQL strings + */ + function _recreate_copy_table($tabname,$dropflds,$tableflds,$tableoptions='') + { + if ($dropflds && !is_array($dropflds)) $dropflds = explode(',',$dropflds); + $copyflds = array(); + foreach($this->MetaColumns($tabname) as $fld) { + if (!$dropflds || !in_array($fld->name,$dropflds)) { + // we need to explicit convert varchar to a number to be able to do an AlterColumn of a char column to a nummeric one + if (preg_match('/'.$fld->name.' (I|I2|I4|I8|N|F)/i',$tableflds,$matches) && + in_array($fld->type,array('varchar','char','text','bytea'))) { + $copyflds[] = "to_number($fld->name,'S9999999999999D99')"; + } else { + $copyflds[] = $fld->name; + } + // identify the sequence name and the fld its on + if ($fld->primary_key && $fld->has_default && + preg_match("/nextval\('([^']+)'::text\)/",$fld->default_value,$matches)) { + $seq_name = $matches[1]; + $seq_fld = $fld->name; + } + } + } + $copyflds = implode(', ',$copyflds); + + $tempname = $tabname.'_tmp'; + $aSql[] = 'BEGIN'; // we use a transaction, to make sure not to loose the content of the table + $aSql[] = "SELECT * INTO TEMPORARY TABLE $tempname FROM $tabname"; + $aSql = array_merge($aSql,$this->DropTableSQL($tabname)); + $aSql = array_merge($aSql,$this->CreateTableSQL($tabname,$tableflds,$tableoptions)); + $aSql[] = "INSERT INTO $tabname SELECT $copyflds FROM $tempname"; + if ($seq_name && $seq_fld) { // if we have a sequence we need to set it again + $seq_name = $tabname.'_'.$seq_fld.'_seq'; // has to be the name of the new implicit sequence + $aSql[] = "SELECT setval('$seq_name',MAX($seq_fld)) FROM $tabname"; + } + $aSql[] = "DROP TABLE $tempname"; + // recreate the indexes, if they not contain one of the droped columns + foreach($this->MetaIndexes($tabname) as $idx_name => $idx_data) + { + if (substr($idx_name,-5) != '_pkey' && (!$dropflds || !count(array_intersect($dropflds,$idx_data['columns'])))) { + $aSql = array_merge($aSql,$this->CreateIndexSQL($idx_name,$tabname,$idx_data['columns'], + $idx_data['unique'] ? array('UNIQUE') : False)); + } + } + $aSql[] = 'COMMIT'; + return $aSql; + } + + function DropTableSQL($tabname) + { + $sql = ADODB_DataDict::DropTableSQL($tabname); + + $drop_seq = $this->_DropAutoIncrement($tabname); + if ($drop_seq) $sql[] = $drop_seq; + + return $sql; + } + + // return string must begin with space + function _CreateSuffix($fname, &$ftype, $fnotnull,$fdefault,$fautoinc,$fconstraint) + { + if ($fautoinc) { + $ftype = 'SERIAL'; + return ''; + } + $suffix = ''; + if (strlen($fdefault)) $suffix .= " DEFAULT $fdefault"; + if ($fnotnull) $suffix .= ' NOT NULL'; + if ($fconstraint) $suffix .= ' '.$fconstraint; + return $suffix; + } + + // search for a sequece for the given table (asumes the seqence-name contains the table-name!) + // if yes return sql to drop it + // this is still necessary if postgres < 7.3 or the SERIAL was created on an earlier version!!! + function _DropAutoIncrement($tabname) + { + $tabname = $this->connection->quote('%'.$tabname.'%'); + + $seq = $this->connection->GetOne("SELECT relname FROM pg_class WHERE NOT relname ~ 'pg_.*' AND relname LIKE $tabname AND relkind='S'"); + + // check if a tables depends on the sequenz and it therefor cant and dont need to be droped separatly + if (!$seq || $this->connection->GetOne("SELECT relname FROM pg_class JOIN pg_depend ON pg_class.relfilenode=pg_depend.objid WHERE relname='$seq' AND relkind='S' AND deptype='i'")) { + return False; + } + return "DROP SEQUENCE ".$seq; + } + + /* + CREATE [ [ LOCAL ] { TEMPORARY | TEMP } ] TABLE table_name ( + { column_name data_type [ DEFAULT default_expr ] [ column_constraint [, ... ] ] + | table_constraint } [, ... ] + ) + [ INHERITS ( parent_table [, ... ] ) ] + [ WITH OIDS | WITHOUT OIDS ] + where column_constraint is: + [ CONSTRAINT constraint_name ] + { NOT NULL | NULL | UNIQUE | PRIMARY KEY | + CHECK (expression) | + REFERENCES reftable [ ( refcolumn ) ] [ MATCH FULL | MATCH PARTIAL ] + [ ON DELETE action ] [ ON UPDATE action ] } + [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] + and table_constraint is: + [ CONSTRAINT constraint_name ] + { UNIQUE ( column_name [, ... ] ) | + PRIMARY KEY ( column_name [, ... ] ) | + CHECK ( expression ) | + FOREIGN KEY ( column_name [, ... ] ) REFERENCES reftable [ ( refcolumn [, ... ] ) ] + [ MATCH FULL | MATCH PARTIAL ] [ ON DELETE action ] [ ON UPDATE action ] } + [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] + */ + + + /* + CREATE [ UNIQUE ] INDEX index_name ON table +[ USING acc_method ] ( column [ ops_name ] [, ...] ) +[ WHERE predicate ] +CREATE [ UNIQUE ] INDEX index_name ON table +[ USING acc_method ] ( func_name( column [, ... ]) [ ops_name ] ) +[ WHERE predicate ] + */ + function _IndexSQL($idxname, $tabname, $flds, $idxoptions) + { + $sql = array(); + + if ( isset($idxoptions['REPLACE']) || isset($idxoptions['DROP']) ) { + $sql[] = sprintf ($this->dropIndex, $idxname, $tabname); + if ( isset($idxoptions['DROP']) ) + return $sql; + } + + if ( empty ($flds) ) { + return $sql; + } + + $unique = isset($idxoptions['UNIQUE']) ? ' UNIQUE' : ''; + + $s = 'CREATE' . $unique . ' INDEX ' . $idxname . ' ON ' . $tabname . ' '; + + if (isset($idxoptions['HASH'])) + $s .= 'USING HASH '; + + if ( isset($idxoptions[$this->upperName]) ) + $s .= $idxoptions[$this->upperName]; + + if ( is_array($flds) ) + $flds = implode(', ',$flds); + $s .= '(' . $flds . ')'; + $sql[] = $s; + + return $sql; + } + + function _GetSize($ftype, $ty, $fsize, $fprec) + { + if (strlen($fsize) && $ty != 'X' && $ty != 'B' && $ty != 'I' && strpos($ftype,'(') === false) { + $ftype .= "(".$fsize; + if (strlen($fprec)) $ftype .= ",".$fprec; + $ftype .= ')'; + } + return $ftype; + } +} +?> diff --git a/classes/adodb-hack/drivers/datadict-sapdb.inc.php b/classes/adodb-hack/drivers/datadict-sapdb.inc.php new file mode 100644 index 000000000..8458a64a3 --- /dev/null +++ b/classes/adodb-hack/drivers/datadict-sapdb.inc.php @@ -0,0 +1,121 @@ +type; + $len = $fieldobj->max_length; + } + static $maxdb_type2adodb = array( + 'VARCHAR' => 'C', + 'CHARACTER' => 'C', + 'LONG' => 'X', // no way to differ between 'X' and 'B' :-( + 'DATE' => 'D', + 'TIMESTAMP' => 'T', + 'BOOLEAN' => 'L', + 'INTEGER' => 'I4', + 'SMALLINT' => 'I2', + 'FLOAT' => 'F', + 'FIXED' => 'N', + ); + $type = isset($maxdb_type2adodb[$t]) ? $maxdb_type2adodb[$t] : 'C'; + + // convert integer-types simulated with fixed back to integer + if ($t == 'FIXED' && !$fieldobj->scale && ($len == 20 || $len == 3)) { + $type = $len == 20 ? 'I8' : 'I1'; + } + if ($fieldobj->auto_increment) $type = 'R'; + + return $type; + } + + // return string must begin with space + function _CreateSuffix($fname,$ftype,$fnotnull,$fdefault,$fautoinc,$fconstraint,$funsigned) + { + $suffix = ''; + if ($funsigned) $suffix .= ' UNSIGNED'; + if ($fnotnull) $suffix .= ' NOT NULL'; + if ($fautoinc) $suffix .= ' DEFAULT SERIAL'; + elseif (strlen($fdefault)) $suffix .= " DEFAULT $fdefault"; + if ($fconstraint) $suffix .= ' '.$fconstraint; + return $suffix; + } + + function AddColumnSQL($tabname, $flds) + { + $tabname = $this->TableName ($tabname); + $sql = array(); + list($lines,$pkey) = $this->_GenFields($flds); + return array( 'ALTER TABLE ' . $tabname . ' ADD (' . implode(', ',$lines) . ')' ); + } + + function AlterColumnSQL($tabname, $flds) + { + $tabname = $this->TableName ($tabname); + $sql = array(); + list($lines,$pkey) = $this->_GenFields($flds); + return array( 'ALTER TABLE ' . $tabname . ' MODIFY (' . implode(', ',$lines) . ')' ); + } + + function DropColumnSQL($tabname, $flds) + { + $tabname = $this->TableName ($tabname); + if (!is_array($flds)) $flds = explode(',',$flds); + foreach($flds as $k => $v) { + $flds[$k] = $this->NameQuote($v); + } + return array( 'ALTER TABLE ' . $tabname . ' DROP (' . implode(', ',$flds) . ')' ); + } +} + +?> \ No newline at end of file diff --git a/classes/adodb-hack/drivers/datadict-sybase.inc.php b/classes/adodb-hack/drivers/datadict-sybase.inc.php new file mode 100644 index 000000000..3fa85c80f --- /dev/null +++ b/classes/adodb-hack/drivers/datadict-sybase.inc.php @@ -0,0 +1,228 @@ +type; + $len = $fieldobj->max_length; + } + + $len = -1; // mysql max_length is not accurate + switch (strtoupper($t)) { + + case 'INT': + case 'INTEGER': return 'I'; + case 'BIT': + case 'TINYINT': return 'I1'; + case 'SMALLINT': return 'I2'; + case 'BIGINT': return 'I8'; + + case 'REAL': + case 'FLOAT': return 'F'; + default: return parent::MetaType($t,$len,$fieldobj); + } + } + + function ActualType($meta) + { + switch(strtoupper($meta)) { + case 'C': return 'VARCHAR'; + case 'XL': + case 'X': return 'TEXT'; + + case 'C2': return 'NVARCHAR'; + case 'X2': return 'NTEXT'; + + case 'B': return 'IMAGE'; + + case 'D': return 'DATETIME'; + case 'T': return 'DATETIME'; + case 'L': return 'BIT'; + + case 'I': return 'INT'; + case 'I1': return 'TINYINT'; + case 'I2': return 'SMALLINT'; + case 'I4': return 'INT'; + case 'I8': return 'BIGINT'; + + case 'F': return 'REAL'; + case 'N': return 'NUMERIC'; + default: + return $meta; + } + } + + + function AddColumnSQL($tabname, $flds) + { + $tabname = $this->TableName ($tabname); + $f = array(); + list($lines,$pkey) = $this->_GenFields($flds); + $s = "ALTER TABLE $tabname $this->addCol"; + foreach($lines as $v) { + $f[] = "\n $v"; + } + $s .= implode(', ',$f); + $sql[] = $s; + return $sql; + } + + function AlterColumnSQL($tabname, $flds) + { + $tabname = $this->TableName ($tabname); + $sql = array(); + list($lines,$pkey) = $this->_GenFields($flds); + foreach($lines as $v) { + $sql[] = "ALTER TABLE $tabname $this->alterCol $v"; + } + + return $sql; + } + + function DropColumnSQL($tabname, $flds) + { + $tabname = $this->TableName($tabname); + if (!is_array($flds)) $flds = explode(',',$flds); + $f = array(); + $s = "ALTER TABLE $tabname"; + foreach($flds as $v) { + $f[] = "\n$this->dropCol ".$this->NameQuote($v); + } + $s .= implode(', ',$f); + $sql[] = $s; + return $sql; + } + + // return string must begin with space + function _CreateSuffix($fname,$ftype,$fnotnull,$fdefault,$fautoinc,$fconstraint) + { + $suffix = ''; + if (strlen($fdefault)) $suffix .= " DEFAULT $fdefault"; + if ($fautoinc) $suffix .= ' DEFAULT AUTOINCREMENT'; + if ($fnotnull) $suffix .= ' NOT NULL'; + else if ($suffix == '') $suffix .= ' NULL'; + if ($fconstraint) $suffix .= ' '.$fconstraint; + return $suffix; + } + + /* +CREATE TABLE + [ database_name.[ owner ] . | owner. ] table_name + ( { < column_definition > + | column_name AS computed_column_expression + | < table_constraint > ::= [ CONSTRAINT constraint_name ] } + + | [ { PRIMARY KEY | UNIQUE } [ ,...n ] + ) + +[ ON { filegroup | DEFAULT } ] +[ TEXTIMAGE_ON { filegroup | DEFAULT } ] + +< column_definition > ::= { column_name data_type } + [ COLLATE < collation_name > ] + [ [ DEFAULT constant_expression ] + | [ IDENTITY [ ( seed , increment ) [ NOT FOR REPLICATION ] ] ] + ] + [ ROWGUIDCOL] + [ < column_constraint > ] [ ...n ] + +< column_constraint > ::= [ CONSTRAINT constraint_name ] + { [ NULL | NOT NULL ] + | [ { PRIMARY KEY | UNIQUE } + [ CLUSTERED | NONCLUSTERED ] + [ WITH FILLFACTOR = fillfactor ] + [ON {filegroup | DEFAULT} ] ] + ] + | [ [ FOREIGN KEY ] + REFERENCES ref_table [ ( ref_column ) ] + [ ON DELETE { CASCADE | NO ACTION } ] + [ ON UPDATE { CASCADE | NO ACTION } ] + [ NOT FOR REPLICATION ] + ] + | CHECK [ NOT FOR REPLICATION ] + ( logical_expression ) + } + +< table_constraint > ::= [ CONSTRAINT constraint_name ] + { [ { PRIMARY KEY | UNIQUE } + [ CLUSTERED | NONCLUSTERED ] + { ( column [ ASC | DESC ] [ ,...n ] ) } + [ WITH FILLFACTOR = fillfactor ] + [ ON { filegroup | DEFAULT } ] + ] + | FOREIGN KEY + [ ( column [ ,...n ] ) ] + REFERENCES ref_table [ ( ref_column [ ,...n ] ) ] + [ ON DELETE { CASCADE | NO ACTION } ] + [ ON UPDATE { CASCADE | NO ACTION } ] + [ NOT FOR REPLICATION ] + | CHECK [ NOT FOR REPLICATION ] + ( search_conditions ) + } + + + */ + + /* + CREATE [ UNIQUE ] [ CLUSTERED | NONCLUSTERED ] INDEX index_name + ON { table | view } ( column [ ASC | DESC ] [ ,...n ] ) + [ WITH < index_option > [ ,...n] ] + [ ON filegroup ] + < index_option > :: = + { PAD_INDEX | + FILLFACTOR = fillfactor | + IGNORE_DUP_KEY | + DROP_EXISTING | + STATISTICS_NORECOMPUTE | + SORT_IN_TEMPDB + } +*/ + function _IndexSQL($idxname, $tabname, $flds, $idxoptions) + { + $sql = array(); + + if ( isset($idxoptions['REPLACE']) || isset($idxoptions['DROP']) ) { + $sql[] = sprintf ($this->dropIndex, $idxname, $tabname); + if ( isset($idxoptions['DROP']) ) + return $sql; + } + + if ( empty ($flds) ) { + return $sql; + } + + $unique = isset($idxoptions['UNIQUE']) ? ' UNIQUE' : ''; + $clustered = isset($idxoptions['CLUSTERED']) ? ' CLUSTERED' : ''; + + if ( is_array($flds) ) + $flds = implode(', ',$flds); + $s = 'CREATE' . $unique . $clustered . ' INDEX ' . $idxname . ' ON ' . $tabname . ' (' . $flds . ')'; + + if ( isset($idxoptions[$this->upperName]) ) + $s .= $idxoptions[$this->upperName]; + + $sql[] = $s; + + return $sql; + } +} +?> \ No newline at end of file diff --git a/classes/adodb-hack/drivers/odbc/odbc_datadict.inc b/classes/adodb-hack/drivers/odbc/odbc_datadict.inc new file mode 100644 index 000000000..0201b8721 --- /dev/null +++ b/classes/adodb-hack/drivers/odbc/odbc_datadict.inc @@ -0,0 +1,89 @@ +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/classes/adodb-hack/drivers/sqlite/sqlite_datadict.inc b/classes/adodb-hack/drivers/sqlite/sqlite_datadict.inc new file mode 100644 index 000000000..7476e512c --- /dev/null +++ b/classes/adodb-hack/drivers/sqlite/sqlite_datadict.inc @@ -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/AccessTestCase.class.php b/tests/AccessTestCase.class.php new file mode 100644 index 000000000..19961431e --- /dev/null +++ b/tests/AccessTestCase.class.php @@ -0,0 +1,39 @@ +assertEqual($this->new["name"],null); + + $this->new["name"] = "Jack"; + $this->assertEqual($this->new["name"],"Jack"); + + $this->assertEqual($this->old["name"],"zYne"); + + $this->old["name"] = "Jack"; + $this->assertEqual($this->old["name"],"Jack"); + } + public function testOverload() { + $this->assertEqual($this->new->name,null); + + $this->new->name = "Jack"; + $this->assertEqual($this->new->name,"Jack"); + + $this->assertEqual($this->old->name,"zYne"); + + $this->old->name = "Jack"; + $this->assertEqual($this->old->name,"Jack"); + } + public function testSet() { + $this->assertEqual($this->new->get("name"),null); + + $this->new->set("name","Jack"); + $this->assertEqual($this->new->get("name"),"Jack"); + + $this->assertEqual($this->old->get("name"),"zYne"); + + $this->old->set("name","Jack"); + $this->assertEqual($this->old->get("name"),"Jack"); + + $this->assertEqual($this->old->getID(),4); + } +} +?> diff --git a/tests/BatchIteratorTestCase.class.php b/tests/BatchIteratorTestCase.class.php new file mode 100644 index 000000000..52c27b8c9 --- /dev/null +++ b/tests/BatchIteratorTestCase.class.php @@ -0,0 +1,15 @@ +session); + $entities = $graph->query("FROM Entity"); + $i = 0; + foreach($entities as $entity) { + $this->assertTrue(is_string($entity->name)); + $i++; + } + $this->assertTrue($i == $entities->count()); + } +} +?> diff --git a/tests/CacheFileTestCase.class.php b/tests/CacheFileTestCase.class.php new file mode 100644 index 000000000..682ff8987 --- /dev/null +++ b/tests/CacheFileTestCase.class.php @@ -0,0 +1,56 @@ +cache->store($this->old); + $this->assertTrue($this->cache->exists(4)); + + $record = $this->cache->fetch(4); + $this->assertTrue($record instanceof Doctrine_Record); + $this->assertTrue($record->getID() == $this->old->getID()); + + $this->assertTrue($this->cache->getTable() == $this->objTable); + } + public function testGetFetched() { + $this->assertTrue(is_array($this->cache->getFetched())); + } + public function testGetFileName() { + $this->assertEqual($this->manager->getRoot().DIRECTORY_SEPARATOR."cache".DIRECTORY_SEPARATOR."entity".DIRECTORY_SEPARATOR."4.cache", $this->cache->getFileName(4)); + } + public function testGetStats() { + $this->assertTrue(gettype($this->cache->getStats()) == "array"); + } + public function testDestructor() { + $this->objTable->setAttribute(Doctrine::ATTR_CACHE_TTL,1); + $this->objTable->setAttribute(Doctrine::ATTR_CACHE_SIZE,5); + $this->cache->__destruct(); + $this->assertTrue($this->cache->count() == 5); + + $this->objTable->setAttribute(Doctrine::ATTR_CACHE_TTL,1); + $this->objTable->setAttribute(Doctrine::ATTR_CACHE_SIZE,1); + $this->cache->__destruct(); + $this->assertTrue($this->cache->count() == 1); + + } + public function testDeleteMultiple() { + $this->objTable->find(5); + $this->objTable->find(6); + + $deleted = $this->cache->deleteMultiple(array(5,6)); + $this->assertTrue($deleted == 2); + } + public function testDeleteAll() { + $this->cache->deleteAll(); + $this->assertTrue($this->cache->count() == 0); + } + public function testExists() { + $this->assertFalse($this->cache->exists(313213123)); + $this->assertTrue($this->cache->exists(4)); + } + public function testGetFactory() { + $this->assertTrue($this->cache->getTable() == $this->objTable); + } + +} +?> diff --git a/tests/CollectionTestCase.class.php b/tests/CollectionTestCase.class.php new file mode 100644 index 000000000..05ffb8c65 --- /dev/null +++ b/tests/CollectionTestCase.class.php @@ -0,0 +1,7 @@ + diff --git a/tests/CompositePrimaryKeyTestCase.class.php b/tests/CompositePrimaryKeyTestCase.class.php new file mode 100644 index 000000000..791a01bbb --- /dev/null +++ b/tests/CompositePrimaryKeyTestCase.class.php @@ -0,0 +1,3 @@ + diff --git a/tests/ConfigurableTestCase.class.php b/tests/ConfigurableTestCase.class.php new file mode 100644 index 000000000..985d2653c --- /dev/null +++ b/tests/ConfigurableTestCase.class.php @@ -0,0 +1,74 @@ +manager->setAttribute(Doctrine::ATTR_CACHE_TTL,100); + $this->assertEqual($this->manager->getAttribute(Doctrine::ATTR_CACHE_TTL),100); + + $this->manager->setAttribute(Doctrine::ATTR_CACHE_SIZE,1); + $this->assertEqual($this->manager->getAttribute(Doctrine::ATTR_CACHE_SIZE),1); + + $this->manager->setAttribute(Doctrine::ATTR_CACHE_DIR,"%ROOT%".DIRECTORY_SEPARATOR."cache"); + $this->assertEqual($this->manager->getAttribute(Doctrine::ATTR_CACHE_DIR),$this->manager->getRoot().DIRECTORY_SEPARATOR."cache"); + + $this->manager->setAttribute(Doctrine::ATTR_FETCHMODE,Doctrine::FETCH_LAZY); + $this->assertEqual($this->manager->getAttribute(Doctrine::ATTR_FETCHMODE),Doctrine::FETCH_LAZY); + + $this->manager->setAttribute(Doctrine::ATTR_BATCH_SIZE, 5); + $this->assertEqual($this->manager->getAttribute(Doctrine::ATTR_BATCH_SIZE),5); + + $this->manager->setAttribute(Doctrine::ATTR_LISTENER, new Doctrine_Debugger()); + $this->assertTrue($this->manager->getAttribute(Doctrine::ATTR_LISTENER) instanceof Doctrine_Debugger); + + $this->manager->setAttribute(Doctrine::ATTR_PK_COLUMNS, array("id")); + $this->assertEqual($this->manager->getAttribute(Doctrine::ATTR_PK_COLUMNS), array("id")); + + $this->manager->setAttribute(Doctrine::ATTR_PK_TYPE, Doctrine::INCREMENT_KEY); + $this->assertEqual($this->manager->getAttribute(Doctrine::ATTR_PK_TYPE), Doctrine::INCREMENT_KEY); + + $this->manager->setAttribute(Doctrine::ATTR_LOCKMODE, Doctrine::LOCK_PESSIMISTIC); + $this->assertEqual($this->manager->getAttribute(Doctrine::ATTR_LOCKMODE), Doctrine::LOCK_PESSIMISTIC); + + // test invalid arguments + try { + $this->manager->setAttribute(Doctrine::ATTR_CACHE_TTL,-12); + } catch(Exception $e) { + $this->assertTrue($e instanceof Exception); + } + try { + $this->manager->setAttribute(Doctrine::ATTR_CACHE_SIZE,-12); + } catch(Exception $e) { + $this->assertTrue($e instanceof Exception); + } + try { + $this->manager->setAttribute(Doctrine::ATTR_BATCH_SIZE,-12); + } catch(Exception $e) { + $this->assertTrue($e instanceof Exception); + } + + try { + $this->session->beginTransaction(); + $this->manager->setAttribute(Doctrine::ATTR_LOCKMODE, Doctrine::LOCK_OPTIMISTIC); + } catch(Exception $e) { + $this->assertTrue($e instanceof Exception); + $this->session->commit(); + } + try { + $this->session->beginTransaction(); + $this->session->setAttribute(Doctrine::ATTR_LOCKMODE, Doctrine::LOCK_PESSIMISTIC); + } catch(Exception $e) { + $this->assertTrue($e instanceof Exception); + $this->session->commit(); + } + try { + $this->manager->setAttribute(Doctrine::ATTR_PK_TYPE,-12); + } catch(Exception $e) { + $this->assertTrue($e instanceof Exception); + } + } + public function testGetAttributes() { + $this->assertTrue(is_array($this->manager->getAttributes())); + } +} +?> diff --git a/tests/DQLParserTestCase.class.php b/tests/DQLParserTestCase.class.php new file mode 100644 index 000000000..56e903d71 --- /dev/null +++ b/tests/DQLParserTestCase.class.php @@ -0,0 +1,194 @@ +session->query("FROM User WHERE User.name = :name", array(":name" => "zYne")); + $this->assertEqual($coll->count(), 1); + } + public function testQuery() { + $graph = new Doctrine_DQL_Parser($this->session); + + $this->graph = $graph; + + $user = $this->objTable->find(5); + + + $album = $this->session->create("Album"); + $album->Song[0]; + + $user->Album[0]->name = "Damage Done"; + $user->Album[1]->name = "Haven"; + + $user->Album[0]->Song[0]->title = "Damage Done"; + + + $user->Album[0]->Song[1]->title = "The Treason Wall"; + $user->Album[0]->Song[2]->title = "Monochromatic Stains"; + $this->assertEqual(count($user->Album[0]->Song), 3); + + + $user->Album[1]->Song[0]->title = "Not Built To Last"; + $user->Album[1]->Song[1]->title = "The Wonders At Your Feet"; + $user->Album[1]->Song[2]->title = "Feast Of Burden"; + $user->Album[1]->Song[3]->title = "Fabric"; + $this->assertEqual(count($user->Album[1]->Song), 4); + + $user->save(); + + $user = $this->objTable->find(5); + + $this->assertEqual(count($user->Album[0]->Song), 3); + $this->assertEqual(count($user->Album[1]->Song), 4); + + $users = $graph->query("FROM User WHERE User.Album.name like '%Damage%'"); + + + + // DYNAMIC COLLECTION EXPANDING + + $user = $this->objTable->find(5); + $user->Group[1]->name = "Tough guys inc."; + $user->Group[2]->name = "Terminators"; + $this->assertEqual($user->Group[0]->name, "Action Actors"); + $user->save(); + $this->assertEqual($user->Group[0]->name, "Action Actors"); + $this->assertEqual(count($user->Group), 3); + + $user = $this->objTable->find(5); + $this->assertEqual(count($user->Group), 3); + + $users = $graph->query("FROM User, User.Group WHERE User.Group.name LIKE 'Action Actors'"); + $this->assertEqual(count($users),1); + + //$this->assertEqual($users[0]->Group[0]->name, "Action Actors"); + //$this->assertEqual(count($users[0]->Group), 1); + + //$this->assertEqual($users[0]->Group[1]->name, "Tough guys inc."); + //$this->assertEqual($users[0]->Group[2]->name, "Terminators"); + //$this->assertEqual(count($users[0]->Group), 3); + + + $this->clearCache(); + + $users = $graph->query("FROM User, User.Phonenumber-l WHERE User.Phonenumber.phonenumber LIKE '%123%'"); + $this->assertEqual(trim($graph->getQuery()), + "SELECT entity.id AS User__id, phonenumber.id AS Phonenumber__id FROM entity LEFT JOIN phonenumber ON entity.id = phonenumber.entity_id WHERE (phonenumber.phonenumber LIKE '%123%') && (entity.type = 0)"); + + $count = $this->session->getDBH()->count(); + + $users[1]->Phonenumber[0]->phonenumber; + $users[1]->Phonenumber[1]->phonenumber; + $this->assertEqual($users[1]->Phonenumber[1]->getState(),Doctrine_Record::STATE_CLEAN); + + $users[1]->Phonenumber[2]->phonenumber; + $this->assertEqual($users[1]->Phonenumber[1]->getState(),Doctrine_Record::STATE_CLEAN); + $count2 = $this->session->getDBH()->count(); + $this->assertEqual($count + 4,$count2); + + + + // DYNAMIC FETCHMODES + try { + $users = $graph->query("FROM User-unknown"); + } catch(Exception $e) { + } + $this->assertTrue($e instanceof DQLException); + + + $users = $graph->query("FROM User-i"); + $this->assertEqual(trim($graph->getQuery()), + "SELECT entity.id AS User__id, entity.name AS User__name, entity.loginname AS User__loginname, entity.password AS User__password, entity.type AS User__type, entity.created AS User__created, entity.updated AS User__updated, entity.email_id AS User__email_id FROM entity WHERE (entity.type = 0)"); + + $count = $this->session->getDBH()->count(); + $this->assertEqual($users[0]->name, "zYne"); + + $this->assertTrue($users instanceof Doctrine_Collection_Immediate); + $count2 = $this->session->getDBH()->count(); + $this->assertEqual($count,$count2); + + + $users = $graph->query("FROM User-b"); + $this->assertEqual(trim($graph->getQuery()), + "SELECT entity.id AS User__id FROM entity WHERE (entity.type = 0)"); + + $this->assertEqual($users[0]->name, "zYne"); + $this->assertTrue($users instanceof Doctrine_Collection_Batch); + + + $users = $graph->query("FROM User-l"); + $this->assertEqual(trim($graph->getQuery()), + "SELECT entity.id AS User__id FROM entity WHERE (entity.type = 0)"); + + $this->assertEqual($users[0]->name, "zYne"); + $this->assertTrue($users instanceof Doctrine_Collection_Lazy); + + + //$this->clearCache(); + + $users = $graph->query("FROM User, User.Phonenumber"); + $this->assertEqual(trim($graph->getQuery()), + "SELECT entity.id AS User__id, phonenumber.id AS Phonenumber__id FROM entity LEFT JOIN phonenumber ON entity.id = phonenumber.entity_id WHERE (entity.type = 0)"); + + $this->assertEqual($users->count(),8); + + // EXPECTED THAT ONE NEW QUERY IS NEEDED TO GET THE FIRST USER's PHONENUMBER + + $count = $this->session->getDBH()->count(); + $users[0]->Phonenumber[0]->phonenumber; + $count2 = $this->session->getDBH()->count(); + $this->assertEqual($count + 1,$count2); + + + + + $users = $graph->query("FROM User, User.Email"); + $this->assertEqual(trim($graph->getQuery()), + "SELECT entity.id AS User__id, email.id AS Email__id FROM entity, email WHERE (entity.email_id = email.id) && (entity.type = 0)"); + + $this->assertEqual($users->count(),8); + + $users = $graph->query("FROM Email WHERE Email.address LIKE '%@example%'"); + $this->assertEqual($graph->getQuery(), + "SELECT email.id AS Email__id FROM email WHERE (email.address LIKE '%@example%')"); + $this->assertEqual($users->count(),8); + + $users = $graph->query("FROM User WHERE User.name LIKE '%Jack%'"); + $this->assertTrue($graph->getQuery() == "SELECT entity.id AS User__id FROM entity WHERE (entity.name LIKE '%Jack%') && (entity.type = 0)"); + $this->assertEqual($users->count(),0); + + + $users = $graph->query("FROM User ORDER BY User.name ASC, User.Email.address"); + $this->assertEqual(trim($graph->getQuery()), + "SELECT entity.id AS User__id FROM entity, email WHERE (entity.email_id = email.id) && (entity.type = 0) ORDER BY entity.name ASC, email.address"); + $this->assertEqual($users->count(),8); + $this->assertTrue($users[0]->name == "Arnold Schwarzenegger"); + + $users = $graph->query("FROM User WHERE User.Phonenumber.phonenumber REGEXP '[123]'"); + $this->assertEqual(trim($graph->getQuery()), + "SELECT entity.id AS User__id FROM entity LEFT JOIN phonenumber ON entity.id = phonenumber.entity_id WHERE (phonenumber.phonenumber REGEXP '[123]') && (entity.type = 0)"); + $this->assertEqual($users->count(),8); + + + $users = $graph->query("FROM User WHERE User.Group.name = 'Action Actors'"); + $this->assertEqual(trim($graph->getQuery()), + "SELECT entity.id AS User__id FROM entity WHERE (entity.id IN (SELECT user_id FROM groupuser WHERE group_id IN (SELECT entity.id AS Group__id FROM entity WHERE (entity.name = 'Action Actors') && (entity.type = 1)))) && (entity.type = 0)"); + $this->assertTrue($users instanceof Doctrine_Collection); + $this->assertEqual($users->count(),1); + + + $users = $graph->query("FROM User WHERE User.Group.Phonenumber.phonenumber LIKE '123 123'"); + $this->assertEqual(trim($graph->getQuery()), + "SELECT entity.id AS User__id FROM entity WHERE (entity.id IN (SELECT user_id FROM groupuser WHERE group_id IN (SELECT entity.id AS Group__id FROM entity, phonenumber WHERE (phonenumber.phonenumber LIKE '123 123') && (entity.type = 1)))) && (entity.type = 0)"); + $this->assertTrue($users instanceof Doctrine_Collection); + $this->assertEqual($users->count(),1); + + + + $values = $graph->query("SELECT COUNT(User.name) AS users, MAX(User.name) AS max FROM User"); + $this->assertEqual(trim($graph->getQuery()),"SELECT COUNT(entity.name) AS users, MAX(entity.name) AS max FROM entity WHERE (entity.type = 0)"); + $this->assertTrue(is_array($values)); + $this->assertTrue(isset($values['users'])); + $this->assertTrue(isset($values['max'])); + } +} +?> diff --git a/tests/EventListenerTestCase.class.php b/tests/EventListenerTestCase.class.php new file mode 100644 index 000000000..d5955506c --- /dev/null +++ b/tests/EventListenerTestCase.class.php @@ -0,0 +1,13 @@ +manager->openSession(Doctrine_DB::getConnection()); + $debug = $this->listener->getMessages(); + $last = end($debug); + $this->assertTrue($last->getObject() instanceof Doctrine_Session); + $this->assertTrue($last->getCode() == Doctrine_Debugger::EVENT_OPEN); + } +} +?> diff --git a/tests/FormBuilderTestCase.class.php b/tests/FormBuilderTestCase.class.php new file mode 100644 index 000000000..af6308785 --- /dev/null +++ b/tests/FormBuilderTestCase.class.php @@ -0,0 +1,7 @@ + diff --git a/tests/ManagerTestCase.class.php b/tests/ManagerTestCase.class.php new file mode 100644 index 000000000..67dcab4bc --- /dev/null +++ b/tests/ManagerTestCase.class.php @@ -0,0 +1,23 @@ +assertTrue(Doctrine_Manager::getInstance() instanceOf Doctrine_Manager); + } + public function testOpenSession() { + $this->assertTrue($this->session instanceOf Doctrine_Session); + } + public function testGetIterator() { + $this->assertTrue($this->manager->getIterator() instanceof ArrayIterator); + } + public function testCount() { + $this->assertEqual(count($this->manager),1); + } + public function testGetCurrentSession() { + $this->assertEqual($this->manager->getCurrentSession(), $this->session); + } + public function testGetSessions() { + $this->assertEqual(count($this->manager->getSessions()),1); + } +} +?> diff --git a/tests/RecordTestCase.class.php b/tests/RecordTestCase.class.php new file mode 100644 index 000000000..f0403e603 --- /dev/null +++ b/tests/RecordTestCase.class.php @@ -0,0 +1,440 @@ +name = "Jack Daniels"; + $this->assertEqual($user->name, "Jack Daniels"); + $this->assertEqual($user->created, null); + $this->assertEqual($user->updated, null); + $user->save(); + $id = $user->getID(); + $user = $user->getTable()->find($id); + $this->assertEqual($user->name, "Jack Daniels"); + $this->assertEqual($user->created, null); + $this->assertEqual($user->updated, null); + + } + public function testNewOperator() { + $user = new User(); + $this->assertTrue($user->getState() == Doctrine_Record::STATE_TCLEAN); + $user->name = "John Locke"; + + $this->assertTrue($user->name,"John Locke"); + $this->assertTrue($user->getState() == Doctrine_Record::STATE_TDIRTY); + $user->save(); + $this->assertTrue($user->getState() == Doctrine_Record::STATE_CLEAN); + $this->assertTrue($user->name,"John Locke"); + } + public function testTreeStructure() { + $e = new Element(); + $e->name = "parent"; + $e->Element[0]->name = "child 1"; + $e->Element[1]->name = "child 2"; + $e->Element[1]->Element[0]->name = "child 1's child 1"; + $e->Element[1]->Element[1]->name = "child 1's child 1"; + + $this->assertEqual($e->name,"parent"); + $this->assertEqual($e->Element[0]->name,"child 1"); + $this->assertEqual($e->Element[1]->name,"child 2"); + $this->assertEqual($e->Element[1]->Element[0]->name,"child 1's child 1"); + $this->assertEqual($e->Element[1]->Element[1]->name,"child 1's child 1"); + + $this->session->flush(); + + $e = $e->getTable()->find($e->getID()); + $this->assertEqual($e->name,"parent"); + $this->assertEqual($e->Element[0]->name,"child 1"); + $this->assertEqual($e->Element[1]->name,"child 2"); + $this->assertEqual($e->Element[1]->Element[0]->name,"child 1's child 1"); + $this->assertEqual($e->Element[1]->Element[1]->name,"child 1's child 1"); + } + public function testUniqueKeyComponent() { + $e = new Error(); + $e->message = "user error"; + $e->file_md5 = md5(0); + $e->code = 1; + + /** + * ADDING NEW RECORD + */ + + $this->assertEqual($e->code,1); + $this->assertEqual($e->file_md5, md5(0)); + $this->assertEqual($e->message, "user error"); + + $e2 = new Error(); + $e2->message = "user error2"; + $e2->file_md5 = md5(1); + $e2->code = 2; + + $this->assertEqual($e2->code,2); + $this->assertEqual($e2->file_md5, md5(1)); + $this->assertEqual($e2->message, "user error2"); + + + $fk = $e->getTable()->getForeignKey("Description"); + $this->assertTrue($fk instanceof Doctrine_ForeignKey); + $this->assertEqual($fk->getLocal(),"file_md5"); + $this->assertEqual($fk->getForeign(),"file_md5"); + $this->assertTrue($fk->getTable() instanceof Doctrine_Table); + + $e->Description[0]->description = "This is the 1st description"; + $e->Description[1]->description = "This is the 2nd description"; + $this->assertEqual($e->Description[0]->description, "This is the 1st description"); + $this->assertEqual($e->Description[1]->description, "This is the 2nd description"); + $this->assertEqual($e->Description[0]->file_md5, $e->file_md5); + $this->assertEqual($e->Description[1]->file_md5, $e->file_md5); + + $this->assertEqual($e2->Description[0]->description, null); + $this->assertEqual($e2->Description[1]->description, null); + $this->assertEqual($e2->Description[0]->file_md5, $e2->file_md5); + $this->assertEqual($e2->Description[1]->file_md5, $e2->file_md5); + + $e->save(); + + $coll = $this->session->query("FROM Error-I"); + $e = $coll[0]; + + + $this->assertEqual($e->code,1); + $this->assertEqual($e->file_md5, md5(0)); + $this->assertEqual($e->message, "user error"); + + $this->assertTrue($e->Description instanceof Doctrine_Collection); + $this->assertTrue($e->Description[0] instanceof Description); + $this->assertTrue($e->Description[1] instanceof Description); + + $this->assertEqual($e->Description[0]->description, "This is the 1st description"); + $this->assertEqual($e->Description[1]->description, "This is the 2nd description"); + + /** + * UPDATING + */ + + $e->code = 2; + $e->message = "changed message"; + $e->Description[0]->description = "1st changed description"; + $e->Description[1]->description = "2nd changed description"; + + + $this->assertEqual($e->code,2); + $this->assertEqual($e->message,"changed message"); + $this->assertEqual($e->Description[0]->description, "1st changed description"); + $this->assertEqual($e->Description[1]->description, "2nd changed description"); + + $e->save(); + $this->assertEqual($e->code,2); + $this->assertEqual($e->message,"changed message"); + $this->assertEqual($e->Description[0]->description, "1st changed description"); + $this->assertEqual($e->Description[1]->description, "2nd changed description"); + + + + } + + public function testInsert() { + $this->new->name = "John Locke"; + $this->new->save(); + + $this->assertTrue($this->new->getModified() == array()); + $this->assertTrue($this->new->getState() == Doctrine_Record::STATE_CLEAN); + + $debug = $this->listener->getMessages(); + $p = array_pop($debug); + $this->assertTrue($p->getObject() instanceof Doctrine_Session); + $this->assertTrue($p->getCode() == Doctrine_Debugger::EVENT_COMMIT); + + $p = array_pop($debug); + + $this->assertTrue($p->getObject() instanceof Doctrine_Record); + $this->assertTrue($p->getCode() == Doctrine_Debugger::EVENT_SLEEP); + + $p = array_pop($debug); + $this->assertTrue($p->getObject() instanceof Doctrine_Record); + $this->assertTrue($p->getCode() == Doctrine_Debugger::EVENT_SAVE); + + $p = array_pop($debug); + $this->assertTrue($p->getObject() instanceof Doctrine_Record); + $this->assertTrue($p->getCode() == Doctrine_Debugger::EVENT_INSERT); + + + $p = array_pop($debug); + $this->assertTrue($p->getObject() instanceof Doctrine_Record); + $this->assertTrue($p->getCode() == Doctrine_Debugger::EVENT_PREINSERT); + + $this->new->delete(); + $this->assertTrue($this->new->getState() == Doctrine_Record::STATE_TCLEAN); + } + + public function testUpdate() { + $this->old->set("name","Jack Daniels",true); + + + $this->old->save(true); + //print $this->old->name; + + $this->assertEqual($this->old->getModified(), array()); + $this->assertEqual($this->old->name, "Jack Daniels"); + + $debug = $this->listener->getMessages(); + $p = array_pop($debug); + $this->assertTrue($p->getObject() instanceof Doctrine_Session); + $this->assertTrue($p->getCode() == Doctrine_Debugger::EVENT_COMMIT); + + $p = array_pop($debug); + $this->assertTrue($p->getObject() instanceof Doctrine_Record); + $this->assertTrue($p->getCode() == Doctrine_Debugger::EVENT_SAVE); + + $p = array_pop($debug); + $this->assertTrue($p->getObject() instanceof Doctrine_Record); + $this->assertTrue($p->getCode() == Doctrine_Debugger::EVENT_UPDATE); + + + } + public function testCopy() { + $new = $this->old->copy(); + $this->assertTrue($new instanceof Doctrine_Record); + $this->assertTrue($new->getState() == Doctrine_Record::STATE_TDIRTY); + } + + public function testReferences() { + + $user = $this->objTable->find(5); + + $pf = $this->session->getTable("Phonenumber"); + + $this->assertTrue($user->Phonenumber instanceof Doctrine_Collection); + $this->assertTrue($user->Phonenumber->count() == 3); + + $coll = new Doctrine_Collection($pf); + + $user->Phonenumber = $coll; + $this->assertTrue($user->Phonenumber->count() == 0); + $user->save(); + unset($user); + $user = $this->objTable->find(5); + + $this->assertEqual($user->Phonenumber->count(), 0); + + // ADDING REFERENCES + + $user->Phonenumber[0]->phonenumber = "123 123"; + $this->assertEqual(gettype($user->Phonenumber[0]->entity_id),"integer"); + + $user->Phonenumber[1]->phonenumber = "123 123"; + $user->save(); + + + $this->assertTrue($user->Phonenumber->count() == 2); + + unset($user); + $user = $this->objTable->find(5); + $this->assertEqual($user->Phonenumber->count(), 2); + + $user->Phonenumber[3]->phonenumber = "123 123"; + $user->save(); + + $this->assertTrue($user->Phonenumber->count() == 3); + unset($user); + $user = $this->objTable->find(5); + $this->assertTrue($user->Phonenumber->count() == 3); + + // DELETING REFERENCES + + $user->Phonenumber->delete(); + + $this->assertTrue($user->Phonenumber->count() == 0); + unset($user); + $user = $this->objTable->find(5); + $this->assertTrue($user->Phonenumber->count() == 0); + + // ADDING REFERENCES WITH STRING KEYS + + $user->Phonenumber["home"]->phonenumber = "123 123"; + $user->Phonenumber["work"]->phonenumber = "444 444"; + $user->save(); + + $this->assertTrue($user->Phonenumber->count() == 2); + unset($user); + $user = $this->objTable->find(5); + $this->assertTrue($user->Phonenumber->count() == 2); + + // REPLACING ONE-TO-MANY REFERENCE + unset($coll); + $coll = new Doctrine_Collection($pf); + $coll[0]->phonenumber = "123 123"; + $coll["home"]->phonenumber = "444 444"; + $coll["work"]->phonenumber = "444 444"; + + $user->Phonenumber = $coll; + $user->save(); + $this->assertEqual($user->Phonenumber->count(), 3); + $user = $this->objTable->find(5); + $this->assertEqual($user->Phonenumber->count(), 3); + + + // ONE-TO-ONE REFERENCES + + $user->Email->address = "drinker@drinkmore.info"; + $this->assertTrue($user->Email instanceof Email); + $user->save(); + $this->assertTrue($user->Email instanceof Email); + $user = $this->objTable->find(5); + $this->assertEqual($user->Email->address, "drinker@drinkmore.info"); + $id = $user->Email->getID(); + + // REPLACING ONE-TO-ONE REFERENCES + + $email = $this->session->create("Email"); + $email->address = "absolutist@nottodrink.com"; + $user->Email = $email; + + $this->assertTrue($user->Email instanceof Email); + $this->assertEqual($user->Email->address, "absolutist@nottodrink.com"); + $user->save(); + unset($user); + + $user = $this->objTable->find(5); + $this->assertTrue($user->Email instanceof Email); + $this->assertEqual($user->Email->address, "absolutist@nottodrink.com"); + + $emails = $this->session->query("FROM Email WHERE Email.id = $id"); + $this->assertEqual(count($emails),0); + } + + public function testDeleteReference() { + $user = $this->objTable->find(5); + $int = $user->Phonenumber->delete(); + + $this->assertTrue($user->Phonenumber->count() == 0); + } + + public function testSaveAssociations() { + $user = $this->objTable->find(5); + + $gf = $this->session->getTable("Group"); + + $this->assertTrue($user->Group instanceof Doctrine_Collection); + + + // ADDING ASSOCIATED REFERENCES + + + $record = $gf->find(1); + $record2 = $gf->find(2); + $user->Group[0] = $record; + $user->Group[1] = $record2; + + $this->assertTrue($user->Group->count() == 2); + + $user->save(); + + + // UNSETTING ASSOCIATED REFERENCES + + + unset($user); + $user = $this->objTable->find(5); + $this->assertTrue($user->Group->count() == 2); + + unset($user->Group[0]); + $this->assertTrue($user->Group->count() == 1); + + unset($user->Group[1]); + $this->assertTrue($user->Group->count() == 0); + + $user->save(); + $this->assertTrue($user->Group->count() == 0); + unset($user); + + + // CHECKING THE PERSISTENCE OF UNSET ASSOCIATED REFERENCES + + + $user = $this->objTable->find(5); + $this->assertTrue($user->Group->count() == 0); + + + // REPLACING OLD ASSOCIATED REFERENCE + + + $user->Group[0] = $record; + $user->save(); + + $user->Group[0] = $record2; + $user->save(); + + $this->assertEqual($user->Group->count(), 1); + $this->assertEqual($user->Group[0]->getID(), $record2->getID()); + $this->assertFalse($user->Group[0]->getID() == $record->getID()); + + + $user->Group[0] = $record; + $user->Group[1] = $gf->find(3); + + $user->save(); + $this->assertEqual($user->Group->count(), 2); + $user = $this->objTable->find(5); + $this->assertEqual($user->Group->count(), 2); + + + + $user->Group = new Doctrine_Collection($gf); + $user->save(); + $this->assertEqual($user->Group->count(), 0); + $user = $this->objTable->find(5); + $this->assertEqual($user->Group->count(), 0); + + + // ACCESSING ASSOCIATION OBJECT PROPERTIES + + $user = new User(); + $this->assertTrue($user->getTable()->getForeignKey("Groupuser") instanceof Doctrine_ForeignKey); + $this->assertTrue($user->Groupuser instanceof Doctrine_Collection); + $this->assertTrue($user->Groupuser[0] instanceof Groupuser); + + $user->name = "Jack Daniels"; + $user->Group[0]->name = "Group #1"; + $user->Group[1]->name = "Group #2"; + $t1 = time(); + $t2 = time(); + $user->Groupuser[0]->added = $t1; + $user->Groupuser[1]->added = $t2; + + $this->assertEqual($user->Groupuser[0]->added, $t1); + $this->assertEqual($user->Groupuser[1]->added, $t2); + + $user->save(); + + $user->refresh(); + $this->assertEqual($user->Groupuser[0]->added, $t1); + $this->assertEqual($user->Groupuser[1]->added, $t2); + } + + public function testCount() { + $this->assertTrue(is_integer($this->old->count())); + } + + public function testGetReference() { + $this->assertTrue($this->old->Email instanceof Doctrine_Record); + $this->assertTrue($this->old->Phonenumber instanceof Doctrine_Collection); + $this->assertTrue($this->old->Group instanceof Doctrine_Collection); + + $this->assertTrue($this->old->Phonenumber->count() == 1); + } + + public function testSerialize() { + $old = $this->old; + $old = serialize($old); + + $this->assertEqual(unserialize($old)->getID(),$this->old->getID()); + } + + public function testGetIterator() { + $this->assertTrue($this->old->getIterator() instanceof ArrayIterator); + } + +} +?> diff --git a/tests/RepositoryTestCase.class.php b/tests/RepositoryTestCase.class.php new file mode 100644 index 000000000..328767413 --- /dev/null +++ b/tests/RepositoryTestCase.class.php @@ -0,0 +1,7 @@ + diff --git a/tests/SessionTestCase.class.php b/tests/SessionTestCase.class.php new file mode 100644 index 000000000..acf2107e4 --- /dev/null +++ b/tests/SessionTestCase.class.php @@ -0,0 +1,263 @@ +session->getTable("User"); + $this->assertTrue($objTable instanceOf Doctrine_Table); + } + public function testFlush() { + + $this->assertEqual(gettype($this->old->Phonenumber[0]->entity_id), "integer"); + + $user = $this->session->create("Email"); + $user = $this->session->create("User"); + $record = $this->session->create("Phonenumber"); + + $user->Email->address = "example@drinkmore.info"; + $this->assertTrue($user->email_id instanceof Email); + + $user->name = "Example user"; + $user->Group[0]->name = "Example group 1"; + $user->Group[1]->name = "Example group 2"; + + $user->Phonenumber[0]->phonenumber = "123 123"; + + $user->Phonenumber[1]->phonenumber = "321 2132"; + $user->Phonenumber[2]->phonenumber = "123 123"; + $user->Phonenumber[3]->phonenumber = "321 2132"; + + + + $this->assertTrue($user->Phonenumber[0]->entity_id instanceof User); + $this->assertTrue($user->Phonenumber[2]->entity_id instanceof User); + + $this->session->flush(); + + $this->assertTrue(gettype($user->getID()) == "integer"); + + $this->assertTrue(gettype($user->email_id) == "integer"); + $this->assertTrue(gettype($user->Phonenumber[0]->entity_id) == "integer"); + + $this->assertEqual(count($user->Group), 2); + + $user = $this->objTable->find(12); + + $this->assertEqual($user->getID(), 12); + + $this->assertTrue(gettype($user->getID()) == "integer"); + $this->assertTrue(gettype($user->email_id) == "integer"); + + $this->assertEqual(gettype($user->Phonenumber[0]->entity_id), "integer"); + $this->assertTrue($user->Phonenumber->count(), 4); + $this->assertEqual($user->Group->count(), 2); + + $user = $this->objTable->find(5); + + $pf = $this->session->getTable("Phonenumber"); + + $this->assertTrue($user->Phonenumber instanceof Doctrine_Collection); + $this->assertTrue($user->Phonenumber->count() == 3); + + $coll = new Doctrine_Collection($pf); + + $user->Phonenumber = $coll; + $this->assertTrue($user->Phonenumber->count() == 0); + + $this->session->flush(); + unset($user); + $user = $this->objTable->find(5); + + $this->assertEqual($user->Phonenumber->count(), 0); + + // ADDING REFERENCES + + $user->Phonenumber[0]->phonenumber = "123 123"; + $this->assertEqual(gettype($user->Phonenumber[0]->entity_id),"integer"); + + $user->Phonenumber[1]->phonenumber = "123 123"; + $this->session->flush(); + + + $this->assertTrue($user->Phonenumber->count() == 2); + + unset($user); + $user = $this->objTable->find(5); + $this->assertEqual($user->Phonenumber->count(), 2); + + $user->Phonenumber[3]->phonenumber = "123 123"; + $this->session->flush(); + + $this->assertTrue($user->Phonenumber->count() == 3); + unset($user); + $user = $this->objTable->find(5); + $this->assertTrue($user->Phonenumber->count() == 3); + + // DELETING REFERENCES + + $user->Phonenumber->delete(); + + $this->assertTrue($user->Phonenumber->count() == 0); + unset($user); + $user = $this->objTable->find(5); + $this->assertTrue($user->Phonenumber->count() == 0); + + // ADDING REFERENCES WITH STRING KEYS + + $user->Phonenumber["home"]->phonenumber = "123 123"; + $user->Phonenumber["work"]->phonenumber = "444 444"; + + $this->assertEqual($user->Phonenumber->count(), 2); + $this->session->flush(); + + $this->assertTrue($user->Phonenumber->count() == 2); + unset($user); + $user = $this->objTable->find(5); + $this->assertTrue($user->Phonenumber->count() == 2); + + // REPLACING ONE-TO-MANY REFERENCE + + unset($coll); + $coll = new Doctrine_Collection($pf); + $coll[0]->phonenumber = "123 123"; + $coll["home"]->phonenumber = "444 444"; + $coll["work"]->phonenumber = "444 444"; + + + + + $user->Phonenumber = $coll; + $this->session->flush(); + $this->assertEqual($user->Phonenumber->count(), 3); + $user = $this->objTable->find(5); + $this->assertEqual($user->Phonenumber->count(), 3); + + + // ONE-TO-ONE REFERENCES + + $user->Email->address = "drinker@drinkmore.info"; + $this->assertTrue($user->Email instanceof Email); + $this->session->flush(); + $this->assertTrue($user->Email instanceof Email); + $user = $this->objTable->find(5); + $this->assertEqual($user->Email->address, "drinker@drinkmore.info"); + $id = $user->Email->getID(); + + // REPLACING ONE-TO-ONE REFERENCES + + $email = $this->session->create("Email"); + $email->address = "absolutist@nottodrink.com"; + $user->Email = $email; + + $this->assertTrue($user->Email instanceof Email); + $this->assertEqual($user->Email->address, "absolutist@nottodrink.com"); + $this->session->flush(); + unset($user); + + $user = $this->objTable->find(5); + $this->assertTrue($user->Email instanceof Email); + $this->assertEqual($user->Email->address, "absolutist@nottodrink.com"); + + $emails = $this->session->query("FROM Email WHERE Email.id = $id"); + $this->assertEqual(count($emails),0); + + + } + + public function testGetManager() { + $this->assertEqual($this->session->getManager(),$this->manager); + } + public function testQuery() { + $this->assertTrue($this->session->query("FROM User") instanceof Doctrine_Collection); + } + + public function testDelete() { + $user = $this->session->create("User"); + $this->session->delete($user); + $this->assertEqual($user->getState(),Doctrine_Record::STATE_TCLEAN); + } + public function testGetTable() { + $table = $this->session->getTable("Group"); + $this->assertTrue($table instanceof Doctrine_Table); + try { + $table = $this->session->getTable("Unknown"); + $f = false; + } catch(Doctrine_Exception $e) { + $f = true; + } + $this->assertTrue($f); + + $table = $this->session->getTable("User"); + $this->assertTrue($table instanceof UserTable); + + } + public function testCreate() { + $email = $this->session->create("Email"); + $this->assertTrue($email instanceof Email); + } + public function testGetDBH() { + $this->assertTrue($this->session->getDBH() instanceof PDO); + } + public function testCount() { + $this->assertTrue(is_integer(count($this->session))); + } + public function testGetIterator() { + $this->assertTrue($this->session->getIterator() instanceof ArrayIterator); + } + public function testGetState() { + $this->assertEqual($this->session->getState(),Doctrine_Session::STATE_OPEN); + $this->assertEqual(Doctrine_Lib::getSessionStateAsString($this->session->getState()), "open"); + } + public function testGetTables() { + $this->assertTrue(is_array($this->session->getTables())); + } + + public function testTransactions() { + + $this->session->beginTransaction(); + $this->assertEqual($this->session->getState(),Doctrine_Session::STATE_ACTIVE); + $this->session->commit(); + $this->assertEqual($this->session->getState(),Doctrine_Session::STATE_OPEN); + + $this->session->beginTransaction(); + + $user = $this->objTable->find(6); + + $user->name = "Jack Daniels"; + $this->session->flush(); + $this->session->commit(); + + $user = $this->objTable->find(6); + $this->assertEqual($user->name, "Jack Daniels"); + + } + + public function testRollback() { + $this->session->beginTransaction(); + $this->assertEqual($this->session->getTransactionLevel(),1); + $this->assertEqual($this->session->getState(),Doctrine_Session::STATE_ACTIVE); + $this->session->rollback(); + $this->assertEqual($this->session->getState(),Doctrine_Session::STATE_OPEN); + $this->assertEqual($this->session->getTransactionLevel(),0); + } + public function testNestedTransactions() { + $this->assertEqual($this->session->getTransactionLevel(),0); + $this->session->beginTransaction(); + $this->assertEqual($this->session->getTransactionLevel(),1); + $this->assertEqual($this->session->getState(),Doctrine_Session::STATE_ACTIVE); + $this->session->beginTransaction(); + $this->assertEqual($this->session->getState(),Doctrine_Session::STATE_BUSY); + $this->assertEqual($this->session->getTransactionLevel(),2); + $this->session->commit(); + $this->assertEqual($this->session->getState(),Doctrine_Session::STATE_ACTIVE); + $this->assertEqual($this->session->getTransactionLevel(),1); + $this->session->commit(); + $this->assertEqual($this->session->getState(),Doctrine_Session::STATE_OPEN); + $this->assertEqual($this->session->getTransactionLevel(),0); + } + public function testClear() { + $this->session->clear(); + $this->assertEqual($this->session->getTables(), array()); + } + +} +?> diff --git a/tests/TableTestCase.class.php b/tests/TableTestCase.class.php new file mode 100644 index 000000000..80c180a73 --- /dev/null +++ b/tests/TableTestCase.class.php @@ -0,0 +1,94 @@ +objTable->getForeignKey("Group"); + $this->assertTrue($fk instanceof Doctrine_Association); + $this->assertTrue($fk->getTable() instanceof Doctrine_Table); + $this->assertTrue($fk->getType() == Doctrine_Table::MANY_AGGREGATE); + $this->assertTrue($fk->getLocal() == "user_id"); + $this->assertTrue($fk->getForeign() == "group_id"); + + $fk = $this->objTable->getForeignKey("Email"); + $this->assertTrue($fk instanceof Doctrine_LocalKey); + $this->assertTrue($fk->getTable() instanceof Doctrine_Table); + $this->assertTrue($fk->getType() == Doctrine_Table::ONE_COMPOSITE); + $this->assertTrue($fk->getLocal() == "email_id"); + $this->assertTrue($fk->getForeign() == "id"); + + + $fk = $this->objTable->getForeignKey("Phonenumber"); + $this->assertTrue($fk instanceof Doctrine_ForeignKey); + $this->assertTrue($fk->getTable() instanceof Doctrine_Table); + $this->assertTrue($fk->getType() == Doctrine_Table::MANY_COMPOSITE); + $this->assertTrue($fk->getLocal() == "id"); + $this->assertTrue($fk->getForeign() == "entity_id"); + } + public function testGetComponentName() { + $this->assertTrue($this->objTable->getComponentName() == "User"); + } + public function testGetTableName() { + $this->assertTrue($this->objTable->getTableName() == "entity"); + } + public function testGetSession() { + $this->assertTrue($this->objTable->getSession() instanceof Doctrine_Session); + } + public function testGetCache() { + $this->assertTrue($this->objTable->getCache() instanceof Doctrine_Cache_File); + } + public function testGetData() { + $this->assertTrue($this->objTable->getData() == array()); + } + public function testSetSequenceName() { + $this->objTable->setSequenceName("test-seq"); + $this->assertEqual($this->objTable->getSequenceName(),"test-seq"); + $this->objTable->setSequenceName(null); + } + public function testCreate() { + $record = $this->objTable->create(); + $this->assertTrue($record instanceof Doctrine_Record); + $this->assertTrue($record->getState() == Doctrine_Record::STATE_TCLEAN); + } + public function testFind() { + $record = $this->objTable->find(4); + $this->assertTrue($record instanceof Doctrine_Record); + + try { + $record = $this->objTable->find(123); + } catch(Exception $e) { + $this->assertTrue($e instanceOf Doctrine_Find_Exception); + } + } + public function testFindAll() { + $users = $this->objTable->findAll(); + $this->assertEqual($users->count(), 8); + $this->assertTrue($users instanceof Doctrine_Collection_Batch); + } + public function testFindBySql() { + $users = $this->objTable->findBySql("name LIKE '%Arnold%'"); + $this->assertEqual($users->count(), 1); + $this->assertTrue($users instanceof Doctrine_Collection_Batch); + } + public function testGetProxy() { + $user = $this->objTable->getProxy(4); + $this->assertTrue($user instanceof Doctrine_Record); + + try { + $record = $this->objTable->find(123); + } catch(Exception $e) { + $this->assertTrue($e instanceOf Doctrine_Find_Exception); + } + } + public function testGetColumns() { + $columns = $this->objTable->getColumns(); + $this->assertTrue(is_array($columns)); + + } + public function testIsNewEntry() { + $this->assertFalse($this->objTable->isNewEntry()); + } + public function testApplyInheritance() { + $this->assertEqual($this->objTable->applyInheritance("id = 3"), "id = 3 && type = ?"); + } +} +?> diff --git a/tests/UnitTestCase.class.php b/tests/UnitTestCase.class.php new file mode 100644 index 000000000..84f8e5131 --- /dev/null +++ b/tests/UnitTestCase.class.php @@ -0,0 +1,146 @@ +manager = Doctrine_Manager::getInstance(); + + + if($this->manager->count() > 0) { + $this->session = $this->manager->getSession(0); + $this->session->clear(); + $this->dbh = $this->session->getDBH(); + $this->listener = $this->manager->getAttribute(Doctrine::ATTR_LISTENER); + } else { + $this->dbh = Doctrine_DB::getConnection(); + $this->session = $this->manager->openSession($this->dbh); + $this->listener = new Doctrine_Debugger(); + $this->manager->setAttribute(Doctrine::ATTR_LISTENER, $this->listener); + } + + $this->tables = array("entity","email","phonenumber","groupuser","album","song","element","error","description"); + $tables = $this->tables; + foreach($tables as $name) { + $this->dbh->query("DROP TABLE IF EXISTS $name"); + } + + 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->prepareData(); + } + public function prepareData() { + $groups = new Doctrine_Collection($this->session->getTable("Group")); + + $groups[0]->name = "Drama Actors"; + $groups[0]->save(); + + $groups[1]->name = "Quality Actors"; + $groups[1]->save(); + + $groups[2]->name = "Action Actors"; + $groups[2]["Phonenumber"][0]->phonenumber = "123 123"; + $groups[2]->save(); + + $users = new Doctrine_Collection($this->session->getTable("User")); + + + $users[0]->name = "zYne"; + $users[0]["Email"]->address = "zYne@example.com"; + $users[0]["Phonenumber"][0]->phonenumber = "123 123"; + + $users[1]->name = "Arnold Schwarzenegger"; + $users[1]->Email->address = "arnold@example.com"; + $users[1]["Phonenumber"][0]->phonenumber = "123 123"; + $users[1]["Phonenumber"][1]->phonenumber = "456 456"; + $users[1]->Phonenumber[2]->phonenumber = "789 789"; + $users[1]->Group[0] = $groups[2]; + + $users[2]->name = "Michael Caine"; + $users[2]->Email->address = "caine@example.com"; + $users[2]->Phonenumber[0]->phonenumber = "123 123"; + + $users[3]->name = "Takeshi Kitano"; + $users[3]->Email->address = "kitano@example.com"; + $users[3]->Phonenumber[0]->phonenumber = "111 222 333"; + + $users[4]->name = "Sylvester Stallone"; + $users[4]->Email->address = "stallone@example.com"; + $users[4]->Phonenumber[0]->phonenumber = "111 555 333"; + $users[4]["Phonenumber"][1]->phonenumber = "123 213"; + $users[4]["Phonenumber"][2]->phonenumber = "444 555"; + + $users[5]->name = "Kurt Russell"; + $users[5]->Email->address = "russell@example.com"; + $users[5]->Phonenumber[0]->phonenumber = "111 222 333"; + + $users[6]->name = "Jean Reno"; + $users[6]->Email->address = "reno@example.com"; + $users[6]->Phonenumber[0]->phonenumber = "111 222 333"; + $users[6]["Phonenumber"][1]->phonenumber = "222 123"; + $users[6]["Phonenumber"][2]->phonenumber = "123 456"; + + $users[7]->name = "Edward Furlong"; + $users[7]->Email->address = "furlong@example.com"; + $users[7]->Phonenumber[0]->phonenumber = "111 567 333"; + + $this->users = $users; + $this->session->flush(); + } + public function clearCache() { + foreach($this->tables as $name) { + $table = $this->session->getTable($name); + $table->getCache()->deleteAll(); + } + } + public function setUp() { + if( ! $this->init) $this->init(); + + $this->init = true; + $this->new = $this->objTable->create(); + $this->old = $this->objTable->find(4); + } +} +?> diff --git a/tests/ValidatorTestCase.class.php b/tests/ValidatorTestCase.class.php new file mode 100644 index 000000000..d04c0bd92 --- /dev/null +++ b/tests/ValidatorTestCase.class.php @@ -0,0 +1,73 @@ + "this is an example of too long password", + "loginname" => "this is an example of too long loginname", + "name" => "valid name", + "created" => "invalid"); + $this->old->setArray($set); + $email = $this->old->Email; + $email->address = "zYne@invalid"; + + $this->assertTrue($this->old->isLoaded()); + $this->assertTrue($this->old->getModified() == $set); + + $validator = new Doctrine_Validator(); + $validator->validateRecord($this->old); + $validator->validateRecord($email); + + $stack = $validator->getErrorStack(); + + $this->assertTrue(is_array($stack)); + $this->assertEqual($stack["User"][0]["loginname"], Doctrine_Validator::ERR_LENGTH); + $this->assertEqual($stack["User"][0]["password"], Doctrine_Validator::ERR_LENGTH); + $this->assertEqual($stack["User"][0]["created"], Doctrine_Validator::ERR_TYPE); + + $this->assertEqual($stack["Email"][0]["address"], Doctrine_Validator::ERR_VALID); + $email->address = "arnold@example.com"; + + $validator->validateRecord($email); + $stack = $validator->getErrorStack(); + $this->assertEqual($stack["Email"][1]["address"], Doctrine_Validator::ERR_UNIQUE); + + } + public function testIsValidEmail() { + + $validator = new Doctrine_Validator_Email(); + + $email = $this->session->create("Email"); + $this->assertFalse($validator->validate($email,"address","example@example")); + $this->assertFalse($validator->validate($email,"address","example@@example")); + $this->assertFalse($validator->validate($email,"address","example@example.")); + $this->assertFalse($validator->validate($email,"address","example@e..")); + + $this->assertFalse($validator->validate($email,"address","example@e..")); + $this->assertTrue($validator->validate($email,"address","example@e.e.e.e.e")); + + } + public function testSave() { + $this->manager->setAttribute(Doctrine::ATTR_VLD, true); + + try { + $this->old->name = "this is an example of too long name not very good example but an example nevertheless"; + $this->old->save(); + } catch(Doctrine_Validator_Exception $e) { + $this->assertEqual($e->getErrorStack(),array("User" => array(array("name" => 0)))); + } + + try { + $user = $this->session->create("User"); + $user->Email->address = "jackdaniels@drinkmore.info..."; + $user->name = "this is an example of too long user name not very good example but an example nevertheles"; + $user->save(); + } catch(Doctrine_Validator_Exception $e) { + $a = $e->getErrorStack(); + } + $this->assertTrue(is_array($a)); + $this->assertEqual($a["Email"][0]["address"], Doctrine_Validator::ERR_VALID); + $this->assertEqual($a["User"][0]["name"], Doctrine_Validator::ERR_LENGTH); + } + +} +?> diff --git a/tests/classes.php b/tests/classes.php new file mode 100644 index 000000000..9aba371f9 --- /dev/null +++ b/tests/classes.php @@ -0,0 +1,103 @@ +ownsOne("Email","Entity.email_id"); + $this->ownsMany("Phonenumber","Phonenumber.entity_id"); + $this->setAttribute(Doctrine::ATTR_FETCHMODE,Doctrine::FETCH_BATCH); + } + public function setTableDefinition() { + $this->hasColumn("name","string",50); + $this->hasColumn("loginname","string",20); + $this->hasColumn("password","string",16); + $this->hasColumn("type","integer",1); + $this->hasColumn("created","integer",11); + $this->hasColumn("updated","integer",11); + $this->hasColumn("email_id","integer"); + } +} + +// grouptable doesn't extend Doctrine_Table -> Doctrine_Session +// won't initialize grouptable when Doctrine_Session->getTable("Group") is called + +class GroupTable { } +class Group extends Entity { + public function setUp() { + parent::setUp(); + $this->hasMany("User","Groupuser.user_id"); + $this->setInheritanceMap(array("type"=>1)); + + } +} +class Error extends Doctrine_Record { + public function setUp() { + $this->ownsMany("Description","Description.file_md5","file_md5"); + } + public function setTableDefinition() { + $this->hasColumn("message","string",200); + $this->hasColumn("code","integer",11); + $this->hasColumn("file_md5","string",32,"primary"); + } +} +class Description extends Doctrine_Record { + public function setTableDefinition() { + $this->hasColumn("description","string",3000); + $this->hasColumn("file_md5","string",32); + } +} +class UserTable extends Doctrine_Table { } +class User extends Entity { + public function setUp() { + parent::setUp(); + $this->ownsMany("Album","Album.user_id"); + $this->hasMany("Group","Groupuser.group_id"); + $this->setInheritanceMap(array("type"=>0)); + } +} +class Groupuser extends Doctrine_Record { + public function setTableDefinition() { + $this->hasColumn("added","integer"); + $this->hasColumn("group_id","integer"); + $this->hasColumn("user_id","integer"); + } +} + +class Phonenumber extends Doctrine_Record { + public function setTableDefinition() { + $this->hasColumn("phonenumber","string",20); + $this->hasColumn("entity_id","integer"); + } +} +class Element extends Doctrine_Record { + public function setTableDefinition() { + $this->hasColumn("name", "string", 100); + $this->hasColumn("parent_id", "integer"); + } + public function setUp() { + $this->hasMany("Element","Element.parent_id"); + } +} +class Email extends Doctrine_Record { + public function setTableDefinition() { + $this->hasColumn("address","string",150,"email|unique"); + } +} +class Album extends Doctrine_Record { + public function setUp() { + $this->ownsMany("Song","Song.album_id"); + } + public function setTableDefinition() { + $this->hasColumn("user_id","integer"); + $this->hasColumn("name","string",20); + } +} +class Song extends Doctrine_Record { + public function setUp() { + $this->hasColumn("genre","string","30"); + } + public function setTableDefinition() { + $this->hasColumn("album_id","integer"); + $this->hasColumn("genre","string",20); + $this->hasColumn("title","string",30); + } +} +?> diff --git a/tests/run.php b/tests/run.php new file mode 100644 index 000000000..9f4019157 --- /dev/null +++ b/tests/run.php @@ -0,0 +1,55 @@ +"; +error_reporting(E_ALL); + +$test = new GroupTest("Doctrine Framework Unit Tests"); + + + + + +$test->addTestCase(new Doctrine_RecordTestCase()); +/** +$test->addTestCase(new Doctrine_SessionTestCase()); +$test->addTestCase(new Doctrine_ValidatorTestCase()); + +$test->addTestCase(new Doctrine_ManagerTestCase()); +$test->addTestCase(new Doctrine_TableTestCase()); + +$test->addTestCase(new Doctrine_AccessTestCase()); +$test->addTestCase(new Doctrine_ConfigurableTestCase()); + + +$test->addTestCase(new Doctrine_EventListenerTestCase()); +$test->addTestCase(new Doctrine_BatchIteratorTestCase()); +$test->addTestCase(new Doctrine_Cache_FileTestCase()); + + + + +$test->addTestCase(new Doctrine_DQL_ParserTestCase()); +*/ + + + +$test->run(new HtmlReporter()); + +$a = Doctrine_Manager::getInstance()->getCurrentSession()->getDBH()->getQueries(); +print "Executed queries: ".count($a)."\n"; + +foreach($a as $query) { + $e = explode(" ",$query); + print $query."\n"; +} +?>