1
0
Fork 0
mirror of synced 2025-04-01 12:26:11 +03:00

sql result set mapping metadata

This commit is contained in:
Fabio B. Silva 2012-02-26 00:16:54 -03:00
parent 91e4702772
commit 2b996128af
7 changed files with 325 additions and 18 deletions

View file

@ -262,15 +262,29 @@ class ClassMetadataInfo implements ClassMetadata
* A native SQL named query definition has the following structure:
* <pre>
* array(
* 'name' => <query name>,
* 'query' => <sql query>,
* 'resultClass' => <class of the result>,
* 'resultSetMapping' => <name of a SqlResultSetMapping>
* 'name' => <query name>,
* 'query' => <sql query>,
* 'resultClass' => <class of the result>,
* 'resultSetMapping' => <name of a SqlResultSetMapping>
* )
* </pre>
*/
public $namedNativeQueries = array();
/**
* READ-ONLY: The mappings of the results of native SQL queries.
*
* A native result mapping definition has the following structure:
* <pre>
* array(
* 'name' => <result name>,
* 'entities' => array(<entity result mapping>),
* 'columns' => array(<column result mapping>)
* )
* </pre>
*/
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.
*

View file

@ -0,0 +1,42 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the LGPL. For more information, see
* <http://www.doctrine-project.org>.
*/
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 <fabio.bat.silva@gmail.com>
* @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;
}

View file

@ -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);

View file

@ -37,4 +37,4 @@ final class NamedNativeQueries implements Annotation
* @var array<\Doctrine\ORM\Mapping\NamedNativeQuery>
*/
public $value;
}
}

View file

@ -60,4 +60,4 @@ final class NamedNativeQuery implements Annotation
*/
public $resultSetMapping;
}
}

View file

@ -37,4 +37,4 @@ final class SqlResultSetMappings implements Annotation
* @var array<\Doctrine\ORM\Mapping\SqlResultSetMapping>
*/
public $value;
}
}

View file

@ -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.