[2.0] Adding insert performance tests.
This commit is contained in:
parent
c07416ac39
commit
4f5b332d34
@ -515,7 +515,7 @@ class Connection
|
||||
{
|
||||
$this->connect();
|
||||
try {
|
||||
echo "DBAL:" . $query . PHP_EOL;
|
||||
//echo "DBAL:" . $query . PHP_EOL;
|
||||
if ( ! empty($params)) {
|
||||
$stmt = $this->prepare($query);
|
||||
$stmt->execute($params);
|
||||
@ -542,9 +542,9 @@ class Connection
|
||||
public function exec($query, array $params = array()) {
|
||||
$this->connect();
|
||||
try {
|
||||
echo $query . PHP_EOL;
|
||||
//echo $query . PHP_EOL;
|
||||
if ( ! empty($params)) {
|
||||
var_dump($params);
|
||||
//var_dump($params);
|
||||
$stmt = $this->prepare($query);
|
||||
$stmt->execute($params);
|
||||
return $stmt->rowCount();
|
||||
|
@ -346,7 +346,7 @@ class EntityManager
|
||||
public function clear($entityName = null)
|
||||
{
|
||||
if ($entityName === null) {
|
||||
$this->_unitOfWork->detachAll();
|
||||
$this->_unitOfWork->clear();
|
||||
} else {
|
||||
//TODO
|
||||
throw DoctrineException::notImplemented();
|
||||
|
@ -360,8 +360,6 @@ final class ClassMetadata
|
||||
*/
|
||||
public $reflFields;
|
||||
|
||||
//private $_insertSql;
|
||||
|
||||
/**
|
||||
* The ID generator used for generating IDs for this class.
|
||||
*
|
||||
@ -392,6 +390,13 @@ final class ClassMetadata
|
||||
*/
|
||||
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
|
||||
* metadata of the class with the given name.
|
||||
@ -1373,6 +1378,8 @@ final class ClassMetadata
|
||||
public function addFieldMapping(array $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
|
||||
$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.
|
||||
*
|
||||
|
@ -136,7 +136,6 @@ class ClassMetadataFactory
|
||||
$class = $this->_newClassMetadataInstance($className);
|
||||
if ($parent) {
|
||||
$class->setInheritanceType($parent->inheritanceType);
|
||||
//$class->setDiscriminatorMap($parent->getDiscriminatorMap());
|
||||
$class->setDiscriminatorColumn($parent->discriminatorColumn);
|
||||
$class->setIdGeneratorType($parent->generatorType);
|
||||
$this->_addInheritedFields($class, $parent);
|
||||
@ -166,10 +165,13 @@ class ClassMetadataFactory
|
||||
if ($parent && $parent->isInheritanceTypeSingleTable()) {
|
||||
$class->setTableName($parent->getTableName());
|
||||
}
|
||||
|
||||
$class->setParentClasses($visited);
|
||||
$class->finishMapping();
|
||||
|
||||
$this->_loadedMetadata[$className] = $class;
|
||||
|
||||
$parent = $class;
|
||||
$class->setParentClasses($visited);
|
||||
array_unshift($visited, $className);
|
||||
}
|
||||
}
|
||||
|
@ -96,7 +96,11 @@ class StandardEntityPersister
|
||||
{
|
||||
$insertData = array();
|
||||
$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();
|
||||
if ($idGen->isPostInsertGenerator()) {
|
||||
return $idGen->generate($this->_em, $entity);
|
||||
@ -111,9 +115,7 @@ class StandardEntityPersister
|
||||
*/
|
||||
public function addInsert($entity)
|
||||
{
|
||||
$insertData = array();
|
||||
$this->_prepareData($entity, $insertData, true);
|
||||
$this->_queuedInserts[] = $insertData;
|
||||
$this->_queuedInserts[] = $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -121,12 +123,27 @@ class StandardEntityPersister
|
||||
*/
|
||||
public function executeInserts()
|
||||
{
|
||||
//$tableName = $this->_class->getTableName();
|
||||
$stmt = $this->_conn->prepare($this->_class->getInsertSql());
|
||||
foreach ($this->_queuedInserts as $insertData) {
|
||||
$stmt->execute(array_values($insertData));
|
||||
if ( ! $this->_queuedInserts) {
|
||||
return;
|
||||
}
|
||||
|
||||
$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));
|
||||
if ($isPostInsertId) {
|
||||
$postInsertIds[$idGen->generate($this->_em, $entity)] = $entity;
|
||||
}
|
||||
}
|
||||
$stmt->closeCursor();
|
||||
$this->_queuedInserts = array();
|
||||
|
||||
return $postInsertIds;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -226,13 +243,13 @@ class StandardEntityPersister
|
||||
|
||||
$columnName = $this->_class->getColumnName($field);
|
||||
|
||||
if ($this->_class->hasAssociation($field)) {
|
||||
$assocMapping = $this->_class->getAssociationMapping($field);
|
||||
if (isset($this->_class->associationMappings[$field])) {
|
||||
$assocMapping = $this->_class->associationMappings[$field];
|
||||
if ( ! $assocMapping->isOneToOne() || $assocMapping->isInverseSide()) {
|
||||
continue;
|
||||
}
|
||||
foreach ($assocMapping->getSourceToTargetKeyColumns() as $sourceColumn => $targetColumn) {
|
||||
$otherClass = $this->_em->getClassMetadata($assocMapping->getTargetEntityName());
|
||||
foreach ($assocMapping->sourceToTargetKeyColumns as $sourceColumn => $targetColumn) {
|
||||
$otherClass = $this->_em->getClassMetadata($assocMapping->targetEntityName);
|
||||
if ($newVal === null) {
|
||||
$result[$sourceColumn] = null;
|
||||
} else {
|
||||
|
@ -235,30 +235,38 @@ class UnitOfWork implements PropertyChangedListener
|
||||
// Now we need a commit order to maintain referential integrity
|
||||
$commitOrder = $this->_getCommitOrder();
|
||||
|
||||
//TODO: begin transaction here?
|
||||
$conn = $this->_em->getConnection();
|
||||
try {
|
||||
$conn->beginTransaction();
|
||||
|
||||
foreach ($commitOrder as $class) {
|
||||
$this->_executeInserts($class);
|
||||
}
|
||||
foreach ($commitOrder as $class) {
|
||||
$this->_executeUpdates($class);
|
||||
}
|
||||
|
||||
// Collection deletions (deletions of complete collections)
|
||||
foreach ($this->_collectionDeletions as $collectionToDelete) {
|
||||
$this->getCollectionPersister($collectionToDelete->getMapping())
|
||||
->delete($collectionToDelete);
|
||||
}
|
||||
// Collection updates (deleteRows, updateRows, insertRows)
|
||||
foreach ($this->_collectionUpdates as $collectionToUpdate) {
|
||||
$this->getCollectionPersister($collectionToUpdate->getMapping())
|
||||
->update($collectionToUpdate);
|
||||
}
|
||||
//TODO: collection recreations (insertions of complete collections)
|
||||
foreach ($commitOrder as $class) {
|
||||
$this->_executeInserts($class);
|
||||
}
|
||||
foreach ($commitOrder as $class) {
|
||||
$this->_executeUpdates($class);
|
||||
}
|
||||
|
||||
// Entity deletions come last and need to be in reverse commit order
|
||||
for ($count = count($commitOrder), $i = $count - 1; $i >= 0; --$i) {
|
||||
$this->_executeDeletions($commitOrder[$i]);
|
||||
// Collection deletions (deletions of complete collections)
|
||||
foreach ($this->_collectionDeletions as $collectionToDelete) {
|
||||
$this->getCollectionPersister($collectionToDelete->getMapping())
|
||||
->delete($collectionToDelete);
|
||||
}
|
||||
// Collection updates (deleteRows, updateRows, insertRows)
|
||||
foreach ($this->_collectionUpdates as $collectionToUpdate) {
|
||||
$this->getCollectionPersister($collectionToUpdate->getMapping())
|
||||
->update($collectionToUpdate);
|
||||
}
|
||||
//TODO: collection recreations (insertions of complete collections)
|
||||
|
||||
// Entity deletions come last and need to be in reverse commit order
|
||||
for ($count = count($commitOrder), $i = $count - 1; $i >= 0; --$i) {
|
||||
$this->_executeDeletions($commitOrder[$i]);
|
||||
}
|
||||
|
||||
$conn->commit();
|
||||
} catch (\Exception $e) {
|
||||
$conn->rollback();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
//TODO: commit transaction here?
|
||||
@ -401,7 +409,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
$assoc = $class->getAssociationMapping($name);
|
||||
//echo PHP_EOL . "INJECTING PCOLL into $name" . PHP_EOL;
|
||||
// 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());
|
||||
$coll->setOwner($entity, $assoc);
|
||||
if ( ! $coll->isEmpty()) {
|
||||
@ -438,7 +446,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
if (isset($changeSet[$propName])) {
|
||||
if ($class->hasAssociation($propName)) {
|
||||
$assoc = $class->getAssociationMapping($propName);
|
||||
if ($assoc->isOneToOne() && $assoc->isOwningSide()) {
|
||||
if ($assoc->isOneToOne() && $assoc->isOwningSide) {
|
||||
$entityIsDirty = true;
|
||||
} else if ($orgValue instanceof PersistentCollection) {
|
||||
// A PersistentCollection was de-referenced, so delete it.
|
||||
@ -538,25 +546,24 @@ class UnitOfWork implements PropertyChangedListener
|
||||
*/
|
||||
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;
|
||||
$persister = $this->getEntityPersister($className);
|
||||
foreach ($this->_entityInsertions as $entity) {
|
||||
if (get_class($entity) == $className) {
|
||||
$returnVal = $persister->insert($entity);
|
||||
if ($returnVal !== null) {
|
||||
// Persister returned a post-insert ID
|
||||
$oid = spl_object_hash($entity);
|
||||
$idField = $class->getSingleIdentifierFieldName();
|
||||
$class->reflFields[$idField]->setValue($entity, $returnVal);
|
||||
$this->_entityIdentifiers[$oid] = array($returnVal);
|
||||
$this->_entityStates[$oid] = self::STATE_MANAGED;
|
||||
$this->_originalEntityData[$oid][$idField] = $returnVal;
|
||||
$this->addToIdentityMap($entity);
|
||||
}
|
||||
$persister->addInsert($entity);
|
||||
}
|
||||
}
|
||||
$postInsertIds = $persister->executeInserts();
|
||||
if ($postInsertIds) {
|
||||
foreach ($postInsertIds as $id => $entity) {
|
||||
// Persister returned a post-insert ID
|
||||
$oid = spl_object_hash($entity);
|
||||
$idField = $class->identifier[0];
|
||||
$class->reflFields[$idField]->setValue($entity, $id);
|
||||
$this->_entityIdentifiers[$oid] = array($id);
|
||||
$this->_entityStates[$oid] = self::STATE_MANAGED;
|
||||
$this->_originalEntityData[$oid][$idField] = $id;
|
||||
$this->addToIdentityMap($entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ class EntityPersisterMock extends \Doctrine\ORM\Persisters\StandardEntityPersist
|
||||
private $_deletes = array();
|
||||
private $_identityColumnValueCounter = 0;
|
||||
private $_mockIdGeneratorType;
|
||||
private $_postInsertIds = array();
|
||||
|
||||
/**
|
||||
* @param <type> $entity
|
||||
@ -22,12 +23,31 @@ class EntityPersisterMock extends \Doctrine\ORM\Persisters\StandardEntityPersist
|
||||
{
|
||||
$this->_inserts[] = $entity;
|
||||
if ( ! is_null($this->_mockIdGeneratorType) && $this->_mockIdGeneratorType == \Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_IDENTITY
|
||||
|| $this->_classMetadata->isIdGeneratorIdentity()) {
|
||||
return $this->_identityColumnValueCounter++;
|
||||
|| $this->_class->isIdGeneratorIdentity()) {
|
||||
$id = $this->_identityColumnValueCounter++;
|
||||
$this->_postInsertIds[$id] = $entity;
|
||||
return $id;
|
||||
}
|
||||
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)
|
||||
{
|
||||
$this->_mockIdGeneratorType = $genType;
|
||||
|
@ -30,7 +30,7 @@ class AllTests
|
||||
|
||||
$suite->addTestSuite('Doctrine\Tests\ORM\UnitOfWorkTest');
|
||||
$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->addTest(Query\AllTests::suite());
|
||||
|
@ -21,6 +21,7 @@ class AllTests
|
||||
$suite = new \Doctrine\Tests\DoctrineTestSuite('Doctrine Orm Performance');
|
||||
|
||||
$suite->addTestSuite('Doctrine\Tests\ORM\Performance\HydrationPerformanceTest');
|
||||
$suite->addTestSuite('Doctrine\Tests\ORM\Performance\InsertPerformanceTest');
|
||||
|
||||
return $suite;
|
||||
}
|
||||
|
@ -0,0 +1,55 @@
|
||||
<?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
|
||||
|
||||
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->setData('thedata');
|
||||
$this->_unitOfWork->save($entity);
|
||||
|
@ -7,16 +7,8 @@ namespace Doctrine\Tests;
|
||||
*
|
||||
* @author robo
|
||||
*/
|
||||
class OrmPerformanceTestCase extends OrmTestCase
|
||||
class OrmPerformanceTestCase extends OrmFunctionalTestCase
|
||||
{
|
||||
protected $_em;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
$this->_em = $this->_getTestEntityManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
|
Loading…
x
Reference in New Issue
Block a user