diff --git a/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php b/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php index b21958b752c..0ce499caff6 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php @@ -1454,6 +1454,14 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements S $current['fields'][$real_name] = $attributes; } + // Add unique keys. + foreach ($schema['unique keys'] as $unique_key_name => $columns) { + $real_name = static::_fieldIndexName($field, $unique_key_name); + foreach ($columns as $column_name) { + $current['unique keys'][$real_name][] = static::_fieldColumnName($field, $column_name); + } + } + // Add indexes. foreach ($schema['indexes'] as $index_name => $columns) { $real_name = static::_fieldIndexName($field, $index_name); diff --git a/core/lib/Drupal/Core/Entity/Schema/ContentEntitySchemaHandler.php b/core/lib/Drupal/Core/Entity/Schema/ContentEntitySchemaHandler.php index 5e7c0e511ad..22947946e0d 100644 --- a/core/lib/Drupal/Core/Entity/Schema/ContentEntitySchemaHandler.php +++ b/core/lib/Drupal/Core/Entity/Schema/ContentEntitySchemaHandler.php @@ -325,13 +325,6 @@ class ContentEntitySchemaHandler implements EntitySchemaHandlerInterface { 'foreign keys' => array(), ); - if ($this->entityType->hasKey('uuid')) { - $uuid_key = $this->entityType->getKey('uuid'); - $schema['unique keys'] = array( - $this->getEntityIndexName($uuid_key) => array($uuid_key), - ); - } - if ($this->entityType->hasKey('revision')) { $revision_key = $this->entityType->getKey('revision'); $key_name = $this->getEntityIndexName($revision_key); @@ -342,6 +335,8 @@ class ContentEntitySchemaHandler implements EntitySchemaHandlerInterface { ); } + $this->addTableDefaults($schema); + return $schema; } @@ -370,6 +365,8 @@ class ContentEntitySchemaHandler implements EntitySchemaHandlerInterface { $schema['indexes'][$this->getEntityIndexName($id_key)] = array($id_key); + $this->addTableDefaults($schema); + return $schema; } @@ -402,6 +399,8 @@ class ContentEntitySchemaHandler implements EntitySchemaHandlerInterface { $schema['indexes'][$this->getEntityIndexName($key)] = array($key); } + $this->addTableDefaults($schema); + return $schema; } @@ -434,9 +433,26 @@ class ContentEntitySchemaHandler implements EntitySchemaHandlerInterface { ), ); + $this->addTableDefaults($schema); + return $schema; } + /** + * Adds defaults to a table schema definition. + * + * @param $schema + * The schema definition array for a single table, passed by reference. + */ + protected function addTableDefaults(&$schema) { + $schema += array( + 'fields' => array(), + 'unique keys' => array(), + 'indexes' => array(), + 'foreign keys' => array(), + ); + } + /** * Processes the gathered schema for a base table. * diff --git a/core/lib/Drupal/Core/Field/FieldDefinition.php b/core/lib/Drupal/Core/Field/FieldDefinition.php index cfeccf850c7..c6a661e4e45 100644 --- a/core/lib/Drupal/Core/Field/FieldDefinition.php +++ b/core/lib/Drupal/Core/Field/FieldDefinition.php @@ -480,6 +480,7 @@ class FieldDefinition extends ListDataDefinition implements FieldDefinitionInter // Fill in default values. $schema += array( 'columns' => array(), + 'unique keys' => array(), 'indexes' => array(), 'foreign keys' => array(), ); diff --git a/core/lib/Drupal/Core/Field/FieldItemInterface.php b/core/lib/Drupal/Core/Field/FieldItemInterface.php index d10790dc819..3943bad553c 100644 --- a/core/lib/Drupal/Core/Field/FieldItemInterface.php +++ b/core/lib/Drupal/Core/Field/FieldItemInterface.php @@ -69,6 +69,8 @@ interface FieldItemInterface extends ComplexDataInterface { * definitions depend on field settings when possible. No assumptions * should be made on how storage engines internally use the original * column name to structure their storage. + * - unique keys: (optional) An array of Schema API unique key definitions. + * Only columns that appear in the 'columns' array are allowed. * - indexes: (optional) An array of Schema API index definitions. Only * columns that appear in the 'columns' array are allowed. Those indexes * will be used as default indexes. Callers of field_create_field() can diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/UuidItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/UuidItem.php index 15cbe745be9..50df9bb3558 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/UuidItem.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/UuidItem.php @@ -7,6 +7,8 @@ namespace Drupal\Core\Field\Plugin\Field\FieldType; +use Drupal\Core\Field\FieldStorageDefinitionInterface; + /** * Defines the 'uuid' entity field type. * @@ -39,4 +41,14 @@ class UuidItem extends StringItem { $this->setValue(array('value' => $uuid->generate()), $notify); return $this; } + + /** + * {@inheritdoc} + */ + public static function schema(FieldStorageDefinitionInterface $field_definition) { + $schema = parent::schema($field_definition); + $schema['unique keys']['value'] = array('value'); + return $schema; + } + } diff --git a/core/modules/field/src/Entity/FieldConfig.php b/core/modules/field/src/Entity/FieldConfig.php index 29f22a34c03..f25af7bacc2 100644 --- a/core/modules/field/src/Entity/FieldConfig.php +++ b/core/modules/field/src/Entity/FieldConfig.php @@ -456,7 +456,11 @@ class FieldConfig extends ConfigEntityBase implements FieldConfigInterface { $class = $this->getFieldItemClass(); $schema = $class::schema($this); // Fill in default values for optional entries. - $schema += array('indexes' => array(), 'foreign keys' => array()); + $schema += array( + 'unique keys' => array(), + 'indexes' => array(), + 'foreign keys' => array(), + ); // Check that the schema does not include forbidden column names. if (array_intersect(array_keys($schema['columns']), static::getReservedColumns())) { diff --git a/core/modules/field/src/Tests/TestItemTest.php b/core/modules/field/src/Tests/TestItemTest.php index f48c48cc558..a7680d31899 100644 --- a/core/modules/field/src/Tests/TestItemTest.php +++ b/core/modules/field/src/Tests/TestItemTest.php @@ -94,6 +94,7 @@ class TestItemTest extends FieldUnitTestBase { 'not null' => FALSE, ), ), + 'unique keys' => array(), 'indexes' => array( 'value' => array('value'), ), diff --git a/core/tests/Drupal/Tests/Core/Entity/ContentEntityDatabaseStorageTest.php b/core/tests/Drupal/Tests/Core/Entity/ContentEntityDatabaseStorageTest.php index cc77d6d6bd0..fc095b5d792 100644 --- a/core/tests/Drupal/Tests/Core/Entity/ContentEntityDatabaseStorageTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/ContentEntityDatabaseStorageTest.php @@ -291,6 +291,7 @@ class ContentEntityDatabaseStorageTest extends UnitTestCase { ), ), 'primary key' => array('id'), + 'unique keys' => array(), 'indexes' => array(), 'foreign keys' => array(), ), @@ -973,6 +974,7 @@ class ContentEntityDatabaseStorageTest extends UnitTestCase { 'not null' => FALSE, ), ), + 'unique keys' => array(), 'indexes' => array(), 'foreign keys' => array(), ); diff --git a/core/tests/Drupal/Tests/Core/Entity/Schema/ContentEntitySchemaHandlerTest.php b/core/tests/Drupal/Tests/Core/Entity/Schema/ContentEntitySchemaHandlerTest.php index b39fbcbc29a..8fac217b35d 100644 --- a/core/tests/Drupal/Tests/Core/Entity/Schema/ContentEntitySchemaHandlerTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/Schema/ContentEntitySchemaHandlerTest.php @@ -95,13 +95,11 @@ class ContentEntitySchemaHandlerTest extends UnitTestCase { /** * Tests the schema for non-revisionable, non-translatable entities. * - * @param bool $uuid_key - * Whether or not the tested entity type should have a UUID key. - * * @covers ::__construct() * @covers ::getSchema() * @covers ::getTables() * @covers ::initializeBaseTable() + * @covers ::addTableDefaults() * @covers ::getEntityIndexName() * @covers ::addFieldSchema() * @covers ::getFieldIndexes() @@ -111,16 +109,11 @@ class ContentEntitySchemaHandlerTest extends UnitTestCase { * @covers ::addDefaultLangcodeSchema() * @covers ::processBaseTable() * @covers ::processIdentifierSchema() - * - * @dataProvider providerTestGetSchemaLayoutBase */ - public function testGetSchemaBase($uuid_key) { + public function testGetSchemaBase() { $this->entityType = new ContentEntityType(array( 'id' => 'entity_test', - 'entity_keys' => array( - 'id' => 'id', - 'uuid' => $uuid_key ? 'uuid' : NULL, - ), + 'entity_keys' => array('id' => 'id'), )); // Add a field with a 'length' constraint. @@ -132,16 +125,6 @@ class ContentEntitySchemaHandlerTest extends UnitTestCase { ), ), )); - if ($uuid_key) { - $this->setUpStorageDefinition('uuid', array( - 'columns' => array( - 'value' => array( - 'type' => 'varchar', - 'length' => 128, - ), - ), - )); - } // Add a multi-column field. $this->setUpStorageDefinition('description', array( 'columns' => array( @@ -155,11 +138,51 @@ class ContentEntitySchemaHandlerTest extends UnitTestCase { ), ), )); + // Add a field with a unique key. + $this->setUpStorageDefinition('uuid', array( + 'columns' => array( + 'value' => array( + 'type' => 'varchar', + 'length' => 128, + ), + ), + 'unique keys' => array( + 'value' => array('value'), + ), + )); + // Add a field with a unique key, specified as column name and length. + $this->setUpStorageDefinition('hash', array( + 'columns' => array( + 'value' => array( + 'type' => 'varchar', + 'length' => 20, + ), + ), + 'unique keys' => array( + 'value' => array(array('value', 10)), + ), + )); + // Add a field with a multi-column unique key. + $this->setUpStorageDefinition('email', array( + 'columns' => array( + 'username' => array( + 'type' => 'varchar', + ), + 'hostname' => array( + 'type' => 'varchar', + ), + 'domain' => array( + 'type' => 'varchar', + ) + ), + 'unique keys' => array( + 'email' => array('username', 'hostname', array('domain', 3)), + ), + )); // Add a field with an index. $this->setUpStorageDefinition('owner', array( 'columns' => array( 'target_id' => array( - 'description' => 'The ID of the target entity.', 'type' => 'int', ), ), @@ -171,7 +194,6 @@ class ContentEntitySchemaHandlerTest extends UnitTestCase { $this->setUpStorageDefinition('translator', array( 'columns' => array( 'target_id' => array( - 'description' => 'The ID of the target entity.', 'type' => 'int', ), ), @@ -260,6 +282,28 @@ class ContentEntitySchemaHandlerTest extends UnitTestCase { 'description' => 'The description field.', 'type' => 'varchar', ), + 'uuid' => array( + 'description' => 'The uuid field.', + 'type' => 'varchar', + 'length' => 128, + ), + 'hash' => array( + 'description' => 'The hash field.', + 'type' => 'varchar', + 'length' => 20, + ), + 'email__username' => array( + 'description' => 'The email field.', + 'type' => 'varchar', + ), + 'email__hostname' => array( + 'description' => 'The email field.', + 'type' => 'varchar', + ), + 'email__domain' => array( + 'description' => 'The email field.', + 'type' => 'varchar', + ), 'owner' => array( 'description' => 'The owner field.', 'type' => 'int', @@ -301,6 +345,15 @@ class ContentEntitySchemaHandlerTest extends UnitTestCase { ), ), 'primary key' => array('id'), + 'unique keys' => array( + 'entity_test_field__uuid__value' => array('uuid'), + 'entity_test_field__hash__value' => array(array('hash', 10)), + 'entity_test_field__email__email' => array( + 'email__username', + 'email__hostname', + array('email__domain', 3), + ), + ), 'indexes' => array( 'entity_test_field__owner__target_id' => array('owner'), 'entity_test_field__translator__target_id' => array( @@ -324,35 +377,11 @@ class ContentEntitySchemaHandlerTest extends UnitTestCase { ), ), ); - if ($uuid_key) { - $expected['entity_test']['fields']['uuid'] = array( - 'type' => 'varchar', - 'length' => 128, - 'description' => 'The uuid field.', - 'not null' => TRUE, - ); - $expected['entity_test']['unique keys']['entity_test__uuid'] = array('uuid'); - } $actual = $this->schemaHandler->getSchema(); $this->assertEquals($expected, $actual); } - /** - * Provides data for testGetSchemaLayoutBase(). - * - * @return array - * Returns a nested array where each inner array returns a boolean, - * indicating whether or not the tested entity type should include a UUID - * key. - */ - public function providerTestGetSchemaLayoutBase() { - return array( - array(FALSE), - array(TRUE), - ); - } - /** * Tests the schema for revisionable, non-translatable entities. * @@ -361,6 +390,7 @@ class ContentEntitySchemaHandlerTest extends UnitTestCase { * @covers ::getTables() * @covers ::initializeBaseTable() * @covers ::initializeRevisionTable() + * @covers ::addTableDefaults() * @covers ::getEntityIndexName() * @covers ::processRevisionTable() * @covers ::processIdentifierSchema() @@ -411,6 +441,9 @@ class ContentEntitySchemaHandlerTest extends UnitTestCase { ) ), 'primary key' => array('id'), + 'unique keys' => array( + 'entity_test__revision_id' => array('revision_id'), + ), 'indexes' => array(), 'foreign keys' => array( 'entity_test__revision' => array( @@ -418,9 +451,6 @@ class ContentEntitySchemaHandlerTest extends UnitTestCase { 'columns' => array('revision_id' => 'revision_id'), ) ), - 'unique keys' => array( - 'entity_test__revision_id' => array('revision_id'), - ), ), 'entity_test_revision' => array( 'description' => 'The revision table for entity_test entities.', @@ -436,6 +466,7 @@ class ContentEntitySchemaHandlerTest extends UnitTestCase { ), ), 'primary key' => array('revision_id'), + 'unique keys' => array(), 'indexes' => array( 'entity_test__id' => array('id'), ), @@ -460,6 +491,7 @@ class ContentEntitySchemaHandlerTest extends UnitTestCase { * @covers ::getSchema() * @covers ::getTables() * @covers ::initializeDataTable() + * @covers ::addTableDefaults() * @covers ::getEntityIndexName() * @covers ::processDataTable() */ @@ -509,6 +541,7 @@ class ContentEntitySchemaHandlerTest extends UnitTestCase { ) ), 'primary key' => array('id'), + 'unique keys' => array(), 'indexes' => array(), 'foreign keys' => array(), ), @@ -527,6 +560,7 @@ class ContentEntitySchemaHandlerTest extends UnitTestCase { ), ), 'primary key' => array('id', 'langcode'), + 'unique keys' => array(), 'indexes' => array(), 'foreign keys' => array( 'entity_test' => array( @@ -549,6 +583,7 @@ class ContentEntitySchemaHandlerTest extends UnitTestCase { * @covers ::getSchema() * @covers ::getTables() * @covers ::initializeDataTable() + * @covers ::addTableDefaults() * @covers ::getEntityIndexName() * @covers ::initializeRevisionDataTable() * @covers ::processRevisionDataTable() @@ -619,10 +654,10 @@ class ContentEntitySchemaHandlerTest extends UnitTestCase { ) ), 'primary key' => array('id'), - 'indexes' => array(), 'unique keys' => array( 'entity_test__revision_id' => array('revision_id'), ), + 'indexes' => array(), 'foreign keys' => array( 'entity_test__revision' => array( 'table' => 'entity_test_revision', @@ -649,6 +684,7 @@ class ContentEntitySchemaHandlerTest extends UnitTestCase { ), ), 'primary key' => array('revision_id'), + 'unique keys' => array(), 'indexes' => array( 'entity_test__id' => array('id'), ), @@ -678,6 +714,7 @@ class ContentEntitySchemaHandlerTest extends UnitTestCase { ), ), 'primary key' => array('id', 'langcode'), + 'unique keys' => array(), 'indexes' => array( 'entity_test__revision_id' => array('revision_id'), ), @@ -707,6 +744,7 @@ class ContentEntitySchemaHandlerTest extends UnitTestCase { ), ), 'primary key' => array('revision_id', 'langcode'), + 'unique keys' => array(), 'indexes' => array(), 'foreign keys' => array( 'entity_test' => array(