From 530e4840dd93357033b93a380be687231b430388 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Sat, 25 Feb 2012 19:58:59 -0200 Subject: [PATCH 01/20] native query annotations --- lib/Doctrine/ORM/Mapping/EntityResult.php | 58 +++++++++++++++++ lib/Doctrine/ORM/Mapping/FieldResult.php | 48 ++++++++++++++ .../ORM/Mapping/NamedNativeQueries.php | 40 ++++++++++++ lib/Doctrine/ORM/Mapping/NamedNativeQuery.php | 63 +++++++++++++++++++ .../ORM/Mapping/SqlResultSetMapping.php | 56 +++++++++++++++++ .../ORM/Mapping/SqlResultSetMappings.php | 40 ++++++++++++ 6 files changed, 305 insertions(+) create mode 100644 lib/Doctrine/ORM/Mapping/EntityResult.php create mode 100644 lib/Doctrine/ORM/Mapping/FieldResult.php create mode 100644 lib/Doctrine/ORM/Mapping/NamedNativeQueries.php create mode 100644 lib/Doctrine/ORM/Mapping/NamedNativeQuery.php create mode 100644 lib/Doctrine/ORM/Mapping/SqlResultSetMapping.php create mode 100644 lib/Doctrine/ORM/Mapping/SqlResultSetMappings.php diff --git a/lib/Doctrine/ORM/Mapping/EntityResult.php b/lib/Doctrine/ORM/Mapping/EntityResult.php new file mode 100644 index 000000000..ae83d52c2 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/EntityResult.php @@ -0,0 +1,58 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * References an entity in the SELECT clause of a SQL query. + * If this annotation is used, the SQL statement should select all of the columns that are mapped to the entity object. + * This should include foreign key columns to related entities. + * The results obtained when insufficient data is available are undefined. + * + * @author Fabio B. Silva + * @since 2.3 + * + * @Annotation + * @Target("ANNOTATION") + */ +final class EntityResult implements Annotation +{ + + /** + * The class of the result + * + * @var string + */ + public $entityClass; + + /** + * Maps the columns specified in the SELECT list of the query to the properties or fields of the entity class. + * + * @var array<\Doctrine\ORM\Mapping\FieldResult> + */ + public $fields; + + /** + * Specifies the column name of the column in the SELECT list that is used to determine the type of the entity instance. + * + * @var string + */ + public $discriminatorColumn; + +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Mapping/FieldResult.php b/lib/Doctrine/ORM/Mapping/FieldResult.php new file mode 100644 index 000000000..c2c49c68d --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/FieldResult.php @@ -0,0 +1,48 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * Is used to map the columns specified in the SELECT list of the query to the properties or fields of the entity class. + * + * @author Fabio B. Silva + * @since 2.3 + * + * @Annotation + * @Target("ANNOTATION") + */ +final class FieldResult implements Annotation +{ + + /** + * Name of the column in the SELECT clause. + * + * @var string + */ + public $name; + + /** + * Name of the persistent field or property of the class. + * + * @var string + */ + public $column; + +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Mapping/NamedNativeQueries.php b/lib/Doctrine/ORM/Mapping/NamedNativeQueries.php new file mode 100644 index 000000000..f957f4ed2 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/NamedNativeQueries.php @@ -0,0 +1,40 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * Is used to specify an array of native SQL named queries. + * The NamedNativeQueries annotation can be applied to an entity or mapped superclass. + * + * @author Fabio B. Silva + * @since 2.3 + * + * @Annotation + * @Target("CLASS") + */ +final class NamedNativeQueries implements Annotation +{ + /** + * One or more NamedNativeQuery annotations. + * + * @var array<\Doctrine\ORM\Mapping\NamedNativeQuery> + */ + public $value; +} diff --git a/lib/Doctrine/ORM/Mapping/NamedNativeQuery.php b/lib/Doctrine/ORM/Mapping/NamedNativeQuery.php new file mode 100644 index 000000000..f7c604461 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/NamedNativeQuery.php @@ -0,0 +1,63 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * Is used to specify a native SQL named query. + * The NamedNativeQuery annotation can be applied to an entity or mapped superclass. + * + * @author Fabio B. Silva + * @since 2.3 + * + * @Annotation + * @Target("CLASS") + */ +final class NamedNativeQuery implements Annotation +{ + + /** + * The name used to refer to the query with the EntityManager methods that create query objects. + * + * @var string + */ + public $name; + + /** + * The SQL query string. + * + * @var string + */ + public $query; + + /** + * The class of the result. + * + * @var string + */ + public $resultClass; + + /** + * The name of a SqlResultSetMapping, as defined in metadata. + * + * @var string + */ + public $resultSetMapping; + +} diff --git a/lib/Doctrine/ORM/Mapping/SqlResultSetMapping.php b/lib/Doctrine/ORM/Mapping/SqlResultSetMapping.php new file mode 100644 index 000000000..a3bc5b4c9 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/SqlResultSetMapping.php @@ -0,0 +1,56 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * The SqlResultSetMapping annotation is used to specify the mapping of the result of a native SQL query. + * The SqlResultSetMapping annotation can be applied to an entity or mapped superclass. + * + * @author Fabio B. Silva + * @since 2.3 + * + * @Annotation + * @Target("CLASS") + */ +final class SqlResultSetMapping implements Annotation +{ + + /** + * The name given to the result set mapping, and used to refer to it in the methods of the Query API. + * + * @var string + */ + public $name; + + /** + * Specifies the result set mapping to entities. + * + * @var array<\Doctrine\ORM\Mapping\EntityResult> + */ + public $entities; + + /** + * Specifies the result set mapping to scalar values. + * + * @var array<\Doctrine\ORM\Mapping\ColumnResult> + */ + public $columns; + +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Mapping/SqlResultSetMappings.php b/lib/Doctrine/ORM/Mapping/SqlResultSetMappings.php new file mode 100644 index 000000000..889924bbc --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/SqlResultSetMappings.php @@ -0,0 +1,40 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * Is used to specify an array of mappings. + * The SqlResultSetMappings annotation can be applied to an entity or mapped superclass. + * + * @author Fabio B. Silva + * @since 2.3 + * + * @Annotation + * @Target("CLASS") + */ +final class SqlResultSetMappings implements Annotation +{ + /** + * One or more SqlResultSetMapping annotations. + * + * @var array<\Doctrine\ORM\Mapping\SqlResultSetMapping> + */ + public $value; +} From 91e47027720bbf387e724a5468bdc7284a8ef8b5 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Sat, 25 Feb 2012 23:13:46 -0200 Subject: [PATCH 02/20] named native query metadata --- .../ORM/Mapping/ClassMetadataInfo.php | 112 +++++++++++++++ lib/Doctrine/ORM/Mapping/MappingException.php | 34 ++++- .../Tests/ORM/Mapping/ClassMetadataTest.php | 131 ++++++++++++++++++ 3 files changed, 270 insertions(+), 7 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 80498b737..eb5b014bc 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -49,16 +49,19 @@ class ClassMetadataInfo implements ClassMetadata * and therefore does not need an inheritance mapping type. */ const INHERITANCE_TYPE_NONE = 1; + /** * JOINED means the class will be persisted according to the rules of * Class Table Inheritance. */ const INHERITANCE_TYPE_JOINED = 2; + /** * SINGLE_TABLE means the class will be persisted according to the rules of * Single Table Inheritance. */ const INHERITANCE_TYPE_SINGLE_TABLE = 3; + /** * TABLE_PER_CLASS means the class will be persisted according to the rules * of Concrete Table Inheritance. @@ -71,17 +74,20 @@ class ClassMetadataInfo implements ClassMetadata * Offers full portability. */ const GENERATOR_TYPE_AUTO = 1; + /** * SEQUENCE means a separate sequence object will be used. Platforms that do * not have native sequence support may emulate it. Full portability is currently * not guaranteed. */ const GENERATOR_TYPE_SEQUENCE = 2; + /** * TABLE means a separate table is used for id generation. * Offers full portability. */ const GENERATOR_TYPE_TABLE = 3; + /** * IDENTITY means an identity column is used for id generation. The database * will fill in the id column on insertion. Platforms that do not support @@ -89,11 +95,13 @@ class ClassMetadataInfo implements ClassMetadata * not guaranteed. */ const GENERATOR_TYPE_IDENTITY = 4; + /** * NONE means the class does not have a generated id. That means the class * must have a natural, manually assigned id. */ const GENERATOR_TYPE_NONE = 5; + /** * UUID means that a UUID/GUID expression is used for id generation. Full * portability is currently not guaranteed. @@ -111,53 +119,64 @@ class ClassMetadataInfo implements ClassMetadata * This is the default change tracking policy. */ const CHANGETRACKING_DEFERRED_IMPLICIT = 1; + /** * DEFERRED_EXPLICIT means that changes of entities are calculated at commit-time * by doing a property-by-property comparison with the original data. This will * be done only for entities that were explicitly saved (through persist() or a cascade). */ const CHANGETRACKING_DEFERRED_EXPLICIT = 2; + /** * NOTIFY means that Doctrine relies on the entities sending out notifications * when their properties change. Such entity classes must implement * the NotifyPropertyChanged interface. */ const CHANGETRACKING_NOTIFY = 3; + /** * Specifies that an association is to be fetched when it is first accessed. */ const FETCH_LAZY = 2; + /** * Specifies that an association is to be fetched when the owner of the * association is fetched. */ const FETCH_EAGER = 3; + /** * Specifies that an association is to be fetched lazy (on first access) and that * commands such as Collection#count, Collection#slice are issued directly against * the database if the collection is not yet initialized. */ const FETCH_EXTRA_LAZY = 4; + /** * Identifies a one-to-one association. */ const ONE_TO_ONE = 1; + /** * Identifies a many-to-one association. */ const MANY_TO_ONE = 2; + /** * Identifies a one-to-many association. */ const ONE_TO_MANY = 4; + /** * Identifies a many-to-many association. */ const MANY_TO_MANY = 8; + /** * Combined bitmask for to-one (single-valued) associations. */ const TO_ONE = 3; + /** * Combined bitmask for to-many (collection-valued) associations. */ @@ -237,6 +256,21 @@ class ClassMetadataInfo implements ClassMetadata */ public $namedQueries = array(); + /** + * READ-ONLY: The named native queries allowed to be called directly from Repository. + * + * A native SQL named query definition has the following structure: + *
+     * array(
+     *     'name'                => ,
+     *     'query'               => ,
+     *     'resultClass'         => ,
+     *     'resultSetMapping'    => 
+     * )
+     * 
+ */ + public $namedNativeQueries = array(); + /** * READ-ONLY: The field names of all fields that are part of the identifier/primary key * of the mapped entity class. @@ -1051,6 +1085,33 @@ class ClassMetadataInfo implements ClassMetadata return $this->namedQueries; } + /** + * Gets the named native query. + * + * @see ClassMetadataInfo::$namedNativeQueries + * @throws MappingException + * @param string $queryName The query name + * @return array + */ + public function getNamedNativeQuery($queryName) + { + if ( ! isset($this->namedNativeQueries[$queryName])) { + throw MappingException::queryNotFound($this->name, $queryName); + } + + return $this->namedNativeQueries[$queryName]; + } + + /** + * Gets all named native queries of the class. + * + * @return array + */ + public function getNamedNativeQueries() + { + return $this->namedNativeQueries; + } + /** * Validates & completes the given field mapping. * @@ -1826,10 +1887,18 @@ class ClassMetadataInfo implements ClassMetadata */ public function addNamedQuery(array $queryMapping) { + if (!isset($queryMapping['name'])) { + throw MappingException::nameIsMandatoryQueryMapping($this->name); + } + if (isset($this->namedQueries[$queryMapping['name']])) { throw MappingException::duplicateQueryMapping($this->name, $queryMapping['name']); } + if (!isset($queryMapping['query'])) { + throw MappingException::emptyQueryMapping($this->name, $queryMapping['name']); + } + $name = $queryMapping['name']; $query = $queryMapping['query']; $dql = str_replace('__CLASS__', $this->name, $query); @@ -1840,6 +1909,38 @@ class ClassMetadataInfo implements ClassMetadata ); } + /** + * INTERNAL: + * Adds a named native query to this class. + * + * @throws MappingException + * @param array $queryMapping + */ + public function addNamedNativeQuery(array $queryMapping) + { + if (!isset($queryMapping['name'])) { + throw MappingException::nameIsMandatoryQueryMapping($this->name); + } + + if (isset($this->namedNativeQueries[$queryMapping['name']])) { + throw MappingException::duplicateQueryMapping($this->name, $queryMapping['name']); + } + + if (!isset($queryMapping['query'])) { + throw MappingException::emptyQueryMapping($this->name, $queryMapping['name']); + } + + if (!isset($queryMapping['resultClass']) && !isset($queryMapping['resultSetMapping'])) { + throw MappingException::missingQueryMapping($this->name, $queryMapping['name']); + } + + if (isset($queryMapping['resultClass']) && $queryMapping['resultClass'] === '__CLASS__') { + $queryMapping['resultClass'] = $this->name; + } + + $this->namedNativeQueries[$queryMapping['name']] = $queryMapping; + } + /** * Adds a one-to-one mapping. * @@ -2061,6 +2162,17 @@ class ClassMetadataInfo implements ClassMetadata return isset($this->namedQueries[$queryName]); } + /** + * Checks whether the class has a named native query with the given query name. + * + * @param string $fieldName + * @return boolean + */ + public function hasNamedNativeQuery($queryName) + { + return isset($this->namedNativeQueries[$queryName]); + } + /** * Checks whether the class has a mapped association with the given field name. * diff --git a/lib/Doctrine/ORM/Mapping/MappingException.php b/lib/Doctrine/ORM/Mapping/MappingException.php index 3e8022280..8d5ffba0e 100644 --- a/lib/Doctrine/ORM/Mapping/MappingException.php +++ b/lib/Doctrine/ORM/Mapping/MappingException.php @@ -93,6 +93,21 @@ class MappingException extends \Doctrine\ORM\ORMException return new self("No query found named '$queryName' on class '$className'."); } + public static function emptyQueryMapping($entity, $queryName) + { + return new self('Query named "'.$queryName.'" in "'.$entity.'" could not be empty.'); + } + + public static function nameIsMandatoryQueryMapping($className) + { + return new self("Query name on entity class '$className' is not defined."); + } + + public static function missingQueryMapping($entity, $queryName) + { + return new self('Query named "'.$queryName.'" in "'.$entity.' requires a result class or result set mapping.'); + } + public static function oneToManyRequiresMappedBy($fieldName) { return new self("OneToMany mapping on field '$fieldName' requires the 'mappedBy' attribute."); @@ -178,27 +193,31 @@ class MappingException extends \Doctrine\ORM\ORMException } /** - * * @param string $entity The entity's name * @param string $fieldName The name of the field that was already declared */ - public static function duplicateFieldMapping($entity, $fieldName) { + public static function duplicateFieldMapping($entity, $fieldName) + { return new self('Property "'.$fieldName.'" in "'.$entity.'" was already declared, but it must be declared only once'); } - public static function duplicateAssociationMapping($entity, $fieldName) { + public static function duplicateAssociationMapping($entity, $fieldName) + { return new self('Property "'.$fieldName.'" in "'.$entity.'" was already declared, but it must be declared only once'); } - public static function duplicateQueryMapping($entity, $queryName) { + public static function duplicateQueryMapping($entity, $queryName) + { return new self('Query named "'.$queryName.'" in "'.$entity.'" was already declared, but it must be declared only once'); } - public static function singleIdNotAllowedOnCompositePrimaryKey($entity) { + public static function singleIdNotAllowedOnCompositePrimaryKey($entity) + { return new self('Single id is not allowed on composite primary key in entity '.$entity); } - public static function unsupportedOptimisticLockingType($entity, $fieldName, $unsupportedType) { + public static function unsupportedOptimisticLockingType($entity, $fieldName, $unsupportedType) + { return new self('Locking type "'.$unsupportedType.'" (specified in "'.$entity.'", field "'.$fieldName.'") ' .'is not supported by Doctrine.' ); @@ -224,7 +243,8 @@ class MappingException extends \Doctrine\ORM\ORMException * @param string $owningClass The class that declares the discriminator map. * @return self */ - public static function invalidClassInDiscriminatorMap($className, $owningClass) { + public static function invalidClassInDiscriminatorMap($className, $owningClass) + { return new self( "Entity class '$className' used in the discriminator map of class '$owningClass' ". "does not exist." diff --git a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php index 4d7ec352f..11f96d712 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php @@ -525,6 +525,59 @@ class ClassMetadataTest extends \Doctrine\Tests\OrmTestCase $this->assertFalse($cm->hasNamedQuery('userById')); } + /** + * @group DDC-1663 + */ + public function testRetrieveOfNamedNativeQuery() + { + $cm = new ClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'); + $cm->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService); + + $cm->addNamedNativeQuery(array( + 'name' => 'find-all', + 'query' => 'SELECT * FROM cms_users', + 'resultSetMapping' => 'result-mapping-name', + 'resultClass' => 'Doctrine\Tests\Models\CMS\CmsUser', + )); + + $cm->addNamedNativeQuery(array( + 'name' => 'find-by-id', + 'query' => 'SELECT * FROM cms_users WHERE id = ?', + 'resultClass' => '__CLASS__', + 'resultSetMapping' => 'result-mapping-name', + )); + + $mapping = $cm->getNamedNativeQuery('find-all'); + $this->assertEquals('SELECT * FROM cms_users', $mapping['query']); + $this->assertEquals('result-mapping-name', $mapping['resultSetMapping']); + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsUser', $mapping['resultClass']); + + $mapping = $cm->getNamedNativeQuery('find-by-id'); + $this->assertEquals('SELECT * FROM cms_users WHERE id = ?', $mapping['query']); + $this->assertEquals('result-mapping-name', $mapping['resultSetMapping']); + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsUser', $mapping['resultClass']); + } + + /** + * @group DDC-1663 + */ + public function testExistanceOfNamedNativeQuery() + { + $cm = new ClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'); + $cm->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService); + + + $cm->addNamedNativeQuery(array( + 'name' => 'find-all', + 'query' => 'SELECT * FROM cms_users', + 'resultClass' => 'Doctrine\Tests\Models\CMS\CmsUser', + 'resultSetMapping' => 'result-mapping-name' + )); + + $this->assertTrue($cm->hasNamedNativeQuery('find-all')); + $this->assertFalse($cm->hasNamedNativeQuery('find-by-id')); + } + public function testRetrieveOfNamedQuery() { $cm = new ClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'); @@ -539,6 +592,26 @@ class ClassMetadataTest extends \Doctrine\Tests\OrmTestCase $this->assertEquals('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id = ?1', $cm->getNamedQuery('userById')); } + /** + * @group DDC-1663 + */ + public function testRetrievalOfNamedNativeQueries() + { + $cm = new ClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'); + $cm->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService); + + $this->assertEquals(0, count($cm->getNamedNativeQueries())); + + $cm->addNamedNativeQuery(array( + 'name' => 'find-all', + 'query' => 'SELECT * FROM cms_users', + 'resultClass' => 'Doctrine\Tests\Models\CMS\CmsUser', + 'resultSetMapping' => 'result-mapping-name' + )); + + $this->assertEquals(1, count($cm->getNamedNativeQueries())); + } + public function testNamingCollisionNamedQueryShouldThrowException() { $cm = new ClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'); @@ -558,6 +631,32 @@ class ClassMetadataTest extends \Doctrine\Tests\OrmTestCase )); } + /** + * @group DDC-1663 + */ + public function testNamingCollisionNamedNativeQueryShouldThrowException() + { + $cm = new ClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'); + $cm->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService); + + + $this->setExpectedException('Doctrine\ORM\Mapping\MappingException'); + + $cm->addNamedNativeQuery(array( + 'name' => 'find-all', + 'query' => 'SELECT * FROM cms_users', + 'resultClass' => 'Doctrine\Tests\Models\CMS\CmsUser', + 'resultSetMapping' => 'result-mapping-name' + )); + + $cm->addNamedNativeQuery(array( + 'name' => 'find-all', + 'query' => 'SELECT * FROM cms_users', + 'resultClass' => 'Doctrine\Tests\Models\CMS\CmsUser', + 'resultSetMapping' => 'result-mapping-name' + )); + } + /** * @group DDC-1068 */ @@ -596,6 +695,38 @@ class ClassMetadataTest extends \Doctrine\Tests\OrmTestCase $cm->validateAssocations(); } + /** + * @group DDC-1663 + * + * @expectedException \Doctrine\ORM\Mapping\MappingException + * @expectedExceptionMessage Query name on entity class 'Doctrine\Tests\Models\CMS\CmsUser' is not defined. + */ + public function testNameIsMandatoryForNamedQueryMappingException() + { + $cm = new ClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'); + $cm->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService); + $cm->addNamedQuery(array( + 'query' => 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u', + )); + } + + /** + * @group DDC-1663 + * + * @expectedException \Doctrine\ORM\Mapping\MappingException + * @expectedExceptionMessage Query name on entity class 'Doctrine\Tests\Models\CMS\CmsUser' is not defined. + */ + public function testNameIsMandatoryForNameNativeQueryMappingException() + { + $cm = new ClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'); + $cm->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService); + $cm->addNamedQuery(array( + 'query' => 'SELECT * FROM cms_users', + 'resultClass' => 'Doctrine\Tests\Models\CMS\CmsUser', + 'resultSetMapping' => 'result-mapping-name' + )); + } + /** * @expectedException \Doctrine\ORM\Mapping\MappingException * @expectedExceptionMessage Discriminator column name on entity class 'Doctrine\Tests\Models\CMS\CmsUser' is not defined. From 2b996128af1359b4ba2f0297c8692e7fa8c6de12 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Sun, 26 Feb 2012 00:16:54 -0300 Subject: [PATCH 03/20] sql result set mapping metadata --- .../ORM/Mapping/ClassMetadataInfo.php | 108 +++++++++++- lib/Doctrine/ORM/Mapping/ColumnResult.php | 42 +++++ lib/Doctrine/ORM/Mapping/MappingException.php | 22 ++- .../ORM/Mapping/NamedNativeQueries.php | 2 +- lib/Doctrine/ORM/Mapping/NamedNativeQuery.php | 2 +- .../ORM/Mapping/SqlResultSetMappings.php | 2 +- .../Tests/ORM/Mapping/ClassMetadataTest.php | 165 +++++++++++++++++- 7 files changed, 325 insertions(+), 18 deletions(-) create mode 100644 lib/Doctrine/ORM/Mapping/ColumnResult.php diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index eb5b014bc..3c4796307 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -262,15 +262,29 @@ class ClassMetadataInfo implements ClassMetadata * A native SQL named query definition has the following structure: *
      * array(
-     *     'name'                => ,
-     *     'query'               => ,
-     *     'resultClass'         => ,
-     *     'resultSetMapping'    => 
+     *     'name'               => ,
+     *     'query'              => ,
+     *     'resultClass'        => ,
+     *     'resultSetMapping'   => 
      * )
      * 
*/ public $namedNativeQueries = array(); + /** + * READ-ONLY: The mappings of the results of native SQL queries. + * + * A native result mapping definition has the following structure: + *
+     * array(
+     *     'name'               => ,
+     *     'entities'           => array(),
+     *     'columns'            => array()
+     * )
+     * 
+ */ + public $sqlResultSetMappings = array(); + /** * READ-ONLY: The field names of all fields that are part of the identifier/primary key * of the mapped entity class. @@ -782,6 +796,14 @@ class ClassMetadataInfo implements ClassMetadata $serialized[] = 'namedQueries'; } + if ($this->namedNativeQueries) { + $serialized[] = 'namedNativeQueries'; + } + + if ($this->sqlResultSetMappings) { + $serialized[] = 'sqlResultSetMappings'; + } + if ($this->isReadOnly) { $serialized[] = 'isReadOnly'; } @@ -1112,6 +1134,33 @@ class ClassMetadataInfo implements ClassMetadata return $this->namedNativeQueries; } + /** + * Gets the result set mapping. + * + * @see ClassMetadataInfo::$sqlResultSetMappings + * @throws MappingException + * @param string $name The result set mapping name + * @return array + */ + public function getSqlResultSetMapping($name) + { + if ( ! isset($this->sqlResultSetMappings[$name])) { + throw MappingException::resultMappingNotFound($this->name, $name); + } + + return $this->sqlResultSetMappings[$name]; + } + + /** + * Gets all sql result set mappings of the class. + * + * @return array + */ + public function getSqlResultSetMappings() + { + return $this->sqlResultSetMappings; + } + /** * Validates & completes the given field mapping. * @@ -1888,7 +1937,7 @@ class ClassMetadataInfo implements ClassMetadata public function addNamedQuery(array $queryMapping) { if (!isset($queryMapping['name'])) { - throw MappingException::nameIsMandatoryQueryMapping($this->name); + throw MappingException::nameIsMandatoryForQueryMapping($this->name); } if (isset($this->namedQueries[$queryMapping['name']])) { @@ -1919,7 +1968,7 @@ class ClassMetadataInfo implements ClassMetadata public function addNamedNativeQuery(array $queryMapping) { if (!isset($queryMapping['name'])) { - throw MappingException::nameIsMandatoryQueryMapping($this->name); + throw MappingException::nameIsMandatoryForQueryMapping($this->name); } if (isset($this->namedNativeQueries[$queryMapping['name']])) { @@ -1941,6 +1990,38 @@ class ClassMetadataInfo implements ClassMetadata $this->namedNativeQueries[$queryMapping['name']] = $queryMapping; } + /** + * INTERNAL: + * Adds a sql result set mapping to this class. + * + * @throws MappingException + * @param array $resultMapping + */ + public function addSqlResultSetMapping(array $resultMapping) + { + if (!isset($resultMapping['name'])) { + throw MappingException::nameIsMandatoryForSqlResultSetMapping($this->name); + } + + if (isset($this->sqlResultSetMappings[$resultMapping['name']])) { + throw MappingException::duplicateResultSetMapping($this->name, $resultMapping['name']); + } + + if (isset($resultMapping['entities'])) { + foreach ($resultMapping['entities'] as $key => $entityResult) { + if (!isset($entityResult['entityClass'])) { + throw MappingException::missingResultSetMappingEntity($this->name, $resultMapping['name']); + } + + if ($entityResult['entityClass'] === '__CLASS__') { + $resultMapping['entities'][$key]['entityClass'] = $this->name; + } + } + } + + $this->sqlResultSetMappings[$resultMapping['name']] = $resultMapping; + } + /** * Adds a one-to-one mapping. * @@ -2154,7 +2235,7 @@ class ClassMetadataInfo implements ClassMetadata /** * Checks whether the class has a named query with the given query name. * - * @param string $fieldName + * @param string $queryName * @return boolean */ public function hasNamedQuery($queryName) @@ -2165,7 +2246,7 @@ class ClassMetadataInfo implements ClassMetadata /** * Checks whether the class has a named native query with the given query name. * - * @param string $fieldName + * @param string $queryName * @return boolean */ public function hasNamedNativeQuery($queryName) @@ -2173,6 +2254,17 @@ class ClassMetadataInfo implements ClassMetadata return isset($this->namedNativeQueries[$queryName]); } + /** + * Checks whether the class has a named native query with the given query name. + * + * @param string $name + * @return boolean + */ + public function hasSqlResultSetMapping($name) + { + return isset($this->sqlResultSetMappings[$name]); + } + /** * Checks whether the class has a mapped association with the given field name. * diff --git a/lib/Doctrine/ORM/Mapping/ColumnResult.php b/lib/Doctrine/ORM/Mapping/ColumnResult.php new file mode 100644 index 000000000..ff5cf272e --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/ColumnResult.php @@ -0,0 +1,42 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * References name of a column in the SELECT clause of a SQL query. + * Scalar result types can be included in the query result by specifying this annotation in the metadata. + * + * @author Fabio B. Silva + * @since 2.3 + * + * @Annotation + * @Target("ANNOTATION") + */ +final class ColumnResult implements Annotation +{ + + /** + * The name of a column in the SELECT clause of a SQL query + * + * @var string + */ + public $name; + +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Mapping/MappingException.php b/lib/Doctrine/ORM/Mapping/MappingException.php index 8d5ffba0e..fffbbcaae 100644 --- a/lib/Doctrine/ORM/Mapping/MappingException.php +++ b/lib/Doctrine/ORM/Mapping/MappingException.php @@ -93,12 +93,17 @@ class MappingException extends \Doctrine\ORM\ORMException return new self("No query found named '$queryName' on class '$className'."); } + public static function resultMappingNotFound($className, $resultName) + { + return new self("No result set mapping found named '$resultName' on class '$className'."); + } + public static function emptyQueryMapping($entity, $queryName) { return new self('Query named "'.$queryName.'" in "'.$entity.'" could not be empty.'); } - public static function nameIsMandatoryQueryMapping($className) + public static function nameIsMandatoryForQueryMapping($className) { return new self("Query name on entity class '$className' is not defined."); } @@ -108,6 +113,16 @@ class MappingException extends \Doctrine\ORM\ORMException return new self('Query named "'.$queryName.'" in "'.$entity.' requires a result class or result set mapping.'); } + public static function missingResultSetMappingEntity($entity, $resultName) + { + return new self('Result set mapping named "'.$resultName.'" in "'.$entity.' requires a entity class name.'); + } + + public static function nameIsMandatoryForSqlResultSetMapping($className) + { + return new self("Result set mapping name on entity class '$className' is not defined."); + } + public static function oneToManyRequiresMappedBy($fieldName) { return new self("OneToMany mapping on field '$fieldName' requires the 'mappedBy' attribute."); @@ -211,6 +226,11 @@ class MappingException extends \Doctrine\ORM\ORMException return new self('Query named "'.$queryName.'" in "'.$entity.'" was already declared, but it must be declared only once'); } + public static function duplicateResultSetMapping($entity, $resultName) + { + return new self('Result set mapping named "'.$resultName.'" in "'.$entity.'" was already declared, but it must be declared only once'); + } + public static function singleIdNotAllowedOnCompositePrimaryKey($entity) { return new self('Single id is not allowed on composite primary key in entity '.$entity); diff --git a/lib/Doctrine/ORM/Mapping/NamedNativeQueries.php b/lib/Doctrine/ORM/Mapping/NamedNativeQueries.php index f957f4ed2..840d29694 100644 --- a/lib/Doctrine/ORM/Mapping/NamedNativeQueries.php +++ b/lib/Doctrine/ORM/Mapping/NamedNativeQueries.php @@ -37,4 +37,4 @@ final class NamedNativeQueries implements Annotation * @var array<\Doctrine\ORM\Mapping\NamedNativeQuery> */ public $value; -} +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Mapping/NamedNativeQuery.php b/lib/Doctrine/ORM/Mapping/NamedNativeQuery.php index f7c604461..022d9801e 100644 --- a/lib/Doctrine/ORM/Mapping/NamedNativeQuery.php +++ b/lib/Doctrine/ORM/Mapping/NamedNativeQuery.php @@ -60,4 +60,4 @@ final class NamedNativeQuery implements Annotation */ public $resultSetMapping; -} +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Mapping/SqlResultSetMappings.php b/lib/Doctrine/ORM/Mapping/SqlResultSetMappings.php index 889924bbc..c8c82a916 100644 --- a/lib/Doctrine/ORM/Mapping/SqlResultSetMappings.php +++ b/lib/Doctrine/ORM/Mapping/SqlResultSetMappings.php @@ -37,4 +37,4 @@ final class SqlResultSetMappings implements Annotation * @var array<\Doctrine\ORM\Mapping\SqlResultSetMapping> */ public $value; -} +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php index 11f96d712..fd6afe960 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php @@ -510,6 +510,29 @@ class ClassMetadataTest extends \Doctrine\Tests\OrmTestCase $this->assertEquals(1, count($cm->getNamedQueries())); } + /** + * @group DDC-1663 + */ + public function testRetrievalOfResultSetMappings() + { + $cm = new ClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'); + $cm->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService); + + + $this->assertEquals(0, count($cm->getSqlResultSetMappings())); + + $cm->addSqlResultSetMapping(array( + 'name' => 'find-all', + 'entities' => array( + array( + 'entityClass' => 'Doctrine\Tests\Models\CMS\CmsUser', + ), + ), + )); + + $this->assertEquals(1, count($cm->getSqlResultSetMappings())); + } + public function testExistanceOfNamedQuery() { $cm = new ClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'); @@ -558,6 +581,85 @@ class ClassMetadataTest extends \Doctrine\Tests\OrmTestCase $this->assertEquals('Doctrine\Tests\Models\CMS\CmsUser', $mapping['resultClass']); } + /** + * @group DDC-1663 + */ + public function testRetrieveOfSqlResultSetMapping() + { + $cm = new ClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'); + $cm->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService); + + $cm->addSqlResultSetMapping(array( + 'name' => 'find-all', + 'entities' => array( + array( + 'entityClass' => '__CLASS__', + 'fields' => array( + array( + 'name' => 'id', + 'column'=> 'id' + ), + array( + 'name' => 'name', + 'column'=> 'name' + ) + ) + ), + array( + 'entityClass' => 'Doctrine\Tests\Models\CMS\CmsEmail', + 'fields' => array( + array( + 'name' => 'id', + 'column'=> 'id' + ), + array( + 'name' => 'email', + 'column'=> 'email' + ) + ) + ) + ), + 'columns' => array( + array( + 'name' => 'scalarColumn' + ) + ) + )); + + $mapping = $cm->getSqlResultSetMapping('find-all'); + + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsUser', $mapping['entities'][0]['entityClass']); + $this->assertEquals(array('name'=>'id','column'=>'id'), $mapping['entities'][0]['fields'][0]); + $this->assertEquals(array('name'=>'name','column'=>'name'), $mapping['entities'][0]['fields'][1]); + + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsEmail', $mapping['entities'][1]['entityClass']); + $this->assertEquals(array('name'=>'id','column'=>'id'), $mapping['entities'][1]['fields'][0]); + $this->assertEquals(array('name'=>'email','column'=>'email'), $mapping['entities'][1]['fields'][1]); + + $this->assertEquals('scalarColumn', $mapping['columns'][0]['name']); + } + + /** + * @group DDC-1663 + */ + public function testExistanceOfSqlResultSetMapping() + { + $cm = new ClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'); + $cm->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService); + + $cm->addSqlResultSetMapping(array( + 'name' => 'find-all', + 'entities' => array( + array( + 'entityClass' => 'Doctrine\Tests\Models\CMS\CmsUser', + ), + ), + )); + + $this->assertTrue($cm->hasSqlResultSetMapping('find-all')); + $this->assertFalse($cm->hasSqlResultSetMapping('find-by-id')); + } + /** * @group DDC-1663 */ @@ -612,14 +714,15 @@ class ClassMetadataTest extends \Doctrine\Tests\OrmTestCase $this->assertEquals(1, count($cm->getNamedNativeQueries())); } + /** + * @expectedException \Doctrine\ORM\Mapping\MappingException + * @expectedExceptionMessage Query named "userById" in "Doctrine\Tests\Models\CMS\CmsUser" was already declared, but it must be declared only once + */ public function testNamingCollisionNamedQueryShouldThrowException() { $cm = new ClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'); $cm->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService); - - $this->setExpectedException('Doctrine\ORM\Mapping\MappingException'); - $cm->addNamedQuery(array( 'name' => 'userById', 'query' => 'SELECT u FROM __CLASS__ u WHERE u.id = ?1' @@ -633,15 +736,15 @@ class ClassMetadataTest extends \Doctrine\Tests\OrmTestCase /** * @group DDC-1663 + * + * @expectedException \Doctrine\ORM\Mapping\MappingException + * @expectedExceptionMessage Query named "find-all" in "Doctrine\Tests\Models\CMS\CmsUser" was already declared, but it must be declared only once */ public function testNamingCollisionNamedNativeQueryShouldThrowException() { $cm = new ClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'); $cm->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService); - - $this->setExpectedException('Doctrine\ORM\Mapping\MappingException'); - $cm->addNamedNativeQuery(array( 'name' => 'find-all', 'query' => 'SELECT * FROM cms_users', @@ -657,6 +760,36 @@ class ClassMetadataTest extends \Doctrine\Tests\OrmTestCase )); } + /** + * @group DDC-1663 + * + * @expectedException \Doctrine\ORM\Mapping\MappingException + * @expectedExceptionMessage Result set mapping named "find-all" in "Doctrine\Tests\Models\CMS\CmsUser" was already declared, but it must be declared only once + */ + public function testNamingCollisionSqlResultSetMappingShouldThrowException() + { + $cm = new ClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'); + $cm->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService); + + $cm->addSqlResultSetMapping(array( + 'name' => 'find-all', + 'entities' => array( + array( + 'entityClass' => 'Doctrine\Tests\Models\CMS\CmsUser', + ), + ), + )); + + $cm->addSqlResultSetMapping(array( + 'name' => 'find-all', + 'entities' => array( + array( + 'entityClass' => 'Doctrine\Tests\Models\CMS\CmsUser', + ), + ), + )); + } + /** * @group DDC-1068 */ @@ -727,6 +860,26 @@ class ClassMetadataTest extends \Doctrine\Tests\OrmTestCase )); } + /** + * @group DDC-1663 + * + * @expectedException \Doctrine\ORM\Mapping\MappingException + * @expectedExceptionMessage Result set mapping named "find-all" in "Doctrine\Tests\Models\CMS\CmsUser requires a entity class name. + */ + public function testNameIsMandatoryForEntityNameSqlResultSetMappingException() + { + $cm = new ClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'); + $cm->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService); + $cm->addSqlResultSetMapping(array( + 'name' => 'find-all', + 'entities' => array( + array( + 'fields' => array() + ) + ), + )); + } + /** * @expectedException \Doctrine\ORM\Mapping\MappingException * @expectedExceptionMessage Discriminator column name on entity class 'Doctrine\Tests\Models\CMS\CmsUser' is not defined. From bfc7986b20f66cd8cc96cc3f6f0b25feccb8f7d9 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Sun, 26 Feb 2012 16:51:39 -0300 Subject: [PATCH 04/20] annotation driver and basic support --- lib/Doctrine/ORM/EntityRepository.php | 16 +++++ .../ORM/Mapping/ClassMetadataInfo.php | 19 ++++-- .../ORM/Mapping/Driver/AnnotationDriver.php | 58 ++++++++++++++++- .../Mapping/Driver/DoctrineAnnotations.php | 7 ++ lib/Doctrine/ORM/Mapping/EntityResult.php | 2 +- lib/Doctrine/ORM/Mapping/MappingException.php | 2 +- .../ORM/Mapping/NamedNativeQueries.php | 2 +- lib/Doctrine/ORM/Mapping/NamedNativeQuery.php | 2 +- .../ORM/Mapping/SqlResultSetMapping.php | 6 +- .../ORM/Mapping/SqlResultSetMappings.php | 2 +- .../ORM/Query/ResultSetMappingBuilder.php | 35 +++++++++- .../Doctrine/Tests/Models/CMS/CmsAddress.php | 25 +++++++ .../Tests/ORM/Functional/NativeQueryTest.php | 36 ++++++++++ .../ORM/Hydration/ResultSetMappingTest.php | 65 +++++++++++++++++++ 14 files changed, 261 insertions(+), 16 deletions(-) diff --git a/lib/Doctrine/ORM/EntityRepository.php b/lib/Doctrine/ORM/EntityRepository.php index 75cbf029e..6c61855ef 100644 --- a/lib/Doctrine/ORM/EntityRepository.php +++ b/lib/Doctrine/ORM/EntityRepository.php @@ -89,6 +89,22 @@ class EntityRepository implements ObjectRepository return $this->_em->createQuery($this->_class->getNamedQuery($queryName)); } + /** + * Creates a native SQL query. + * + * @param string $queryName + * @return NativeQuery + */ + public function createNativeNamedQuery($queryName) + { + $queryMapping = $this->_class->getNamedNativeQuery($queryName); + $resultMapping = $this->_class->getSqlResultSetMapping($queryMapping['resultSetMapping']); + $rsm = new Query\ResultSetMappingBuilder($this->_em); + $rsm->addSqlResultSetMapping($resultMapping); + + return $this->_em->createNativeQuery($queryMapping['query'], $rsm); + } + /** * Clears the repository, causing all managed entities to become detached. */ diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 3c4796307..798b011e7 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -1983,8 +1983,15 @@ class ClassMetadataInfo implements ClassMetadata throw MappingException::missingQueryMapping($this->name, $queryMapping['name']); } - if (isset($queryMapping['resultClass']) && $queryMapping['resultClass'] === '__CLASS__') { - $queryMapping['resultClass'] = $this->name; + if (isset($queryMapping['resultClass'])) { + + if($queryMapping['resultClass'] === '__CLASS__') { + $queryMapping['resultClass'] = $this->name; + } else if (strlen($this->namespace) > 0 && strpos($queryMapping['resultClass'], '\\') === false) { + $queryMapping['resultClass'] = $this->namespace . '\\' . $queryMapping['resultClass']; + } + + $mapping['targetEntity'] = ltrim($queryMapping['resultClass'], '\\'); } $this->namedNativeQueries[$queryMapping['name']] = $queryMapping; @@ -2013,9 +2020,13 @@ class ClassMetadataInfo implements ClassMetadata throw MappingException::missingResultSetMappingEntity($this->name, $resultMapping['name']); } - if ($entityResult['entityClass'] === '__CLASS__') { - $resultMapping['entities'][$key]['entityClass'] = $this->name; + if($entityResult['entityClass'] === '__CLASS__') { + $entityResult['entityClass'] = $this->name; + } else if (strlen($this->namespace) > 0 && strpos($entityResult['entityClass'], '\\') === false) { + $entityResult['entityClass'] = $this->namespace . '\\' . $entityResult['entityClass']; } + + $resultMapping['entities'][$key]['entityClass'] = ltrim($entityResult['entityClass'], '\\'); } } diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php index 22a1250ba..59b1733e0 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php @@ -204,15 +204,67 @@ class AnnotationDriver implements Driver $metadata->setPrimaryTable($primaryTable); } + // Evaluate NamedNativeQueries annotation + if (isset($classAnnotations['Doctrine\ORM\Mapping\NamedNativeQueries'])) { + $namedNativeQueriesAnnot = $classAnnotations['Doctrine\ORM\Mapping\NamedNativeQueries']; + + foreach ($namedNativeQueriesAnnot->value as $namedNativeQuery) { + $metadata->addNamedNativeQuery(array( + 'name' => $namedNativeQuery->name, + 'query' => $namedNativeQuery->query, + 'resultClass' => $namedNativeQuery->resultClass, + 'resultSetMapping' => $namedNativeQuery->resultSetMapping, + )); + } + } + + // Evaluate SqlResultSetMappings annotation + if (isset($classAnnotations['Doctrine\ORM\Mapping\SqlResultSetMappings'])) { + $sqlResultSetMappingsAnnot = $classAnnotations['Doctrine\ORM\Mapping\SqlResultSetMappings']; + + foreach ($sqlResultSetMappingsAnnot->value as $resultSetMapping) { + $entities = array(); + foreach ($resultSetMapping->entities as $entityResultAnnot) { + $entityResult = array( + 'fields' => array(), + 'entityClass' => $entityResultAnnot->entityClass, + 'discriminatorColumn' => $entityResultAnnot->discriminatorColumn, + ); + + foreach ($entityResultAnnot->fields as $fieldResultAnnot) { + $entityResult['fields'][] = array( + 'name' => $fieldResultAnnot->name, + 'column' => $fieldResultAnnot->column + ); + } + + $entities[] = $entityResult; + } + + $columns = array(); + foreach ($resultSetMapping->columns as $columnResultAnnot) { + $columns[] = array( + 'name' => $resultSetMapping->name, + ); + } + + $metadata->addSqlResultSetMapping(array( + 'name' => $resultSetMapping->name, + 'entities' => $entities, + 'columns' => $columns + )); + } + } + // Evaluate NamedQueries annotation if (isset($classAnnotations['Doctrine\ORM\Mapping\NamedQueries'])) { - $namedQueriesAnnot = $classAnnotations['Doctrine\ORM\Mapping\NamedQueries']; + $namedNativeQueriesAnnot = $classAnnotations['Doctrine\ORM\Mapping\NamedQueries']; - if (!is_array($namedQueriesAnnot->value)) { + if (!is_array($namedNativeQueriesAnnot->value)) { throw new \UnexpectedValueException("@NamedQueries should contain an array of @NamedQuery annotations."); } - foreach ($namedQueriesAnnot->value as $namedQuery) { + foreach ($namedNativeQueriesAnnot->value as $namedQuery) { if (!($namedQuery instanceof \Doctrine\ORM\Mapping\NamedQuery)) { throw new \UnexpectedValueException("@NamedQueries should contain an array of @NamedQuery annotations."); } diff --git a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php index bd1632b2c..46fa1551b 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php +++ b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php @@ -53,3 +53,10 @@ require_once __DIR__.'/../PreRemove.php'; require_once __DIR__.'/../PostRemove.php'; require_once __DIR__.'/../PostLoad.php'; require_once __DIR__.'/../PreFlush.php'; +require_once __DIR__.'/../FieldResult.php'; +require_once __DIR__.'/../ColumnResult.php'; +require_once __DIR__.'/../EntityResult.php'; +require_once __DIR__.'/../NamedNativeQuery.php'; +require_once __DIR__.'/../NamedNativeQueries.php'; +require_once __DIR__.'/../SqlResultSetMapping.php'; +require_once __DIR__.'/../SqlResultSetMappings.php'; \ No newline at end of file diff --git a/lib/Doctrine/ORM/Mapping/EntityResult.php b/lib/Doctrine/ORM/Mapping/EntityResult.php index ae83d52c2..1be9206e7 100644 --- a/lib/Doctrine/ORM/Mapping/EntityResult.php +++ b/lib/Doctrine/ORM/Mapping/EntityResult.php @@ -46,7 +46,7 @@ final class EntityResult implements Annotation * * @var array<\Doctrine\ORM\Mapping\FieldResult> */ - public $fields; + public $fields = array(); /** * Specifies the column name of the column in the SELECT list that is used to determine the type of the entity instance. diff --git a/lib/Doctrine/ORM/Mapping/MappingException.php b/lib/Doctrine/ORM/Mapping/MappingException.php index fffbbcaae..c76138582 100644 --- a/lib/Doctrine/ORM/Mapping/MappingException.php +++ b/lib/Doctrine/ORM/Mapping/MappingException.php @@ -117,7 +117,7 @@ class MappingException extends \Doctrine\ORM\ORMException { return new self('Result set mapping named "'.$resultName.'" in "'.$entity.' requires a entity class name.'); } - + public static function nameIsMandatoryForSqlResultSetMapping($className) { return new self("Result set mapping name on entity class '$className' is not defined."); diff --git a/lib/Doctrine/ORM/Mapping/NamedNativeQueries.php b/lib/Doctrine/ORM/Mapping/NamedNativeQueries.php index 840d29694..f01090ee6 100644 --- a/lib/Doctrine/ORM/Mapping/NamedNativeQueries.php +++ b/lib/Doctrine/ORM/Mapping/NamedNativeQueries.php @@ -36,5 +36,5 @@ final class NamedNativeQueries implements Annotation * * @var array<\Doctrine\ORM\Mapping\NamedNativeQuery> */ - public $value; + public $value = array(); } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Mapping/NamedNativeQuery.php b/lib/Doctrine/ORM/Mapping/NamedNativeQuery.php index 022d9801e..052eded64 100644 --- a/lib/Doctrine/ORM/Mapping/NamedNativeQuery.php +++ b/lib/Doctrine/ORM/Mapping/NamedNativeQuery.php @@ -27,7 +27,7 @@ namespace Doctrine\ORM\Mapping; * @since 2.3 * * @Annotation - * @Target("CLASS") + * @Target("ANNOTATION") */ final class NamedNativeQuery implements Annotation { diff --git a/lib/Doctrine/ORM/Mapping/SqlResultSetMapping.php b/lib/Doctrine/ORM/Mapping/SqlResultSetMapping.php index a3bc5b4c9..e009ceea2 100644 --- a/lib/Doctrine/ORM/Mapping/SqlResultSetMapping.php +++ b/lib/Doctrine/ORM/Mapping/SqlResultSetMapping.php @@ -27,7 +27,7 @@ namespace Doctrine\ORM\Mapping; * @since 2.3 * * @Annotation - * @Target("CLASS") + * @Target("ANNOTATION") */ final class SqlResultSetMapping implements Annotation { @@ -44,13 +44,13 @@ final class SqlResultSetMapping implements Annotation * * @var array<\Doctrine\ORM\Mapping\EntityResult> */ - public $entities; + public $entities = array(); /** * Specifies the result set mapping to scalar values. * * @var array<\Doctrine\ORM\Mapping\ColumnResult> */ - public $columns; + public $columns = array(); } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Mapping/SqlResultSetMappings.php b/lib/Doctrine/ORM/Mapping/SqlResultSetMappings.php index c8c82a916..fa04387a2 100644 --- a/lib/Doctrine/ORM/Mapping/SqlResultSetMappings.php +++ b/lib/Doctrine/ORM/Mapping/SqlResultSetMappings.php @@ -36,5 +36,5 @@ final class SqlResultSetMappings implements Annotation * * @var array<\Doctrine\ORM\Mapping\SqlResultSetMapping> */ - public $value; + public $value = array(); } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php b/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php index 316a2012c..138b51ede 100644 --- a/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php +++ b/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php @@ -106,4 +106,37 @@ class ResultSetMappingBuilder extends ResultSetMapping } } } -} + + + /** + * @param array $mapping + */ + public function addSqlResultSetMapping(array $mapping) + { + if (isset($mapping['entities'])) { + foreach ($mapping['entities'] as $key => $map) { + $simpleName = $map['entityClass']; + if (strpos($simpleName, '\\') !== false) { + $simpleName = substr($simpleName, strrpos($simpleName, '\\') + 1); + } + + $className = $map['entityClass']; + $alias = strtolower($simpleName[0]) . $key; + + $this->addEntityResult($className, $alias); + + if (isset($map['fields'])) { + foreach ($map['fields'] as $field) { + $this->addFieldResult($alias, $field['column'], $field['name'], $className); + } + } + } + } + + if (isset($mapping['columns'])) { + foreach ($mapping['columns'] as $map) { + $this->addScalarResult($map['name'], $map['name']); + } + } + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/Models/CMS/CmsAddress.php b/tests/Doctrine/Tests/Models/CMS/CmsAddress.php index d32416a5e..48084cc86 100644 --- a/tests/Doctrine/Tests/Models/CMS/CmsAddress.php +++ b/tests/Doctrine/Tests/Models/CMS/CmsAddress.php @@ -8,6 +8,31 @@ namespace Doctrine\Tests\Models\CMS; * @author Roman S. Borschel * @Entity * @Table(name="cms_addresses") + * + * @NamedNativeQueries({ + * @NamedNativeQuery( + * name = "find-all", + * resultSetMapping = "mapping-find-all", + * query = "SELECT id, country, city FROM cms_addresses" + * ) + * }) + * + * @SqlResultSetMappings({ + * @SqlResultSetMapping( + * name = "mapping-find-all", + * entities= { + * @EntityResult( + * entityClass = "CmsAddress", + * fields = { + * @FieldResult(name = "id", column="id"), + * @FieldResult(name = "city", column="city"), + * @FieldResult(name = "country", column="country") + * } + * ) + * } + * ) + * }) + * */ class CmsAddress { diff --git a/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php b/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php index eb46329f1..db02b3d4b 100644 --- a/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php @@ -329,5 +329,41 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase ); $users = $query->getResult(); } + + + /** + * @group DDC-1663 + */ + public function testBasicNativeNamedQuery() + { + $user = new CmsUser; + $user->name = 'Fabio B. Silva'; + $user->username = 'FabioBatSilva'; + $user->status = 'dev'; + + $addr = new CmsAddress; + $addr->country = 'Brazil'; + $addr->zip = 10827; + $addr->city = 'São Paulo'; + + $user->setAddress($addr); + + $this->_em->clear(); + $this->_em->persist($user); + $this->_em->flush(); + + $this->_em->clear(); + + + $repository = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsAddress'); + $query = $repository->createNativeNamedQuery('find-all'); + $result = $query->getResult(); + + $this->assertCount(1, $result); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsAddress', $result[0]); + $this->assertEquals($addr->id, $result[0]->id); + $this->assertEquals($addr->city, $result[0]->city); + $this->assertEquals($addr->country, $result[0]->country); + } } diff --git a/tests/Doctrine/Tests/ORM/Hydration/ResultSetMappingTest.php b/tests/Doctrine/Tests/ORM/Hydration/ResultSetMappingTest.php index 785c19b74..ba7421c7a 100644 --- a/tests/Doctrine/Tests/ORM/Hydration/ResultSetMappingTest.php +++ b/tests/Doctrine/Tests/ORM/Hydration/ResultSetMappingTest.php @@ -93,5 +93,70 @@ class ResultSetMappingTest extends \Doctrine\Tests\OrmTestCase $this->assertTrue($rms->hasParentAlias('p')); $this->assertTrue($rms->isMixedResult()); } + + /** + * @group DDC-1663 + */ + public function testAddSqlResultSetMapping() + { + $cm = new ClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'); + $cm->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService); + + $cm->addSqlResultSetMapping(array( + 'name' => 'find-all', + 'entities' => array( + array( + 'entityClass' => '__CLASS__', + 'fields' => array( + array( + 'name' => 'id', + 'column'=> 'user_id' + ), + array( + 'name' => 'name', + 'column'=> 'user_name' + ) + ) + ), + array( + 'entityClass' => 'CmsEmail', + 'fields' => array( + array( + 'name' => 'id', + 'column'=> 'email_id' + ), + array( + 'name' => 'email', + 'column'=> 'email_email' + ) + ) + ) + ), + 'columns' => array( + array( + 'name' => 'scalarColumn' + ) + ) + )); + + + $rsm = new \Doctrine\ORM\Query\ResultSetMappingBuilder($this->_em); + $rsm->addSqlResultSetMapping($cm->getSqlResultSetMapping('find-all')); + + $this->assertEquals('scalarColumn', $rsm->getScalarAlias('scalarColumn')); + + $this->assertEquals('c0', $rsm->getEntityAlias('user_id')); + $this->assertEquals('c0', $rsm->getEntityAlias('user_name')); + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsUser', $rsm->getClassName('c0')); + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsUser', $rsm->getDeclaringClass('user_id')); + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsUser', $rsm->getDeclaringClass('user_name')); + + + $this->assertEquals('c1', $rsm->getEntityAlias('email_id')); + $this->assertEquals('c1', $rsm->getEntityAlias('email_email')); + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsEmail', $rsm->getClassName('c1')); + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsEmail', $rsm->getDeclaringClass('email_id')); + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsEmail', $rsm->getDeclaringClass('email_email')); + } } From 015ea809b0d9580bba546ea2fa8d8d0edab0d372 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Sun, 26 Feb 2012 19:27:42 -0300 Subject: [PATCH 05/20] added support for resultClass and empty fields --- lib/Doctrine/ORM/EntityRepository.php | 3 +- .../ORM/Mapping/Driver/AnnotationDriver.php | 6 +- .../ORM/Query/ResultSetMappingBuilder.php | 57 +++++++--- .../ORM/Hydration/ResultSetMappingTest.php | 100 ++++++++++++++++-- 4 files changed, 136 insertions(+), 30 deletions(-) diff --git a/lib/Doctrine/ORM/EntityRepository.php b/lib/Doctrine/ORM/EntityRepository.php index 6c61855ef..f2f6a396f 100644 --- a/lib/Doctrine/ORM/EntityRepository.php +++ b/lib/Doctrine/ORM/EntityRepository.php @@ -98,9 +98,8 @@ class EntityRepository implements ObjectRepository public function createNativeNamedQuery($queryName) { $queryMapping = $this->_class->getNamedNativeQuery($queryName); - $resultMapping = $this->_class->getSqlResultSetMapping($queryMapping['resultSetMapping']); $rsm = new Query\ResultSetMappingBuilder($this->_em); - $rsm->addSqlResultSetMapping($resultMapping); + $rsm->addNamedNativeQueryMapping($this->_class, $queryMapping); return $this->_em->createNativeQuery($queryMapping['query'], $rsm); } diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php index 59b1733e0..9040b2103 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php @@ -258,13 +258,13 @@ class AnnotationDriver implements Driver // Evaluate NamedQueries annotation if (isset($classAnnotations['Doctrine\ORM\Mapping\NamedQueries'])) { - $namedNativeQueriesAnnot = $classAnnotations['Doctrine\ORM\Mapping\NamedQueries']; + $namedQueriesAnnot = $classAnnotations['Doctrine\ORM\Mapping\NamedQueries']; - if (!is_array($namedNativeQueriesAnnot->value)) { + if (!is_array($namedQueriesAnnot->value)) { throw new \UnexpectedValueException("@NamedQueries should contain an array of @NamedQuery annotations."); } - foreach ($namedNativeQueriesAnnot->value as $namedQuery) { + foreach ($namedQueriesAnnot->value as $namedQuery) { if (!($namedQuery instanceof \Doctrine\ORM\Mapping\NamedQuery)) { throw new \UnexpectedValueException("@NamedQueries should contain an array of @NamedQuery annotations."); } diff --git a/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php b/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php index 138b51ede..37a3ea45a 100644 --- a/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php +++ b/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php @@ -109,34 +109,57 @@ class ResultSetMappingBuilder extends ResultSetMapping /** - * @param array $mapping + * Adds a the mappings of the results of native SQL queries to the result set. + * + * @param ClassMetadataInfo $class + * @param array $queryMapping + * @return ResultSetMappingBuilder */ - public function addSqlResultSetMapping(array $mapping) + public function addNamedNativeQueryMapping(ClassMetadataInfo $class, array $queryMapping) { - if (isset($mapping['entities'])) { - foreach ($mapping['entities'] as $key => $map) { - $simpleName = $map['entityClass']; - if (strpos($simpleName, '\\') !== false) { - $simpleName = substr($simpleName, strrpos($simpleName, '\\') + 1); - } + if (isset($queryMapping['resultClass'])) { + $classMetadata = $this->em->getClassMetadata($queryMapping['resultClass']); + $shortName = $classMetadata->reflClass->getShortName(); + $alias = strtolower($shortName[0]).'0'; - $className = $map['entityClass']; - $alias = strtolower($simpleName[0]) . $key; + $this->addEntityResult($class->name, $alias); + + foreach ($classMetadata->getColumnNames() as $key => $columnName) { + $propertyName = $classMetadata->getFieldName($columnName); + $this->addFieldResult($alias, $columnName, $propertyName); + } + + return $this; + } + + $resultMapping = $class->getSqlResultSetMapping($queryMapping['resultSetMapping']); + if (isset($resultMapping['entities'])) { + foreach ($resultMapping['entities'] as $key => $entityMapping) { + $classMetadata = $this->em->getClassMetadata($entityMapping['entityClass']); + $shortName = $classMetadata->reflClass->getShortName(); + $alias = strtolower($shortName[0]) . $key; - $this->addEntityResult($className, $alias); + $this->addEntityResult($classMetadata->name, $alias); - if (isset($map['fields'])) { - foreach ($map['fields'] as $field) { - $this->addFieldResult($alias, $field['column'], $field['name'], $className); + if (isset($entityMapping['fields']) && !empty($entityMapping['fields'])) { + foreach ($entityMapping['fields'] as $field) { + $this->addFieldResult($alias, $field['column'], $field['name'], $classMetadata->name); + } + } else { + foreach ($classMetadata->getColumnNames() as $columnName) { + $propertyName = $classMetadata->getFieldName($columnName); + $this->addFieldResult($alias, $columnName, $propertyName); } } } } - if (isset($mapping['columns'])) { - foreach ($mapping['columns'] as $map) { - $this->addScalarResult($map['name'], $map['name']); + if (isset($resultMapping['columns'])) { + foreach ($resultMapping['columns'] as $entityMapping) { + $this->addScalarResult($entityMapping['name'], $entityMapping['name']); } } + + return $this; } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Hydration/ResultSetMappingTest.php b/tests/Doctrine/Tests/ORM/Hydration/ResultSetMappingTest.php index ba7421c7a..9c057a0b1 100644 --- a/tests/Doctrine/Tests/ORM/Hydration/ResultSetMappingTest.php +++ b/tests/Doctrine/Tests/ORM/Hydration/ResultSetMappingTest.php @@ -97,11 +97,16 @@ class ResultSetMappingTest extends \Doctrine\Tests\OrmTestCase /** * @group DDC-1663 */ - public function testAddSqlResultSetMapping() + public function testAddNamedNativeQueryResultSetMapping() { $cm = new ClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'); $cm->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService); + $cm->addNamedNativeQuery(array( + 'name' => 'find-all', + 'query' => 'SELECT u.id AS user_id, e.id AS email_id, u.name, e.email, u.id + e.id AS scalarColumn FROM cms_users u INNER JOIN cms_emails e ON e.id = u.email_id', + 'resultSetMapping' => 'find-all', + )); $cm->addSqlResultSetMapping(array( 'name' => 'find-all', 'entities' => array( @@ -114,7 +119,7 @@ class ResultSetMappingTest extends \Doctrine\Tests\OrmTestCase ), array( 'name' => 'name', - 'column'=> 'user_name' + 'column'=> 'name' ) ) ), @@ -127,7 +132,7 @@ class ResultSetMappingTest extends \Doctrine\Tests\OrmTestCase ), array( 'name' => 'email', - 'column'=> 'email_email' + 'column'=> 'email' ) ) ) @@ -140,23 +145,102 @@ class ResultSetMappingTest extends \Doctrine\Tests\OrmTestCase )); + $queryMapping = $cm->getNamedNativeQuery('find-all'); + $rsm = new \Doctrine\ORM\Query\ResultSetMappingBuilder($this->_em); - $rsm->addSqlResultSetMapping($cm->getSqlResultSetMapping('find-all')); + $rsm->addNamedNativeQueryMapping($cm, $queryMapping); $this->assertEquals('scalarColumn', $rsm->getScalarAlias('scalarColumn')); $this->assertEquals('c0', $rsm->getEntityAlias('user_id')); - $this->assertEquals('c0', $rsm->getEntityAlias('user_name')); + $this->assertEquals('c0', $rsm->getEntityAlias('name')); $this->assertEquals('Doctrine\Tests\Models\CMS\CmsUser', $rsm->getClassName('c0')); + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsUser', $rsm->getDeclaringClass('name')); $this->assertEquals('Doctrine\Tests\Models\CMS\CmsUser', $rsm->getDeclaringClass('user_id')); - $this->assertEquals('Doctrine\Tests\Models\CMS\CmsUser', $rsm->getDeclaringClass('user_name')); $this->assertEquals('c1', $rsm->getEntityAlias('email_id')); - $this->assertEquals('c1', $rsm->getEntityAlias('email_email')); + $this->assertEquals('c1', $rsm->getEntityAlias('email')); $this->assertEquals('Doctrine\Tests\Models\CMS\CmsEmail', $rsm->getClassName('c1')); + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsEmail', $rsm->getDeclaringClass('email')); $this->assertEquals('Doctrine\Tests\Models\CMS\CmsEmail', $rsm->getDeclaringClass('email_id')); - $this->assertEquals('Doctrine\Tests\Models\CMS\CmsEmail', $rsm->getDeclaringClass('email_email')); + } + + /** + * @group DDC-1663 + */ + public function testAddNamedNativeQueryResultSetMappingWithoutFields() + { + $cm = new ClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'); + $cm->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService); + + $cm->addNamedNativeQuery(array( + 'name' => 'find-all', + 'query' => 'SELECT u.id AS user_id, e.id AS email_id, u.name, e.email, u.id + e.id AS scalarColumn FROM cms_users u INNER JOIN cms_emails e ON e.id = u.email_id', + 'resultSetMapping' => 'find-all', + )); + $cm->addSqlResultSetMapping(array( + 'name' => 'find-all', + 'entities' => array( + array( + 'entityClass' => '__CLASS__', + ) + ), + 'columns' => array( + array( + 'name' => 'scalarColumn' + ) + ) + )); + + + $queryMapping = $cm->getNamedNativeQuery('find-all'); + + $rsm = new \Doctrine\ORM\Query\ResultSetMappingBuilder($this->_em); + $rsm->addNamedNativeQueryMapping($cm, $queryMapping); + + $this->assertEquals('scalarColumn', $rsm->getScalarAlias('scalarColumn')); + + $this->assertEquals('c0', $rsm->getEntityAlias('id')); + $this->assertEquals('c0', $rsm->getEntityAlias('name')); + $this->assertEquals('c0', $rsm->getEntityAlias('status')); + $this->assertEquals('c0', $rsm->getEntityAlias('username')); + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsUser', $rsm->getClassName('c0')); + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsUser', $rsm->getDeclaringClass('id')); + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsUser', $rsm->getDeclaringClass('name')); + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsUser', $rsm->getDeclaringClass('status')); + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsUser', $rsm->getDeclaringClass('username')); + } + + /** + * @group DDC-1663 + */ + public function testAddNamedNativeQueryResultClass() + { + $cm = new ClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'); + $cm->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService); + + $cm->addNamedNativeQuery(array( + 'name' => 'find-all', + 'resultClass' => '__CLASS__', + 'query' => 'SELECT * FROM cms_users', + )); + + $queryMapping = $cm->getNamedNativeQuery('find-all'); + + $rsm = new \Doctrine\ORM\Query\ResultSetMappingBuilder($this->_em); + $rsm->addNamedNativeQueryMapping($cm, $queryMapping); + + + $this->assertEquals('c0', $rsm->getEntityAlias('id')); + $this->assertEquals('c0', $rsm->getEntityAlias('name')); + $this->assertEquals('c0', $rsm->getEntityAlias('status')); + $this->assertEquals('c0', $rsm->getEntityAlias('username')); + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsUser', $rsm->getClassName('c0')); + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsUser', $rsm->getDeclaringClass('id')); + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsUser', $rsm->getDeclaringClass('name')); + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsUser', $rsm->getDeclaringClass('status')); + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsUser', $rsm->getDeclaringClass('username')); } } From 3b79951824fbbfdaf1ff45560c5a63ae5e0c9d0c Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Sun, 26 Feb 2012 21:10:19 -0300 Subject: [PATCH 06/20] mapping driver tests --- .../ORM/Mapping/Driver/AnnotationDriver.php | 2 +- .../Doctrine/Tests/Models/CMS/CmsAddress.php | 26 +++++++ .../ORM/Mapping/AbstractMappingDriverTest.php | 77 +++++++++++++++++++ 3 files changed, 104 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php index 9040b2103..1e6cab682 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php @@ -244,7 +244,7 @@ class AnnotationDriver implements Driver $columns = array(); foreach ($resultSetMapping->columns as $columnResultAnnot) { $columns[] = array( - 'name' => $resultSetMapping->name, + 'name' => $columnResultAnnot->name, ); } diff --git a/tests/Doctrine/Tests/Models/CMS/CmsAddress.php b/tests/Doctrine/Tests/Models/CMS/CmsAddress.php index 48084cc86..0b4f1ad9a 100644 --- a/tests/Doctrine/Tests/Models/CMS/CmsAddress.php +++ b/tests/Doctrine/Tests/Models/CMS/CmsAddress.php @@ -14,6 +14,16 @@ namespace Doctrine\Tests\Models\CMS; * name = "find-all", * resultSetMapping = "mapping-find-all", * query = "SELECT id, country, city FROM cms_addresses" + * ), + * @NamedNativeQuery( + * name = "find-by-id", + * resultClass = "CmsAddress", + * query = "SELECT * FROM cms_addresses WHERE id = ?" + * ), + * @NamedNativeQuery( + * name = "count", + * resultSetMapping= "mapping-count", + * query = "SELECT COUNT(*) AS count FROM cms_addresses" * ) * }) * @@ -30,6 +40,22 @@ namespace Doctrine\Tests\Models\CMS; * } * ) * } + * ), + * @SqlResultSetMapping( + * name = "mapping-without-fields", + * entities= { + * @EntityResult( + * entityClass = "__CLASS__" + * ) + * } + * ), + * @SqlResultSetMapping( + * name = "mapping-count", + * columns = { + * @ColumnResult( + * name = "count" + * ) + * } * ) * }) * diff --git a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php index 3233c3104..0d0db044a 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php @@ -467,6 +467,7 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase } /** +<<<<<<< HEAD * @group DDC-889 * @expectedException Doctrine\ORM\Mapping\MappingException * @expectedExceptionMessage Class "Doctrine\Tests\Models\DDC889\DDC889Class" sub class of "Doctrine\Tests\Models\DDC889\DDC889SuperClass" is not a valid entity or mapped super class. @@ -488,6 +489,82 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase $factory->getMetadataFor('Doctrine\Tests\Models\DDC889\DDC889Entity'); } + + /** + * @group DDC-1663 + */ + public function testNamedNativeQuery() + { + if (!$this instanceof AnnotationDriverTest) { + $this->markTestIncomplete(); + } + + $class = $this->createClassMetadata('Doctrine\Tests\Models\CMS\CmsAddress'); + + $this->assertCount(3, $class->namedNativeQueries); + $this->assertArrayHasKey('find-all', $class->namedNativeQueries); + $this->assertArrayHasKey('find-by-id', $class->namedNativeQueries); + + $findAllQuery = $class->namedNativeQueries['find-all']; + $this->assertEquals('find-all', $findAllQuery['name']); + $this->assertEquals('mapping-find-all', $findAllQuery['resultSetMapping']); + $this->assertEquals('SELECT id, country, city FROM cms_addresses', $findAllQuery['query']); + + $findByIdQuery = $class->namedNativeQueries['find-by-id']; + $this->assertEquals('find-by-id', $findByIdQuery['name']); + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsAddress',$findByIdQuery['resultClass']); + $this->assertEquals('SELECT * FROM cms_addresses WHERE id = ?', $findByIdQuery['query']); + + $countQuery = $class->namedNativeQueries['count']; + $this->assertEquals('count', $countQuery['name']); + $this->assertEquals('mapping-count', $countQuery['resultSetMapping']); + $this->assertEquals('SELECT COUNT(*) AS count FROM cms_addresses', $countQuery['query']); + + } + + /** + * @group DDC-1663 + */ + public function testSqlResultSetMapping() + { + if (!$this instanceof AnnotationDriverTest) { + $this->markTestIncomplete(); + } + + $class = $this->createClassMetadata('Doctrine\Tests\Models\CMS\CmsAddress'); + + $this->assertCount(3, $class->sqlResultSetMappings); + $this->assertArrayHasKey('mapping-find-all', $class->sqlResultSetMappings); + $this->assertArrayHasKey('mapping-without-fields', $class->sqlResultSetMappings); + + // empty fields select all fields + $withoutFieldsMapping = $class->sqlResultSetMappings['mapping-without-fields']; + $this->assertEquals('mapping-without-fields', $withoutFieldsMapping['name']); + $this->assertCount(1, $withoutFieldsMapping['entities']); + $this->assertCount(0, $withoutFieldsMapping['columns']); + $this->assertEquals(array(), $withoutFieldsMapping['entities'][0]['fields']); + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsAddress', $withoutFieldsMapping['entities'][0]['entityClass']); + + + $findAllMapping = $class->sqlResultSetMappings['mapping-find-all']; + $this->assertEquals('mapping-find-all', $findAllMapping['name']); + $this->assertCount(1, $findAllMapping['entities']); + $this->assertCount(0, $findAllMapping['columns']); + $this->assertEquals(array( + array('name'=>'id','column'=>'id'), + array('name'=>'city','column'=>'city'), + array('name'=>'country','column' => 'country') + ), $findAllMapping['entities'][0]['fields']); + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsAddress', $findAllMapping['entities'][0]['entityClass']); + + + $countMapping = $class->sqlResultSetMappings['mapping-count']; + $this->assertEquals('mapping-count', $countMapping['name']); + $this->assertCount(0, $countMapping['entities']); + $this->assertCount(1, $countMapping['columns']); + $this->assertEquals(array(array('name'=>'count')),$countMapping['columns']); + + } } /** From f018a56d6dbf587865fc59ee2ee5970724f74e29 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Mon, 27 Feb 2012 21:16:35 -0300 Subject: [PATCH 07/20] test native query with result class --- .../ORM/Query/ResultSetMappingBuilder.php | 9 +++ tests/Doctrine/Tests/Models/CMS/CmsUser.php | 13 +++++ .../Tests/ORM/Functional/NativeQueryTest.php | 57 ++++++++++++++++++- .../Doctrine/Tests/OrmFunctionalTestCase.php | 1 + 4 files changed, 79 insertions(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php b/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php index 37a3ea45a..fcb12ecc2 100644 --- a/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php +++ b/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php @@ -129,6 +129,15 @@ class ResultSetMappingBuilder extends ResultSetMapping $this->addFieldResult($alias, $columnName, $propertyName); } + foreach ($classMetadata->associationMappings as $associationMapping) { + if ($associationMapping['isOwningSide'] && $associationMapping['type'] & ClassMetadataInfo::TO_ONE) { + foreach ($associationMapping['joinColumns'] as $joinColumn) { + $columnName = $joinColumn['name']; + $this->addMetaResult($alias, $columnName, $columnName, $classMetadata->isIdentifier($columnName)); + } + } + } + return $this; } diff --git a/tests/Doctrine/Tests/Models/CMS/CmsUser.php b/tests/Doctrine/Tests/Models/CMS/CmsUser.php index 1e5465784..a388081e6 100644 --- a/tests/Doctrine/Tests/Models/CMS/CmsUser.php +++ b/tests/Doctrine/Tests/Models/CMS/CmsUser.php @@ -10,6 +10,19 @@ use Doctrine\Common\Collections\ArrayCollection; * @NamedQueries({ * @NamedQuery(name="all", query="SELECT u FROM __CLASS__ u") * }) + * + * @NamedNativeQueries({ + * @NamedNativeQuery( + * name = "fetchIdAndUsernameWithResultClass", + * resultClass = "CmsUser", + * query = "SELECT id, username FROM cms_users WHERE username = ?" + * ), + * @NamedNativeQuery( + * name = "fetchAllColumnsWithResultClass", + * resultClass = "CmsUser", + * query = "SELECT * FROM cms_users WHERE username = ?" + * ), + * }) */ class CmsUser { diff --git a/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php b/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php index db02b3d4b..80c580366 100644 --- a/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php @@ -7,6 +7,7 @@ use Doctrine\ORM\Query\ResultSetMappingBuilder; use Doctrine\Tests\Models\CMS\CmsUser; use Doctrine\Tests\Models\CMS\CmsPhonenumber; use Doctrine\Tests\Models\CMS\CmsAddress; +use Doctrine\Tests\Models\CMS\CmsEmail; use Doctrine\Tests\Models\Company\CompanyFixContract; use Doctrine\Tests\Models\Company\CompanyEmployee; @@ -334,7 +335,7 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase /** * @group DDC-1663 */ - public function testBasicNativeNamedQuery() + public function testBasicNativeNamedQueryWithSqlResultSetMapping() { $user = new CmsUser; $user->name = 'Fabio B. Silva'; @@ -365,5 +366,59 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals($addr->city, $result[0]->city); $this->assertEquals($addr->country, $result[0]->country); } + + /** + * @group DDC-1663 + */ + public function testBasicNativeQueryWithResultClass() + { + //$this->_em->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger); + + $user = new CmsUser; + $user->name = 'Fabio B. Silva'; + $user->username = 'FabioBatSilva'; + $user->status = 'dev'; + + $email = new CmsEmail(); + $email->email = 'fabio.bat.silva@gmail.com'; + + $user->setEmail($email); + + $this->_em->clear(); + $this->_em->persist($user); + $this->_em->flush(); + + $this->_em->clear(); + + $repository = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser'); + + + $result = $repository->createNativeNamedQuery('fetchIdAndUsernameWithResultClass') + ->setParameter(1, 'FabioBatSilva')->getResult(); + $this->assertEquals(1, count($result)); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0]); + $this->assertNull($result[0]->name); + $this->assertNull($result[0]->email); + $this->assertEquals($user->id, $result[0]->id); + $this->assertEquals('FabioBatSilva', $result[0]->username); + + $this->_em->clear(); + + + $result = $repository->createNativeNamedQuery('fetchAllColumnsWithResultClass') + ->setParameter(1, 'FabioBatSilva')->getResult(); + + $this->assertEquals(1, count($result)); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0]); + $this->assertEquals($user->id, $result[0]->id); + $this->assertEquals('Fabio B. Silva', $result[0]->name); + $this->assertEquals('FabioBatSilva', $result[0]->username); + $this->assertEquals('dev', $result[0]->status); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsEmail', $result[0]->email); + + + $this->_em->clear(); + + } } diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index 4b92ed5d3..9d9776ba9 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -147,6 +147,7 @@ abstract class OrmFunctionalTestCase extends OrmTestCase $conn->executeUpdate('DELETE FROM cms_comments'); $conn->executeUpdate('DELETE FROM cms_articles'); $conn->executeUpdate('DELETE FROM cms_users'); + $conn->executeUpdate('DELETE FROM cms_emails'); } if (isset($this->_usedModelSets['ecommerce'])) { From f813223036667f2eac66a85ab828d00ff46c278e Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Mon, 27 Feb 2012 23:46:27 -0300 Subject: [PATCH 08/20] test joined one-to-many and one-to-one --- .../ORM/Mapping/ClassMetadataInfo.php | 19 ++++- lib/Doctrine/ORM/Mapping/MappingException.php | 5 ++ .../ORM/Query/ResultSetMappingBuilder.php | 24 +++++- tests/Doctrine/Tests/Models/CMS/CmsUser.php | 44 +++++++++++ .../Tests/ORM/Functional/NativeQueryTest.php | 79 ++++++++++++++++++- 5 files changed, 165 insertions(+), 6 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 798b011e7..52f847f4f 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -1991,7 +1991,7 @@ class ClassMetadataInfo implements ClassMetadata $queryMapping['resultClass'] = $this->namespace . '\\' . $queryMapping['resultClass']; } - $mapping['targetEntity'] = ltrim($queryMapping['resultClass'], '\\'); + $queryMapping['resultClass'] = ltrim($queryMapping['resultClass'], '\\'); } $this->namedNativeQueries[$queryMapping['name']] = $queryMapping; @@ -2027,6 +2027,23 @@ class ClassMetadataInfo implements ClassMetadata } $resultMapping['entities'][$key]['entityClass'] = ltrim($entityResult['entityClass'], '\\'); + + if (isset($entityResult['fields'])) { + foreach ($entityResult['fields'] as $k => $field) { + if (!isset($field['name'])) { + throw MappingException::missingResultSetMappingFieldName($this->name, $resultMapping['name']); + } + + if (!isset($field['column'])) { + $fieldName = $field['name']; + if(strpos($fieldName, '.')){ + list(, $fieldName) = explode('.', $fieldName); + } + + $resultMapping['entities'][$key]['fields'][$k]['column'] = $fieldName; + } + } + } } } diff --git a/lib/Doctrine/ORM/Mapping/MappingException.php b/lib/Doctrine/ORM/Mapping/MappingException.php index c76138582..5c3ed8ef2 100644 --- a/lib/Doctrine/ORM/Mapping/MappingException.php +++ b/lib/Doctrine/ORM/Mapping/MappingException.php @@ -117,6 +117,11 @@ class MappingException extends \Doctrine\ORM\ORMException { return new self('Result set mapping named "'.$resultName.'" in "'.$entity.' requires a entity class name.'); } + + public static function missingResultSetMappingFieldName($entity, $resultName) + { + return new self('Result set mapping named "'.$resultName.'" in "'.$entity.' requires a field name.'); + } public static function nameIsMandatoryForSqlResultSetMapping($className) { diff --git a/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php b/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php index fcb12ecc2..bfc1826c0 100644 --- a/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php +++ b/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php @@ -149,11 +149,31 @@ class ResultSetMappingBuilder extends ResultSetMapping $alias = strtolower($shortName[0]) . $key; $this->addEntityResult($classMetadata->name, $alias); - if (isset($entityMapping['fields']) && !empty($entityMapping['fields'])) { foreach ($entityMapping['fields'] as $field) { - $this->addFieldResult($alias, $field['column'], $field['name'], $classMetadata->name); + $fieldName = $field['name']; + $relation = null; + + if(strpos($fieldName, '.')){ + list($relation, $fieldName) = explode('.', $fieldName); + } + + if (isset($classMetadata->associationMappings[$relation])) { + if($relation) { + $associationMapping = $classMetadata->associationMappings[$relation]; + $joinAlias = $alias.$relation; + $parentAlias = $alias; + + $this->addJoinedEntityResult($associationMapping['targetEntity'], $joinAlias, $parentAlias, $relation); + $this->addFieldResult($joinAlias, $field['column'], $fieldName); + }else { + $this->addFieldResult($alias, $field['column'], $fieldName, $classMetadata->name); + } + } else { + $this->addFieldResult($alias, $field['column'], $fieldName, $classMetadata->name); + } } + } else { foreach ($classMetadata->getColumnNames() as $columnName) { $propertyName = $classMetadata->getFieldName($columnName); diff --git a/tests/Doctrine/Tests/Models/CMS/CmsUser.php b/tests/Doctrine/Tests/Models/CMS/CmsUser.php index a388081e6..5f8441247 100644 --- a/tests/Doctrine/Tests/Models/CMS/CmsUser.php +++ b/tests/Doctrine/Tests/Models/CMS/CmsUser.php @@ -22,6 +22,50 @@ use Doctrine\Common\Collections\ArrayCollection; * resultClass = "CmsUser", * query = "SELECT * FROM cms_users WHERE username = ?" * ), + * @NamedNativeQuery( + * name = "fetchJoinedAddressWithResultSetMapping", + * resultSetMapping= "mappingJoinedAddress", + * query = "SELECT u.id, u.name, u.status, a.id AS a_id, a.country, a.zip, a.city FROM cms_users u INNER JOIN cms_addresses a ON u.id = a.user_id WHERE u.username = ?" + * ), + * @NamedNativeQuery( + * name = "fetchJoinedPhonenumberWithResultSetMapping", + * resultSetMapping= "mappingJoinedPhonenumber", + * query = "SELECT id, name, status, phonenumber AS number FROM cms_users INNER JOIN cms_phonenumbers ON id = user_id WHERE username = ?" + * ), + * }) + * + * @SqlResultSetMappings({ + * @SqlResultSetMapping( + * name = "mappingJoinedAddress", + * entities= { + * @EntityResult( + * entityClass = "__CLASS__", + * fields = { + * @FieldResult(name = "id"), + * @FieldResult(name = "name"), + * @FieldResult(name = "status"), + * @FieldResult(name = "address.zip"), + * @FieldResult(name = "address.city"), + * @FieldResult(name = "address.country"), + * @FieldResult(name = "address.id", column = "a_id"), + * } + * ) + * } + * ), + * @SqlResultSetMapping( + * name = "mappingJoinedPhonenumber", + * entities= { + * @EntityResult( + * entityClass = "CmsUser", + * fields = { + * @FieldResult(name = "id"), + * @FieldResult(name = "name"), + * @FieldResult(name = "status"), + * @FieldResult(name = "phonenumbers.phonenumber" , column = "number"), + * } + * ) + * } + * ) * }) */ class CmsUser diff --git a/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php b/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php index 80c580366..79d8e8a50 100644 --- a/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php @@ -370,7 +370,7 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase /** * @group DDC-1663 */ - public function testBasicNativeQueryWithResultClass() + public function testBasicNativeNamedQueryWithResultClass() { //$this->_em->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger); @@ -393,8 +393,9 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $repository = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser'); - $result = $repository->createNativeNamedQuery('fetchIdAndUsernameWithResultClass') + $result = $repository->createNativeNamedQuery('fetchIdAndUsernameWithResultClass') ->setParameter(1, 'FabioBatSilva')->getResult(); + $this->assertEquals(1, count($result)); $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0]); $this->assertNull($result[0]->name); @@ -405,7 +406,7 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->clear(); - $result = $repository->createNativeNamedQuery('fetchAllColumnsWithResultClass') + $result = $repository->createNativeNamedQuery('fetchAllColumnsWithResultClass') ->setParameter(1, 'FabioBatSilva')->getResult(); $this->assertEquals(1, count($result)); @@ -415,10 +416,82 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals('FabioBatSilva', $result[0]->username); $this->assertEquals('dev', $result[0]->status); $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsEmail', $result[0]->email); + } + public function testJoinedOneToOneNativeNamedQueryWithResultSetMapping() + { + $user = new CmsUser; + $user->name = 'Fabio B. Silva'; + $user->username = 'FabioBatSilva'; + $user->status = 'dev'; + + $addr = new CmsAddress; + $addr->country = 'Brazil'; + $addr->zip = 10827; + $addr->city = 'São Paulo'; + + + $user->setAddress($addr); + + $this->_em->persist($user); + $this->_em->flush(); + $this->_em->clear(); + $repository = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser'); + + + $result = $repository->createNativeNamedQuery('fetchJoinedAddressWithResultSetMapping') + ->setParameter(1, 'FabioBatSilva')->getResult(); + + $this->assertEquals(1, count($result)); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0]); + $this->assertEquals('Fabio B. Silva', $result[0]->name); + $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[0]->getPhonenumbers()); + $this->assertFalse($result[0]->getPhonenumbers()->isInitialized()); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsAddress', $result[0]->getAddress()); + $this->assertTrue($result[0]->getAddress()->getUser() == $result[0]); + $this->assertEquals('Brazil', $result[0]->getAddress()->getCountry()); + $this->assertEquals(10827, $result[0]->getAddress()->getZipCode()); + $this->assertEquals('São Paulo', $result[0]->getAddress()->getCity()); } + + + public function testJoinedOneToManyNativeNamedQueryWithResultSetMapping() + { + $user = new CmsUser; + $user->name = 'Fabio B. Silva'; + $user->username = 'FabioBatSilva'; + $user->status = 'dev'; + + $phone = new CmsPhonenumber; + $phone->phonenumber = 424242; + + $user->addPhonenumber($phone); + + $this->_em->persist($user); + $this->_em->flush(); + + $this->_em->clear(); + + $repository = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser'); + + $result = $repository->createNativeNamedQuery('fetchJoinedPhonenumberWithResultSetMapping') + ->setParameter(1, 'FabioBatSilva')->getResult(); + + $this->assertEquals(1, count($result)); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0]); + $this->assertEquals('Fabio B. Silva', $result[0]->name); + $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $result[0]->getPhonenumbers()); + $this->assertTrue($result[0]->getPhonenumbers()->isInitialized()); + $this->assertEquals(1, count($result[0]->getPhonenumbers())); + $phones = $result[0]->getPhonenumbers(); + $this->assertEquals(424242, $phones[0]->phonenumber); + $this->assertTrue($phones[0]->getUser() === $result[0]); + } + + + } From 0cc2583a026fe01435c0a1573897e02061ca170a Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Tue, 28 Feb 2012 22:18:25 -0300 Subject: [PATCH 09/20] test scalar result --- .../ORM/Query/ResultSetMappingBuilder.php | 3 + tests/Doctrine/Tests/Models/CMS/CmsUser.php | 29 ++++++-- .../Tests/ORM/Functional/NativeQueryTest.php | 66 +++++++++++++++++-- 3 files changed, 87 insertions(+), 11 deletions(-) diff --git a/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php b/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php index bfc1826c0..310e0c2d9 100644 --- a/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php +++ b/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php @@ -170,6 +170,9 @@ class ResultSetMappingBuilder extends ResultSetMapping $this->addFieldResult($alias, $field['column'], $fieldName, $classMetadata->name); } } else { + if(!isset($classMetadata->fieldMappings[$fieldName])) { + throw new \InvalidArgumentException("Entity '".$classMetadata->name."' has no field '".$fieldName."'. "); + } $this->addFieldResult($alias, $field['column'], $fieldName, $classMetadata->name); } } diff --git a/tests/Doctrine/Tests/Models/CMS/CmsUser.php b/tests/Doctrine/Tests/Models/CMS/CmsUser.php index 5f8441247..348f29329 100644 --- a/tests/Doctrine/Tests/Models/CMS/CmsUser.php +++ b/tests/Doctrine/Tests/Models/CMS/CmsUser.php @@ -18,20 +18,25 @@ use Doctrine\Common\Collections\ArrayCollection; * query = "SELECT id, username FROM cms_users WHERE username = ?" * ), * @NamedNativeQuery( - * name = "fetchAllColumnsWithResultClass", + * name = "fetchAllColumns", * resultClass = "CmsUser", * query = "SELECT * FROM cms_users WHERE username = ?" * ), * @NamedNativeQuery( - * name = "fetchJoinedAddressWithResultSetMapping", + * name = "fetchJoinedAddress", * resultSetMapping= "mappingJoinedAddress", * query = "SELECT u.id, u.name, u.status, a.id AS a_id, a.country, a.zip, a.city FROM cms_users u INNER JOIN cms_addresses a ON u.id = a.user_id WHERE u.username = ?" * ), * @NamedNativeQuery( - * name = "fetchJoinedPhonenumberWithResultSetMapping", + * name = "fetchJoinedPhonenumber", * resultSetMapping= "mappingJoinedPhonenumber", * query = "SELECT id, name, status, phonenumber AS number FROM cms_users INNER JOIN cms_phonenumbers ON id = user_id WHERE username = ?" * ), + * @NamedNativeQuery( + * name = "fetchUserPhonenumberCount", + * resultSetMapping= "mappingUserPhonenumberCount", + * query = "SELECT id, name, status, COUNT(phonenumber) AS numPhones FROM cms_users INNER JOIN cms_phonenumbers ON id = user_id WHERE username IN (?) GROUP BY status, user_id ORDER BY username" + * ), * }) * * @SqlResultSetMappings({ @@ -58,12 +63,28 @@ use Doctrine\Common\Collections\ArrayCollection; * @EntityResult( * entityClass = "CmsUser", * fields = { + * @FieldResult("id"), + * @FieldResult("name"), + * @FieldResult("status"), + * @FieldResult("phonenumbers.phonenumber" , column = "number"), + * } + * ) + * } + * ), + * @SqlResultSetMapping( + * name = "mappingUserPhonenumberCount", + * entities= { + * @EntityResult( + * entityClass = "CmsUser", + * fields = { * @FieldResult(name = "id"), * @FieldResult(name = "name"), * @FieldResult(name = "status"), - * @FieldResult(name = "phonenumbers.phonenumber" , column = "number"), * } * ) + * }, + * columns = { + * @ColumnResult("numPhones") * } * ) * }) diff --git a/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php b/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php index 79d8e8a50..8c9657fda 100644 --- a/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php @@ -372,8 +372,6 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase */ public function testBasicNativeNamedQueryWithResultClass() { - //$this->_em->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger); - $user = new CmsUser; $user->name = 'Fabio B. Silva'; $user->username = 'FabioBatSilva'; @@ -406,7 +404,7 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->_em->clear(); - $result = $repository->createNativeNamedQuery('fetchAllColumnsWithResultClass') + $result = $repository->createNativeNamedQuery('fetchAllColumns') ->setParameter(1, 'FabioBatSilva')->getResult(); $this->assertEquals(1, count($result)); @@ -419,6 +417,9 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase } + /** + * @group DDC-1663 + */ public function testJoinedOneToOneNativeNamedQueryWithResultSetMapping() { $user = new CmsUser; @@ -442,7 +443,7 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $repository = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser'); - $result = $repository->createNativeNamedQuery('fetchJoinedAddressWithResultSetMapping') + $result = $repository->createNativeNamedQuery('fetchJoinedAddress') ->setParameter(1, 'FabioBatSilva')->getResult(); $this->assertEquals(1, count($result)); @@ -457,7 +458,9 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals('São Paulo', $result[0]->getAddress()->getCity()); } - + /** + * @group DDC-1663 + */ public function testJoinedOneToManyNativeNamedQueryWithResultSetMapping() { $user = new CmsUser; @@ -477,7 +480,7 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $repository = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser'); - $result = $repository->createNativeNamedQuery('fetchJoinedPhonenumberWithResultSetMapping') + $result = $repository->createNativeNamedQuery('fetchJoinedPhonenumber') ->setParameter(1, 'FabioBatSilva')->getResult(); $this->assertEquals(1, count($result)); @@ -491,7 +494,56 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertTrue($phones[0]->getUser() === $result[0]); } + /** + * @group DDC-1663 + */ + public function testMixedNativeNamedQueryNormalJoin() + { + $user1 = new CmsUser; + $user1->name = 'Fabio B. Silva'; + $user1->username = 'FabioBatSilva'; + $user1->status = 'dev'; + $user2 = new CmsUser; + $user2->name = 'test tester'; + $user2->username = 'test'; + $user2->status = 'tester'; + + $phone1 = new CmsPhonenumber; + $phone2 = new CmsPhonenumber; + $phone3 = new CmsPhonenumber; + $phone1->phonenumber = 11111111; + $phone2->phonenumber = 22222222; + $phone3->phonenumber = 33333333; -} + $user1->addPhonenumber($phone1); + $user1->addPhonenumber($phone2); + $user2->addPhonenumber($phone3); + $this->_em->persist($user1); + $this->_em->persist($user2); + $this->_em->flush(); + + $this->_em->clear(); + + $repository = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser'); + + $result = $repository->createNativeNamedQuery('fetchUserPhonenumberCount') + ->setParameter(1, array('test','FabioBatSilva'))->getResult(); + + $this->assertEquals(2, count($result)); + $this->assertTrue(is_array($result[0])); + $this->assertTrue(is_array($result[1])); + + // first user => 2 phonenumbers + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0][0]); + $this->assertEquals('Fabio B. Silva', $result[0][0]->name); + $this->assertEquals(2, $result[0]['numPhones']); + + // second user => 1 phonenumbers + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[1][0]); + $this->assertEquals('test tester', $result[1][0]->name); + $this->assertEquals(1, $result[1]['numPhones']); + } + +} \ No newline at end of file From 68665af6e8da91bc1ea350ba02c7e6ba65aae963 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Tue, 28 Feb 2012 23:17:29 -0300 Subject: [PATCH 10/20] test discriminator column --- .../ORM/Query/ResultSetMappingBuilder.php | 14 +++++- tests/Doctrine/Tests/Models/CMS/CmsUser.php | 2 +- .../Tests/Models/Company/CompanyPerson.php | 36 +++++++++++-- .../Tests/ORM/Functional/NativeQueryTest.php | 50 +++++++++++++++++++ 4 files changed, 97 insertions(+), 5 deletions(-) diff --git a/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php b/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php index 310e0c2d9..e334665db 100644 --- a/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php +++ b/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php @@ -124,6 +124,12 @@ class ResultSetMappingBuilder extends ResultSetMapping $this->addEntityResult($class->name, $alias); + if ($classMetadata->discriminatorColumn) { + $discriminatorColumn = $classMetadata->discriminatorColumn; + $this->setDiscriminatorColumn($alias, $discriminatorColumn['name']); + $this->addMetaResult($alias, $discriminatorColumn['name'], $discriminatorColumn['fieldName']); + } + foreach ($classMetadata->getColumnNames() as $key => $columnName) { $propertyName = $classMetadata->getFieldName($columnName); $this->addFieldResult($alias, $columnName, $propertyName); @@ -147,6 +153,12 @@ class ResultSetMappingBuilder extends ResultSetMapping $classMetadata = $this->em->getClassMetadata($entityMapping['entityClass']); $shortName = $classMetadata->reflClass->getShortName(); $alias = strtolower($shortName[0]) . $key; + + if (isset($entityMapping['discriminatorColumn']) && $entityMapping['discriminatorColumn']) { + $discriminatorColumn = $entityMapping['discriminatorColumn']; + $this->setDiscriminatorColumn($alias, $discriminatorColumn); + $this->addMetaResult($alias, $discriminatorColumn, $discriminatorColumn); + } $this->addEntityResult($classMetadata->name, $alias); if (isset($entityMapping['fields']) && !empty($entityMapping['fields'])) { @@ -191,7 +203,7 @@ class ResultSetMappingBuilder extends ResultSetMapping $this->addScalarResult($entityMapping['name'], $entityMapping['name']); } } - + return $this; } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/Models/CMS/CmsUser.php b/tests/Doctrine/Tests/Models/CMS/CmsUser.php index 348f29329..bec1b143d 100644 --- a/tests/Doctrine/Tests/Models/CMS/CmsUser.php +++ b/tests/Doctrine/Tests/Models/CMS/CmsUser.php @@ -35,7 +35,7 @@ use Doctrine\Common\Collections\ArrayCollection; * @NamedNativeQuery( * name = "fetchUserPhonenumberCount", * resultSetMapping= "mappingUserPhonenumberCount", - * query = "SELECT id, name, status, COUNT(phonenumber) AS numPhones FROM cms_users INNER JOIN cms_phonenumbers ON id = user_id WHERE username IN (?) GROUP BY status, user_id ORDER BY username" + * query = "SELECT id, name, status, COUNT(phonenumber) AS numPhones FROM cms_users INNER JOIN cms_phonenumbers ON id = user_id WHERE username IN (?) GROUP BY id, name, status ORDER BY username" * ), * }) * diff --git a/tests/Doctrine/Tests/Models/Company/CompanyPerson.php b/tests/Doctrine/Tests/Models/Company/CompanyPerson.php index 0dfe9191c..f1cc318db 100644 --- a/tests/Doctrine/Tests/Models/Company/CompanyPerson.php +++ b/tests/Doctrine/Tests/Models/Company/CompanyPerson.php @@ -11,9 +11,39 @@ namespace Doctrine\Tests\Models\Company; * @InheritanceType("JOINED") * @DiscriminatorColumn(name="discr", type="string") * @DiscriminatorMap({ - * "person" = "CompanyPerson", - * "manager" = "CompanyManager", - * "employee" = "CompanyEmployee"}) + * "person" = "CompanyPerson", + * "manager" = "CompanyManager", + * "employee" = "CompanyEmployee" + * }) + * + * @NamedNativeQueries({ + * @NamedNativeQuery( + * name = "fetchAllWithResultClass", + * resultClass = "__CLASS__", + * query = "SELECT id, name, discr FROM company_persons ORDER BY name" + * ), + * @NamedNativeQuery( + * name = "fetchAllWithSqlResultSetMapping", + * resultSetMapping= "mappingFetchAll", + * query = "SELECT id, name, discr AS discriminator FROM company_persons ORDER BY name" + * ) + * }) + * + * @SqlResultSetMappings({ + * @SqlResultSetMapping( + * name = "mappingFetchAll", + * entities= { + * @EntityResult( + * entityClass = "__CLASS__", + * discriminatorColumn = "discriminator", + * fields = { + * @FieldResult("id"), + * @FieldResult("name"), + * } + * ) + * } + * ) + * }) */ class CompanyPerson { diff --git a/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php b/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php index 8c9657fda..12dbbc6d4 100644 --- a/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php @@ -10,6 +10,7 @@ use Doctrine\Tests\Models\CMS\CmsAddress; use Doctrine\Tests\Models\CMS\CmsEmail; use Doctrine\Tests\Models\Company\CompanyFixContract; use Doctrine\Tests\Models\Company\CompanyEmployee; +use Doctrine\Tests\Models\Company\CompanyPerson; require_once __DIR__ . '/../../TestInit.php'; @@ -24,6 +25,7 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase protected function setUp() { $this->useModelSet('cms'); + $this->useModelSet('company'); parent::setUp(); $this->platform = $this->_em->getConnection()->getDatabasePlatform(); } @@ -546,4 +548,52 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals(1, $result[1]['numPhones']); } + /** + * @group DDC-1663 + */ + public function testNativeNamedQueryInheritance() + { + $person = new CompanyPerson; + $person->setName('Fabio B. Silva'); + + $employee = new CompanyEmployee; + $employee->setName('Fabio Silva'); + $employee->setSalary(100000); + $employee->setDepartment('IT'); + + $this->_em->persist($person); + $this->_em->persist($employee); + + $this->_em->flush(); + $this->_em->clear(); + + $repository = $this->_em->getRepository('Doctrine\Tests\Models\Company\CompanyPerson'); + + $result = $repository->createNativeNamedQuery('fetchAllWithSqlResultSetMapping') + ->getResult(); + + $this->assertEquals(2, count($result)); + $this->assertInstanceOf('Doctrine\Tests\Models\Company\CompanyPerson', $result[0]); + $this->assertInstanceOf('Doctrine\Tests\Models\Company\CompanyEmployee', $result[1]); + $this->assertTrue(is_numeric($result[0]->getId())); + $this->assertTrue(is_numeric($result[1]->getId())); + $this->assertEquals('Fabio B. Silva', $result[0]->getName()); + $this->assertEquals('Fabio Silva', $result[1]->getName()); + + + $this->_em->clear(); + + + $result = $repository->createNativeNamedQuery('fetchAllWithResultClass') + ->getResult(); + + $this->assertEquals(2, count($result)); + $this->assertInstanceOf('Doctrine\Tests\Models\Company\CompanyPerson', $result[0]); + $this->assertInstanceOf('Doctrine\Tests\Models\Company\CompanyEmployee', $result[1]); + $this->assertTrue(is_numeric($result[0]->getId())); + $this->assertTrue(is_numeric($result[1]->getId())); + $this->assertEquals('Fabio B. Silva', $result[0]->getName()); + $this->assertEquals('Fabio Silva', $result[1]->getName()); + } + } \ No newline at end of file From 8c407af1fce4b4391724018faed9e77b5996062f Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Tue, 28 Feb 2012 23:46:28 -0300 Subject: [PATCH 11/20] fix postgres test --- tests/Doctrine/Tests/Models/CMS/CmsUser.php | 4 ++-- tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Doctrine/Tests/Models/CMS/CmsUser.php b/tests/Doctrine/Tests/Models/CMS/CmsUser.php index bec1b143d..e414ee95e 100644 --- a/tests/Doctrine/Tests/Models/CMS/CmsUser.php +++ b/tests/Doctrine/Tests/Models/CMS/CmsUser.php @@ -35,7 +35,7 @@ use Doctrine\Common\Collections\ArrayCollection; * @NamedNativeQuery( * name = "fetchUserPhonenumberCount", * resultSetMapping= "mappingUserPhonenumberCount", - * query = "SELECT id, name, status, COUNT(phonenumber) AS numPhones FROM cms_users INNER JOIN cms_phonenumbers ON id = user_id WHERE username IN (?) GROUP BY id, name, status ORDER BY username" + * query = "SELECT id, name, status, COUNT(phonenumber) AS numphones FROM cms_users INNER JOIN cms_phonenumbers ON id = user_id WHERE username IN (?) GROUP BY id, name, status, username ORDER BY username" * ), * }) * @@ -84,7 +84,7 @@ use Doctrine\Common\Collections\ArrayCollection; * ) * }, * columns = { - * @ColumnResult("numPhones") + * @ColumnResult("numphones") * } * ) * }) diff --git a/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php b/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php index 12dbbc6d4..c9496c0ec 100644 --- a/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php @@ -540,12 +540,12 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase // first user => 2 phonenumbers $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0][0]); $this->assertEquals('Fabio B. Silva', $result[0][0]->name); - $this->assertEquals(2, $result[0]['numPhones']); + $this->assertEquals(2, $result[0]['numphones']); // second user => 1 phonenumbers $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[1][0]); $this->assertEquals('test tester', $result[1][0]->name); - $this->assertEquals(1, $result[1]['numPhones']); + $this->assertEquals(1, $result[1]['numphones']); } /** From fdc9fdae3e46fe3a7537706ebcac823e449e8623 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Tue, 6 Mar 2012 21:17:35 -0300 Subject: [PATCH 12/20] refactory ResultSetMappingBuilder#addNamedNativeQueryMapping into small submethods --- .../ORM/Query/ResultSetMappingBuilder.php | 87 ++++++++++++------- 1 file changed, 56 insertions(+), 31 deletions(-) diff --git a/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php b/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php index e334665db..2a47be7fe 100644 --- a/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php +++ b/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php @@ -109,7 +109,7 @@ class ResultSetMappingBuilder extends ResultSetMapping /** - * Adds a the mappings of the results of native SQL queries to the result set. + * Adds the mappings of the results of native SQL queries to the result set. * * @param ClassMetadataInfo $class * @param array $queryMapping @@ -118,36 +118,61 @@ class ResultSetMappingBuilder extends ResultSetMapping public function addNamedNativeQueryMapping(ClassMetadataInfo $class, array $queryMapping) { if (isset($queryMapping['resultClass'])) { - $classMetadata = $this->em->getClassMetadata($queryMapping['resultClass']); - $shortName = $classMetadata->reflClass->getShortName(); - $alias = strtolower($shortName[0]).'0'; - - $this->addEntityResult($class->name, $alias); - - if ($classMetadata->discriminatorColumn) { - $discriminatorColumn = $classMetadata->discriminatorColumn; - $this->setDiscriminatorColumn($alias, $discriminatorColumn['name']); - $this->addMetaResult($alias, $discriminatorColumn['name'], $discriminatorColumn['fieldName']); - } - - foreach ($classMetadata->getColumnNames() as $key => $columnName) { - $propertyName = $classMetadata->getFieldName($columnName); - $this->addFieldResult($alias, $columnName, $propertyName); - } - - foreach ($classMetadata->associationMappings as $associationMapping) { - if ($associationMapping['isOwningSide'] && $associationMapping['type'] & ClassMetadataInfo::TO_ONE) { - foreach ($associationMapping['joinColumns'] as $joinColumn) { - $columnName = $joinColumn['name']; - $this->addMetaResult($alias, $columnName, $columnName, $classMetadata->isIdentifier($columnName)); - } - } - } - - return $this; + return $this->addNamedNativeQueryResultClassMapping($class, $queryMapping['resultClass']); } - $resultMapping = $class->getSqlResultSetMapping($queryMapping['resultSetMapping']); + return $this->addNamedNativeQueryResultSetMapping($class, $queryMapping['resultSetMapping']); + } + + /** + * Adds the class mapping of the results of native SQL queries to the result set. + * + * @param ClassMetadataInfo $class + * @param string $resultClassName + * @return ResultSetMappingBuilder + */ + public function addNamedNativeQueryResultClassMapping(ClassMetadataInfo $class, $resultClassName) + { + + $classMetadata = $this->em->getClassMetadata($resultClassName); + $shortName = $classMetadata->reflClass->getShortName(); + $alias = strtolower($shortName[0]).'0'; + + $this->addEntityResult($class->name, $alias); + + if ($classMetadata->discriminatorColumn) { + $discriminatorColumn = $classMetadata->discriminatorColumn; + $this->setDiscriminatorColumn($alias, $discriminatorColumn['name']); + $this->addMetaResult($alias, $discriminatorColumn['name'], $discriminatorColumn['fieldName']); + } + + foreach ($classMetadata->getColumnNames() as $key => $columnName) { + $propertyName = $classMetadata->getFieldName($columnName); + $this->addFieldResult($alias, $columnName, $propertyName); + } + + foreach ($classMetadata->associationMappings as $associationMapping) { + if ($associationMapping['isOwningSide'] && $associationMapping['type'] & ClassMetadataInfo::TO_ONE) { + foreach ($associationMapping['joinColumns'] as $joinColumn) { + $columnName = $joinColumn['name']; + $this->addMetaResult($alias, $columnName, $columnName, $classMetadata->isIdentifier($columnName)); + } + } + } + + return $this; + } + + /** + * Adds the result set mapping of the results of native SQL queries to the result set. + * + * @param ClassMetadataInfo $class + * @param string $resultSetMappingName + * @return ResultSetMappingBuilder + */ + public function addNamedNativeQueryResultSetMapping(ClassMetadataInfo $class, $resultSetMappingName) + { + $resultMapping = $class->getSqlResultSetMapping($resultSetMappingName); if (isset($resultMapping['entities'])) { foreach ($resultMapping['entities'] as $key => $entityMapping) { $classMetadata = $this->em->getClassMetadata($entityMapping['entityClass']); @@ -159,7 +184,7 @@ class ResultSetMappingBuilder extends ResultSetMapping $this->setDiscriminatorColumn($alias, $discriminatorColumn); $this->addMetaResult($alias, $discriminatorColumn, $discriminatorColumn); } - + $this->addEntityResult($classMetadata->name, $alias); if (isset($entityMapping['fields']) && !empty($entityMapping['fields'])) { foreach ($entityMapping['fields'] as $field) { @@ -203,7 +228,7 @@ class ResultSetMappingBuilder extends ResultSetMapping $this->addScalarResult($entityMapping['name'], $entityMapping['name']); } } - + return $this; } } \ No newline at end of file From 6e93186db4f8a977df230f3ed0a763606424a67a Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Tue, 6 Mar 2012 22:31:23 -0300 Subject: [PATCH 13/20] mapping driver tests --- .../ORM/Mapping/AbstractMappingDriverTest.php | 100 ++++++++++++------ 1 file changed, 70 insertions(+), 30 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php index 0d0db044a..6e0ff36e5 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php @@ -501,25 +501,49 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase $class = $this->createClassMetadata('Doctrine\Tests\Models\CMS\CmsAddress'); + //named native query $this->assertCount(3, $class->namedNativeQueries); $this->assertArrayHasKey('find-all', $class->namedNativeQueries); $this->assertArrayHasKey('find-by-id', $class->namedNativeQueries); - $findAllQuery = $class->namedNativeQueries['find-all']; + + $findAllQuery = $class->getNamedNativeQuery('find-all'); $this->assertEquals('find-all', $findAllQuery['name']); $this->assertEquals('mapping-find-all', $findAllQuery['resultSetMapping']); $this->assertEquals('SELECT id, country, city FROM cms_addresses', $findAllQuery['query']); - $findByIdQuery = $class->namedNativeQueries['find-by-id']; + $findByIdQuery = $class->getNamedNativeQuery('find-by-id'); $this->assertEquals('find-by-id', $findByIdQuery['name']); $this->assertEquals('Doctrine\Tests\Models\CMS\CmsAddress',$findByIdQuery['resultClass']); $this->assertEquals('SELECT * FROM cms_addresses WHERE id = ?', $findByIdQuery['query']); - $countQuery = $class->namedNativeQueries['count']; + $countQuery = $class->getNamedNativeQuery('count'); $this->assertEquals('count', $countQuery['name']); $this->assertEquals('mapping-count', $countQuery['resultSetMapping']); $this->assertEquals('SELECT COUNT(*) AS count FROM cms_addresses', $countQuery['query']); + // result set mapping + $this->assertCount(3, $class->sqlResultSetMappings); + $this->assertArrayHasKey('mapping-count', $class->sqlResultSetMappings); + $this->assertArrayHasKey('mapping-find-all', $class->sqlResultSetMappings); + $this->assertArrayHasKey('mapping-without-fields', $class->sqlResultSetMappings); + + $findAllMapping = $class->getSqlResultSetMapping('mapping-find-all'); + $this->assertEquals('mapping-find-all', $findAllMapping['name']); + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsAddress', $findAllMapping['entities'][0]['entityClass']); + $this->assertEquals(array('name'=>'id','column'=>'id'), $findAllMapping['entities'][0]['fields'][0]); + $this->assertEquals(array('name'=>'city','column'=>'city'), $findAllMapping['entities'][0]['fields'][1]); + $this->assertEquals(array('name'=>'country','column'=>'country'), $findAllMapping['entities'][0]['fields'][2]); + + $withoutFieldsMapping = $class->getSqlResultSetMapping('mapping-without-fields'); + $this->assertEquals('mapping-without-fields', $withoutFieldsMapping['name']); + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsAddress', $withoutFieldsMapping['entities'][0]['entityClass']); + $this->assertEquals(array(), $withoutFieldsMapping['entities'][0]['fields']); + + $countMapping = $class->getSqlResultSetMapping('mapping-count'); + $this->assertEquals('mapping-count', $countMapping['name']); + $this->assertEquals(array('name'=>'count'), $countMapping['columns'][0]); + } /** @@ -531,39 +555,55 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase $this->markTestIncomplete(); } - $class = $this->createClassMetadata('Doctrine\Tests\Models\CMS\CmsAddress'); + $userMetadata = $this->createClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'); + $personMetadata = $this->createClassMetadata('Doctrine\Tests\Models\Company\CompanyPerson'); - $this->assertCount(3, $class->sqlResultSetMappings); - $this->assertArrayHasKey('mapping-find-all', $class->sqlResultSetMappings); - $this->assertArrayHasKey('mapping-without-fields', $class->sqlResultSetMappings); + // user asserts + $this->assertCount(3, $userMetadata->getSqlResultSetMappings()); - // empty fields select all fields - $withoutFieldsMapping = $class->sqlResultSetMappings['mapping-without-fields']; - $this->assertEquals('mapping-without-fields', $withoutFieldsMapping['name']); - $this->assertCount(1, $withoutFieldsMapping['entities']); - $this->assertCount(0, $withoutFieldsMapping['columns']); - $this->assertEquals(array(), $withoutFieldsMapping['entities'][0]['fields']); - $this->assertEquals('Doctrine\Tests\Models\CMS\CmsAddress', $withoutFieldsMapping['entities'][0]['entityClass']); + $mapping = $userMetadata->getSqlResultSetMapping('mappingJoinedAddress'); + $this->assertEquals(array(),$mapping['columns']); + $this->assertEquals('mappingJoinedAddress', $mapping['name']); + $this->assertNull($mapping['entities'][0]['discriminatorColumn']); + $this->assertEquals(array('name'=>'id','column'=>'id'), $mapping['entities'][0]['fields'][0]); + $this->assertEquals(array('name'=>'name','column'=>'name'), $mapping['entities'][0]['fields'][1]); + $this->assertEquals(array('name'=>'status','column'=>'status'), $mapping['entities'][0]['fields'][2]); + $this->assertEquals(array('name'=>'address.zip','column'=>'zip'), $mapping['entities'][0]['fields'][3]); + $this->assertEquals(array('name'=>'address.city','column'=>'city'), $mapping['entities'][0]['fields'][4]); + $this->assertEquals(array('name'=>'address.country','column'=>'country'), $mapping['entities'][0]['fields'][5]); + $this->assertEquals(array('name'=>'address.id','column'=>'a_id'), $mapping['entities'][0]['fields'][6]); + $this->assertEquals($userMetadata->name, $mapping['entities'][0]['entityClass']); - $findAllMapping = $class->sqlResultSetMappings['mapping-find-all']; - $this->assertEquals('mapping-find-all', $findAllMapping['name']); - $this->assertCount(1, $findAllMapping['entities']); - $this->assertCount(0, $findAllMapping['columns']); - $this->assertEquals(array( - array('name'=>'id','column'=>'id'), - array('name'=>'city','column'=>'city'), - array('name'=>'country','column' => 'country') - ), $findAllMapping['entities'][0]['fields']); - $this->assertEquals('Doctrine\Tests\Models\CMS\CmsAddress', $findAllMapping['entities'][0]['entityClass']); + $mapping = $userMetadata->getSqlResultSetMapping('mappingJoinedPhonenumber'); + $this->assertEquals(array(),$mapping['columns']); + $this->assertEquals('mappingJoinedPhonenumber', $mapping['name']); + $this->assertNull($mapping['entities'][0]['discriminatorColumn']); + $this->assertEquals(array('name'=>'id','column'=>'id'), $mapping['entities'][0]['fields'][0]); + $this->assertEquals(array('name'=>'name','column'=>'name'), $mapping['entities'][0]['fields'][1]); + $this->assertEquals(array('name'=>'status','column'=>'status'), $mapping['entities'][0]['fields'][2]); + $this->assertEquals(array('name'=>'phonenumbers.phonenumber','column'=>'number'), $mapping['entities'][0]['fields'][3]); + $this->assertEquals($userMetadata->name, $mapping['entities'][0]['entityClass']); - - $countMapping = $class->sqlResultSetMappings['mapping-count']; - $this->assertEquals('mapping-count', $countMapping['name']); - $this->assertCount(0, $countMapping['entities']); - $this->assertCount(1, $countMapping['columns']); - $this->assertEquals(array(array('name'=>'count')),$countMapping['columns']); + $mapping = $userMetadata->getSqlResultSetMapping('mappingUserPhonenumberCount'); + $this->assertEquals(array('name'=>'numphones'),$mapping['columns'][0]); + $this->assertEquals('mappingUserPhonenumberCount', $mapping['name']); + $this->assertNull($mapping['entities'][0]['discriminatorColumn']); + $this->assertEquals(array('name'=>'id','column'=>'id'), $mapping['entities'][0]['fields'][0]); + $this->assertEquals(array('name'=>'name','column'=>'name'), $mapping['entities'][0]['fields'][1]); + $this->assertEquals(array('name'=>'status','column'=>'status'), $mapping['entities'][0]['fields'][2]); + $this->assertEquals($userMetadata->name, $mapping['entities'][0]['entityClass']); + //person asserts + $this->assertCount(1, $personMetadata->getSqlResultSetMappings()); + + $mapping = $personMetadata->getSqlResultSetMapping('mappingFetchAll'); + $this->assertEquals(array(),$mapping['columns']); + $this->assertEquals('mappingFetchAll', $mapping['name']); + $this->assertEquals('discriminator', $mapping['entities'][0]['discriminatorColumn']); + $this->assertEquals(array('name'=>'id','column'=>'id'), $mapping['entities'][0]['fields'][0]); + $this->assertEquals(array('name'=>'name','column'=>'name'), $mapping['entities'][0]['fields'][1]); + $this->assertEquals($personMetadata->name, $mapping['entities'][0]['entityClass']); } } From 531eb68d56270ac912e997f4be478d3da92f30a8 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Wed, 7 Mar 2012 22:03:24 -0300 Subject: [PATCH 14/20] xml driver --- lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php | 52 ++++++++ .../ORM/Mapping/AbstractMappingDriverTest.php | 4 +- ...ctrine.Tests.Models.CMS.CmsAddress.dcm.xml | 63 +++++++++ .../Doctrine.Tests.Models.CMS.CmsUser.dcm.xml | 121 ++++++++++++++++++ ...Tests.Models.Company.CompanyPerson.dcm.xml | 59 +++++++++ 5 files changed, 297 insertions(+), 2 deletions(-) create mode 100644 tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.CMS.CmsAddress.dcm.xml create mode 100644 tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.CMS.CmsUser.dcm.xml create mode 100644 tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.Company.CompanyPerson.dcm.xml diff --git a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php index 70a530046..ea4326d3b 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php @@ -85,6 +85,58 @@ class XmlDriver extends AbstractFileDriver } } + // Evaluate native named queries + if (isset($xmlRoot->{'named-native-queries'})) { + foreach ($xmlRoot->{'named-native-queries'}->{'named-native-query'} as $nativeQueryElement) { + $metadata->addNamedNativeQuery(array( + 'name' => isset($nativeQueryElement['name']) ? (string)$nativeQueryElement['name'] : null, + 'query' => isset($nativeQueryElement->query) ? (string)$nativeQueryElement->query : null, + 'resultClass' => isset($nativeQueryElement['result-class']) ? (string)$nativeQueryElement['result-class'] : null, + 'resultSetMapping' => isset($nativeQueryElement['result-set-mapping']) ? (string)$nativeQueryElement['result-set-mapping'] : null, + )); + } + } + + // Evaluate sql result set mapping + if (isset($xmlRoot->{'sql-result-set-mappings'})) { + foreach ($xmlRoot->{'sql-result-set-mappings'}->{'sql-result-set-mapping'} as $rsmElement) { + $entities = array(); + $columns = array(); + foreach ($rsmElement as $entityElement) { + // + if (isset($entityElement['entity-class'])) { + $entityResult = array( + 'fields' => array(), + 'entityClass' => (string)$entityElement['entity-class'], + 'discriminatorColumn' => isset($entityElement['discriminator-column']) ? (string)$entityElement['discriminator-column'] : null, + ); + + foreach ($entityElement as $fieldElement) { + $entityResult['fields'][] = array( + 'name' => isset($fieldElement['name']) ? (string)$fieldElement['name'] : null, + 'column' => isset($fieldElement['column']) ? (string)$fieldElement['column'] : null, + ); + } + + $entities[] = $entityResult; + } + + // + if (isset($entityElement['name'])) { + $columns[] = array( + 'name' => (string)$entityElement['name'], + ); + } + } + + $metadata->addSqlResultSetMapping(array( + 'name' => (string)$rsmElement['name'], + 'entities' => $entities, + 'columns' => $columns + )); + } + } + /* not implemented specially anyway. use table = schema.table if (isset($xmlRoot['schema'])) { $metadata->table['schema'] = (string)$xmlRoot['schema']; diff --git a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php index 6e0ff36e5..df7490132 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php @@ -495,7 +495,7 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase */ public function testNamedNativeQuery() { - if (!$this instanceof AnnotationDriverTest) { + if (!$this instanceof AnnotationDriverTest && !$this instanceof XmlMappingDriverTest) { $this->markTestIncomplete(); } @@ -551,7 +551,7 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase */ public function testSqlResultSetMapping() { - if (!$this instanceof AnnotationDriverTest) { + if (!$this instanceof AnnotationDriverTest && !$this instanceof XmlMappingDriverTest) { $this->markTestIncomplete(); } diff --git a/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.CMS.CmsAddress.dcm.xml b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.CMS.CmsAddress.dcm.xml new file mode 100644 index 000000000..a65d08371 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.CMS.CmsAddress.dcm.xml @@ -0,0 +1,63 @@ + + + + + + + + + SELECT id, country, city FROM cms_addresses + + + + SELECT * FROM cms_addresses WHERE id = ? + + + + SELECT COUNT(*) AS count FROM cms_addresses + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.CMS.CmsUser.dcm.xml b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.CMS.CmsUser.dcm.xml new file mode 100644 index 000000000..1045deba9 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.CMS.CmsUser.dcm.xml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + SELECT id, username FROM cms_users WHERE username = ? + + + + SELECT * FROM cms_users WHERE username = ? + + + + SELECT u.id, u.name, u.status, a.id AS a_id, a.country, a.zip, a.city FROM cms_users u INNER JOIN cms_addresses a ON u.id = a.user_id WHERE u.username = ? + + + + SELECT id, name, status, phonenumber AS number FROM cms_users INNER JOIN cms_phonenumbers ON id = user_id WHERE username = ? + + + + SELECT id, name, status, COUNT(phonenumber) AS numphones FROM cms_users INNER JOIN cms_phonenumbers ON id = user_id WHERE username IN (?) GROUP BY id, name, status, username ORDER BY username + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.Company.CompanyPerson.dcm.xml b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.Company.CompanyPerson.dcm.xml new file mode 100644 index 000000000..6902b7366 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.Company.CompanyPerson.dcm.xml @@ -0,0 +1,59 @@ + + + + + + + + + SELECT id, name, discr FROM company_persons ORDER BY name + + + + + + SELECT id, name, discr AS discriminator FROM company_persons ORDER BY name + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From b49180875c50642a88387114fe4c0b85bdfff905 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Fri, 9 Mar 2012 23:03:25 -0300 Subject: [PATCH 15/20] yml driver --- .../ORM/Mapping/Driver/AnnotationDriver.php | 2 +- .../ORM/Mapping/Driver/YamlDriver.php | 74 ++++++++++- .../ORM/Mapping/AbstractMappingDriverTest.php | 8 +- ...ctrine.Tests.Models.CMS.CmsAddress.dcm.xml | 12 +- ...ctrine.Tests.Models.CMS.CmsAddress.dcm.yml | 62 +++++++++ .../Doctrine.Tests.Models.CMS.CmsUser.dcm.yml | 124 ++++++++++++++++++ ...Tests.Models.Company.CompanyPerson.dcm.yml | 75 +++++++++++ 7 files changed, 340 insertions(+), 17 deletions(-) create mode 100644 tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.CMS.CmsAddress.dcm.yml create mode 100644 tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.CMS.CmsUser.dcm.yml create mode 100644 tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.Company.CompanyPerson.dcm.yml diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php index 1e6cab682..6a89131fe 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php @@ -224,6 +224,7 @@ class AnnotationDriver implements Driver foreach ($sqlResultSetMappingsAnnot->value as $resultSetMapping) { $entities = array(); + $columns = array(); foreach ($resultSetMapping->entities as $entityResultAnnot) { $entityResult = array( 'fields' => array(), @@ -241,7 +242,6 @@ class AnnotationDriver implements Driver $entities[] = $entityResult; } - $columns = array(); foreach ($resultSetMapping->columns as $columnResultAnnot) { $columns[] = array( 'name' => $columnResultAnnot->name, diff --git a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php index 0c4fb43cb..d602ed975 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php @@ -83,6 +83,68 @@ class YamlDriver extends AbstractFileDriver } } + // Evaluate named native queries + if (isset($element['namedNativeQueries'])) { + foreach ($element['namedNativeQueries'] as $name => $mappingElement) { + if (!isset($mappingElement['name'])) { + $mappingElement['name'] = $name; + } + $metadata->addNamedNativeQuery(array( + 'name' => $mappingElement['name'], + 'query' => isset($mappingElement['query']) ? $mappingElement['query'] : null, + 'resultClass' => isset($mappingElement['resultClass']) ? $mappingElement['resultClass'] : null, + 'resultSetMapping' => isset($mappingElement['resultSetMapping']) ? $mappingElement['resultSetMapping'] : null, + )); + } + } + + // Evaluate sql result set mappings + if (isset($element['sqlResultSetMappings'])) { + foreach ($element['sqlResultSetMappings'] as $name => $resultSetMapping) { + if (!isset($resultSetMapping['name'])) { + $resultSetMapping['name'] = $name; + } + + $entities = array(); + $columns = array(); + if (isset($resultSetMapping['entityResult'])) { + foreach ($resultSetMapping['entityResult'] as $entityResultElement) { + $entityResult = array( + 'fields' => array(), + 'entityClass' => isset($entityResultElement['entityClass']) ? $entityResultElement['entityClass'] : null, + 'discriminatorColumn' => isset($entityResultElement['discriminatorColumn']) ? $entityResultElement['discriminatorColumn'] : null, + ); + + if (isset($entityResultElement['fieldResult'])) { + foreach ($entityResultElement['fieldResult'] as $fieldResultElement) { + $entityResult['fields'][] = array( + 'name' => isset($fieldResultElement['name']) ? $fieldResultElement['name'] : null, + 'column' => isset($fieldResultElement['column']) ? $fieldResultElement['column'] : null, + ); + } + } + + $entities[] = $entityResult; + } + } + + + if (isset($resultSetMapping['columnResult'])) { + foreach ($resultSetMapping['columnResult'] as $columnResultAnnot) { + $columns[] = array( + 'name' => isset($columnResultAnnot['name']) ? $columnResultAnnot['name'] : null, + ); + } + } + + $metadata->addSqlResultSetMapping(array( + 'name' => $resultSetMapping['name'], + 'entities' => $entities, + 'columns' => $columns + )); + } + } + /* not implemented specially anyway. use table = schema.table if (isset($element['schema'])) { $metadata->table['schema'] = $element['schema']; @@ -484,10 +546,14 @@ class YamlDriver extends AbstractFileDriver */ private function _getJoinColumnMapping($joinColumnElement) { - $joinColumn = array( - 'name' => $joinColumnElement['name'], - 'referencedColumnName' => $joinColumnElement['referencedColumnName'] - ); + $joinColumn = array(); + if (isset($joinColumnElement['referencedColumnName'])) { + $joinColumn['referencedColumnName'] = (string) $joinColumnElement['referencedColumnName']; + } + + if (isset($joinColumnElement['name'])) { + $joinColumn['name'] = (string) $joinColumnElement['name']; + } if (isset($joinColumnElement['fieldName'])) { $joinColumn['fieldName'] = (string) $joinColumnElement['fieldName']; diff --git a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php index df7490132..1060bae84 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php @@ -495,7 +495,9 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase */ public function testNamedNativeQuery() { - if (!$this instanceof AnnotationDriverTest && !$this instanceof XmlMappingDriverTest) { + if (!$this instanceof AnnotationDriverTest && + !$this instanceof XmlMappingDriverTest && + !$this instanceof YamlMappingDriverTest) { $this->markTestIncomplete(); } @@ -551,7 +553,9 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase */ public function testSqlResultSetMapping() { - if (!$this instanceof AnnotationDriverTest && !$this instanceof XmlMappingDriverTest) { + if (!$this instanceof AnnotationDriverTest && + !$this instanceof XmlMappingDriverTest && + !$this instanceof YamlMappingDriverTest) { $this->markTestIncomplete(); } diff --git a/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.CMS.CmsAddress.dcm.xml b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.CMS.CmsAddress.dcm.xml index a65d08371..0af5facda 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.CMS.CmsAddress.dcm.xml +++ b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.CMS.CmsAddress.dcm.xml @@ -46,17 +46,9 @@ - - - - - - - - - - + + diff --git a/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.CMS.CmsAddress.dcm.yml b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.CMS.CmsAddress.dcm.yml new file mode 100644 index 000000000..604acb293 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.CMS.CmsAddress.dcm.yml @@ -0,0 +1,62 @@ +Doctrine\Tests\Models\CMS\CmsAddress: + type: entity + table: cms_address + namedNativeQueries: + find-all: + resultSetMapping: mapping-find-all + query: SELECT id, country, city FROM cms_addresses + find-by-id: + name: find-by-id + resultClass: CmsAddress + query: SELECT * FROM cms_addresses WHERE id = ? + count: + name: count + resultSetMapping: mapping-count + query: SELECT COUNT(*) AS count FROM cms_addresses + + sqlResultSetMappings: + mapping-find-all: + entityResult: + address: + entityClass: CmsAddress + fieldResult: + 0: + name: id + column: id + 1: + name: city + column: city + 2: + name: country + column: country + mapping-without-fields: + name: mapping-without-fields + entityResult: + address: + entityClass: CmsAddress + mapping-count: + name: mapping-count + columnResult: + count: + name: count + id: + id: + type: integer + generator: + strategy: AUTO + fields: + country: + type: string + length: 50 + city: + type: string + length: 50 + zip: + type: string + length: 50 + oneToOne: + address: + targetEntity: CmsUser + inversedBy: address + joinColumn: + referencedColumnName: id \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.CMS.CmsUser.dcm.yml b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.CMS.CmsUser.dcm.yml new file mode 100644 index 000000000..ce860b251 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.CMS.CmsUser.dcm.yml @@ -0,0 +1,124 @@ +Doctrine\Tests\Models\CMS\CmsUser: + type: entity + table: cms_users + namedQueries: + all: SELECT u FROM __CLASS__ u + namedNativeQueries: + fetchIdAndUsernameWithResultClass: + resultClass: CmsUser + query: SELECT id, username FROM cms_users WHERE username = ? + fetchAllColumns: + name: fetchAllColumns + resultClass: CmsUser + query: SELECT * FROM cms_users WHERE username = ? + fetchJoinedAddress: + name: fetchJoinedAddress + resultSetMapping: mappingJoinedAddress + query: SELECT u.id, u.name, u.status, a.id AS a_id, a.country, a.zip, a.city FROM cms_users u INNER JOIN cms_addresses a ON u.id = a.user_id WHERE u.username = ? + fetchJoinedPhonenumber: + name: fetchJoinedPhonenumber + resultSetMapping: mappingJoinedPhonenumber + query: SELECT id, name, status, phonenumber AS number FROM cms_users INNER JOIN cms_phonenumbers ON id = user_id WHERE username = ? + fetchUserPhonenumberCount: + name: fetchUserPhonenumberCount + resultSetMapping: mappingUserPhonenumberCount + query: SELECT id, name, status, COUNT(phonenumber) AS numphones FROM cms_users INNER JOIN cms_phonenumbers ON id = user_id WHERE username IN (?) GROUP BY id, name, status, username ORDER BY username + + sqlResultSetMappings: + mappingJoinedAddress: + entityResult: + 0: + entityClass: __CLASS__ + fieldResult: + 0: + name: id + 1: + name: name + 2: + name: status + 3: + name: address.zip + 4: + name: address.city + 5: + name: address.country + 6: + name: address.id + column: a_id + mappingJoinedPhonenumber: + name: mappingJoinedPhonenumber + entityResult: + user: + entityClass: CmsUser + fieldResult: + 0: + name: id + 1: + name: name + 2: + name: status + 3: + name: phonenumbers.phonenumber + column: number + mappingUserPhonenumberCount: + name: mappingUserPhonenumberCount + columnResult: + 0: + name: numphones + entityResult: + user_0: + entityClass: CmsUser + fieldResult: + 0: + name: id + 1: + name: name + 2: + name: status + id: + id: + type: integer + generator: + strategy: AUTO + fields: + name: + type: string + length: 255 + username: + type: string + length: 255 + unique: true + status: + type: string + length: 50 + unique: true + oneToOne: + address: + targetEntity: CmsAddress + orphanRemoval: true + inversedBy: user + joinColumn: + name: address_id + referencedColumnName: id + cascade: [ persist ] + oneToOne: + email: + targetEntity: CmsEmail + orphanRemoval: true + inversedBy: user + joinColumn: + nullable: true + referencedColumnName: id + cascade: [ persist ] + manyToMany: + groups: + targetEntity: CmsGroup + joinTable: + name: cms_users_groups + joinColumns: + user_id: + referencedColumnName: id + inverseJoinColumns: + group_id: + referencedColumnName: id + cascade: [ persist , detach, merge] \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.Company.CompanyPerson.dcm.yml b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.Company.CompanyPerson.dcm.yml new file mode 100644 index 000000000..26846c5de --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.Company.CompanyPerson.dcm.yml @@ -0,0 +1,75 @@ +Doctrine\Tests\Models\Company\CompanyPerson: + type: entity + table: company_persons + inheritanceType: JOINED + discriminatorMap: + person: CompanyPerson + manager: CompanyManager + employee: CompanyEmployee + namedNativeQueries: + fetchAllWithResultClass: + resultClass: __CLASS__ + query: SELECT id, name, discr FROM company_persons ORDER BY name + fetchAllWithSqlResultSetMapping: + name: fetchAllWithSqlResultSetMapping + resultSetMapping: mappingFetchAll + query: SELECT id, name, discr AS discriminator FROM company_persons ORDER BY name + + sqlResultSetMappings: + mappingFetchAll: + entityResult: + 0: + entityClass: __CLASS__ + discriminatorColumn: discriminator + fieldResult: + 0: + name: id + 1: + name: name + id: + id: + type: integer + generator: + strategy: AUTO + fields: + name: + type: string + length: 255 + username: + type: string + length: 255 + unique: true + status: + type: string + length: 50 + unique: true + oneToOne: + address: + targetEntity: CmsAddress + orphanRemoval: true + inversedBy: user + joinColumn: + name: address_id + referencedColumnName: id + cascade: [ persist ] + oneToOne: + email: + targetEntity: CmsEmail + orphanRemoval: true + inversedBy: user + joinColumn: + nullable: true + referencedColumnName: id + cascade: [ persist ] + manyToMany: + groups: + targetEntity: CmsGroup + joinTable: + name: cms_users_groups + joinColumns: + user_id: + referencedColumnName: id + inverseJoinColumns: + group_id: + referencedColumnName: id + cascade: [ persist , detach, merge] \ No newline at end of file From 4aa67a7598e52d299d7cf379faf2764291d817ee Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Sat, 10 Mar 2012 00:04:46 -0300 Subject: [PATCH 16/20] static driver --- .../Doctrine/Tests/Models/CMS/CmsAddress.php | 68 +++++++++ tests/Doctrine/Tests/Models/CMS/CmsUser.php | 135 ++++++++++++++++++ .../Tests/Models/Company/CompanyPerson.php | 40 ++++++ .../ORM/Mapping/AbstractMappingDriverTest.php | 10 -- 4 files changed, 243 insertions(+), 10 deletions(-) diff --git a/tests/Doctrine/Tests/Models/CMS/CmsAddress.php b/tests/Doctrine/Tests/Models/CMS/CmsAddress.php index 0b4f1ad9a..9833f3dfb 100644 --- a/tests/Doctrine/Tests/Models/CMS/CmsAddress.php +++ b/tests/Doctrine/Tests/Models/CMS/CmsAddress.php @@ -120,4 +120,72 @@ class CmsAddress $user->setAddress($this); } } + + public static function loadMetadata(\Doctrine\ORM\Mapping\ClassMetadataInfo $metadata) + { + $metadata->setPrimaryTable(array( + 'name' => 'company_person', + )); + + $metadata->addNamedNativeQuery(array ( + 'name' => 'find-all', + 'query' => 'SELECT id, country, city FROM cms_addresses', + 'resultSetMapping' => 'mapping-find-all', + )); + + $metadata->addNamedNativeQuery(array ( + 'name' => 'find-by-id', + 'query' => 'SELECT * FROM cms_addresses WHERE id = ?', + 'resultClass' => 'Doctrine\\Tests\\Models\\CMS\\CmsAddress', + )); + + $metadata->addNamedNativeQuery(array ( + 'name' => 'count', + 'query' => 'SELECT COUNT(*) AS count FROM cms_addresses', + 'resultSetMapping' => 'mapping-count', + )); + + + $metadata->addSqlResultSetMapping(array ( + 'name' => 'mapping-find-all', + 'columns' => array(), + 'entities' => array ( array ( + 'fields' => array ( + array ( + 'name' => 'id', + 'column' => 'id', + ), + array ( + 'name' => 'city', + 'column' => 'city', + ), + array ( + 'name' => 'country', + 'column' => 'country', + ), + ), + 'entityClass' => 'Doctrine\Tests\Models\CMS\CmsAddress', + ), + ), + )); + + $metadata->addSqlResultSetMapping(array ( + 'name' => 'mapping-without-fields', + 'columns' => array(), + 'entities' => array(array ( + 'entityClass' => 'Doctrine\\Tests\\Models\\CMS\\CmsAddress', + 'fields' => array() + ) + ) + )); + + $metadata->addSqlResultSetMapping(array ( + 'name' => 'mapping-count', + 'columns' =>array ( + array ( + 'name' => 'count', + ), + ) + )); + } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/Models/CMS/CmsUser.php b/tests/Doctrine/Tests/Models/CMS/CmsUser.php index e414ee95e..d3a8b46c0 100644 --- a/tests/Doctrine/Tests/Models/CMS/CmsUser.php +++ b/tests/Doctrine/Tests/Models/CMS/CmsUser.php @@ -218,4 +218,139 @@ class CmsUser } } } + + public static function loadMetadata(\Doctrine\ORM\Mapping\ClassMetadataInfo $metadata) + { + $metadata->setPrimaryTable(array( + 'name' => 'cms_users', + )); + + $metadata->addNamedNativeQuery(array ( + 'name' => 'fetchIdAndUsernameWithResultClass', + 'query' => 'SELECT id, username FROM cms_users WHERE username = ?', + 'resultClass' => 'Doctrine\\Tests\\Models\\CMS\\CmsUser', + )); + + $metadata->addNamedNativeQuery(array ( + 'name' => 'fetchAllColumns', + 'query' => 'SELECT * FROM cms_users WHERE username = ?', + 'resultClass' => 'Doctrine\\Tests\\Models\\CMS\\CmsUser', + )); + + $metadata->addNamedNativeQuery(array ( + 'name' => 'fetchJoinedAddress', + 'query' => 'SELECT u.id, u.name, u.status, a.id AS a_id, a.country, a.zip, a.city FROM cms_users u INNER JOIN cms_addresses a ON u.id = a.user_id WHERE u.username = ?', + 'resultSetMapping' => 'mappingJoinedAddress', + )); + + $metadata->addNamedNativeQuery(array ( + 'name' => 'fetchJoinedPhonenumber', + 'query' => 'SELECT id, name, status, phonenumber AS number FROM cms_users INNER JOIN cms_phonenumbers ON id = user_id WHERE username = ?', + 'resultSetMapping' => 'mappingJoinedPhonenumber', + )); + + $metadata->addNamedNativeQuery(array ( + 'name' => 'fetchUserPhonenumberCount', + 'query' => 'SELECT id, name, status, COUNT(phonenumber) AS numphones FROM cms_users INNER JOIN cms_phonenumbers ON id = user_id WHERE username IN (?) GROUP BY id, name, status, username ORDER BY username', + 'resultSetMapping' => 'mappingUserPhonenumberCount', + )); + + $metadata->addSqlResultSetMapping(array ( + 'name' => 'mappingJoinedAddress', + 'columns' => array(), + 'entities' => array(array ( + 'fields'=> array ( + array ( + 'name' => 'id', + 'column' => 'id', + ), + array ( + 'name' => 'name', + 'column' => 'name', + ), + array ( + 'name' => 'status', + 'column' => 'status', + ), + array ( + 'name' => 'address.zip', + 'column' => 'zip', + ), + array ( + 'name' => 'address.city', + 'column' => 'city', + ), + array ( + 'name' => 'address.country', + 'column' => 'country', + ), + array ( + 'name' => 'address.id', + 'column' => 'a_id', + ), + ), + 'entityClass' => 'Doctrine\Tests\Models\CMS\CmsUser', + 'discriminatorColumn' => null + ), + ), + )); + + $metadata->addSqlResultSetMapping(array ( + 'name' => 'mappingJoinedPhonenumber', + 'columns' => array(), + 'entities' => array(array( + 'fields'=> array ( + array ( + 'name' => 'id', + 'column' => 'id', + ), + array ( + 'name' => 'name', + 'column' => 'name', + ), + array ( + 'name' => 'status', + 'column' => 'status', + ), + array ( + 'name' => 'phonenumbers.phonenumber', + 'column' => 'number', + ), + ), + 'entityClass' => 'Doctrine\\Tests\\Models\\CMS\\CmsUser', + 'discriminatorColumn' => null + ), + ), + )); + + $metadata->addSqlResultSetMapping(array ( + 'name' => 'mappingUserPhonenumberCount', + 'columns' => array(), + 'entities' => array ( + array( + 'fields' => array ( + array ( + 'name' => 'id', + 'column' => 'id', + ), + array ( + 'name' => 'name', + 'column' => 'name', + ), + array ( + 'name' => 'status', + 'column' => 'status', + ) + ), + 'entityClass' => 'Doctrine\Tests\Models\CMS\CmsUser', + 'discriminatorColumn' => null + ) + ), + 'columns' => array ( + array ( + 'name' => 'numphones', + ) + ) + )); + } } diff --git a/tests/Doctrine/Tests/Models/Company/CompanyPerson.php b/tests/Doctrine/Tests/Models/Company/CompanyPerson.php index f1cc318db..1bc916d81 100644 --- a/tests/Doctrine/Tests/Models/Company/CompanyPerson.php +++ b/tests/Doctrine/Tests/Models/Company/CompanyPerson.php @@ -108,5 +108,45 @@ class CompanyPerson $this->spouse->setSpouse($this); } } + + public static function loadMetadata(\Doctrine\ORM\Mapping\ClassMetadataInfo $metadata) + { + + $metadata->setPrimaryTable(array( + 'name' => 'company_person', + )); + + $metadata->addNamedNativeQuery(array ( + 'name' => 'fetchAllWithResultClass', + 'query' => 'SELECT id, name, discr FROM company_persons ORDER BY name', + 'resultClass' => 'Doctrine\\Tests\\Models\\Company\\CompanyPerson', + )); + + $metadata->addNamedNativeQuery(array ( + 'name' => 'fetchAllWithSqlResultSetMapping', + 'query' => 'SELECT id, name, discr AS discriminator FROM company_persons ORDER BY name', + 'resultSetMapping' => 'mappingFetchAll', + )); + + $metadata->addSqlResultSetMapping(array ( + 'name' => 'mappingFetchAll', + 'columns' => array(), + 'entities' => array ( array ( + 'fields' => array ( + array ( + 'name' => 'id', + 'column' => 'id', + ), + array ( + 'name' => 'name', + 'column' => 'name', + ), + ), + 'entityClass' => 'Doctrine\Tests\Models\Company\CompanyPerson', + 'discriminatorColumn' => 'discriminator', + ), + ), + )); + } } diff --git a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php index 1060bae84..49d2ab426 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php @@ -495,11 +495,6 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase */ public function testNamedNativeQuery() { - if (!$this instanceof AnnotationDriverTest && - !$this instanceof XmlMappingDriverTest && - !$this instanceof YamlMappingDriverTest) { - $this->markTestIncomplete(); - } $class = $this->createClassMetadata('Doctrine\Tests\Models\CMS\CmsAddress'); @@ -553,11 +548,6 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase */ public function testSqlResultSetMapping() { - if (!$this instanceof AnnotationDriverTest && - !$this instanceof XmlMappingDriverTest && - !$this instanceof YamlMappingDriverTest) { - $this->markTestIncomplete(); - } $userMetadata = $this->createClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'); $personMetadata = $this->createClassMetadata('Doctrine\Tests\Models\Company\CompanyPerson'); From 9c87b5c68978566a4658bb6045d393e01556c293 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Sat, 10 Mar 2012 00:06:32 -0300 Subject: [PATCH 17/20] php driver --- .../Doctrine.Tests.Models.CMS.CmsAddress.php | 69 +++++++++ .../php/Doctrine.Tests.Models.CMS.CmsUser.php | 135 ++++++++++++++++++ ...ine.Tests.Models.Company.CompanyPerson.php | 39 +++++ 3 files changed, 243 insertions(+) create mode 100644 tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.CMS.CmsAddress.php create mode 100644 tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.CMS.CmsUser.php create mode 100644 tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.Company.CompanyPerson.php diff --git a/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.CMS.CmsAddress.php b/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.CMS.CmsAddress.php new file mode 100644 index 000000000..964065cbd --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.CMS.CmsAddress.php @@ -0,0 +1,69 @@ +setPrimaryTable(array( + 'name' => 'company_person', +)); + + +$metadata->addNamedNativeQuery(array ( + 'name' => 'find-all', + 'query' => 'SELECT id, country, city FROM cms_addresses', + 'resultSetMapping' => 'mapping-find-all', +)); + +$metadata->addNamedNativeQuery(array ( + 'name' => 'find-by-id', + 'query' => 'SELECT * FROM cms_addresses WHERE id = ?', + 'resultClass' => 'Doctrine\\Tests\\Models\\CMS\\CmsAddress', +)); + +$metadata->addNamedNativeQuery(array ( + 'name' => 'count', + 'query' => 'SELECT COUNT(*) AS count FROM cms_addresses', + 'resultSetMapping' => 'mapping-count', +)); + + +$metadata->addSqlResultSetMapping(array ( + 'name' => 'mapping-find-all', + 'columns' => array(), + 'entities' => array ( array ( + 'fields' => array ( + array ( + 'name' => 'id', + 'column' => 'id', + ), + array ( + 'name' => 'city', + 'column' => 'city', + ), + array ( + 'name' => 'country', + 'column' => 'country', + ), + ), + 'entityClass' => 'Doctrine\Tests\Models\CMS\CmsAddress', + ), + ), +)); + +$metadata->addSqlResultSetMapping(array ( + 'name' => 'mapping-without-fields', + 'columns' => array(), + 'entities' => array(array ( + 'entityClass' => 'Doctrine\\Tests\\Models\\CMS\\CmsAddress', + 'fields' => array() + ) + ) +)); + +$metadata->addSqlResultSetMapping(array ( + 'name' => 'mapping-count', + 'columns' =>array ( + array ( + 'name' => 'count', + ), + ) +)); \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.CMS.CmsUser.php b/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.CMS.CmsUser.php new file mode 100644 index 000000000..4dac4b77f --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.CMS.CmsUser.php @@ -0,0 +1,135 @@ +setPrimaryTable(array( + 'name' => 'cms_users', +)); + +$metadata->addNamedNativeQuery(array ( + 'name' => 'fetchIdAndUsernameWithResultClass', + 'query' => 'SELECT id, username FROM cms_users WHERE username = ?', + 'resultClass' => 'Doctrine\\Tests\\Models\\CMS\\CmsUser', +)); + +$metadata->addNamedNativeQuery(array ( + 'name' => 'fetchAllColumns', + 'query' => 'SELECT * FROM cms_users WHERE username = ?', + 'resultClass' => 'Doctrine\\Tests\\Models\\CMS\\CmsUser', +)); + +$metadata->addNamedNativeQuery(array ( + 'name' => 'fetchJoinedAddress', + 'query' => 'SELECT u.id, u.name, u.status, a.id AS a_id, a.country, a.zip, a.city FROM cms_users u INNER JOIN cms_addresses a ON u.id = a.user_id WHERE u.username = ?', + 'resultSetMapping' => 'mappingJoinedAddress', +)); + +$metadata->addNamedNativeQuery(array ( + 'name' => 'fetchJoinedPhonenumber', + 'query' => 'SELECT id, name, status, phonenumber AS number FROM cms_users INNER JOIN cms_phonenumbers ON id = user_id WHERE username = ?', + 'resultSetMapping' => 'mappingJoinedPhonenumber', +)); + +$metadata->addNamedNativeQuery(array ( + 'name' => 'fetchUserPhonenumberCount', + 'query' => 'SELECT id, name, status, COUNT(phonenumber) AS numphones FROM cms_users INNER JOIN cms_phonenumbers ON id = user_id WHERE username IN (?) GROUP BY id, name, status, username ORDER BY username', + 'resultSetMapping' => 'mappingUserPhonenumberCount', +)); + +$metadata->addSqlResultSetMapping(array ( + 'name' => 'mappingJoinedAddress', + 'columns' => array(), + 'entities' => array(array ( + 'fields'=> array ( + array ( + 'name' => 'id', + 'column' => 'id', + ), + array ( + 'name' => 'name', + 'column' => 'name', + ), + array ( + 'name' => 'status', + 'column' => 'status', + ), + array ( + 'name' => 'address.zip', + 'column' => 'zip', + ), + array ( + 'name' => 'address.city', + 'column' => 'city', + ), + array ( + 'name' => 'address.country', + 'column' => 'country', + ), + array ( + 'name' => 'address.id', + 'column' => 'a_id', + ), + ), + 'entityClass' => 'Doctrine\Tests\Models\CMS\CmsUser', + 'discriminatorColumn' => null + ), + ), +)); + +$metadata->addSqlResultSetMapping(array ( + 'name' => 'mappingJoinedPhonenumber', + 'columns' => array(), + 'entities' => array(array( + 'fields'=> array ( + array ( + 'name' => 'id', + 'column' => 'id', + ), + array ( + 'name' => 'name', + 'column' => 'name', + ), + array ( + 'name' => 'status', + 'column' => 'status', + ), + array ( + 'name' => 'phonenumbers.phonenumber', + 'column' => 'number', + ), + ), + 'entityClass' => 'Doctrine\\Tests\\Models\\CMS\\CmsUser', + 'discriminatorColumn' => null + ), + ), +)); + +$metadata->addSqlResultSetMapping(array ( + 'name' => 'mappingUserPhonenumberCount', + 'columns' => array(), + 'entities' => array ( + array( + 'fields' => array ( + array ( + 'name' => 'id', + 'column' => 'id', + ), + array ( + 'name' => 'name', + 'column' => 'name', + ), + array ( + 'name' => 'status', + 'column' => 'status', + ) + ), + 'entityClass' => 'Doctrine\Tests\Models\CMS\CmsUser', + 'discriminatorColumn' => null + ) + ), + 'columns' => array ( + array ( + 'name' => 'numphones', + ) + ) +)); \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.Company.CompanyPerson.php b/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.Company.CompanyPerson.php new file mode 100644 index 000000000..68703f40a --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.Company.CompanyPerson.php @@ -0,0 +1,39 @@ +setPrimaryTable(array( + 'name' => 'company_person', +)); + +$metadata->addNamedNativeQuery(array ( + 'name' => 'fetchAllWithResultClass', + 'query' => 'SELECT id, name, discr FROM company_persons ORDER BY name', + 'resultClass' => 'Doctrine\\Tests\\Models\\Company\\CompanyPerson', +)); + +$metadata->addNamedNativeQuery(array ( + 'name' => 'fetchAllWithSqlResultSetMapping', + 'query' => 'SELECT id, name, discr AS discriminator FROM company_persons ORDER BY name', + 'resultSetMapping' => 'mappingFetchAll', +)); + +$metadata->addSqlResultSetMapping(array ( + 'name' => 'mappingFetchAll', + 'columns' => array(), + 'entities' => array ( array ( + 'fields' => array ( + array ( + 'name' => 'id', + 'column' => 'id', + ), + array ( + 'name' => 'name', + 'column' => 'name', + ), + ), + 'entityClass' => 'Doctrine\Tests\Models\Company\CompanyPerson', + 'discriminatorColumn' => 'discriminator', + ), + ), +)); \ No newline at end of file From 52c49b444e93911a556fa6c01ff920c550dbea90 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Sun, 11 Mar 2012 12:10:15 -0300 Subject: [PATCH 18/20] test multiple entity results --- .../ORM/Mapping/ClassMetadataInfo.php | 15 +++ .../ORM/Query/ResultSetMappingBuilder.php | 112 +++++++++++------- tests/Doctrine/Tests/Models/CMS/CmsUser.php | 81 +++++++++++++ .../Tests/ORM/Functional/NativeQueryTest.php | 52 ++++++++ .../ORM/Hydration/ResultSetMappingTest.php | 15 ++- .../ORM/Mapping/AbstractMappingDriverTest.php | 16 ++- .../php/Doctrine.Tests.Models.CMS.CmsUser.php | 51 ++++++++ .../Doctrine.Tests.Models.CMS.CmsUser.dcm.xml | 18 +++ ...Tests.Models.Company.CompanyPerson.dcm.xml | 4 +- .../Doctrine.Tests.Models.CMS.CmsUser.dcm.yml | 34 ++++++ 10 files changed, 352 insertions(+), 46 deletions(-) diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 52f847f4f..b518f0394 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -2629,4 +2629,19 @@ class ClassMetadataInfo implements ClassMetadata { return $this->associationMappings[$fieldName]['mappedBy']; } + + /** + * @param string $targetClass + * @return array + */ + public function getAssociationsByTargetClass($targetClass) + { + $relations = array(); + foreach ($this->associationMappings as $mapping) { + if ($mapping['targetEntity'] == $targetClass) { + $relations[$mapping['fieldName']] = $mapping; + } + } + return $relations; + } } diff --git a/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php b/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php index 2a47be7fe..7c187dd77 100644 --- a/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php +++ b/lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php @@ -172,54 +172,30 @@ class ResultSetMappingBuilder extends ResultSetMapping */ public function addNamedNativeQueryResultSetMapping(ClassMetadataInfo $class, $resultSetMappingName) { - $resultMapping = $class->getSqlResultSetMapping($resultSetMappingName); + $counter = 0; + $resultMapping = $class->getSqlResultSetMapping($resultSetMappingName); + $rooShortName = $class->reflClass->getShortName(); + $rootAlias = strtolower($rooShortName[0]) . $counter; + + if (isset($resultMapping['entities'])) { foreach ($resultMapping['entities'] as $key => $entityMapping) { $classMetadata = $this->em->getClassMetadata($entityMapping['entityClass']); - $shortName = $classMetadata->reflClass->getShortName(); - $alias = strtolower($shortName[0]) . $key; - - if (isset($entityMapping['discriminatorColumn']) && $entityMapping['discriminatorColumn']) { - $discriminatorColumn = $entityMapping['discriminatorColumn']; - $this->setDiscriminatorColumn($alias, $discriminatorColumn); - $this->addMetaResult($alias, $discriminatorColumn, $discriminatorColumn); - } - - $this->addEntityResult($classMetadata->name, $alias); - if (isset($entityMapping['fields']) && !empty($entityMapping['fields'])) { - foreach ($entityMapping['fields'] as $field) { - $fieldName = $field['name']; - $relation = null; - - if(strpos($fieldName, '.')){ - list($relation, $fieldName) = explode('.', $fieldName); - } - - if (isset($classMetadata->associationMappings[$relation])) { - if($relation) { - $associationMapping = $classMetadata->associationMappings[$relation]; - $joinAlias = $alias.$relation; - $parentAlias = $alias; - - $this->addJoinedEntityResult($associationMapping['targetEntity'], $joinAlias, $parentAlias, $relation); - $this->addFieldResult($joinAlias, $field['column'], $fieldName); - }else { - $this->addFieldResult($alias, $field['column'], $fieldName, $classMetadata->name); - } - } else { - if(!isset($classMetadata->fieldMappings[$fieldName])) { - throw new \InvalidArgumentException("Entity '".$classMetadata->name."' has no field '".$fieldName."'. "); - } - $this->addFieldResult($alias, $field['column'], $fieldName, $classMetadata->name); - } - } + if ($class->reflClass->name == $classMetadata->reflClass->name) { + $this->addEntityResult($classMetadata->name, $rootAlias); + $this->addNamedNativeQueryEntityResultMapping($classMetadata, $entityMapping, $rootAlias); } else { - foreach ($classMetadata->getColumnNames() as $columnName) { - $propertyName = $classMetadata->getFieldName($columnName); - $this->addFieldResult($alias, $columnName, $propertyName); + $shortName = $classMetadata->reflClass->getShortName(); + $joinAlias = strtolower($shortName[0]) . ++ $counter; + $associations = $class->getAssociationsByTargetClass($classMetadata->name); + + foreach ($associations as $relation => $mapping) { + $this->addJoinedEntityResult($mapping['targetEntity'], $joinAlias, $rootAlias, $relation); + $this->addNamedNativeQueryEntityResultMapping($classMetadata, $entityMapping, $joinAlias); } } + } } @@ -231,4 +207,58 @@ class ResultSetMappingBuilder extends ResultSetMapping return $this; } + + /** + * Adds the entity result mapping of the results of native SQL queries to the result set. + * + * @param ClassMetadataInfo $classMetadata + * @param array $entityMapping + * @param string $alias + * @return ResultSetMappingBuilder + */ + public function addNamedNativeQueryEntityResultMapping(ClassMetadataInfo $classMetadata, array $entityMapping, $alias) + { + if (isset($entityMapping['discriminatorColumn']) && $entityMapping['discriminatorColumn']) { + $discriminatorColumn = $entityMapping['discriminatorColumn']; + $this->setDiscriminatorColumn($alias, $discriminatorColumn); + $this->addMetaResult($alias, $discriminatorColumn, $discriminatorColumn); + } + + if (isset($entityMapping['fields']) && !empty($entityMapping['fields'])) { + foreach ($entityMapping['fields'] as $field) { + $fieldName = $field['name']; + $relation = null; + + if(strpos($fieldName, '.')){ + list($relation, $fieldName) = explode('.', $fieldName); + } + + if (isset($classMetadata->associationMappings[$relation])) { + if($relation) { + $associationMapping = $classMetadata->associationMappings[$relation]; + $joinAlias = $alias.$relation; + $parentAlias = $alias; + + $this->addJoinedEntityResult($associationMapping['targetEntity'], $joinAlias, $parentAlias, $relation); + $this->addFieldResult($joinAlias, $field['column'], $fieldName); + }else { + $this->addFieldResult($alias, $field['column'], $fieldName, $classMetadata->name); + } + } else { + if(!isset($classMetadata->fieldMappings[$fieldName])) { + throw new \InvalidArgumentException("Entity '".$classMetadata->name."' has no field '".$fieldName."'. "); + } + $this->addFieldResult($alias, $field['column'], $fieldName, $classMetadata->name); + } + } + + } else { + foreach ($classMetadata->getColumnNames() as $columnName) { + $propertyName = $classMetadata->getFieldName($columnName); + $this->addFieldResult($alias, $columnName, $propertyName); + } + } + + return $this; + } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/Models/CMS/CmsUser.php b/tests/Doctrine/Tests/Models/CMS/CmsUser.php index d3a8b46c0..2bae6ed4f 100644 --- a/tests/Doctrine/Tests/Models/CMS/CmsUser.php +++ b/tests/Doctrine/Tests/Models/CMS/CmsUser.php @@ -37,6 +37,11 @@ use Doctrine\Common\Collections\ArrayCollection; * resultSetMapping= "mappingUserPhonenumberCount", * query = "SELECT id, name, status, COUNT(phonenumber) AS numphones FROM cms_users INNER JOIN cms_phonenumbers ON id = user_id WHERE username IN (?) GROUP BY id, name, status, username ORDER BY username" * ), + * @NamedNativeQuery( + * name = "fetchMultipleJoinsEntityResults", + * resultSetMapping= "mappingMultipleJoinsEntityResults", + * query = "SELECT u.id AS u_id, u.name AS u_name, u.status AS u_status, a.id AS a_id, a.zip AS a_zip, a.country AS a_country, COUNT(p.phonenumber) AS numphones FROM cms_users u INNER JOIN cms_addresses a ON u.id = a.user_id INNER JOIN cms_phonenumbers p ON u.id = p.user_id GROUP BY u.id, u.name, u.status, u.username, a.id, a.zip, a.country ORDER BY u.username" + * ), * }) * * @SqlResultSetMappings({ @@ -86,6 +91,30 @@ use Doctrine\Common\Collections\ArrayCollection; * columns = { * @ColumnResult("numphones") * } + * ), + * @SqlResultSetMapping( + * name = "mappingMultipleJoinsEntityResults", + * entities= { + * @EntityResult( + * entityClass = "__CLASS__", + * fields = { + * @FieldResult(name = "id", column="u_id"), + * @FieldResult(name = "name", column="u_name"), + * @FieldResult(name = "status", column="u_status"), + * } + * ), + * @EntityResult( + * entityClass = "CmsAddress", + * fields = { + * @FieldResult(name = "id", column="a_id"), + * @FieldResult(name = "zip", column="a_zip"), + * @FieldResult(name = "country", column="a_country"), + * } + * ) + * }, + * columns = { + * @ColumnResult("numphones") + * } * ) * }) */ @@ -255,6 +284,12 @@ class CmsUser 'resultSetMapping' => 'mappingUserPhonenumberCount', )); + $metadata->addNamedNativeQuery(array ( + "name" => "fetchMultipleJoinsEntityResults", + "resultSetMapping" => "mappingMultipleJoinsEntityResults", + "query" => "SELECT u.id AS u_id, u.name AS u_name, u.status AS u_status, a.id AS a_id, a.zip AS a_zip, a.country AS a_country, COUNT(p.phonenumber) AS numphones FROM cms_users u INNER JOIN cms_addresses a ON u.id = a.user_id INNER JOIN cms_phonenumbers p ON u.id = p.user_id GROUP BY u.id, u.name, u.status, u.username, a.id, a.zip, a.country ORDER BY u.username" + )); + $metadata->addSqlResultSetMapping(array ( 'name' => 'mappingJoinedAddress', 'columns' => array(), @@ -352,5 +387,51 @@ class CmsUser ) ) )); + + $metadata->addSqlResultSetMapping(array( + 'name' => 'mappingMultipleJoinsEntityResults', + 'entities' => array(array( + 'fields' => array( + array( + 'name' => 'id', + 'column' => 'u_id', + ), + array( + 'name' => 'name', + 'column' => 'u_name', + ), + array( + 'name' => 'status', + 'column' => 'u_status', + ) + ), + 'entityClass' => 'Doctrine\Tests\Models\CMS\CmsUser', + 'discriminatorColumn' => null, + ), + array( + 'fields' => array( + array( + 'name' => 'id', + 'column' => 'a_id', + ), + array( + 'name' => 'zip', + 'column' => 'a_zip', + ), + array( + 'name' => 'country', + 'column' => 'a_country', + ), + ), + 'entityClass' => 'Doctrine\Tests\Models\CMS\CmsAddress', + 'discriminatorColumn' => null, + ), + ), + 'columns' => array(array( + 'name' => 'numphones', + ) + ) + )); + } } diff --git a/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php b/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php index c9496c0ec..0f3e6b533 100644 --- a/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php @@ -8,6 +8,7 @@ use Doctrine\Tests\Models\CMS\CmsUser; use Doctrine\Tests\Models\CMS\CmsPhonenumber; use Doctrine\Tests\Models\CMS\CmsAddress; use Doctrine\Tests\Models\CMS\CmsEmail; +use Doctrine\Tests\Models\CMS\CmsArticle; use Doctrine\Tests\Models\Company\CompanyFixContract; use Doctrine\Tests\Models\Company\CompanyEmployee; use Doctrine\Tests\Models\Company\CompanyPerson; @@ -596,4 +597,55 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase $this->assertEquals('Fabio Silva', $result[1]->getName()); } + /** + * @group DDC-1663 + * DQL : SELECT u, a, COUNT(p) AS numphones FROM Doctrine\Tests\Models\CMS\CmsUser u JOIN u.address a JOIN u.phonenumbers p + */ + public function testMultipleEntityResults() + { + + $user = new CmsUser; + $user->name = 'Fabio B. Silva'; + $user->username = 'FabioBatSilva'; + $user->status = 'dev'; + + $addr = new CmsAddress; + $addr->country = 'Brazil'; + $addr->zip = 10827; + $addr->city = 'São Paulo'; + + $phone = new CmsPhonenumber; + $phone->phonenumber = 424242; + + + $user->setAddress($addr); + $user->addPhonenumber($phone); + + + $this->_em->clear(); + $this->_em->persist($user); + $this->_em->flush(); + + $this->_em->clear(); + + + $repository = $this->_em->getRepository('Doctrine\Tests\Models\CMS\CmsUser'); + $query = $repository->createNativeNamedQuery('fetchMultipleJoinsEntityResults'); + $result = $query->getResult(); + + + $this->assertEquals(1, count($result)); + $this->assertTrue(is_array($result[0])); + + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsUser', $result[0][0]); + $this->assertEquals('Fabio B. Silva', $result[0][0]->name); + $this->assertInstanceOf('Doctrine\Tests\Models\CMS\CmsAddress', $result[0][0]->getAddress()); + $this->assertTrue($result[0][0]->getAddress()->getUser() == $result[0][0]); + $this->assertEquals('Brazil', $result[0][0]->getAddress()->getCountry()); + $this->assertEquals(10827, $result[0][0]->getAddress()->getZipCode()); + + $this->assertEquals(1, $result[0]['numphones']); + + } + } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Hydration/ResultSetMappingTest.php b/tests/Doctrine/Tests/ORM/Hydration/ResultSetMappingTest.php index 9c057a0b1..801937f2d 100644 --- a/tests/Doctrine/Tests/ORM/Hydration/ResultSetMappingTest.php +++ b/tests/Doctrine/Tests/ORM/Hydration/ResultSetMappingTest.php @@ -102,6 +102,19 @@ class ResultSetMappingTest extends \Doctrine\Tests\OrmTestCase $cm = new ClassMetadata('Doctrine\Tests\Models\CMS\CmsUser'); $cm->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService); + $cm->mapOneToOne(array( + 'fieldName' => 'email', + 'targetEntity' => 'Doctrine\Tests\Models\CMS\CmsEmail', + 'cascade' => array('persist'), + 'inversedBy' => 'user', + 'orphanRemoval' => false, + 'joinColumns' => array(array( + 'nullable' => true, + 'referencedColumnName' => 'id', + ) + ) + )); + $cm->addNamedNativeQuery(array( 'name' => 'find-all', 'query' => 'SELECT u.id AS user_id, e.id AS email_id, u.name, e.email, u.id + e.id AS scalarColumn FROM cms_users u INNER JOIN cms_emails e ON e.id = u.email_id', @@ -144,7 +157,7 @@ class ResultSetMappingTest extends \Doctrine\Tests\OrmTestCase ) )); - + $queryMapping = $cm->getNamedNativeQuery('find-all'); $rsm = new \Doctrine\ORM\Query\ResultSetMappingBuilder($this->_em); diff --git a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php index 49d2ab426..0efb3aca2 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php @@ -553,7 +553,7 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase $personMetadata = $this->createClassMetadata('Doctrine\Tests\Models\Company\CompanyPerson'); // user asserts - $this->assertCount(3, $userMetadata->getSqlResultSetMappings()); + $this->assertCount(4, $userMetadata->getSqlResultSetMappings()); $mapping = $userMetadata->getSqlResultSetMapping('mappingJoinedAddress'); $this->assertEquals(array(),$mapping['columns']); @@ -588,6 +588,20 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase $this->assertEquals(array('name'=>'status','column'=>'status'), $mapping['entities'][0]['fields'][2]); $this->assertEquals($userMetadata->name, $mapping['entities'][0]['entityClass']); + $mapping = $userMetadata->getSqlResultSetMapping('mappingMultipleJoinsEntityResults'); + $this->assertEquals(array('name'=>'numphones'),$mapping['columns'][0]); + $this->assertEquals('mappingMultipleJoinsEntityResults', $mapping['name']); + $this->assertNull($mapping['entities'][0]['discriminatorColumn']); + $this->assertEquals(array('name'=>'id','column'=>'u_id'), $mapping['entities'][0]['fields'][0]); + $this->assertEquals(array('name'=>'name','column'=>'u_name'), $mapping['entities'][0]['fields'][1]); + $this->assertEquals(array('name'=>'status','column'=>'u_status'), $mapping['entities'][0]['fields'][2]); + $this->assertEquals($userMetadata->name, $mapping['entities'][0]['entityClass']); + $this->assertNull($mapping['entities'][1]['discriminatorColumn']); + $this->assertEquals(array('name'=>'id','column'=>'a_id'), $mapping['entities'][1]['fields'][0]); + $this->assertEquals(array('name'=>'zip','column'=>'a_zip'), $mapping['entities'][1]['fields'][1]); + $this->assertEquals(array('name'=>'country','column'=>'a_country'), $mapping['entities'][1]['fields'][2]); + $this->assertEquals('Doctrine\Tests\Models\CMS\CmsAddress', $mapping['entities'][1]['entityClass']); + //person asserts $this->assertCount(1, $personMetadata->getSqlResultSetMappings()); diff --git a/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.CMS.CmsUser.php b/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.CMS.CmsUser.php index 4dac4b77f..9484bf750 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.CMS.CmsUser.php +++ b/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.CMS.CmsUser.php @@ -36,6 +36,12 @@ $metadata->addNamedNativeQuery(array ( 'resultSetMapping' => 'mappingUserPhonenumberCount', )); +$metadata->addNamedNativeQuery(array ( + "name" => "fetchMultipleJoinsEntityResults", + "resultSetMapping" => "mappingMultipleJoinsEntityResults", + "query" => "SELECT u.id AS u_id, u.name AS u_name, u.status AS u_status, a.id AS a_id, a.zip AS a_zip, a.country AS a_country, COUNT(p.phonenumber) AS numphones FROM cms_users u INNER JOIN cms_addresses a ON u.id = a.user_id INNER JOIN cms_phonenumbers p ON u.id = p.user_id GROUP BY u.id, u.name, u.status, u.username, a.id, a.zip, a.country ORDER BY u.username" +)); + $metadata->addSqlResultSetMapping(array ( 'name' => 'mappingJoinedAddress', 'columns' => array(), @@ -132,4 +138,49 @@ $metadata->addSqlResultSetMapping(array ( 'name' => 'numphones', ) ) +)); + +$metadata->addSqlResultSetMapping(array( + 'name' => 'mappingMultipleJoinsEntityResults', + 'entities' => array(array( + 'fields' => array( + array( + 'name' => 'id', + 'column' => 'u_id', + ), + array( + 'name' => 'name', + 'column' => 'u_name', + ), + array( + 'name' => 'status', + 'column' => 'u_status', + ) + ), + 'entityClass' => 'Doctrine\Tests\Models\CMS\CmsUser', + 'discriminatorColumn' => null, + ), + array( + 'fields' => array( + array( + 'name' => 'id', + 'column' => 'a_id', + ), + array( + 'name' => 'zip', + 'column' => 'a_zip', + ), + array( + 'name' => 'country', + 'column' => 'a_country', + ), + ), + 'entityClass' => 'Doctrine\Tests\Models\CMS\CmsAddress', + 'discriminatorColumn' => null, + ), + ), + 'columns' => array(array( + 'name' => 'numphones', + ) + ) )); \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.CMS.CmsUser.dcm.xml b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.CMS.CmsUser.dcm.xml index 1045deba9..64a545df6 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.CMS.CmsUser.dcm.xml +++ b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.CMS.CmsUser.dcm.xml @@ -31,6 +31,10 @@ SELECT id, name, status, COUNT(phonenumber) AS numphones FROM cms_users INNER JOIN cms_phonenumbers ON id = user_id WHERE username IN (?) GROUP BY id, name, status, username ORDER BY username + + + SELECT u.id AS u_id, u.name AS u_name, u.status AS u_status, a.id AS a_id, a.zip AS a_zip, a.country AS a_country, COUNT(p.phonenumber) AS numphones FROM cms_users u INNER JOIN cms_addresses a ON u.id = a.user_id INNER JOIN cms_phonenumbers p ON u.id = p.user_id GROUP BY u.id, u.name, u.status, u.username, a.id, a.zip, a.country ORDER BY u.username + @@ -63,6 +67,20 @@ + + + + + + + + + + + + + + diff --git a/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.Company.CompanyPerson.dcm.xml b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.Company.CompanyPerson.dcm.xml index 6902b7366..c573504e0 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.Company.CompanyPerson.dcm.xml +++ b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.Company.CompanyPerson.dcm.xml @@ -11,9 +11,7 @@ SELECT id, name, discr FROM company_persons ORDER BY name - - - + SELECT id, name, discr AS discriminator FROM company_persons ORDER BY name diff --git a/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.CMS.CmsUser.dcm.yml b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.CMS.CmsUser.dcm.yml index ce860b251..3a03dd6c3 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.CMS.CmsUser.dcm.yml +++ b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.CMS.CmsUser.dcm.yml @@ -23,6 +23,10 @@ Doctrine\Tests\Models\CMS\CmsUser: name: fetchUserPhonenumberCount resultSetMapping: mappingUserPhonenumberCount query: SELECT id, name, status, COUNT(phonenumber) AS numphones FROM cms_users INNER JOIN cms_phonenumbers ON id = user_id WHERE username IN (?) GROUP BY id, name, status, username ORDER BY username + fetchMultipleJoinsEntityResults: + name: fetchMultipleJoinsEntityResults + resultSetMapping: mappingMultipleJoinsEntityResults + query: SELECT u.id AS u_id, u.name AS u_name, u.status AS u_status, a.id AS a_id, a.zip AS a_zip, a.country AS a_country, COUNT(p.phonenumber) AS numphones FROM cms_users u INNER JOIN cms_addresses a ON u.id = a.user_id INNER JOIN cms_phonenumbers p ON u.id = p.user_id GROUP BY u.id, u.name, u.status, u.username, a.id, a.zip, a.country ORDER BY u.username sqlResultSetMappings: mappingJoinedAddress: @@ -75,6 +79,36 @@ Doctrine\Tests\Models\CMS\CmsUser: name: name 2: name: status + mappingMultipleJoinsEntityResults: + name: mappingMultipleJoinsEntityResults + columnResult: + 0: + name: numphones + entityResult: + 0: + entityClass: __CLASS__ + fieldResult: + 0: + name: id + column: u_id + 1: + name: name + column: u_name + 2: + name: status + column: u_status + 1: + entityClass: CmsAddress + fieldResult: + 0: + name: id + column: a_id + 1: + name: zip + column: a_zip + 2: + name: country + column: a_country id: id: type: integer From f8b1915efd604bb9c2177a8fccb2d3248be796ec Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Sun, 11 Mar 2012 21:46:31 -0300 Subject: [PATCH 19/20] named native query inheritance --- .../ORM/Mapping/ClassMetadataFactory.php | 60 +++++++++++++++++++ .../ORM/Mapping/ClassMetadataInfo.php | 9 +++ .../Tests/Models/Company/CompanyContract.php | 42 +++++++++++++ .../Models/Company/CompanyFlexContract.php | 42 +++++++++++++ .../Tests/ORM/Functional/NativeQueryTest.php | 53 ++++++++++++++++ 5 files changed, 206 insertions(+) diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php index 2aaa30a44..63ac01c25 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php @@ -326,6 +326,14 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface $this->addInheritedNamedQueries($class, $parent); } + if ($parent && !empty ($parent->namedNativeQueries)) { + $this->addInheritedNamedNativeQueries($class, $parent); + } + + if ($parent && !empty ($parent->sqlResultSetMappings)) { + $this->addInheritedSqlResultSetMappings($class, $parent); + } + $class->setParentClasses($visited); if ($this->evm->hasListeners(Events::loadClassMetadata)) { @@ -466,6 +474,58 @@ class ClassMetadataFactory implements ClassMetadataFactoryInterface } } + /** + * Adds inherited named native queries to the subclass mapping. + * + * @since 2.3 + * @param \Doctrine\ORM\Mapping\ClassMetadata $subClass + * @param \Doctrine\ORM\Mapping\ClassMetadata $parentClass + */ + private function addInheritedNamedNativeQueries(ClassMetadata $subClass, ClassMetadata $parentClass) + { + foreach ($parentClass->namedNativeQueries as $name => $query) { + if (!isset ($subClass->namedNativeQueries[$name])) { + $subClass->addNamedNativeQuery(array( + 'name' => $query['name'], + 'query' => $query['query'], + 'isSelfClass' => $query['isSelfClass'], + 'resultSetMapping' => $query['resultSetMapping'], + 'resultClass' => $query['isSelfClass'] ? $subClass->name : $query['resultClass'], + )); + } + } + } + + /** + * Adds inherited sql result set mappings to the subclass mapping. + * + * @since 2.3 + * @param \Doctrine\ORM\Mapping\ClassMetadata $subClass + * @param \Doctrine\ORM\Mapping\ClassMetadata $parentClass + */ + private function addInheritedSqlResultSetMappings(ClassMetadata $subClass, ClassMetadata $parentClass) + { + foreach ($parentClass->sqlResultSetMappings as $name => $mapping) { + if (!isset ($subClass->sqlResultSetMappings[$name])) { + $entities = array(); + foreach ($mapping['entities'] as $entity) { + $entities[] = array( + 'fields' => $entity['fields'], + 'isSelfClass' => $entity['isSelfClass'], + 'discriminatorColumn' => $entity['discriminatorColumn'], + 'entityClass' => $entity['isSelfClass'] ? $subClass->name : $entity['entityClass'], + ); + } + + $subClass->addSqlResultSetMapping(array( + 'name' => $mapping['name'], + 'columns' => $mapping['columns'], + 'entities' => $entities, + )); + } + } + } + /** * Completes the ID generator mapping. If "auto" is specified we choose the generator * most appropriate for the targeted database platform. diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index b518f0394..1f3b00dbf 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -1983,10 +1983,14 @@ class ClassMetadataInfo implements ClassMetadata throw MappingException::missingQueryMapping($this->name, $queryMapping['name']); } + $queryMapping['isSelfClass'] = false; if (isset($queryMapping['resultClass'])) { if($queryMapping['resultClass'] === '__CLASS__') { + + $queryMapping['isSelfClass'] = true; $queryMapping['resultClass'] = $this->name; + } else if (strlen($this->namespace) > 0 && strpos($queryMapping['resultClass'], '\\') === false) { $queryMapping['resultClass'] = $this->namespace . '\\' . $queryMapping['resultClass']; } @@ -2020,13 +2024,18 @@ class ClassMetadataInfo implements ClassMetadata throw MappingException::missingResultSetMappingEntity($this->name, $resultMapping['name']); } + $entityResult['isSelfClass'] = false; if($entityResult['entityClass'] === '__CLASS__') { + + $entityResult['isSelfClass'] = true; $entityResult['entityClass'] = $this->name; + } else if (strlen($this->namespace) > 0 && strpos($entityResult['entityClass'], '\\') === false) { $entityResult['entityClass'] = $this->namespace . '\\' . $entityResult['entityClass']; } $resultMapping['entities'][$key]['entityClass'] = ltrim($entityResult['entityClass'], '\\'); + $resultMapping['entities'][$key]['isSelfClass'] = $entityResult['isSelfClass']; if (isset($entityResult['fields'])) { foreach ($entityResult['fields'] as $k => $field) { diff --git a/tests/Doctrine/Tests/Models/Company/CompanyContract.php b/tests/Doctrine/Tests/Models/Company/CompanyContract.php index 7787e96be..bc8503dfe 100644 --- a/tests/Doctrine/Tests/Models/Company/CompanyContract.php +++ b/tests/Doctrine/Tests/Models/Company/CompanyContract.php @@ -12,6 +12,48 @@ namespace Doctrine\Tests\Models\Company; * "flexible" = "CompanyFlexContract", * "flexultra" = "CompanyFlexUltraContract" * }) + * + * @NamedNativeQueries({ + * @NamedNativeQuery( + * name = "all-contracts", + * resultClass = "__CLASS__", + * query = "SELECT id, completed, discr FROM company_contracts" + * ), + * @NamedNativeQuery( + * name = "all", + * resultClass = "__CLASS__", + * query = "SELECT id, completed, discr FROM company_contracts" + * ), + * }) + * + * @SqlResultSetMappings({ + * @SqlResultSetMapping( + * name = "mapping-all-contracts", + * entities= { + * @EntityResult( + * entityClass = "__CLASS__", + * discriminatorColumn = "discr", + * fields = { + * @FieldResult("id"), + * @FieldResult("completed"), + * } + * ) + * } + * ), + * @SqlResultSetMapping( + * name = "mapping-all", + * entities= { + * @EntityResult( + * entityClass = "__CLASS__", + * discriminatorColumn = "discr", + * fields = { + * @FieldResult("id"), + * @FieldResult("completed"), + * } + * ) + * } + * ), + * }) */ abstract class CompanyContract { diff --git a/tests/Doctrine/Tests/Models/Company/CompanyFlexContract.php b/tests/Doctrine/Tests/Models/Company/CompanyFlexContract.php index e32288897..121d8ec8e 100644 --- a/tests/Doctrine/Tests/Models/Company/CompanyFlexContract.php +++ b/tests/Doctrine/Tests/Models/Company/CompanyFlexContract.php @@ -3,6 +3,48 @@ namespace Doctrine\Tests\Models\Company; /** * @Entity + * + * @NamedNativeQueries({ + * @NamedNativeQuery( + * name = "all", + * resultClass = "__CLASS__", + * query = "SELECT id, hoursWorked, discr FROM company_contracts" + * ), + * @NamedNativeQuery( + * name = "all-flex", + * resultClass = "CompanyFlexContract", + * query = "SELECT id, hoursWorked, discr FROM company_contracts" + * ), + * }) + * + * @SqlResultSetMappings({ + * @SqlResultSetMapping( + * name = "mapping-all-flex", + * entities= { + * @EntityResult( + * entityClass = "__CLASS__", + * discriminatorColumn = "discr", + * fields = { + * @FieldResult("id"), + * @FieldResult("hoursWorked"), + * } + * ) + * } + * ), + * @SqlResultSetMapping( + * name = "mapping-all", + * entities= { + * @EntityResult( + * entityClass = "CompanyFlexContract", + * discriminatorColumn = "discr", + * fields = { + * @FieldResult("id"), + * @FieldResult("hoursWorked"), + * } + * ) + * } + * ), + * }) */ class CompanyFlexContract extends CompanyContract { diff --git a/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php b/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php index 0f3e6b533..a6a9016a4 100644 --- a/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/NativeQueryTest.php @@ -648,4 +648,57 @@ class NativeQueryTest extends \Doctrine\Tests\OrmFunctionalTestCase } + /** + * @group DDC-1663 + */ + public function testNamedNativeQueryInheritance() + { + $contractMetadata = $this->_em->getClassMetadata('Doctrine\Tests\Models\Company\CompanyContract'); + $flexMetadata = $this->_em->getClassMetadata('Doctrine\Tests\Models\Company\CompanyFlexContract'); + + $contractQueries = $contractMetadata->getNamedNativeQueries(); + $flexQueries = $flexMetadata->getNamedNativeQueries(); + + $contractMappings = $contractMetadata->getSqlResultSetMappings(); + $flexMappings = $flexMetadata->getSqlResultSetMappings(); + + + // contract queries + $this->assertEquals('all-contracts', $contractQueries['all-contracts']['name']); + $this->assertEquals('Doctrine\Tests\Models\Company\CompanyContract', $contractQueries['all-contracts']['resultClass']); + + $this->assertEquals('all', $contractQueries['all']['name']); + $this->assertEquals('Doctrine\Tests\Models\Company\CompanyContract', $contractQueries['all']['resultClass']); + + + // flex contract queries + $this->assertEquals('all-contracts', $flexQueries['all-contracts']['name']); + $this->assertEquals('Doctrine\Tests\Models\Company\CompanyFlexContract', $flexQueries['all-contracts']['resultClass']); + + $this->assertEquals('all-flex', $flexQueries['all-flex']['name']); + $this->assertEquals('Doctrine\Tests\Models\Company\CompanyFlexContract', $flexQueries['all-flex']['resultClass']); + + $this->assertEquals('all', $flexQueries['all']['name']); + $this->assertEquals('Doctrine\Tests\Models\Company\CompanyFlexContract', $flexQueries['all']['resultClass']); + + + // contract result mapping + $this->assertEquals('mapping-all-contracts', $contractMappings['mapping-all-contracts']['name']); + $this->assertEquals('Doctrine\Tests\Models\Company\CompanyContract', $contractMappings['mapping-all-contracts']['entities'][0]['entityClass']); + + $this->assertEquals('mapping-all', $contractMappings['mapping-all']['name']); + $this->assertEquals('Doctrine\Tests\Models\Company\CompanyContract', $contractMappings['mapping-all-contracts']['entities'][0]['entityClass']); + + // flex contract result mapping + $this->assertEquals('mapping-all-contracts', $flexMappings['mapping-all-contracts']['name']); + $this->assertEquals('Doctrine\Tests\Models\Company\CompanyFlexContract', $flexMappings['mapping-all-contracts']['entities'][0]['entityClass']); + + $this->assertEquals('mapping-all', $flexMappings['mapping-all']['name']); + $this->assertEquals('Doctrine\Tests\Models\Company\CompanyFlexContract', $flexMappings['mapping-all']['entities'][0]['entityClass']); + + $this->assertEquals('mapping-all-flex', $flexMappings['mapping-all-flex']['name']); + $this->assertEquals('Doctrine\Tests\Models\Company\CompanyFlexContract', $flexMappings['mapping-all-flex']['entities'][0]['entityClass']); + + } + } \ No newline at end of file From ad9d590a151e1ca548d85e5696dcf628477b03a4 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Sun, 11 Mar 2012 22:18:08 -0300 Subject: [PATCH 20/20] added tags on doctrine-mapping.xsd --- doctrine-mapping.xsd | 46 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/doctrine-mapping.xsd b/doctrine-mapping.xsd index a25dd56f6..80da4eb93 100644 --- a/doctrine-mapping.xsd +++ b/doctrine-mapping.xsd @@ -84,6 +84,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -93,6 +138,7 @@ +