Commit 90d34063 authored by doctrine's avatar doctrine

Preliminary support for composite primary keys

Many-to-Many alias bug fixed
parent 814eef80
......@@ -128,7 +128,7 @@ class Doctrine_DBStatement extends PDOStatement {
/**
* @param array $params
*/
public function execute($params) {
public function execute(array $params = null) {
$time = microtime();
$result = parent::execute($params);
......
......@@ -16,5 +16,9 @@ class Doctrine_Identifier {
* constant for normal identifier
*/
const NORMAL = 3;
/**
* constant for composite identifier
*/
const COMPOSITE = 4;
}
?>
......@@ -128,25 +128,24 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite
// clean data array
$cols = $this->cleanData();
if( ! $exists) {
$this->prepareIdentifiers($exists);
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;
$this->prepareIdentifiers();
// listen the onLoad event
$this->table->getAttribute(Doctrine::ATTR_LISTENER)->onLoad($this);
......@@ -200,16 +199,26 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite
*
* @return void
*/
public function prepareIdentifiers() {
private function prepareIdentifiers($exists = true) {
switch($this->table->getIdentifierType()):
case Doctrine_Identifier::AUTO_INCREMENT:
case Doctrine_Identifier::SEQUENCE:
if($exists) {
$name = $this->table->getIdentifier();
if(isset($this->data[$name]))
$this->id = $this->data[$name];
unset($this->data[$name]);
}
break;
case Doctrine_Identifier::COMPOSITE:
$names = $this->table->getIdentifier();
$this->id = array();
foreach($names as $name) {
$this->id[$name] = isset($this->data[$name])?$this->data[$name]:null;
}
break;
endswitch;
}
......@@ -605,7 +614,7 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite
foreach($this->table->getForeignKeys() as $fk):
$table = $fk->getTable();
$name = $table->getComponentName();
$alias = $this->table->getAlias($name);
if($fk instanceof Doctrine_Association) {
switch($fk->getType()):
......@@ -614,15 +623,16 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite
break;
case Doctrine_Relation::MANY_AGGREGATE:
$asf = $fk->getAssociationFactory();
if(isset($this->references[$name])) {
$new = $this->references[$name];
if(isset($this->references[$alias])) {
if( ! isset($this->originals[$name])) {
$this->loadReference($name);
$new = $this->references[$alias];
if( ! isset($this->originals[$alias])) {
$this->loadReference($alias);
}
$r = $this->getRelationOperations($name,$new);
$r = $this->getRelationOperations($alias,$new);
foreach($r["delete"] as $record) {
$query = "DELETE FROM ".$asf->getTableName()." WHERE ".$fk->getForeign()." = ?"
......@@ -635,7 +645,7 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite
$reldao->set($fk->getLocal(),$this);
$reldao->save();
}
$this->originals[$name] = clone $this->references[$name];
$this->originals[$alias] = clone $this->references[$alias];
}
break;
endswitch;
......@@ -644,24 +654,24 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite
switch($fk->getType()):
case Doctrine_Relation::ONE_COMPOSITE:
if(isset($this->originals[$name]) && $this->originals[$name]->getID() != $this->references[$name]->getID())
$this->originals[$name]->delete();
if(isset($this->originals[$alias]) && $this->originals[$alias]->getID() != $this->references[$alias]->getID())
$this->originals[$alias]->delete();
break;
case Doctrine_Relation::MANY_COMPOSITE:
if(isset($this->references[$name])) {
$new = $this->references[$name];
if(isset($this->references[$alias])) {
$new = $this->references[$alias];
if( ! isset($this->originals[$name]))
$this->loadReference($name);
if( ! isset($this->originals[$alias]))
$this->loadReference($alias);
$r = $this->getRelationOperations($name,$new);
$r = $this->getRelationOperations($alias,$new);
foreach($r["delete"] as $record) {
$record->delete();
}
$this->originals[$name] = clone $this->references[$name];
$this->originals[$alias] = clone $this->references[$alias];
}
break;
endswitch;
......@@ -675,7 +685,7 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite
*
* 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)
* the complexity of this algorithm is O(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).
......@@ -764,6 +774,10 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite
$this->cleanData();
$this->state = Doctrine_Record::STATE_TCLEAN;
$this->modified = array();
} elseif($id === true) {
$this->prepareIdentifiers(false);
$this->state = Doctrine_Record::STATE_CLEAN;
$this->modified = array();
} else {
$this->id = $id;
$this->state = Doctrine_Record::STATE_CLEAN;
......@@ -771,8 +785,8 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite
}
}
/**
* return the primary key this object is pointing at
* @return int id
* return the primary key(s) this object is pointing at
* @return mixed id
*/
final public function getID() {
return $this->id;
......@@ -916,6 +930,8 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite
}
/**
* binds One-to-One composite relation
*
* @param string $objTableName
* @param string $fkField
* @return void
......@@ -924,6 +940,8 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite
$this->table->bind($componentName,$foreignKey,Doctrine_Relation::ONE_COMPOSITE, $localKey);
}
/**
* binds One-to-Many composite relation
*
* @param string $objTableName
* @param string $fkField
* @return void
......@@ -932,6 +950,8 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite
$this->table->bind($componentName,$foreignKey,Doctrine_Relation::MANY_COMPOSITE, $localKey);
}
/**
* binds One-to-One aggregate relation
*
* @param string $objTableName
* @param string $fkField
* @return void
......@@ -940,6 +960,8 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite
$this->table->bind($componentName,$foreignKey,Doctrine_Relation::ONE_AGGREGATE, $localKey);
}
/**
* binds One-to-Many aggregate relation
*
* @param string $objTableName
* @param string $fkField
* @return void
......@@ -957,7 +979,7 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite
}
/**
* setPrimaryKey
* @param string $key
* @param mixed $key
*/
final public function setPrimaryKey($key) {
$this->table->setPrimaryKey($key);
......@@ -982,6 +1004,8 @@ abstract class Doctrine_Record extends Doctrine_Access implements Countable, Ite
}
/**
* hasColumn
* sets a column definition
*
* @param string $name
* @param string $type
* @param integer $length
......
......@@ -155,6 +155,12 @@ class Sensei extends Doctrine_Access {
return false;
}
}
/**
* returns a session variable
*
* @param mixed $name
* @return mixed
*/
public function get($name) {
foreach($this->vars as $var) {
if($var->name == $name) {
......@@ -162,6 +168,14 @@ class Sensei extends Doctrine_Access {
}
}
}
/**
* sets a session variable
* returns true on success, false on failure
*
* @param mixed $name
* @param mixed $value
* @return boolean
*/
public function set($name,$value) {
foreach($this->vars as $var) {
if($var->name == $name) {
......
......@@ -41,7 +41,7 @@ abstract class Doctrine_Session extends Doctrine_Configurable implements Countab
private $transaction_level = 0;
/**
* @var PDO $cacheHandler
* @var PDO $cacheHandler cache handler
*/
private $cacheHandler;
/**
......@@ -328,7 +328,7 @@ abstract class Doctrine_Session extends Doctrine_Configurable implements Countab
}
/**
* clear
* clears the whole registry
* clears all repositories
*
* @return void
*/
......@@ -341,6 +341,7 @@ abstract class Doctrine_Session extends Doctrine_Configurable implements Countab
/**
* close
* closes the session
*
* @return void
*/
public function close() {
......@@ -353,7 +354,8 @@ abstract class Doctrine_Session extends Doctrine_Configurable implements Countab
}
/**
* get the current transaction nesting level
* @return integer transaction nesting level
*
* @return integer
*/
public function getTransactionLevel() {
return $this->transaction_level;
......@@ -471,16 +473,17 @@ abstract class Doctrine_Session extends Doctrine_Configurable implements Countab
$this->insert($record);
if($increment && $k == 0) {
if($increment) {
if($k == 0) {
// record uses auto_increment column
$id = $table->getMaxIdentifier();
}
$record->setID($id);
$id++;
} else
$record->setID(true);
// listen the onInsert event
$record->getTable()->getAttribute(Doctrine::ATTR_LISTENER)->onInsert($record);
......@@ -693,14 +696,22 @@ abstract class Doctrine_Session extends Doctrine_Configurable implements Countab
}
$params = array_values($array);
$params[] = $record->getID();
$id = $record->getID();
if( ! is_array($id))
$id = array($id);
$id = array_values($id);
$params = array_merge($params, $id);
$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->setID(true);
return true;
}
......
......@@ -77,6 +77,12 @@ class Doctrine_Table extends Doctrine_Configurable {
* @var array $bound bound relations
*/
private $bound = array();
/**
* @var array $boundAliases bound relation aliases
*/
private $boundAliases = array();
/**
* @var array $inheritanceMap inheritanceMap is used for inheritance mapping, keys representing columns and values
* the column values that should correspond to child classes
......@@ -144,6 +150,11 @@ class Doctrine_Table extends Doctrine_Configurable {
$this->identifierType = Doctrine_Identifier::AUTO_INCREMENT;
break;
default:
if(count($this->primaryKeys) > 1) {
$this->identifier = $this->primaryKeys;
$this->identifierType = Doctrine_Identifier::COMPOSITE;
} else {
foreach($this->primaryKeys as $pk) {
$o = $this->columns[$pk][2];
$e = explode("|",$o);
......@@ -176,6 +187,7 @@ class Doctrine_Table extends Doctrine_Configurable {
$this->identifier = $pk;
}
}
endswitch;
if($this->getAttribute(Doctrine::ATTR_CREATE_TABLES)) {
......@@ -259,6 +271,20 @@ class Doctrine_Table extends Doctrine_Configurable {
final public function hasColumn($name) {
return isset($this->columns[$name]);
}
/**
* @param mixed $key
* @return void
*/
final public function setPrimaryKey($key) {
switch(gettype($key)):
case "array":
$this->primaryKeys = array_values($key);
break;
case "string":
$this->primaryKeys[] = $key;
break;
endswitch;
}
/**
* returns all primary keys
* @return array
......@@ -348,6 +374,32 @@ class Doctrine_Table extends Doctrine_Configurable {
return $this->bound[$name];
}
/**
* returns a bound relation array
*
* @param string $name
* @return array
*/
final public function getBoundForName($name) {
foreach($this->bound as $k => $bound) {
if($bound[3] == $name) {
return $this->bound[$k];
}
}
throw new InvalidKeyException();
}
/**
* returns the alias for given component name
*
* @param string $name
* @return string
*/
final public function getAlias($name) {
if(isset($this->boundAliases[$name]))
return $this->boundAliases[$name];
return $name;
}
/**
* @param string $name
* @param string $field
......@@ -360,11 +412,13 @@ class Doctrine_Table extends Doctrine_Configurable {
$e = explode(" as ",$name);
$name = $e[0];
if(isset($e[1]))
if(isset($e[1])) {
$alias = $e[1];
else
$this->boundAliases[$name] = $alias;
} else
$alias = $name;
$this->bound[$alias] = array($field,$type,$localKey,$name);
}
/**
......@@ -395,7 +449,6 @@ class Doctrine_Table extends Doctrine_Configurable {
return $this->relations[$name];
if(isset($this->bound[$name])) {
$type = $this->bound[$name][1];
$local = $this->bound[$name][2];
$e = explode(".",$this->bound[$name][0]);
......@@ -436,7 +489,7 @@ class Doctrine_Table extends Doctrine_Configurable {
foreach($classes as $class) {
try {
$bound = $table->getBound($class);
$bound = $table->getBoundForName($class);
break;
} catch(InvalidKeyException $exc) {
......@@ -517,10 +570,15 @@ class Doctrine_Table extends Doctrine_Configurable {
*/
public function find($id) {
if($id !== null) {
if( ! is_array($id))
$id = array($id);
else
$id = array_values($id);
$query = $this->query." WHERE ".implode(" = ? AND ",$this->primaryKeys)." = ?";
$query = $this->applyInheritance($query);
$params = array_merge(array($id), array_values($this->inheritanceMap));
$params = array_merge($id, array_values($this->inheritanceMap));
$this->data = $this->session->execute($query,$params)->fetch(PDO::FETCH_ASSOC);
......@@ -588,8 +646,19 @@ class Doctrine_Table extends Doctrine_Configurable {
*/
public function getRecord() {
$key = $this->getIdentifier();
if(isset($this->data[$key])) {
$id = $this->data[$key];
if( ! is_array($key))
$key = array($key);
foreach($key as $k) {
if( ! isset($this->data[$k]))
throw new Doctrine_Exception("No primary key found");
$id[] = $this->data[$k];
}
$id = implode(' ', $id);
if(isset($this->identityMap[$id]))
$record = $this->identityMap[$id];
else {
......@@ -600,8 +669,6 @@ class Doctrine_Table extends Doctrine_Configurable {
return $record;
}
throw new Doctrine_Exception("No primary key found");
}
/**
* @param $id database row id
* @throws Doctrine_Find_Exception
......
<?php
/**
* Doctrine_Validator
* Doctrine_Session uses this class for transaction validation
*
* @package Doctrine ORM
* @url www.phpdoctrine.com
* @license LGPL
*/
class Doctrine_Validator {
/**
......
......@@ -3,13 +3,65 @@ require_once("UnitTestCase.class.php");
class Doctrine_RecordTestCase extends Doctrine_UnitTestCase {
public function testCompositePK() {
$record = new EntityReference();
$this->assertEqual($record->getTable()->getIdentifier(), array("entity1","entity2"));
$this->assertEqual($record->getTable()->getIdentifierType(), Doctrine_Identifier::COMPOSITE);
$this->assertEqual($record->getID(), array("entity1" => null, "entity2" => null));
$this->assertEqual($record->getState(), Doctrine_Record::STATE_TCLEAN);
$record->entity1 = 3;
$record->entity2 = 4;
$this->assertEqual($record->entity2, 4);
$this->assertEqual($record->entity1, 3);
$this->assertEqual($record->getState(), Doctrine_Record::STATE_TDIRTY);
$this->assertEqual($record->getID(), array("entity1" => null, "entity2" => null));
$record->save();
$this->assertEqual($record->getState(), Doctrine_Record::STATE_CLEAN);
$this->assertEqual($record->entity2, 4);
$this->assertEqual($record->entity1, 3);
$this->assertEqual($record->getID(), array("entity1" => 3, "entity2" => 4));
$record = $record->getTable()->find($record->getID());
$this->assertEqual($record->getState(), Doctrine_Record::STATE_CLEAN);
$this->assertEqual($record->entity2, 4);
$this->assertEqual($record->entity1, 3);
$this->assertEqual($record->getID(), array("entity1" => 3, "entity2" => 4));
$record->entity2 = 5;
$record->entity1 = 2;
$this->assertEqual($record->getState(), Doctrine_Record::STATE_DIRTY);
$this->assertEqual($record->entity2, 5);
$this->assertEqual($record->entity1, 2);
$this->assertEqual($record->getID(), array("entity1" => 3, "entity2" => 4));
$record->save();
$this->assertEqual($record->getState(), Doctrine_Record::STATE_CLEAN);
$this->assertEqual($record->entity2, 5);
$this->assertEqual($record->entity1, 2);
$this->assertEqual($record->getID(), array("entity1" => 2, "entity2" => 5));
$record = $record->getTable()->find($record->getID());
$this->assertEqual($record->getState(), Doctrine_Record::STATE_CLEAN);
$this->assertEqual($record->entity2, 5);
$this->assertEqual($record->entity1, 2);
$this->assertEqual($record->getID(), array("entity1" => 2, "entity2" => 5));
}
public function testManyToManyTreeStructure() {
$task = $this->session->create("Task");
$this->assertEqual($task->getTable()->getAlias("Resource"), "ResourceAlias");
$task->name = "Task 1";
$task->Resource[0]->name = "Resource 1";
$task->ResourceAlias[0]->name = "Resource 1";
$this->session->flush();
$this->assertTrue($task->ResourceAlias[0] instanceof Resource);
$this->assertEqual($task->ResourceAlias[0]->name, "Resource 1");
$this->assertEqual($this->dbh->query("SELECT COUNT(*) FROM assignment")->fetch(PDO::FETCH_NUM),array(1));
$task = new Task();
......@@ -18,16 +70,16 @@ class Doctrine_RecordTestCase extends Doctrine_UnitTestCase {
$this->assertTrue($task->Subtask[0] instanceof Task);
$this->assertEqual($task->Subtask[0]->getState(), Doctrine_Record::STATE_TCLEAN);
$this->assertTrue($task->Resource[0] instanceof Resource);
$this->assertEqual($task->Resource[0]->getState(), Doctrine_Record::STATE_TCLEAN);
$this->assertTrue($task->ResourceAlias[0] instanceof Resource);
$this->assertEqual($task->ResourceAlias[0]->getState(), Doctrine_Record::STATE_TCLEAN);
$task->name = "Task 1";
$task->Resource[0]->name = "Resource 1";
$task->ResourceAlias[0]->name = "Resource 1";
$task->Subtask[0]->name = "Subtask 1";
$this->assertEqual($task->name, "Task 1");
$this->assertEqual($task->Resource[0]->name, "Resource 1");
$this->assertEqual($task->Resource->count(), 1);
$this->assertEqual($task->ResourceAlias[0]->name, "Resource 1");
$this->assertEqual($task->ResourceAlias->count(), 1);
$this->assertEqual($task->Subtask[0]->name, "Subtask 1");
$this->session->flush();
......@@ -35,8 +87,8 @@ class Doctrine_RecordTestCase extends Doctrine_UnitTestCase {
$task = $task->getTable()->find($task->getID());
$this->assertEqual($task->name, "Task 1");
$this->assertEqual($task->Resource[0]->name, "Resource 1");
$this->assertEqual($task->Resource->count(), 1);
$this->assertEqual($task->ResourceAlias[0]->name, "Resource 1");
$this->assertEqual($task->ResourceAlias->count(), 1);
$this->assertEqual($task->Subtask[0]->name, "Subtask 1");
}
......@@ -553,5 +605,6 @@ class Doctrine_RecordTestCase extends Doctrine_UnitTestCase {
$user = $this->session->getTable("User")->find(4);
$this->assertTrue($user->getIterator() instanceof ArrayIterator);
}
}
?>
......@@ -92,11 +92,16 @@ class Sensei_UnitTestCase extends UnitTestCase {
$this->assertEqual($this->record->entity_id, 1);
}
public function testLogout() {
$this->assertTrue($this->sensei->logout());
$this->assertEqual($this->record->logged_in, 0);
$this->assertEqual($this->record->entity_id, 0);
$this->assertEqual($this->record->getState(), Doctrine_Record::STATE_DIRTY);
$this->assertEqual($this->record->getTable()->getIdentifierType(), Doctrine_Identifier::AUTO_INCREMENT);
$this->assertEqual($this->record->getID(), 1);
$this->sensei->flush();
......@@ -104,5 +109,6 @@ class Sensei_UnitTestCase extends UnitTestCase {
$this->assertEqual($this->record->entity_id, 0);
}
}
?>
......@@ -31,7 +31,7 @@ class Doctrine_UnitTestCase extends UnitTestCase {
$this->manager->setAttribute(Doctrine::ATTR_CACHE, Doctrine::CACHE_NONE);
$this->manager->setAttribute(Doctrine::ATTR_FETCHMODE, Doctrine::FETCH_IMMEDIATE);
$this->tables = array("entity","email","phonenumber","groupuser","album","song","element","error","description","address","account","task","resource","assignment");
$this->tables = array("entity","entityReference","email","phonenumber","groupuser","album","song","element","error","description","address","account","task","resource","assignment");
......
......@@ -16,7 +16,13 @@ class Entity extends Doctrine_Record {
$this->hasColumn("email_id","integer");
}
}
class EntityReference extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn("entity1","integer");
$this->hasColumn("entity2","integer");
$this->setPrimaryKey(array("entity1","entity2"));
}
}
class Account extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn("entity_id","integer");
......@@ -128,7 +134,7 @@ class Song extends Doctrine_Record {
class Task extends Doctrine_Record {
public function setUp() {
$this->hasMany("Resource","Assignment.resource_id");
$this->hasMany("Resource as ResourceAlias","Assignment.resource_id");
$this->hasMany("Task as Subtask","Subtask.parent_id");
}
public function setTableDefinition() {
......@@ -139,7 +145,7 @@ class Task extends Doctrine_Record {
class Resource extends Doctrine_Record {
public function setUp() {
$this->hasMany("Task","Assignment.task_id");
$this->hasMany("Task as TaskAlias","Assignment.task_id");
}
public function setTableDefinition() {
$this->hasColumn("name","string",100);
......
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