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 ...@@ -645,7 +645,7 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
} }
/** /**
* save * save
* saves all records * saves all records of this collection
* *
* @return void * @return void
*/ */
...@@ -653,19 +653,34 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator ...@@ -653,19 +653,34 @@ class Doctrine_Collection extends Doctrine_Access implements Countable, Iterator
if ($conn == null) { if ($conn == null) {
$conn = $this->table->getConnection(); $conn = $this->table->getConnection();
} }
$conn->saveCollection($this); $conn->beginTransaction();
foreach($this as $key => $record):
$record->save();
endforeach;
$conn->commit();
} }
/** /**
* single shot delete * single shot delete
* deletes all records from this collection * 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 * @return boolean
*/ */
public function delete(Doctrine_Connection $conn = null) { public function delete(Doctrine_Connection $conn = null) {
if ($conn == null) { if ($conn == null) {
$conn = $this->table->getConnection(); $conn = $this->table->getConnection();
} }
$ids = $conn->deleteCollection($this);
$conn->beginTransaction();
foreach($this as $key => $record) {
$record->delete();
}
$conn->commit();
$this->data = array(); $this->data = array();
} }
/** /**
......
This diff is collapsed.
...@@ -35,6 +35,28 @@ class Doctrine_Connection_Mysql extends Doctrine_Connection_Common { ...@@ -35,6 +35,28 @@ class Doctrine_Connection_Mysql extends Doctrine_Connection_Common {
public function __construct(Doctrine_Manager $manager,PDO $pdo) { public function __construct(Doctrine_Manager $manager,PDO $pdo) {
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, true); $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
$this->setAttribute(Doctrine::ATTR_QUERY_LIMIT, Doctrine::LIMIT_ROWS); $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); parent::__construct($manager,$pdo);
} }
/** /**
......
...@@ -45,6 +45,11 @@ class Doctrine_Connection_UnitOfWork implements IteratorAggregate, Countable { ...@@ -45,6 +45,11 @@ class Doctrine_Connection_UnitOfWork implements IteratorAggregate, Countable {
* buildFlushTree * buildFlushTree
* builds a flush tree that is used in transactions * 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 * @return array
*/ */
public function buildFlushTree(array $tables) { public function buildFlushTree(array $tables) {
...@@ -133,7 +138,81 @@ class Doctrine_Connection_UnitOfWork implements IteratorAggregate, Countable { ...@@ -133,7 +138,81 @@ class Doctrine_Connection_UnitOfWork implements IteratorAggregate, Countable {
} }
return array_values($tree); 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 getIterator() { }
public function count() { } public function count() { }
......
...@@ -598,25 +598,26 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { ...@@ -598,25 +598,26 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
} }
} }
/** /**
* DQL PARSER * splitQuery
* parses a DQL query * splits the given dql query into an array where keys
* first splits the query in parts and then uses individual * represent different query part names and values are
* parsers for each part * 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 string $query DQL query
* @param boolean $clear whether or not to clear the aliases
* @throws Doctrine_Query_Exception if some generic parsing error occurs * @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) { public function splitQuery($query) {
if($clear) $e = self::sqlExplode($query, ' ');
$this->clear();
$query = trim($query);
$query = str_replace("\n"," ",$query);
$query = str_replace("\r"," ",$query);
$e = self::sqlExplode($query," ","(",")");
foreach($e as $k=>$part) { foreach($e as $k=>$part) {
$part = trim($part); $part = trim($part);
...@@ -651,6 +652,28 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { ...@@ -651,6 +652,28 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
$parts[$p][] = $part; $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) { foreach($parts as $k => $part) {
$part = implode(" ",$part); $part = implode(" ",$part);
...@@ -753,11 +776,18 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { ...@@ -753,11 +776,18 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
} }
/** /**
* bracketExplode * bracketExplode
* usage: *
* example:
*
* parameters:
* $str = (age < 20 AND age > 18) AND email LIKE 'John@example.com' * $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: * 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 $str
* @param string $d the delimeter which explodes the string * @param string $d the delimeter which explodes the string
...@@ -765,7 +795,7 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { ...@@ -765,7 +795,7 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
* @param string $e2 the second bracket, usually ')' * @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)) { if(is_array($d)) {
$a = preg_split('/('.implode('|', $d).')/', $str); $a = preg_split('/('.implode('|', $d).')/', $str);
$d = stripslashes($d[0]); $d = stripslashes($d[0]);
...@@ -777,13 +807,13 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { ...@@ -777,13 +807,13 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
foreach($a as $key=>$val) { foreach($a as $key=>$val) {
if (empty($term[$i])) { if (empty($term[$i])) {
$term[$i] = trim($val); $term[$i] = trim($val);
$s1 = substr_count($term[$i],"$e1"); $s1 = substr_count($term[$i], "$e1");
$s2 = substr_count($term[$i],"$e2"); $s2 = substr_count($term[$i], "$e2");
if($s1 == $s2) $i++; if($s1 == $s2) $i++;
} else { } else {
$term[$i] .= "$d".trim($val); $term[$i] .= "$d".trim($val);
$c1 = substr_count($term[$i],"$e1"); $c1 = substr_count($term[$i], "$e1");
$c2 = substr_count($term[$i],"$e2"); $c2 = substr_count($term[$i], "$e2");
if($c1 == $c2) $i++; if($c1 == $c2) $i++;
} }
} }
...@@ -795,6 +825,21 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { ...@@ -795,6 +825,21 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
* explodes a string into array using custom brackets and * explodes a string into array using custom brackets and
* quote delimeters * 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 $str
* @param string $d the delimeter which explodes the string * @param string $d the delimeter which explodes the string
* @param string $e1 the first bracket, usually '(' * @param string $e1 the first bracket, usually '('
...@@ -802,7 +847,7 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable { ...@@ -802,7 +847,7 @@ class Doctrine_Query extends Doctrine_Hydrate implements Countable {
* *
* @return array * @return array
*/ */
public static function sqlExplode($str,$d = " ",$e1 = '(',$e2 = ')') { public static function sqlExplode($str, $d = ' ', $e1 = '(', $e2 = ')') {
if(is_array($d)) { if(is_array($d)) {
$str = preg_split('/('.implode('|', $d).')/', $str); $str = preg_split('/('.implode('|', $d).')/', $str);
$d = stripslashes($d[0]); $d = stripslashes($d[0]);
......
...@@ -858,7 +858,8 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite ...@@ -858,7 +858,8 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite
} }
$conn->beginTransaction(); $conn->beginTransaction();
$saveLater = $conn->saveRelated($this);
$saveLater = $conn->getUnitOfWork()->saveRelated($this);
if ($this->isValid()) { if ($this->isValid()) {
$conn->save($this); $conn->save($this);
...@@ -878,7 +879,8 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite ...@@ -878,7 +879,8 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite
// save the MANY-TO-MANY associations // save the MANY-TO-MANY associations
$this->saveAssociations(); $conn->getUnitOfWork()->saveAssociations($this);
//$this->saveAssociations();
$conn->commit(); $conn->commit();
} }
...@@ -1012,98 +1014,17 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite ...@@ -1012,98 +1014,17 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite
public function getIterator() { public function getIterator() {
return new Doctrine_Record_Iterator($this); 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 * getOriginals
* returns an original collection of related component * returns an original collection of related component
* *
* @return Doctrine_Collection * @return Doctrine_Collection|false
*/ */
final public function getOriginals($name) { public function obtainOriginals($name) {
if( ! isset($this->originals[$name])) if(isset($this->originals[$name]))
throw new InvalidKeyException();
return $this->originals[$name]; return $this->originals[$name];
return false;
} }
/** /**
* deletes this data access object and all the related composites * deletes this data access object and all the related composites
......
...@@ -52,6 +52,45 @@ class Doctrine_Relation_Association extends Doctrine_Relation { ...@@ -52,6 +52,45 @@ class Doctrine_Relation_Association extends Doctrine_Relation {
public function getAssociationFactory() { public function getAssociationFactory() {
return $this->associationTable; 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 * getRelationDql
* *
......
...@@ -28,6 +28,35 @@ Doctrine::autoload('Doctrine_Relation'); ...@@ -28,6 +28,35 @@ Doctrine::autoload('Doctrine_Relation');
* @package Doctrine * @package Doctrine
*/ */
class Doctrine_Relation_ForeignKey extends Doctrine_Relation { 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 * fetchRelatedFor
* *
......
...@@ -28,6 +28,18 @@ Doctrine::autoload('Doctrine_Relation'); ...@@ -28,6 +28,18 @@ Doctrine::autoload('Doctrine_Relation');
* @package Doctrine * @package Doctrine
*/ */
class Doctrine_Relation_LocalKey extends Doctrine_Relation { 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 * fetchRelatedFor
* *
......
...@@ -64,16 +64,14 @@ print '<pre>'; ...@@ -64,16 +64,14 @@ print '<pre>';
$test = new GroupTest('Doctrine Framework Unit Tests'); $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_ManyToMany_TestCase());
$test->addTestCase(new Doctrine_Relation_TestCase()); $test->addTestCase(new Doctrine_Relation_TestCase());
$test->addTestCase(new Doctrine_Record_TestCase());
$test->addTestCase(new Doctrine_Record_State_TestCase()); $test->addTestCase(new Doctrine_Record_State_TestCase());
$test->addTestCase(new Doctrine_Import_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