Commit 7ef869ee authored by zYne's avatar zYne

Refactored Doctrine_Connection and Doctrine_Record, fixes #212

parent d8dddffc
......@@ -645,7 +645,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
}
/**
* save
* saves all records
* saves all records of this collection
*
* @return void
*/
......@@ -653,19 +653,34 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
if ($conn == null) {
$conn = $this->table->getConnection();
}
$conn->saveCollection($this);
$conn->beginTransaction();
foreach($this as $key => $record):
$record->save();
endforeach;
$conn->commit();
}
/**
* single shot delete
* deletes all records from this collection
* uses only one database query to perform this operation
* and uses only one database query to perform this operation
*
* @return boolean
*/
public function delete(Doctrine_Connection $conn = null) {
if ($conn == null) {
$conn = $this->table->getConnection();
}
$ids = $conn->deleteCollection($this);
$conn->beginTransaction();
foreach($this as $key => $record) {
$record->delete();
}
$conn->commit();
$this->data = array();
}
/**
......
......@@ -48,13 +48,30 @@ abstract class Doctrine_Connection extends Doctrine_Configurable implements Coun
* @var Doctrine_DataDict $dataDict
*/
private $dataDict;
private static $availibleDrivers = array(
"Mysql",
"Pgsql",
"Oracle",
"Informix",
"Mssql",
"Sqlite",
"Firebird"
);
private static $driverMap = array('oracle' => 'oci8',
'postgres' => 'pgsql',
'oci' => 'oci8',
'sqlite2' => 'sqlite',
'sqlite3' => 'sqlite');
/**
* the constructor
*
* @param Doctrine_Manager $manager the manager object
* @param PDO $pdo the database handler
*/
public function __construct(Doctrine_Manager $manager,PDO $pdo) {
public function __construct(Doctrine_Manager $manager, PDO $pdo) {
$this->dbh = $pdo;
$this->transaction = new Doctrine_Connection_Transaction($this);
......@@ -112,6 +129,13 @@ abstract class Doctrine_Connection extends Doctrine_Configurable implements Coun
public function getDBH() {
return $this->dbh;
}
/**
* converts given driver name
*
* @param
*/
public function driverName($name) {
}
/**
* returns a datadict object
*
......@@ -202,7 +226,7 @@ abstract class Doctrine_Connection extends Doctrine_Configurable implements Coun
*/
public function select($query,$limit = 0,$offset = 0) {
if($limit > 0 || $offset > 0)
$query = $this->modifyLimitQuery($query,$limit,$offset);
$query = $this->modifyLimitQuery($query, $limit, $offset);
return $this->dbh->query($query);
}
......@@ -332,7 +356,7 @@ abstract class Doctrine_Connection extends Doctrine_Configurable implements Coun
foreach($tree as $name) {
$table = $this->tables[$name];
foreach($table->getRepository() as $record) {
$record->saveAssociations();
$this->unitOfWork->saveAssociations($record);
}
}
}
......@@ -409,60 +433,6 @@ abstract class Doctrine_Connection extends Doctrine_Configurable implements Coun
public function rollback() {
$this->transaction->rollback();
}
/**
* returns maximum identifier values
*
* @param array $names an array of component names
* @return array
*/
public function getMaximumValues(array $names) {
$values = array();
foreach($names as $name) {
$table = $this->tables[$name];
$keys = $table->getPrimaryKeys();
$tablename = $table->getTableName();
if(count($keys) == 1 && $keys[0] == $table->getIdentifier()) {
// record uses auto_increment column
$sql = "SELECT MAX(".$table->getIdentifier().") FROM ".$tablename;
$stmt = $this->dbh->query($sql);
$data = $stmt->fetch(PDO::FETCH_NUM);
$values[$tablename] = $data[0];
$stmt->closeCursor();
}
}
return $values;
}
/**
* saves a collection
*
* @param Doctrine_Collection $coll
* @return void
*/
public function saveCollection(Doctrine_Collection $coll) {
$this->beginTransaction();
foreach($coll as $key=>$record):
$record->save();
endforeach;
$this->commit();
}
/**
* deletes all records from collection
*
* @param Doctrine_Collection $coll
* @return void
*/
public function deleteCollection(Doctrine_Collection $coll) {
$this->beginTransaction();
foreach($coll as $k=>$record) {
$record->delete();
}
$this->commit();
}
/**
* saves the given record
*
......@@ -489,125 +459,6 @@ abstract class Doctrine_Connection extends Doctrine_Configurable implements Coun
$record->getTable()->getAttribute(Doctrine::ATTR_LISTENER)->onSave($record);
}
/**
* saves all related records to $record
*
* @param Doctrine_Record $record
*/
public function saveRelated(Doctrine_Record $record) {
$saveLater = array();
foreach($record->getReferences() as $k=>$v) {
$fk = $record->getTable()->getRelation($k);
if($fk instanceof Doctrine_Relation_ForeignKey ||
$fk instanceof Doctrine_Relation_LocalKey) {
if($fk->isComposite()) {
$local = $fk->getLocal();
$foreign = $fk->getForeign();
if($record->getTable()->hasPrimaryKey($fk->getLocal())) {
if( ! $record->exists())
$saveLater[$k] = $fk;
else
$v->save();
} else {
// ONE-TO-ONE relationship
$obj = $record->get($fk->getTable()->getComponentName());
if($obj->getState() != Doctrine_Record::STATE_TCLEAN)
$obj->save();
}
}
} elseif($fk instanceof Doctrine_Relation_Association) {
$v->save();
}
}
return $saveLater;
}
/**
* deletes all related composites
* this method is always called internally when a record is deleted
*
* @return void
*/
final public function deleteComposites(Doctrine_Record $record) {
foreach($record->getTable()->getRelations() as $fk) {
switch($fk->getType()):
case Doctrine_Relation::ONE_COMPOSITE:
case Doctrine_Relation::MANY_COMPOSITE:
$obj = $record->get($fk->getAlias());
$obj->delete();
break;
endswitch;
}
}
/**
* 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(Doctrine_Record $record) {
foreach($record->getTable()->table->getRelations() as $rel):
$table = $rel->getTable();
$name = $table->getComponentName();
$alias = $this->table->getAlias($name);
if($rel instanceof Doctrine_Relation_Association) {
$asf = $rel->getAssociationFactory();
if($record->hasReference($alias)) {
$new = $record->getReference($alias);
if( ! $this->hasOriginalsFor($alias))
$record->loadReference($alias);
$operations = Doctrine_Relation::getDeleteOperations($this->originals[$alias],$new);
foreach($operations as $r) {
$query = "DELETE FROM ".$asf->getTableName()." WHERE ".$fk->getForeign()." = ?"
." AND ".$fk->getLocal()." = ?";
$this->table->getConnection()->execute($query, array($r->getIncremented(),$record->getIncremented()));
}
$operations = Doctrine_Relation::getInsertOperations($record->obtainOriginals($alias),$new);
foreach($operations as $r) {
$reldao = $asf->create();
$reldao->set($fk->getForeign(),$r);
$reldao->set($fk->getLocal(),$this);
$reldao->save();
}
$record->assignOriginals($alias, clone $this->references[$alias]);
}
} elseif($fk instanceof Doctrine_Relation_ForeignKey ||
$fk instanceof Doctrine_Relation_LocalKey) {
if($fk->isOneToOne()) {
if($record->obtainOriginals($alias) && $record->obtainOriginals($alias)->obtainIdentifier() != $this->references[$alias]->obtainIdentifier())
$record->obtainOriginals($alias)->delete();
} else {
if(isset($this->references[$alias])) {
$new = $this->references[$alias];
if( ! isset($this->originals[$alias]))
$record->loadReference($alias);
$operations = Doctrine_Relation::getDeleteOperations($this->originals[$alias], $new);
foreach($operations as $r) {
$r->delete();
}
$record->assignOriginals($alias, clone $this->references[$alias]);
}
}
}
endforeach;
}
/**
* deletes this data access object and all the related composites
* this operation is isolated by a transaction
......@@ -616,7 +467,7 @@ abstract class Doctrine_Connection extends Doctrine_Configurable implements Coun
*
* @return boolean true on success, false on failure
*/
final public function delete(Doctrine_Record $record) {
public function delete(Doctrine_Record $record) {
if( ! $record->exists())
return false;
......@@ -624,7 +475,7 @@ abstract class Doctrine_Connection extends Doctrine_Configurable implements Coun
$record->getTable()->getListener()->onPreDelete($record);
$this->deleteComposites($record);
$this->unitOfWork->deleteComposites($record);
$this->transaction->addDelete($record);
......
......@@ -35,6 +35,28 @@ class Doctrine_Connection_Mysql extends Doctrine_Connection_Common {
public function __construct(Doctrine_Manager $manager,PDO $pdo) {
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
$this->setAttribute(Doctrine::ATTR_QUERY_LIMIT, Doctrine::LIMIT_ROWS);
$this->supported = array(
'sequences' => 'emulated',
'indexes' => true,
'affected_rows' => true,
'transactions' => true,
'savepoints' => false,
'summary_functions' => true,
'order_by_text' => true,
'current_id' => 'emulated',
'limit_queries' => true,
'LOBs' => true,
'replace' => true,
'sub_selects' => true,
'auto_increment' => true,
'primary_key' => true,
'result_introspection' => true,
'prepared_statements' => 'emulated',
'identifier_quoting' => true,
'pattern_escaping' => true
);
parent::__construct($manager,$pdo);
}
/**
......
......@@ -45,6 +45,11 @@ class Doctrine_Connection_UnitOfWork implements IteratorAggregate, Countable {
* buildFlushTree
* builds a flush tree that is used in transactions
*
* The returned array has all the initialized components in
* 'correct' order. Basically this means that the records of those
* components can be saved safely in the order specified by the returned array.
*
* @param array $tables
* @return array
*/
public function buildFlushTree(array $tables) {
......@@ -133,7 +138,81 @@ class Doctrine_Connection_UnitOfWork implements IteratorAggregate, Countable {
}
return array_values($tree);
}
/**
* saveRelated
* saves all related records to $record
*
* @param Doctrine_Record $record
*/
public function saveRelated(Doctrine_Record $record) {
$saveLater = array();
foreach($record->getReferences() as $k=>$v) {
$fk = $record->getTable()->getRelation($k);
if($fk instanceof Doctrine_Relation_ForeignKey ||
$fk instanceof Doctrine_Relation_LocalKey) {
if($fk->isComposite()) {
$local = $fk->getLocal();
$foreign = $fk->getForeign();
if($record->getTable()->hasPrimaryKey($fk->getLocal())) {
if( ! $record->exists())
$saveLater[$k] = $fk;
else
$v->save();
} else {
// ONE-TO-ONE relationship
$obj = $record->get($fk->getTable()->getComponentName());
if($obj->getState() != Doctrine_Record::STATE_TCLEAN)
$obj->save();
}
}
} elseif($fk instanceof Doctrine_Relation_Association) {
$v->save();
}
}
return $saveLater;
}
/**
* saveAssociations
*
* this method takes a diff of one-to-many / many-to-many original and
* current collections and applies the changes
*
* for example if original many-to-many related collection has records with
* primary keys 1,2 and 3 and the new collection has records with primary keys
* 3, 4 and 5, this method would first destroy the associations to 1 and 2 and then
* save new associations to 4 and 5
*
* @param Doctrine_Record $record
* @return void
*/
public function saveAssociations(Doctrine_Record $record) {
foreach($record->getTable()->getRelations() as $rel) {
$table = $rel->getTable();
$alias = $rel->getAlias();
$rel->processDiff($record);
}
}
/**
* deletes all related composites
* this method is always called internally when a record is deleted
*
* @return void
*/
public function deleteComposites(Doctrine_Record $record) {
foreach($record->getTable()->getRelations() as $fk) {
switch($fk->getType()):
case Doctrine_Relation::ONE_COMPOSITE:
case Doctrine_Relation::MANY_COMPOSITE:
$obj = $record->get($fk->getAlias());
$obj->delete();
break;
endswitch;
}
}
public function getIterator() { }
public function count() { }
......
......@@ -598,25 +598,26 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
}
}
/**
* DQL PARSER
* parses a DQL query
* first splits the query in parts and then uses individual
* parsers for each part
* splitQuery
* splits the given dql query into an array where keys
* represent different query part names and values are
* arrays splitted using sqlExplode method
*
* example:
*
* parameter:
* $query = "SELECT u.* FROM User u WHERE u.name LIKE ?"
* returns:
* array('select' => array('u.*'),
* 'from' => array('User', 'u'),
* 'where' => array('u.name', 'LIKE', '?'))
*
* @param string $query DQL query
* @param boolean $clear whether or not to clear the aliases
* @throws Doctrine_Query_Exception if some generic parsing error occurs
* @return Doctrine_Query
* @return array an array containing the query string parts
*/
public function parseQuery($query, $clear = true) {
if($clear)
$this->clear();
$query = trim($query);
$query = str_replace("\n"," ",$query);
$query = str_replace("\r"," ",$query);
$e = self::sqlExplode($query," ","(",")");
public function splitQuery($query) {
$e = self::sqlExplode($query, ' ');
foreach($e as $k=>$part) {
$part = trim($part);
......@@ -651,6 +652,28 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
$parts[$p][] = $part;
}
}
return $parts;
}
/**
* DQL PARSER
* parses a DQL query
* first splits the query in parts and then uses individual
* parsers for each part
*
* @param string $query DQL query
* @param boolean $clear whether or not to clear the aliases
* @throws Doctrine_Query_Exception if some generic parsing error occurs
* @return Doctrine_Query
*/
public function parseQuery($query, $clear = true) {
if($clear)
$this->clear();
$query = trim($query);
$query = str_replace("\n"," ",$query);
$query = str_replace("\r"," ",$query);
$parts = $this->splitQuery($query);
foreach($parts as $k => $part) {
$part = implode(" ",$part);
......@@ -753,11 +776,18 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
}
/**
* bracketExplode
* usage:
*
* example:
*
* parameters:
* $str = (age < 20 AND age > 18) AND email LIKE 'John@example.com'
* now exploding $str with parameters $d = ' AND ', $e1 = '(' and $e2 = ')'
* $d = ' AND '
* $e1 = '('
* $e2 = ')'
*
* would return an array:
* array("(age < 20 AND age > 18)", "email LIKE 'John@example.com'")
* array("(age < 20 AND age > 18)",
* "email LIKE 'John@example.com'")
*
* @param string $str
* @param string $d the delimeter which explodes the string
......@@ -765,7 +795,7 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
* @param string $e2 the second bracket, usually ')'
*
*/
public static function bracketExplode($str,$d,$e1 = '(',$e2 = ')') {
public static function bracketExplode($str, $d = ' ', $e1 = '(', $e2 = ')') {
if(is_array($d)) {
$a = preg_split('/('.implode('|', $d).')/', $str);
$d = stripslashes($d[0]);
......@@ -777,13 +807,13 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
foreach($a as $key=>$val) {
if (empty($term[$i])) {
$term[$i] = trim($val);
$s1 = substr_count($term[$i],"$e1");
$s2 = substr_count($term[$i],"$e2");
$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");
$c1 = substr_count($term[$i], "$e1");
$c2 = substr_count($term[$i], "$e2");
if($c1 == $c2) $i++;
}
}
......@@ -795,6 +825,21 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
* explodes a string into array using custom brackets and
* quote delimeters
*
*
* example:
*
* parameters:
* $str = "(age < 20 AND age > 18) AND name LIKE 'John Doe'"
* $d = ' '
* $e1 = '('
* $e2 = ')'
*
* would return an array:
* array('(age < 20 AND age > 18)',
* 'name',
* 'LIKE',
* 'John Doe')
*
* @param string $str
* @param string $d the delimeter which explodes the string
* @param string $e1 the first bracket, usually '('
......@@ -802,7 +847,7 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
*
* @return array
*/
public static function sqlExplode($str,$d = " ",$e1 = '(',$e2 = ')') {
public static function sqlExplode($str, $d = ' ', $e1 = '(', $e2 = ')') {
if(is_array($d)) {
$str = preg_split('/('.implode('|', $d).')/', $str);
$d = stripslashes($d[0]);
......
......@@ -858,7 +858,8 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite
}
$conn->beginTransaction();
$saveLater = $conn->saveRelated($this);
$saveLater = $conn->getUnitOfWork()->saveRelated($this);
if ($this->isValid()) {
$conn->save($this);
......@@ -878,7 +879,8 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite
// save the MANY-TO-MANY associations
$this->saveAssociations();
$conn->getUnitOfWork()->saveAssociations($this);
//$this->saveAssociations();
$conn->commit();
}
......@@ -1012,98 +1014,17 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite
public function getIterator() {
return new Doctrine_Record_Iterator($this);
}
public function obtainOriginals($name) {
if(isset($this->originals[$name]))
return $this->originals[$name];
return false;
}
/**
* 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->getRelations() as $fk) {
$table = $fk->getTable();
$name = $table->getComponentName();
$alias = $this->_table->getAlias($name);
if($fk instanceof Doctrine_Relation_Association) {
switch($fk->getType()):
case Doctrine_Relation::MANY_AGGREGATE:
$asf = $fk->getAssociationFactory();
if(isset($this->references[$alias])) {
$new = $this->references[$alias];
if( ! isset($this->originals[$alias])) {
$this->loadReference($alias);
}
$r = Doctrine_Relation::getDeleteOperations($this->originals[$alias],$new);
foreach($r as $record) {
$query = "DELETE FROM ".$asf->getTableName()." WHERE ".$fk->getForeign()." = ?"
." AND ".$fk->getLocal()." = ?";
$this->_table->getConnection()->execute($query, array($record->getIncremented(),$this->getIncremented()));
}
$r = Doctrine_Relation::getInsertOperations($this->originals[$alias],$new);
foreach($r as $record) {
$reldao = $asf->create();
$reldao->set($fk->getForeign(),$record);
$reldao->set($fk->getLocal(),$this);
$reldao->save();
}
$this->originals[$alias] = clone $this->references[$alias];
}
break;
endswitch;
} elseif($fk instanceof Doctrine_Relation_ForeignKey ||
$fk instanceof Doctrine_Relation_LocalKey) {
if($fk->isOneToOne()) {
if(isset($this->originals[$alias]) && $this->originals[$alias]->obtainIdentifier() != $this->references[$alias]->obtainIdentifier())
$this->originals[$alias]->delete();
} else {
if(isset($this->references[$alias])) {
$new = $this->references[$alias];
if( ! isset($this->originals[$alias]))
$this->loadReference($alias);
$r = Doctrine_Relation::getDeleteOperations($this->originals[$alias], $new);
foreach($r as $record) {
$record->delete();
}
$this->originals[$alias] = clone $this->references[$alias];
}
}
}
}
}
/**
* getOriginals
* returns an original collection of related component
*
* @return Doctrine_Collection
* @return Doctrine_Collection|false
*/
final public function getOriginals($name) {
if( ! isset($this->originals[$name]))
throw new InvalidKeyException();
public function obtainOriginals($name) {
if(isset($this->originals[$name]))
return $this->originals[$name];
return false;
}
/**
* deletes this data access object and all the related composites
......
......@@ -52,6 +52,45 @@ class Doctrine_Relation_Association extends Doctrine_Relation {
public function getAssociationFactory() {
return $this->associationTable;
}
/**
* processDiff
*
* @param Doctrine_Record
*/
public function processDiff(Doctrine_Record $record) {
$asf = $this->getAssociationFactory();
$alias = $this->getAlias();
if($record->hasReference($alias)) {
$new = $record->obtainReference($alias);
if( ! $record->obtainOriginals($alias))
$record->loadReference($alias);
$operations = Doctrine_Relation::getDeleteOperations($record->obtainOriginals($alias), $new);
foreach($operations as $r) {
$query = 'DELETE FROM ' . $asf->getTableName()
. ' WHERE ' . $this->getForeign() . ' = ?'
. ' AND ' . $this->getLocal() . ' = ?';
$this->getTable()->getConnection()->execute($query, array($r->getIncremented(),$record->getIncremented()));
}
$operations = Doctrine_Relation::getInsertOperations($record->obtainOriginals($alias),$new);
foreach($operations as $r) {
$reldao = $asf->create();
$reldao->set($this->getForeign(), $r);
$reldao->set($this->getLocal(), $record);
$reldao->save();
}
$record->assignOriginals($alias, clone $record->get($alias));
}
}
/**
* getRelationDql
*
......
......@@ -28,6 +28,35 @@ Doctrine::autoload('Doctrine_Relation');
* @package Doctrine
*/
class Doctrine_Relation_ForeignKey extends Doctrine_Relation {
/**
* processDiff
*
* @param Doctrine_Record $record
*/
public function processDiff(Doctrine_Record $record) {
$alias = $this->getAlias();
if($this->isOneToOne()) {
if($record->obtainOriginals($alias) &&
$record->obtainOriginals($alias)->obtainIdentifier() != $this->obtainReference($alias)->obtainIdentifier())
$record->obtainOriginals($alias)->delete();
} else {
if($record->hasReference($alias)) {
$new = $record->obtainReference($alias);
if( ! $record->obtainOriginals($alias))
$record->loadReference($alias);
$operations = Doctrine_Relation::getDeleteOperations($record->obtainOriginals($alias), $new);
foreach($operations as $r) {
$r->delete();
}
$record->assignOriginals($alias, clone $record->get($alias));
}
}
}
/**
* fetchRelatedFor
*
......
......@@ -28,6 +28,18 @@ Doctrine::autoload('Doctrine_Relation');
* @package Doctrine
*/
class Doctrine_Relation_LocalKey extends Doctrine_Relation {
/**
* processDiff
*
* @param Doctrine_Record $record
*/
public function processDiff(Doctrine_Record $record) {
$alias = $this->getAlias();
if($record->obtainOriginals($alias) &&
$record->obtainOriginals($alias)->obtainIdentifier() != $this->references[$alias]->obtainIdentifier())
$record->obtainOriginals($alias)->delete();
}
/**
* fetchRelatedFor
*
......
......@@ -64,16 +64,14 @@ print '<pre>';
$test = new GroupTest('Doctrine Framework Unit Tests');
$test->addTestCase(new Doctrine_DataDict_Pgsql_TestCase());
$test->addTestCase(new Doctrine_Record_TestCase());
$test->addTestCase(new Doctrine_DataDict_Pgsql_TestCase());
$test->addTestCase(new Doctrine_Relation_ManyToMany_TestCase());
$test->addTestCase(new Doctrine_Relation_TestCase());
$test->addTestCase(new Doctrine_Record_TestCase());
$test->addTestCase(new Doctrine_Record_State_TestCase());
$test->addTestCase(new Doctrine_Import_TestCase());
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment