Commit 4f5b332d authored by romanb's avatar romanb

[2.0] Adding insert performance tests.

parent c07416ac
...@@ -515,7 +515,7 @@ class Connection ...@@ -515,7 +515,7 @@ class Connection
{ {
$this->connect(); $this->connect();
try { try {
echo "DBAL:" . $query . PHP_EOL; //echo "DBAL:" . $query . PHP_EOL;
if ( ! empty($params)) { if ( ! empty($params)) {
$stmt = $this->prepare($query); $stmt = $this->prepare($query);
$stmt->execute($params); $stmt->execute($params);
...@@ -542,9 +542,9 @@ class Connection ...@@ -542,9 +542,9 @@ class Connection
public function exec($query, array $params = array()) { public function exec($query, array $params = array()) {
$this->connect(); $this->connect();
try { try {
echo $query . PHP_EOL; //echo $query . PHP_EOL;
if ( ! empty($params)) { if ( ! empty($params)) {
var_dump($params); //var_dump($params);
$stmt = $this->prepare($query); $stmt = $this->prepare($query);
$stmt->execute($params); $stmt->execute($params);
return $stmt->rowCount(); return $stmt->rowCount();
......
...@@ -346,7 +346,7 @@ class EntityManager ...@@ -346,7 +346,7 @@ class EntityManager
public function clear($entityName = null) public function clear($entityName = null)
{ {
if ($entityName === null) { if ($entityName === null) {
$this->_unitOfWork->detachAll(); $this->_unitOfWork->clear();
} else { } else {
//TODO //TODO
throw DoctrineException::notImplemented(); throw DoctrineException::notImplemented();
......
...@@ -360,8 +360,6 @@ final class ClassMetadata ...@@ -360,8 +360,6 @@ final class ClassMetadata
*/ */
public $reflFields; public $reflFields;
//private $_insertSql;
/** /**
* The ID generator used for generating IDs for this class. * The ID generator used for generating IDs for this class.
* *
...@@ -392,6 +390,13 @@ final class ClassMetadata ...@@ -392,6 +390,13 @@ final class ClassMetadata
*/ */
public $changeTrackingPolicy = self::CHANGETRACKING_DEFERRED_IMPLICIT; public $changeTrackingPolicy = self::CHANGETRACKING_DEFERRED_IMPLICIT;
/**
* The SQL INSERT string for entities of this class.
*
* @var string
*/
public $insertSql;
/** /**
* Initializes a new ClassMetadata instance that will hold the object-relational mapping * Initializes a new ClassMetadata instance that will hold the object-relational mapping
* metadata of the class with the given name. * metadata of the class with the given name.
...@@ -1373,6 +1378,8 @@ final class ClassMetadata ...@@ -1373,6 +1378,8 @@ final class ClassMetadata
public function addFieldMapping(array $fieldMapping) public function addFieldMapping(array $fieldMapping)
{ {
$this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping; $this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping;
$this->columnNames[$fieldMapping['fieldName']] = $fieldMapping['columnName'];
$this->fieldNames[$fieldMapping['columnName']] = $fieldMapping['fieldName'];
} }
/** /**
...@@ -1754,6 +1761,46 @@ final class ClassMetadata ...@@ -1754,6 +1761,46 @@ final class ClassMetadata
$this->sequenceGeneratorDefinition = $definition; $this->sequenceGeneratorDefinition = $definition;
} }
/**
* INTERNAL: Called by ClassMetadataFactory.
*
* Tells this class descriptor to finish the mapping definition, making any
* final adjustments, i.e. generating some SQL strings.
*/
public function finishMapping()
{
$columns = $values = array();
if ($this->inheritanceType == self::INHERITANCE_TYPE_JOINED) {
//TODO
} else {
foreach ($this->reflFields as $name => $field) {
if (isset($this->associationMappings[$name])) {
$assoc = $this->associationMappings[$name];
if ($assoc->isOwningSide && $assoc->isOneToOne()) {
foreach ($assoc->targetToSourceKeyColumns as $sourceCol) {
$columns[] = $sourceCol;
$values[] = '?';
}
}
} else if ($this->generatorType != self::GENERATOR_TYPE_IDENTITY || $this->identifier[0] != $name) {
$columns[] = $this->columnNames[$name];
$values[] = '?';
}
}
}
if ($this->discriminatorColumn) {
$columns[] = $this->discriminatorColumn['name'];
$values[] = '?';
}
$this->insertSql = 'INSERT INTO ' . $this->primaryTable['name']
. ' (' . implode(', ', $columns) . ') '
. 'VALUES (' . implode(', ', $values) . ')';
}
/** /**
* Creates a string representation of this instance. * Creates a string representation of this instance.
* *
......
...@@ -136,7 +136,6 @@ class ClassMetadataFactory ...@@ -136,7 +136,6 @@ class ClassMetadataFactory
$class = $this->_newClassMetadataInstance($className); $class = $this->_newClassMetadataInstance($className);
if ($parent) { if ($parent) {
$class->setInheritanceType($parent->inheritanceType); $class->setInheritanceType($parent->inheritanceType);
//$class->setDiscriminatorMap($parent->getDiscriminatorMap());
$class->setDiscriminatorColumn($parent->discriminatorColumn); $class->setDiscriminatorColumn($parent->discriminatorColumn);
$class->setIdGeneratorType($parent->generatorType); $class->setIdGeneratorType($parent->generatorType);
$this->_addInheritedFields($class, $parent); $this->_addInheritedFields($class, $parent);
...@@ -167,9 +166,12 @@ class ClassMetadataFactory ...@@ -167,9 +166,12 @@ class ClassMetadataFactory
$class->setTableName($parent->getTableName()); $class->setTableName($parent->getTableName());
} }
$class->setParentClasses($visited);
$class->finishMapping();
$this->_loadedMetadata[$className] = $class; $this->_loadedMetadata[$className] = $class;
$parent = $class; $parent = $class;
$class->setParentClasses($visited);
array_unshift($visited, $className); array_unshift($visited, $className);
} }
} }
......
...@@ -96,7 +96,11 @@ class StandardEntityPersister ...@@ -96,7 +96,11 @@ class StandardEntityPersister
{ {
$insertData = array(); $insertData = array();
$this->_prepareData($entity, $insertData, true); $this->_prepareData($entity, $insertData, true);
$this->_conn->insert($this->_class->getTableName(), $insertData);
$stmt = $this->_conn->prepare($this->_class->insertSql);
$stmt->execute(array_values($insertData));
$stmt->closeCursor();
$idGen = $this->_class->getIdGenerator(); $idGen = $this->_class->getIdGenerator();
if ($idGen->isPostInsertGenerator()) { if ($idGen->isPostInsertGenerator()) {
return $idGen->generate($this->_em, $entity); return $idGen->generate($this->_em, $entity);
...@@ -111,9 +115,7 @@ class StandardEntityPersister ...@@ -111,9 +115,7 @@ class StandardEntityPersister
*/ */
public function addInsert($entity) public function addInsert($entity)
{ {
$insertData = array(); $this->_queuedInserts[] = $entity;
$this->_prepareData($entity, $insertData, true);
$this->_queuedInserts[] = $insertData;
} }
/** /**
...@@ -121,12 +123,27 @@ class StandardEntityPersister ...@@ -121,12 +123,27 @@ class StandardEntityPersister
*/ */
public function executeInserts() public function executeInserts()
{ {
//$tableName = $this->_class->getTableName(); if ( ! $this->_queuedInserts) {
$stmt = $this->_conn->prepare($this->_class->getInsertSql()); return;
foreach ($this->_queuedInserts as $insertData) { }
$postInsertIds = array();
$idGen = $this->_class->getIdGenerator();
$isPostInsertId = $idGen->isPostInsertGenerator();
$stmt = $this->_conn->prepare($this->_class->insertSql);
foreach ($this->_queuedInserts as $entity) {
$insertData = array();
$this->_prepareData($entity, $insertData, true);
$stmt->execute(array_values($insertData)); $stmt->execute(array_values($insertData));
if ($isPostInsertId) {
$postInsertIds[$idGen->generate($this->_em, $entity)] = $entity;
} }
}
$stmt->closeCursor();
$this->_queuedInserts = array(); $this->_queuedInserts = array();
return $postInsertIds;
} }
/** /**
...@@ -226,13 +243,13 @@ class StandardEntityPersister ...@@ -226,13 +243,13 @@ class StandardEntityPersister
$columnName = $this->_class->getColumnName($field); $columnName = $this->_class->getColumnName($field);
if ($this->_class->hasAssociation($field)) { if (isset($this->_class->associationMappings[$field])) {
$assocMapping = $this->_class->getAssociationMapping($field); $assocMapping = $this->_class->associationMappings[$field];
if ( ! $assocMapping->isOneToOne() || $assocMapping->isInverseSide()) { if ( ! $assocMapping->isOneToOne() || $assocMapping->isInverseSide()) {
continue; continue;
} }
foreach ($assocMapping->getSourceToTargetKeyColumns() as $sourceColumn => $targetColumn) { foreach ($assocMapping->sourceToTargetKeyColumns as $sourceColumn => $targetColumn) {
$otherClass = $this->_em->getClassMetadata($assocMapping->getTargetEntityName()); $otherClass = $this->_em->getClassMetadata($assocMapping->targetEntityName);
if ($newVal === null) { if ($newVal === null) {
$result[$sourceColumn] = null; $result[$sourceColumn] = null;
} else { } else {
......
...@@ -235,7 +235,9 @@ class UnitOfWork implements PropertyChangedListener ...@@ -235,7 +235,9 @@ class UnitOfWork implements PropertyChangedListener
// Now we need a commit order to maintain referential integrity // Now we need a commit order to maintain referential integrity
$commitOrder = $this->_getCommitOrder(); $commitOrder = $this->_getCommitOrder();
//TODO: begin transaction here? $conn = $this->_em->getConnection();
try {
$conn->beginTransaction();
foreach ($commitOrder as $class) { foreach ($commitOrder as $class) {
$this->_executeInserts($class); $this->_executeInserts($class);
...@@ -261,6 +263,12 @@ class UnitOfWork implements PropertyChangedListener ...@@ -261,6 +263,12 @@ class UnitOfWork implements PropertyChangedListener
$this->_executeDeletions($commitOrder[$i]); $this->_executeDeletions($commitOrder[$i]);
} }
$conn->commit();
} catch (\Exception $e) {
$conn->rollback();
throw $e;
}
//TODO: commit transaction here? //TODO: commit transaction here?
// Take new snapshots from visited collections // Take new snapshots from visited collections
...@@ -401,7 +409,7 @@ class UnitOfWork implements PropertyChangedListener ...@@ -401,7 +409,7 @@ class UnitOfWork implements PropertyChangedListener
$assoc = $class->getAssociationMapping($name); $assoc = $class->getAssociationMapping($name);
//echo PHP_EOL . "INJECTING PCOLL into $name" . PHP_EOL; //echo PHP_EOL . "INJECTING PCOLL into $name" . PHP_EOL;
// Inject PersistentCollection // Inject PersistentCollection
$coll = new PersistentCollection($this->_em, $this->_em->getClassMetadata($assoc->getTargetEntityName()), $coll = new PersistentCollection($this->_em, $this->_em->getClassMetadata($assoc->targetEntityName),
$actualData[$name] ? $actualData[$name] : array()); $actualData[$name] ? $actualData[$name] : array());
$coll->setOwner($entity, $assoc); $coll->setOwner($entity, $assoc);
if ( ! $coll->isEmpty()) { if ( ! $coll->isEmpty()) {
...@@ -438,7 +446,7 @@ class UnitOfWork implements PropertyChangedListener ...@@ -438,7 +446,7 @@ class UnitOfWork implements PropertyChangedListener
if (isset($changeSet[$propName])) { if (isset($changeSet[$propName])) {
if ($class->hasAssociation($propName)) { if ($class->hasAssociation($propName)) {
$assoc = $class->getAssociationMapping($propName); $assoc = $class->getAssociationMapping($propName);
if ($assoc->isOneToOne() && $assoc->isOwningSide()) { if ($assoc->isOneToOne() && $assoc->isOwningSide) {
$entityIsDirty = true; $entityIsDirty = true;
} else if ($orgValue instanceof PersistentCollection) { } else if ($orgValue instanceof PersistentCollection) {
// A PersistentCollection was de-referenced, so delete it. // A PersistentCollection was de-referenced, so delete it.
...@@ -538,28 +546,27 @@ class UnitOfWork implements PropertyChangedListener ...@@ -538,28 +546,27 @@ class UnitOfWork implements PropertyChangedListener
*/ */
private function _executeInserts($class) private function _executeInserts($class)
{ {
//TODO: Maybe $persister->addInsert($entity) in the loop and
// $persister->executeInserts() at the end to allow easy prepared
// statement reuse and maybe bulk operations in the persister.
// Same for update/delete.
$className = $class->name; $className = $class->name;
$persister = $this->getEntityPersister($className); $persister = $this->getEntityPersister($className);
foreach ($this->_entityInsertions as $entity) { foreach ($this->_entityInsertions as $entity) {
if (get_class($entity) == $className) { if (get_class($entity) == $className) {
$returnVal = $persister->insert($entity); $persister->addInsert($entity);
if ($returnVal !== null) { }
}
$postInsertIds = $persister->executeInserts();
if ($postInsertIds) {
foreach ($postInsertIds as $id => $entity) {
// Persister returned a post-insert ID // Persister returned a post-insert ID
$oid = spl_object_hash($entity); $oid = spl_object_hash($entity);
$idField = $class->getSingleIdentifierFieldName(); $idField = $class->identifier[0];
$class->reflFields[$idField]->setValue($entity, $returnVal); $class->reflFields[$idField]->setValue($entity, $id);
$this->_entityIdentifiers[$oid] = array($returnVal); $this->_entityIdentifiers[$oid] = array($id);
$this->_entityStates[$oid] = self::STATE_MANAGED; $this->_entityStates[$oid] = self::STATE_MANAGED;
$this->_originalEntityData[$oid][$idField] = $returnVal; $this->_originalEntityData[$oid][$idField] = $id;
$this->addToIdentityMap($entity); $this->addToIdentityMap($entity);
} }
} }
} }
}
/** /**
* Executes all entity updates for entities of the specified type. * Executes all entity updates for entities of the specified type.
......
...@@ -12,6 +12,7 @@ class EntityPersisterMock extends \Doctrine\ORM\Persisters\StandardEntityPersist ...@@ -12,6 +12,7 @@ class EntityPersisterMock extends \Doctrine\ORM\Persisters\StandardEntityPersist
private $_deletes = array(); private $_deletes = array();
private $_identityColumnValueCounter = 0; private $_identityColumnValueCounter = 0;
private $_mockIdGeneratorType; private $_mockIdGeneratorType;
private $_postInsertIds = array();
/** /**
* @param <type> $entity * @param <type> $entity
...@@ -22,12 +23,31 @@ class EntityPersisterMock extends \Doctrine\ORM\Persisters\StandardEntityPersist ...@@ -22,12 +23,31 @@ class EntityPersisterMock extends \Doctrine\ORM\Persisters\StandardEntityPersist
{ {
$this->_inserts[] = $entity; $this->_inserts[] = $entity;
if ( ! is_null($this->_mockIdGeneratorType) && $this->_mockIdGeneratorType == \Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_IDENTITY if ( ! is_null($this->_mockIdGeneratorType) && $this->_mockIdGeneratorType == \Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_IDENTITY
|| $this->_classMetadata->isIdGeneratorIdentity()) { || $this->_class->isIdGeneratorIdentity()) {
return $this->_identityColumnValueCounter++; $id = $this->_identityColumnValueCounter++;
$this->_postInsertIds[$id] = $entity;
return $id;
} }
return null; return null;
} }
public function addInsert($entity)
{
$this->_inserts[] = $entity;
if ( ! is_null($this->_mockIdGeneratorType) && $this->_mockIdGeneratorType == \Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_IDENTITY
|| $this->_class->isIdGeneratorIdentity()) {
$id = $this->_identityColumnValueCounter++;
$this->_postInsertIds[$id] = $entity;
return $id;
}
return null;
}
public function executeInserts()
{
return $this->_postInsertIds;
}
public function setMockIdGeneratorType($genType) public function setMockIdGeneratorType($genType)
{ {
$this->_mockIdGeneratorType = $genType; $this->_mockIdGeneratorType = $genType;
......
...@@ -30,7 +30,7 @@ class AllTests ...@@ -30,7 +30,7 @@ class AllTests
$suite->addTestSuite('Doctrine\Tests\ORM\UnitOfWorkTest'); $suite->addTestSuite('Doctrine\Tests\ORM\UnitOfWorkTest');
$suite->addTestSuite('Doctrine\Tests\ORM\EntityManagerTest'); $suite->addTestSuite('Doctrine\Tests\ORM\EntityManagerTest');
$suite->addTestSuite('Doctrine\Tests\ORM\EntityPersisterTest'); //$suite->addTestSuite('Doctrine\Tests\ORM\EntityPersisterTest');
$suite->addTestSuite('Doctrine\Tests\ORM\CommitOrderCalculatorTest'); $suite->addTestSuite('Doctrine\Tests\ORM\CommitOrderCalculatorTest');
$suite->addTest(Query\AllTests::suite()); $suite->addTest(Query\AllTests::suite());
......
...@@ -21,6 +21,7 @@ class AllTests ...@@ -21,6 +21,7 @@ class AllTests
$suite = new \Doctrine\Tests\DoctrineTestSuite('Doctrine Orm Performance'); $suite = new \Doctrine\Tests\DoctrineTestSuite('Doctrine Orm Performance');
$suite->addTestSuite('Doctrine\Tests\ORM\Performance\HydrationPerformanceTest'); $suite->addTestSuite('Doctrine\Tests\ORM\Performance\HydrationPerformanceTest');
$suite->addTestSuite('Doctrine\Tests\ORM\Performance\InsertPerformanceTest');
return $suite; return $suite;
} }
......
<?php
namespace Doctrine\Tests\ORM\Performance;
require_once __DIR__ . '/../../TestInit.php';
use Doctrine\Tests\Models\CMS\CmsUser;
/**
* Description of InsertPerformanceTest
*
* @author robo
*/
class InsertPerformanceTest extends \Doctrine\Tests\OrmPerformanceTestCase
{
protected function setUp() {
$this->useModelSet('cms');
parent::setUp();
}
/**
* [romanb: 10000 objects in ~8 seconds]
*/
public function testInsertPerformance()
{
$s = microtime(true);
$conn = $this->_em->getConnection();
$this->setMaxRunningTime(10);
//$mem = memory_get_usage();
//echo "Memory usage before: " . ($mem / 1024) . " KB" . PHP_EOL;
for ($i=0; $i<10000; ++$i) {
$user = new CmsUser;
$user->status = 'user';
$user->username = 'user' . $i;
$user->name = 'Mr.Smith-' . $i;
$this->_em->save($user);
if (($i % 20) == 0) {
$this->_em->flush();
$this->_em->clear();
}
}
//$memAfter = memory_get_usage();
//echo "Memory usage after: " . ($memAfter / 1024) . " KB" . PHP_EOL;
$e = microtime(true);
echo ' Inserted 10000 records in ' . ($e - $s) . ' seconds' . PHP_EOL;
}
}
...@@ -125,6 +125,9 @@ class UnitOfWorkTest extends \Doctrine\Tests\OrmTestCase ...@@ -125,6 +125,9 @@ class UnitOfWorkTest extends \Doctrine\Tests\OrmTestCase
public function testChangeTrackingNotify() public function testChangeTrackingNotify()
{ {
$persister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata("Doctrine\Tests\ORM\NotifyChangedEntity"));
$this->_unitOfWork->setEntityPersister('Doctrine\Tests\ORM\NotifyChangedEntity', $persister);
$entity = new NotifyChangedEntity; $entity = new NotifyChangedEntity;
$entity->setData('thedata'); $entity->setData('thedata');
$this->_unitOfWork->save($entity); $this->_unitOfWork->save($entity);
......
...@@ -7,16 +7,8 @@ namespace Doctrine\Tests; ...@@ -7,16 +7,8 @@ namespace Doctrine\Tests;
* *
* @author robo * @author robo
*/ */
class OrmPerformanceTestCase extends OrmTestCase class OrmPerformanceTestCase extends OrmFunctionalTestCase
{ {
protected $_em;
protected function setUp()
{
parent::setUp();
$this->_em = $this->_getTestEntityManager();
}
/** /**
* @var integer * @var integer
*/ */
......
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