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