3 namespace Drupal\Tests\Core\Entity\Sql;
5 use Drupal\Core\Entity\ContentEntityType;
6 use Drupal\Core\Entity\ContentEntityTypeInterface;
7 use Drupal\Core\Entity\Sql\DefaultTableMapping;
8 use Drupal\Tests\UnitTestCase;
11 * @coversDefaultClass \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema
14 class SqlContentEntityStorageSchemaTest extends UnitTestCase {
17 * The mocked DB schema handler.
19 * @var \Drupal\Core\Database\Schema|\PHPUnit_Framework_MockObject_MockObject
21 protected $dbSchemaHandler;
24 * The mocked entity manager used in this test.
26 * @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject
28 protected $entityManager;
31 * The mocked entity type used in this test.
33 * @var \Drupal\Core\Entity\ContentEntityTypeInterface
35 protected $entityType;
38 * The mocked SQL storage used in this test.
40 * @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage|\PHPUnit_Framework_MockObject_MockObject
45 * The mocked field definitions used in this test.
47 * @var \Drupal\Core\Field\FieldStorageDefinitionInterface[]|\PHPUnit_Framework_MockObject_MockObject[]
49 protected $storageDefinitions;
52 * The storage schema handler used in this test.
54 * @var \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema|\PHPUnit_Framework_MockObject_MockObject
56 protected $storageSchema;
61 protected function setUp() {
62 $this->entityManager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
63 $this->storage = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorage')
64 ->disableOriginalConstructor()
67 $this->storage->expects($this->any())
68 ->method('getBaseTable')
69 ->will($this->returnValue('entity_test'));
71 // Add an ID field. This also acts as a test for a simple, single-column
73 $this->setUpStorageDefinition('id', [
83 * Tests the schema for non-revisionable, non-translatable entities.
85 * @covers ::__construct
86 * @covers ::getEntitySchemaTables
87 * @covers ::initializeBaseTable
88 * @covers ::addTableDefaults
89 * @covers ::getEntityIndexName
90 * @covers ::getFieldIndexes
91 * @covers ::getFieldUniqueKeys
92 * @covers ::getFieldForeignKeys
93 * @covers ::getFieldSchemaData
94 * @covers ::processBaseTable
95 * @covers ::processIdentifierSchema
97 public function testGetSchemaBase() {
98 $this->entityType = new ContentEntityType([
99 'id' => 'entity_test',
100 'entity_keys' => ['id' => 'id'],
103 // Add a field with a 'length' constraint.
104 $this->setUpStorageDefinition('name', [
112 // Add a multi-column field.
113 $this->setUpStorageDefinition('description', [
123 // Add a field with a unique key.
124 $this->setUpStorageDefinition('uuid', [
132 'value' => ['value'],
135 // Add a field with a unique key, specified as column name and length.
136 $this->setUpStorageDefinition('hash', [
144 'value' => [['value', 10]],
147 // Add a field with a multi-column unique key.
148 $this->setUpStorageDefinition('email', [
161 'email' => ['username', 'hostname', ['domain', 3]],
164 // Add a field with an index.
165 $this->setUpStorageDefinition('owner', [
172 'target_id' => ['target_id'],
175 // Add a field with an index, specified as column name and length.
176 $this->setUpStorageDefinition('translator', [
183 'target_id' => [['target_id', 10]],
186 // Add a field with a multi-column index.
187 $this->setUpStorageDefinition('location', [
200 'country_state_city' => ['country', 'state', ['city', 10]],
203 // Add a field with a foreign key.
204 $this->setUpStorageDefinition('editor', [
213 'columns' => ['target_id' => 'uid'],
217 // Add a multi-column field with a foreign key.
218 $this->setUpStorageDefinition('editor_revision', [
223 'target_revision_id' => [
230 'columns' => ['target_id' => 'uid'],
234 // Add a field with a really long index.
235 $this->setUpStorageDefinition('long_index_name', [
237 'long_index_name' => [
242 'long_index_name_really_long_long_name' => [['long_index_name', 10]],
248 'description' => 'The base table for entity_test entities.',
259 'description__value' => [
263 'description__format' => [
277 'email__username' => [
281 'email__hostname' => [
297 'location__country' => [
301 'location__state' => [
305 'location__city' => [
313 'editor_revision__target_id' => [
317 'editor_revision__target_revision_id' => [
321 'long_index_name' => [
326 'primary key' => ['id'],
328 'entity_test_field__uuid__value' => ['uuid'],
329 'entity_test_field__hash__value' => [['hash', 10]],
330 'entity_test_field__email__email' => [
333 ['email__domain', 3],
337 'entity_test_field__owner__target_id' => ['owner'],
338 'entity_test_field__translator__target_id' => [
341 'entity_test_field__location__country_state_city' => [
344 ['location__city', 10],
346 'entity_test__b588603cb9' => [
347 ['long_index_name', 10],
352 'entity_test_field__editor__user_id' => [
354 'columns' => ['editor' => 'uid'],
356 'entity_test_field__editor_revision__user_id' => [
358 'columns' => ['editor_revision__target_id' => 'uid'],
364 $this->setUpStorageSchema($expected);
366 $table_mapping = new DefaultTableMapping($this->entityType, $this->storageDefinitions);
367 $table_mapping->setFieldNames('entity_test', array_keys($this->storageDefinitions));
368 $table_mapping->setExtraColumns('entity_test', ['default_langcode']);
370 $this->storage->expects($this->any())
371 ->method('getTableMapping')
372 ->will($this->returnValue($table_mapping));
373 $this->storage->expects($this->any())
374 ->method('getCustomTableMapping')
375 ->will($this->returnValue($table_mapping));
378 $this->storageSchema->onEntityTypeCreate($this->entityType)
383 * Tests the schema for revisionable, non-translatable entities.
385 * @covers ::__construct
386 * @covers ::getEntitySchemaTables
387 * @covers ::initializeBaseTable
388 * @covers ::initializeRevisionTable
389 * @covers ::addTableDefaults
390 * @covers ::getEntityIndexName
391 * @covers ::processRevisionTable
392 * @covers ::processIdentifierSchema
394 public function testGetSchemaRevisionable() {
395 $this->entityType = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityType')
396 ->setConstructorArgs([
398 'id' => 'entity_test',
401 'revision' => 'revision_id',
405 ->setMethods(['getRevisionMetadataKeys'])
408 $this->entityType->expects($this->any())
409 ->method('getRevisionMetadataKeys')
410 ->will($this->returnValue([]));
412 $this->storage->expects($this->exactly(2))
413 ->method('getRevisionTable')
414 ->will($this->returnValue('entity_test_revision'));
416 $this->setUpStorageDefinition('revision_id', [
426 'description' => 'The base table for entity_test entities.',
437 'primary key' => ['id'],
439 'entity_test__revision_id' => ['revision_id'],
443 'entity_test__revision' => [
444 'table' => 'entity_test_revision',
445 'columns' => ['revision_id' => 'revision_id'],
449 'entity_test_revision' => [
450 'description' => 'The revision table for entity_test entities.',
461 'primary key' => ['revision_id'],
464 'entity_test__id' => ['id'],
467 'entity_test__revisioned' => [
468 'table' => 'entity_test',
469 'columns' => ['id' => 'id'],
475 $this->setUpStorageSchema($expected);
477 $table_mapping = new DefaultTableMapping($this->entityType, $this->storageDefinitions);
478 $table_mapping->setFieldNames('entity_test', array_keys($this->storageDefinitions));
479 $table_mapping->setFieldNames('entity_test_revision', array_keys($this->storageDefinitions));
481 $this->storage->expects($this->any())
482 ->method('getTableMapping')
483 ->will($this->returnValue($table_mapping));
484 $this->storage->expects($this->any())
485 ->method('getCustomTableMapping')
486 ->will($this->returnValue($table_mapping));
488 $this->storageSchema->onEntityTypeCreate($this->entityType);
492 * Tests the schema for non-revisionable, translatable entities.
494 * @covers ::__construct
495 * @covers ::getEntitySchemaTables
496 * @covers ::initializeDataTable
497 * @covers ::addTableDefaults
498 * @covers ::getEntityIndexName
499 * @covers ::processDataTable
501 public function testGetSchemaTranslatable() {
502 $this->entityType = new ContentEntityType([
503 'id' => 'entity_test',
506 'langcode' => 'langcode',
510 $this->storage->expects($this->any())
511 ->method('getDataTable')
512 ->will($this->returnValue('entity_test_field_data'));
514 $this->setUpStorageDefinition('langcode', [
522 $this->setUpStorageDefinition('default_langcode', [
533 'description' => 'The base table for entity_test entities.',
544 'primary key' => ['id'],
547 'foreign keys' => [],
549 'entity_test_field_data' => [
550 'description' => 'The data table for entity_test entities.',
560 'default_langcode' => [
566 'primary key' => ['id', 'langcode'],
569 'entity_test__id__default_langcode__langcode' => [
571 1 => 'default_langcode',
577 'table' => 'entity_test',
578 'columns' => ['id' => 'id'],
584 $this->setUpStorageSchema($expected);
586 $table_mapping = new DefaultTableMapping($this->entityType, $this->storageDefinitions);
587 $non_data_fields = array_keys($this->storageDefinitions);
588 unset($non_data_fields[array_search('default_langcode', $non_data_fields)]);
589 $table_mapping->setFieldNames('entity_test', $non_data_fields);
590 $table_mapping->setFieldNames('entity_test_field_data', array_keys($this->storageDefinitions));
592 $this->storage->expects($this->any())
593 ->method('getTableMapping')
594 ->will($this->returnValue($table_mapping));
595 $this->storage->expects($this->any())
596 ->method('getCustomTableMapping')
597 ->will($this->returnValue($table_mapping));
600 $this->storageSchema->onEntityTypeCreate($this->entityType)
605 * Tests the schema for revisionable, translatable entities.
607 * @covers ::__construct
608 * @covers ::getEntitySchemaTables
609 * @covers ::initializeDataTable
610 * @covers ::addTableDefaults
611 * @covers ::getEntityIndexName
612 * @covers ::initializeRevisionDataTable
613 * @covers ::processRevisionDataTable
615 public function testGetSchemaRevisionableTranslatable() {
616 $this->entityType = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityType')
617 ->setConstructorArgs([
619 'id' => 'entity_test',
622 'revision' => 'revision_id',
623 'langcode' => 'langcode',
627 ->setMethods(['getRevisionMetadataKeys'])
630 $this->entityType->expects($this->any())
631 ->method('getRevisionMetadataKeys')
632 ->will($this->returnValue([]));
634 $this->storage->expects($this->exactly(3))
635 ->method('getRevisionTable')
636 ->will($this->returnValue('entity_test_revision'));
637 $this->storage->expects($this->once())
638 ->method('getDataTable')
639 ->will($this->returnValue('entity_test_field_data'));
640 $this->storage->expects($this->once())
641 ->method('getRevisionDataTable')
642 ->will($this->returnValue('entity_test_revision_field_data'));
644 $this->setUpStorageDefinition('revision_id', [
651 $this->setUpStorageDefinition('langcode', [
658 $this->setUpStorageDefinition('default_langcode', [
669 'description' => 'The base table for entity_test entities.',
684 'primary key' => ['id'],
686 'entity_test__revision_id' => ['revision_id'],
690 'entity_test__revision' => [
691 'table' => 'entity_test_revision',
692 'columns' => ['revision_id' => 'revision_id'],
696 'entity_test_revision' => [
697 'description' => 'The revision table for entity_test entities.',
712 'primary key' => ['revision_id'],
715 'entity_test__id' => ['id'],
718 'entity_test__revisioned' => [
719 'table' => 'entity_test',
720 'columns' => ['id' => 'id'],
724 'entity_test_field_data' => [
725 'description' => 'The data table for entity_test entities.',
739 'default_langcode' => [
745 'primary key' => ['id', 'langcode'],
748 'entity_test__revision_id' => ['revision_id'],
749 'entity_test__id__default_langcode__langcode' => [
751 1 => 'default_langcode',
757 'table' => 'entity_test',
758 'columns' => ['id' => 'id'],
762 'entity_test_revision_field_data' => [
763 'description' => 'The revision data table for entity_test entities.',
777 'default_langcode' => [
783 'primary key' => ['revision_id', 'langcode'],
786 'entity_test__id__default_langcode__langcode' => [
788 1 => 'default_langcode',
794 'table' => 'entity_test',
795 'columns' => ['id' => 'id'],
797 'entity_test__revision' => [
798 'table' => 'entity_test_revision',
799 'columns' => ['revision_id' => 'revision_id'],
805 $this->setUpStorageSchema($expected);
807 $table_mapping = new DefaultTableMapping($this->entityType, $this->storageDefinitions);
808 $non_data_fields = array_keys($this->storageDefinitions);
809 unset($non_data_fields[array_search('default_langcode', $non_data_fields)]);
810 $table_mapping->setFieldNames('entity_test', $non_data_fields);
811 $table_mapping->setFieldNames('entity_test_revision', $non_data_fields);
812 $table_mapping->setFieldNames('entity_test_field_data', array_keys($this->storageDefinitions));
813 $table_mapping->setFieldNames('entity_test_revision_field_data', array_keys($this->storageDefinitions));
815 $this->storage->expects($this->any())
816 ->method('getTableMapping')
817 ->will($this->returnValue($table_mapping));
818 $this->storage->expects($this->any())
819 ->method('getCustomTableMapping')
820 ->will($this->returnValue($table_mapping));
822 $this->storageSchema->onEntityTypeCreate($this->entityType);
826 * Tests the schema for a field dedicated table.
828 * @covers ::onFieldStorageDefinitionCreate
829 * @covers ::getDedicatedTableSchema
830 * @covers ::createDedicatedTableSchema
832 public function testDedicatedTableSchema() {
833 $entity_type_id = 'entity_test';
834 $this->entityType = new ContentEntityType([
835 'id' => 'entity_test',
836 'entity_keys' => ['id' => 'id'],
839 // Setup a field having a dedicated schema.
840 $field_name = $this->getRandomGenerator()->name();
841 $this->setUpStorageDefinition($field_name, [
874 'shape' => [['shape', 10]],
877 'depth' => ['depth'],
878 'color' => [['color', 3]],
882 $field_storage = $this->storageDefinitions[$field_name];
884 ->expects($this->any())
886 ->will($this->returnValue('shape'));
888 ->expects($this->any())
889 ->method('getTargetEntityTypeId')
890 ->will($this->returnValue($entity_type_id));
892 ->expects($this->any())
893 ->method('isMultiple')
894 ->will($this->returnValue(TRUE));
896 $this->storageDefinitions['id']
897 ->expects($this->any())
899 ->will($this->returnValue('integer'));
902 $entity_type_id . '__' . $field_name => [
903 'description' => "Data storage for $entity_type_id field $field_name.",
906 'type' => 'varchar_ascii',
910 'description' => 'The field instance bundle to which this row belongs, used when deleting a field instance',
917 'description' => 'A boolean indicating whether this data item has been deleted',
923 'description' => 'The entity id this data is attached to',
929 'description' => 'The entity revision id this data is attached to, which for an unversioned entity type is the same as the entity id',
932 'type' => 'varchar_ascii',
936 'description' => 'The language code for this data item.',
942 'description' => 'The sequence number for this data item, used for multi-value fields',
944 $field_name . '_shape' => [
949 $field_name . '_color' => [
954 $field_name . '_area' => [
959 $field_name . '_depth' => [
965 'primary key' => ['entity_id', 'deleted', 'delta', 'langcode'],
967 'bundle' => ['bundle'],
968 'revision_id' => ['revision_id'],
969 $field_name . '_depth' => [$field_name . '_depth'],
970 $field_name . '_color' => [[$field_name . '_color', 3]],
973 $field_name . '_area' => [$field_name . '_area'],
974 $field_name . '_shape' => [[$field_name . '_shape', 10]],
977 $field_name . '_color' => [
980 $field_name . '_color' => 'id',
987 $this->setUpStorageSchema($expected);
989 $table_mapping = new DefaultTableMapping($this->entityType, $this->storageDefinitions);
990 $table_mapping->setFieldNames($entity_type_id, array_keys($this->storageDefinitions));
991 $table_mapping->setExtraColumns($entity_type_id, ['default_langcode']);
993 $this->storage->expects($this->any())
994 ->method('getTableMapping')
995 ->will($this->returnValue($table_mapping));
998 $this->storageSchema->onFieldStorageDefinitionCreate($field_storage)
1003 * Tests the schema for a field dedicated table for an entity with a string identifier.
1005 * @covers ::onFieldStorageDefinitionCreate
1006 * @covers ::getDedicatedTableSchema
1007 * @covers ::createDedicatedTableSchema
1009 public function testDedicatedTableSchemaForEntityWithStringIdentifier() {
1010 $entity_type_id = 'entity_test';
1011 $this->entityType = new ContentEntityType([
1012 'id' => 'entity_test',
1013 'entity_keys' => ['id' => 'id'],
1016 // Setup a field having a dedicated schema.
1017 $field_name = $this->getRandomGenerator()->name();
1018 $this->setUpStorageDefinition($field_name, [
1021 'type' => 'varchar',
1023 'not null' => FALSE,
1026 'type' => 'varchar',
1028 'not null' => FALSE,
1039 'unique keys' => [],
1043 $field_storage = $this->storageDefinitions[$field_name];
1045 ->expects($this->any())
1047 ->will($this->returnValue('shape'));
1049 ->expects($this->any())
1050 ->method('getTargetEntityTypeId')
1051 ->will($this->returnValue($entity_type_id));
1053 ->expects($this->any())
1054 ->method('isMultiple')
1055 ->will($this->returnValue(TRUE));
1057 $this->storageDefinitions['id']
1058 ->expects($this->any())
1060 ->will($this->returnValue('string'));
1063 $entity_type_id . '__' . $field_name => [
1064 'description' => "Data storage for $entity_type_id field $field_name.",
1067 'type' => 'varchar_ascii',
1071 'description' => 'The field instance bundle to which this row belongs, used when deleting a field instance',
1078 'description' => 'A boolean indicating whether this data item has been deleted',
1081 'type' => 'varchar_ascii',
1084 'description' => 'The entity id this data is attached to',
1087 'type' => 'varchar_ascii',
1090 'description' => 'The entity revision id this data is attached to, which for an unversioned entity type is the same as the entity id',
1093 'type' => 'varchar_ascii',
1097 'description' => 'The language code for this data item.',
1103 'description' => 'The sequence number for this data item, used for multi-value fields',
1105 $field_name . '_shape' => [
1106 'type' => 'varchar',
1108 'not null' => FALSE,
1110 $field_name . '_color' => [
1111 'type' => 'varchar',
1113 'not null' => FALSE,
1116 'primary key' => ['entity_id', 'deleted', 'delta', 'langcode'],
1118 'bundle' => ['bundle'],
1119 'revision_id' => ['revision_id'],
1122 $field_name . '_color' => [
1125 $field_name . '_color' => 'id',
1132 $this->setUpStorageSchema($expected);
1134 $table_mapping = new DefaultTableMapping($this->entityType, $this->storageDefinitions);
1135 $table_mapping->setFieldNames($entity_type_id, array_keys($this->storageDefinitions));
1136 $table_mapping->setExtraColumns($entity_type_id, ['default_langcode']);
1138 $this->storage->expects($this->any())
1139 ->method('getTableMapping')
1140 ->will($this->returnValue($table_mapping));
1143 $this->storageSchema->onFieldStorageDefinitionCreate($field_storage)
1147 public function providerTestRequiresEntityDataMigration() {
1148 $updated_entity_type_definition = $this->getMock('\Drupal\Core\Entity\EntityTypeInterface');
1149 $updated_entity_type_definition->expects($this->any())
1150 ->method('getStorageClass')
1151 // A class that exists, *any* class.
1152 ->willReturn('\Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema');
1153 $original_entity_type_definition = $this->getMock('\Drupal\Core\Entity\EntityTypeInterface');
1154 $original_entity_type_definition->expects($this->any())
1155 ->method('getStorageClass')
1156 // A class that exists, *any* class.
1157 ->willReturn('\Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema');
1158 $original_entity_type_definition_other_nonexisting = $this->getMock('\Drupal\Core\Entity\EntityTypeInterface');
1159 $original_entity_type_definition_other_nonexisting->expects($this->any())
1160 ->method('getStorageClass')
1161 ->willReturn('bar');
1162 $original_entity_type_definition_other_existing = $this->getMock('\Drupal\Core\Entity\EntityTypeInterface');
1163 $original_entity_type_definition_other_existing->expects($this->any())
1164 ->method('getStorageClass')
1165 // A class that exists, *any* class.
1166 ->willReturn('\Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema');
1169 // Case 1: same storage class, ::hasData() === TRUE.
1170 [$updated_entity_type_definition, $original_entity_type_definition, TRUE, TRUE, TRUE],
1171 // Case 2: same storage class, ::hasData() === FALSE.
1172 [$updated_entity_type_definition, $original_entity_type_definition, FALSE, TRUE, FALSE],
1173 // Case 3: different storage class, original storage class does not exist.
1174 [$updated_entity_type_definition, $original_entity_type_definition_other_nonexisting, NULL, TRUE, TRUE],
1175 // Case 4: different storage class, original storage class exists,
1176 // ::hasData() === TRUE.
1177 [$updated_entity_type_definition, $original_entity_type_definition_other_existing, TRUE, TRUE, TRUE],
1178 // Case 5: different storage class, original storage class exists,
1179 // ::hasData() === FALSE.
1180 [$updated_entity_type_definition, $original_entity_type_definition_other_existing, FALSE, TRUE, FALSE],
1181 // Case 6: same storage class, ::hasData() === TRUE, no structure changes.
1182 [$updated_entity_type_definition, $original_entity_type_definition, TRUE, FALSE, FALSE],
1183 // Case 7: different storage class, original storage class exists,
1184 // ::hasData() === TRUE, no structure changes.
1185 [$updated_entity_type_definition, $original_entity_type_definition_other_existing, TRUE, FALSE, FALSE],
1190 * @covers ::requiresEntityDataMigration
1192 * @dataProvider providerTestRequiresEntityDataMigration
1194 public function testRequiresEntityDataMigration($updated_entity_type_definition, $original_entity_type_definition, $original_storage_has_data, $shared_table_structure_changed, $migration_required) {
1195 $this->entityType = new ContentEntityType([
1196 'id' => 'entity_test',
1197 'entity_keys' => ['id' => 'id'],
1200 $original_storage = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorage')
1201 ->disableOriginalConstructor()
1204 $original_storage->expects($this->exactly(is_null($original_storage_has_data) || !$shared_table_structure_changed ? 0 : 1))
1206 ->willReturn($original_storage_has_data);
1208 // Assert hasData() is never called on the new storage definition.
1209 $this->storage->expects($this->never())
1210 ->method('hasData');
1212 $connection = $this->getMockBuilder('Drupal\Core\Database\Connection')
1213 ->disableOriginalConstructor()
1216 $this->entityManager->expects($this->any())
1217 ->method('createHandlerInstance')
1218 ->willReturn($original_storage);
1220 $this->storageSchema = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema')
1221 ->setConstructorArgs([$this->entityManager, $this->entityType, $this->storage, $connection])
1222 ->setMethods(['installedStorageSchema', 'hasSharedTableStructureChange'])
1225 $this->storageSchema->expects($this->any())
1226 ->method('hasSharedTableStructureChange')
1227 ->with($updated_entity_type_definition, $original_entity_type_definition)
1228 ->willReturn($shared_table_structure_changed);
1230 $this->assertEquals($migration_required, $this->storageSchema->requiresEntityDataMigration($updated_entity_type_definition, $original_entity_type_definition));
1234 * Data provider for ::testRequiresEntityStorageSchemaChanges().
1236 public function providerTestRequiresEntityStorageSchemaChanges() {
1240 $updated_entity_type_definition = $this->getMock('\Drupal\Core\Entity\ContentEntityTypeInterface');
1241 $original_entity_type_definition = $this->getMock('\Drupal\Core\Entity\ContentEntityTypeInterface');
1243 $updated_entity_type_definition->expects($this->any())
1245 ->willReturn('entity_test');
1246 $updated_entity_type_definition->expects($this->any())
1249 $original_entity_type_definition->expects($this->any())
1251 ->willReturn('entity_test');
1252 $original_entity_type_definition->expects($this->any())
1256 // Storage class changes should not impact this at all, and should not be
1258 $updated = clone $updated_entity_type_definition;
1259 $original = clone $original_entity_type_definition;
1260 $updated->expects($this->never())
1261 ->method('getStorageClass');
1262 $original->expects($this->never())
1263 ->method('getStorageClass');
1265 // Case 1: No shared table changes should not require change.
1266 $cases[] = [$updated, $original, FALSE, FALSE, FALSE];
1268 // Case 2: A change in the entity schema should result in required changes.
1269 $cases[] = [$updated, $original, TRUE, TRUE, FALSE];
1271 // Case 3: Has shared table changes should result in required changes.
1272 $cases[] = [$updated, $original, TRUE, FALSE, TRUE];
1274 // Case 4: Changing translation should result in required changes.
1275 $updated = clone $updated_entity_type_definition;
1276 $updated->expects($this->once())
1277 ->method('isTranslatable')
1278 ->willReturn(FALSE);
1279 $original = clone $original_entity_type_definition;
1280 $original->expects($this->once())
1281 ->method('isTranslatable')
1283 $cases[] = [$updated, $original, TRUE, FALSE, FALSE];
1285 // Case 5: Changing revisionable should result in required changes.
1286 $updated = clone $updated_entity_type_definition;
1287 $updated->expects($this->once())
1288 ->method('isRevisionable')
1289 ->willReturn(FALSE);
1290 $original = clone $original_entity_type_definition;
1291 $original->expects($this->once())
1292 ->method('isRevisionable')
1294 $cases[] = [$updated, $original, TRUE, FALSE, FALSE];
1300 * @covers ::requiresEntityStorageSchemaChanges
1302 * @dataProvider providerTestRequiresEntityStorageSchemaChanges
1304 public function testRequiresEntityStorageSchemaChanges(ContentEntityTypeInterface $updated, ContentEntityTypeInterface $original, $requires_change, $change_schema, $change_shared_table) {
1306 $this->entityType = new ContentEntityType([
1307 'id' => 'entity_test',
1308 'entity_keys' => ['id' => 'id'],
1311 $this->setUpStorageSchema();
1312 $table_mapping = new DefaultTableMapping($this->entityType, $this->storageDefinitions);
1313 $table_mapping->setFieldNames('entity_test', array_keys($this->storageDefinitions));
1314 $table_mapping->setExtraColumns('entity_test', ['default_langcode']);
1315 $this->storage->expects($this->any())
1316 ->method('getTableMapping')
1317 ->will($this->returnValue($table_mapping));
1318 $this->storage->expects($this->any())
1319 ->method('getCustomTableMapping')
1320 ->will($this->returnValue($table_mapping));
1322 // Setup storage schema.
1323 if ($change_schema) {
1324 $this->storageSchema->expects($this->once())
1325 ->method('loadEntitySchemaData')
1331 'primary key' => ['id'],
1334 $this->storageSchema->expects($this->any())
1335 ->method('loadEntitySchemaData')
1336 ->willReturn($expected);
1339 if ($change_shared_table) {
1340 $this->storageSchema->expects($this->once())
1341 ->method('hasSharedTableNameChanges')
1345 $this->assertEquals($requires_change, $this->storageSchema->requiresEntityStorageSchemaChanges($updated, $original));
1349 * Sets up the storage schema object to test.
1351 * This uses the field definitions set in $this->storageDefinitions.
1353 * @param array $expected
1354 * (optional) An associative array describing the expected entity schema to
1355 * be created. Defaults to expecting nothing.
1357 protected function setUpStorageSchema(array $expected = []) {
1358 $this->entityManager->expects($this->any())
1359 ->method('getDefinition')
1360 ->with($this->entityType->id())
1361 ->will($this->returnValue($this->entityType));
1363 $this->entityManager->expects($this->any())
1364 ->method('getFieldStorageDefinitions')
1365 ->with($this->entityType->id())
1366 ->will($this->returnValue($this->storageDefinitions));
1368 $this->dbSchemaHandler = $this->getMockBuilder('Drupal\Core\Database\Schema')
1369 ->disableOriginalConstructor()
1373 $invocation_count = 0;
1374 $expected_table_names = array_keys($expected);
1375 $expected_table_schemas = array_values($expected);
1377 $this->dbSchemaHandler->expects($this->any())
1378 ->method('createTable')
1380 $this->callback(function ($table_name) use (&$invocation_count, $expected_table_names) {
1381 return $expected_table_names[$invocation_count] == $table_name;
1383 $this->callback(function ($table_schema) use (&$invocation_count, $expected_table_schemas) {
1384 return $expected_table_schemas[$invocation_count] == $table_schema;
1387 ->will($this->returnCallback(function () use (&$invocation_count) {
1388 $invocation_count++;
1392 $connection = $this->getMockBuilder('Drupal\Core\Database\Connection')
1393 ->disableOriginalConstructor()
1395 $connection->expects($this->any())
1397 ->will($this->returnValue($this->dbSchemaHandler));
1399 $key_value = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreInterface');
1400 $this->storageSchema = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema')
1401 ->setConstructorArgs([$this->entityManager, $this->entityType, $this->storage, $connection])
1402 ->setMethods(['installedStorageSchema', 'loadEntitySchemaData', 'hasSharedTableNameChanges', 'isTableEmpty'])
1404 $this->storageSchema
1405 ->expects($this->any())
1406 ->method('installedStorageSchema')
1407 ->will($this->returnValue($key_value));
1408 $this->storageSchema
1409 ->expects($this->any())
1410 ->method('isTableEmpty')
1411 ->willReturn(FALSE);
1415 * Sets up a field definition.
1417 * @param string $field_name
1419 * @param array $schema
1420 * The schema array of the field definition, as returned from
1421 * FieldStorageDefinitionInterface::getSchema().
1423 public function setUpStorageDefinition($field_name, array $schema) {
1424 $this->storageDefinitions[$field_name] = $this->getMock('Drupal\Tests\Core\Field\TestBaseFieldDefinitionInterface');
1425 $this->storageDefinitions[$field_name]->expects($this->any())
1426 ->method('isBaseField')
1427 ->will($this->returnValue(TRUE));
1428 // getName() is called once for each table.
1429 $this->storageDefinitions[$field_name]->expects($this->any())
1431 ->will($this->returnValue($field_name));
1432 // getSchema() is called once for each table.
1433 $this->storageDefinitions[$field_name]->expects($this->any())
1434 ->method('getSchema')
1435 ->will($this->returnValue($schema));
1436 $this->storageDefinitions[$field_name]->expects($this->any())
1437 ->method('getColumns')
1438 ->will($this->returnValue($schema['columns']));
1439 // Add property definitions.
1440 if (!empty($schema['columns'])) {
1441 $property_definitions = [];
1442 foreach ($schema['columns'] as $column => $info) {
1443 $property_definitions[$column] = $this->getMock('Drupal\Core\TypedData\DataDefinitionInterface');
1444 $property_definitions[$column]->expects($this->any())
1445 ->method('isRequired')
1446 ->will($this->returnValue(!empty($info['not null'])));
1448 $this->storageDefinitions[$field_name]->expects($this->any())
1449 ->method('getPropertyDefinitions')
1450 ->will($this->returnValue($property_definitions));
1455 * ::onEntityTypeUpdate
1457 public function testonEntityTypeUpdateWithNewIndex() {
1458 $this->entityType = $original_entity_type = new ContentEntityType([
1459 'id' => 'entity_test',
1460 'entity_keys' => ['id' => 'id'],
1463 // Add a field with a really long index.
1464 $this->setUpStorageDefinition('long_index_name', [
1466 'long_index_name' => [
1471 'long_index_name_really_long_long_name' => [['long_index_name', 10]],
1477 'description' => 'The base table for entity_test entities.',
1483 'long_index_name' => [
1485 'not null' => FALSE,
1489 'entity_test__b588603cb9' => [
1490 ['long_index_name', 10],
1496 $this->setUpStorageSchema($expected);
1498 $table_mapping = new DefaultTableMapping($this->entityType, $this->storageDefinitions);
1499 $table_mapping->setFieldNames('entity_test', array_keys($this->storageDefinitions));
1500 $table_mapping->setExtraColumns('entity_test', ['default_langcode']);
1502 $this->storage->expects($this->any())
1503 ->method('getTableMapping')
1504 ->will($this->returnValue($table_mapping));
1505 $this->storage->expects($this->any())
1506 ->method('getCustomTableMapping')
1507 ->will($this->returnValue($table_mapping));
1509 $this->storageSchema->expects($this->any())
1510 ->method('loadEntitySchemaData')
1514 // A changed index definition.
1515 'entity_test__b588603cb9' => ['longer_index_name'],
1516 // An index that has been removed.
1517 'entity_test__removed_field' => ['removed_field'],
1522 // The original indexes should be dropped before the new one is added.
1523 $this->dbSchemaHandler->expects($this->at(0))
1524 ->method('dropIndex')
1525 ->with('entity_test', 'entity_test__b588603cb9');
1526 $this->dbSchemaHandler->expects($this->at(1))
1527 ->method('dropIndex')
1528 ->with('entity_test', 'entity_test__removed_field');
1530 $this->dbSchemaHandler->expects($this->atLeastOnce())
1531 ->method('fieldExists')
1533 $this->dbSchemaHandler->expects($this->atLeastOnce())
1534 ->method('addIndex')
1535 ->with('entity_test', 'entity_test__b588603cb9', [['long_index_name', 10]], $this->callback(function ($actual_value) use ($expected) {
1536 $this->assertEquals($expected['entity_test']['indexes'], $actual_value['indexes']);
1537 $this->assertEquals($expected['entity_test']['fields'], $actual_value['fields']);
1538 // If the parameters don't match, the assertions above will throw an
1544 $this->storageSchema->onEntityTypeUpdate($this->entityType, $original_entity_type)