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));
375 $this->storageSchema->onEntityTypeCreate($this->entityType)
380 * Tests the schema for revisionable, non-translatable entities.
382 * @covers ::__construct
383 * @covers ::getEntitySchemaTables
384 * @covers ::initializeBaseTable
385 * @covers ::initializeRevisionTable
386 * @covers ::addTableDefaults
387 * @covers ::getEntityIndexName
388 * @covers ::processRevisionTable
389 * @covers ::processIdentifierSchema
391 public function testGetSchemaRevisionable() {
392 $this->entityType = new ContentEntityType([
393 'id' => 'entity_test',
396 'revision' => 'revision_id',
400 $this->storage->expects($this->exactly(2))
401 ->method('getRevisionTable')
402 ->will($this->returnValue('entity_test_revision'));
404 $this->setUpStorageDefinition('revision_id', [
414 'description' => 'The base table for entity_test entities.',
425 'primary key' => ['id'],
427 'entity_test__revision_id' => ['revision_id'],
431 'entity_test__revision' => [
432 'table' => 'entity_test_revision',
433 'columns' => ['revision_id' => 'revision_id'],
437 'entity_test_revision' => [
438 'description' => 'The revision table for entity_test entities.',
449 'primary key' => ['revision_id'],
452 'entity_test__id' => ['id'],
455 'entity_test__revisioned' => [
456 'table' => 'entity_test',
457 'columns' => ['id' => 'id'],
463 $this->setUpStorageSchema($expected);
465 $table_mapping = new DefaultTableMapping($this->entityType, $this->storageDefinitions);
466 $table_mapping->setFieldNames('entity_test', array_keys($this->storageDefinitions));
467 $table_mapping->setFieldNames('entity_test_revision', array_keys($this->storageDefinitions));
469 $this->storage->expects($this->any())
470 ->method('getTableMapping')
471 ->will($this->returnValue($table_mapping));
473 $this->storageSchema->onEntityTypeCreate($this->entityType);
477 * Tests the schema for non-revisionable, translatable entities.
479 * @covers ::__construct
480 * @covers ::getEntitySchemaTables
481 * @covers ::initializeDataTable
482 * @covers ::addTableDefaults
483 * @covers ::getEntityIndexName
484 * @covers ::processDataTable
486 public function testGetSchemaTranslatable() {
487 $this->entityType = new ContentEntityType([
488 'id' => 'entity_test',
491 'langcode' => 'langcode',
495 $this->storage->expects($this->any())
496 ->method('getDataTable')
497 ->will($this->returnValue('entity_test_field_data'));
499 $this->setUpStorageDefinition('langcode', [
507 $this->setUpStorageDefinition('default_langcode', [
518 'description' => 'The base table for entity_test entities.',
529 'primary key' => ['id'],
532 'foreign keys' => [],
534 'entity_test_field_data' => [
535 'description' => 'The data table for entity_test entities.',
545 'default_langcode' => [
551 'primary key' => ['id', 'langcode'],
554 'entity_test__id__default_langcode__langcode' => [
556 1 => 'default_langcode',
562 'table' => 'entity_test',
563 'columns' => ['id' => 'id'],
569 $this->setUpStorageSchema($expected);
571 $table_mapping = new DefaultTableMapping($this->entityType, $this->storageDefinitions);
572 $non_data_fields = array_keys($this->storageDefinitions);
573 unset($non_data_fields[array_search('default_langcode', $non_data_fields)]);
574 $table_mapping->setFieldNames('entity_test', $non_data_fields);
575 $table_mapping->setFieldNames('entity_test_field_data', array_keys($this->storageDefinitions));
577 $this->storage->expects($this->any())
578 ->method('getTableMapping')
579 ->will($this->returnValue($table_mapping));
582 $this->storageSchema->onEntityTypeCreate($this->entityType)
587 * Tests the schema for revisionable, translatable entities.
589 * @covers ::__construct
590 * @covers ::getEntitySchemaTables
591 * @covers ::initializeDataTable
592 * @covers ::addTableDefaults
593 * @covers ::getEntityIndexName
594 * @covers ::initializeRevisionDataTable
595 * @covers ::processRevisionDataTable
597 public function testGetSchemaRevisionableTranslatable() {
598 $this->entityType = new ContentEntityType([
599 'id' => 'entity_test',
602 'revision' => 'revision_id',
603 'langcode' => 'langcode',
607 $this->storage->expects($this->exactly(3))
608 ->method('getRevisionTable')
609 ->will($this->returnValue('entity_test_revision'));
610 $this->storage->expects($this->once())
611 ->method('getDataTable')
612 ->will($this->returnValue('entity_test_field_data'));
613 $this->storage->expects($this->once())
614 ->method('getRevisionDataTable')
615 ->will($this->returnValue('entity_test_revision_field_data'));
617 $this->setUpStorageDefinition('revision_id', [
624 $this->setUpStorageDefinition('langcode', [
631 $this->setUpStorageDefinition('default_langcode', [
642 'description' => 'The base table for entity_test entities.',
657 'primary key' => ['id'],
659 'entity_test__revision_id' => ['revision_id'],
663 'entity_test__revision' => [
664 'table' => 'entity_test_revision',
665 'columns' => ['revision_id' => 'revision_id'],
669 'entity_test_revision' => [
670 'description' => 'The revision table for entity_test entities.',
685 'primary key' => ['revision_id'],
688 'entity_test__id' => ['id'],
691 'entity_test__revisioned' => [
692 'table' => 'entity_test',
693 'columns' => ['id' => 'id'],
697 'entity_test_field_data' => [
698 'description' => 'The data table for entity_test entities.',
712 'default_langcode' => [
718 'primary key' => ['id', 'langcode'],
721 'entity_test__revision_id' => ['revision_id'],
722 'entity_test__id__default_langcode__langcode' => [
724 1 => 'default_langcode',
730 'table' => 'entity_test',
731 'columns' => ['id' => 'id'],
735 'entity_test_revision_field_data' => [
736 'description' => 'The revision data table for entity_test entities.',
750 'default_langcode' => [
756 'primary key' => ['revision_id', 'langcode'],
759 'entity_test__id__default_langcode__langcode' => [
761 1 => 'default_langcode',
767 'table' => 'entity_test',
768 'columns' => ['id' => 'id'],
770 'entity_test__revision' => [
771 'table' => 'entity_test_revision',
772 'columns' => ['revision_id' => 'revision_id'],
778 $this->setUpStorageSchema($expected);
780 $table_mapping = new DefaultTableMapping($this->entityType, $this->storageDefinitions);
781 $non_data_fields = array_keys($this->storageDefinitions);
782 unset($non_data_fields[array_search('default_langcode', $non_data_fields)]);
783 $table_mapping->setFieldNames('entity_test', $non_data_fields);
784 $table_mapping->setFieldNames('entity_test_revision', $non_data_fields);
785 $table_mapping->setFieldNames('entity_test_field_data', array_keys($this->storageDefinitions));
786 $table_mapping->setFieldNames('entity_test_revision_field_data', array_keys($this->storageDefinitions));
788 $this->storage->expects($this->any())
789 ->method('getTableMapping')
790 ->will($this->returnValue($table_mapping));
792 $this->storageSchema->onEntityTypeCreate($this->entityType);
796 * Tests the schema for a field dedicated table.
798 * @covers ::onFieldStorageDefinitionCreate
799 * @covers ::getDedicatedTableSchema
800 * @covers ::createDedicatedTableSchema
802 public function testDedicatedTableSchema() {
803 $entity_type_id = 'entity_test';
804 $this->entityType = new ContentEntityType([
805 'id' => 'entity_test',
806 'entity_keys' => ['id' => 'id'],
809 // Setup a field having a dedicated schema.
810 $field_name = $this->getRandomGenerator()->name();
811 $this->setUpStorageDefinition($field_name, [
844 'shape' => [['shape', 10]],
847 'depth' => ['depth'],
848 'color' => [['color', 3]],
852 $field_storage = $this->storageDefinitions[$field_name];
854 ->expects($this->any())
856 ->will($this->returnValue('shape'));
858 ->expects($this->any())
859 ->method('getTargetEntityTypeId')
860 ->will($this->returnValue($entity_type_id));
862 ->expects($this->any())
863 ->method('isMultiple')
864 ->will($this->returnValue(TRUE));
866 $this->storageDefinitions['id']
867 ->expects($this->any())
869 ->will($this->returnValue('integer'));
872 $entity_type_id . '__' . $field_name => [
873 'description' => "Data storage for $entity_type_id field $field_name.",
876 'type' => 'varchar_ascii',
880 'description' => 'The field instance bundle to which this row belongs, used when deleting a field instance',
887 'description' => 'A boolean indicating whether this data item has been deleted',
893 'description' => 'The entity id this data is attached to',
899 'description' => 'The entity revision id this data is attached to, which for an unversioned entity type is the same as the entity id',
902 'type' => 'varchar_ascii',
906 'description' => 'The language code for this data item.',
912 'description' => 'The sequence number for this data item, used for multi-value fields',
914 $field_name . '_shape' => [
919 $field_name . '_color' => [
924 $field_name . '_area' => [
929 $field_name . '_depth' => [
935 'primary key' => ['entity_id', 'deleted', 'delta', 'langcode'],
937 'bundle' => ['bundle'],
938 'revision_id' => ['revision_id'],
939 $field_name . '_depth' => [$field_name . '_depth'],
940 $field_name . '_color' => [[$field_name . '_color', 3]],
943 $field_name . '_area' => [$field_name . '_area'],
944 $field_name . '_shape' => [[$field_name . '_shape', 10]],
947 $field_name . '_color' => [
950 $field_name . '_color' => 'id',
957 $this->setUpStorageSchema($expected);
959 $table_mapping = new DefaultTableMapping($this->entityType, $this->storageDefinitions);
960 $table_mapping->setFieldNames($entity_type_id, array_keys($this->storageDefinitions));
961 $table_mapping->setExtraColumns($entity_type_id, ['default_langcode']);
963 $this->storage->expects($this->any())
964 ->method('getTableMapping')
965 ->will($this->returnValue($table_mapping));
968 $this->storageSchema->onFieldStorageDefinitionCreate($field_storage)
973 * Tests the schema for a field dedicated table for an entity with a string identifier.
975 * @covers ::onFieldStorageDefinitionCreate
976 * @covers ::getDedicatedTableSchema
977 * @covers ::createDedicatedTableSchema
979 public function testDedicatedTableSchemaForEntityWithStringIdentifier() {
980 $entity_type_id = 'entity_test';
981 $this->entityType = new ContentEntityType([
982 'id' => 'entity_test',
983 'entity_keys' => ['id' => 'id'],
986 // Setup a field having a dedicated schema.
987 $field_name = $this->getRandomGenerator()->name();
988 $this->setUpStorageDefinition($field_name, [
1009 'unique keys' => [],
1013 $field_storage = $this->storageDefinitions[$field_name];
1015 ->expects($this->any())
1017 ->will($this->returnValue('shape'));
1019 ->expects($this->any())
1020 ->method('getTargetEntityTypeId')
1021 ->will($this->returnValue($entity_type_id));
1023 ->expects($this->any())
1024 ->method('isMultiple')
1025 ->will($this->returnValue(TRUE));
1027 $this->storageDefinitions['id']
1028 ->expects($this->any())
1030 ->will($this->returnValue('string'));
1033 $entity_type_id . '__' . $field_name => [
1034 'description' => "Data storage for $entity_type_id field $field_name.",
1037 'type' => 'varchar_ascii',
1041 'description' => 'The field instance bundle to which this row belongs, used when deleting a field instance',
1048 'description' => 'A boolean indicating whether this data item has been deleted',
1051 'type' => 'varchar_ascii',
1054 'description' => 'The entity id this data is attached to',
1057 'type' => 'varchar_ascii',
1060 'description' => 'The entity revision id this data is attached to, which for an unversioned entity type is the same as the entity id',
1063 'type' => 'varchar_ascii',
1067 'description' => 'The language code for this data item.',
1073 'description' => 'The sequence number for this data item, used for multi-value fields',
1075 $field_name . '_shape' => [
1076 'type' => 'varchar',
1078 'not null' => FALSE,
1080 $field_name . '_color' => [
1081 'type' => 'varchar',
1083 'not null' => FALSE,
1086 'primary key' => ['entity_id', 'deleted', 'delta', 'langcode'],
1088 'bundle' => ['bundle'],
1089 'revision_id' => ['revision_id'],
1092 $field_name . '_color' => [
1095 $field_name . '_color' => 'id',
1102 $this->setUpStorageSchema($expected);
1104 $table_mapping = new DefaultTableMapping($this->entityType, $this->storageDefinitions);
1105 $table_mapping->setFieldNames($entity_type_id, array_keys($this->storageDefinitions));
1106 $table_mapping->setExtraColumns($entity_type_id, ['default_langcode']);
1108 $this->storage->expects($this->any())
1109 ->method('getTableMapping')
1110 ->will($this->returnValue($table_mapping));
1113 $this->storageSchema->onFieldStorageDefinitionCreate($field_storage)
1117 public function providerTestRequiresEntityDataMigration() {
1118 $updated_entity_type_definition = $this->getMock('\Drupal\Core\Entity\EntityTypeInterface');
1119 $updated_entity_type_definition->expects($this->any())
1120 ->method('getStorageClass')
1121 // A class that exists, *any* class.
1122 ->willReturn('\Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema');
1123 $original_entity_type_definition = $this->getMock('\Drupal\Core\Entity\EntityTypeInterface');
1124 $original_entity_type_definition->expects($this->any())
1125 ->method('getStorageClass')
1126 // A class that exists, *any* class.
1127 ->willReturn('\Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema');
1128 $original_entity_type_definition_other_nonexisting = $this->getMock('\Drupal\Core\Entity\EntityTypeInterface');
1129 $original_entity_type_definition_other_nonexisting->expects($this->any())
1130 ->method('getStorageClass')
1131 ->willReturn('bar');
1132 $original_entity_type_definition_other_existing = $this->getMock('\Drupal\Core\Entity\EntityTypeInterface');
1133 $original_entity_type_definition_other_existing->expects($this->any())
1134 ->method('getStorageClass')
1135 // A class that exists, *any* class.
1136 ->willReturn('\Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema');
1139 // Case 1: same storage class, ::hasData() === TRUE.
1140 [$updated_entity_type_definition, $original_entity_type_definition, TRUE, TRUE, TRUE],
1141 // Case 2: same storage class, ::hasData() === FALSE.
1142 [$updated_entity_type_definition, $original_entity_type_definition, FALSE, TRUE, FALSE],
1143 // Case 3: different storage class, original storage class does not exist.
1144 [$updated_entity_type_definition, $original_entity_type_definition_other_nonexisting, NULL, TRUE, TRUE],
1145 // Case 4: different storage class, original storage class exists,
1146 // ::hasData() === TRUE.
1147 [$updated_entity_type_definition, $original_entity_type_definition_other_existing, TRUE, TRUE, TRUE],
1148 // Case 5: different storage class, original storage class exists,
1149 // ::hasData() === FALSE.
1150 [$updated_entity_type_definition, $original_entity_type_definition_other_existing, FALSE, TRUE, FALSE],
1151 // Case 6: same storage class, ::hasData() === TRUE, no structure changes.
1152 [$updated_entity_type_definition, $original_entity_type_definition, TRUE, FALSE, FALSE],
1153 // Case 7: different storage class, original storage class exists,
1154 // ::hasData() === TRUE, no structure changes.
1155 [$updated_entity_type_definition, $original_entity_type_definition_other_existing, TRUE, FALSE, FALSE],
1160 * @covers ::requiresEntityDataMigration
1162 * @dataProvider providerTestRequiresEntityDataMigration
1164 public function testRequiresEntityDataMigration($updated_entity_type_definition, $original_entity_type_definition, $original_storage_has_data, $shared_table_structure_changed, $migration_required) {
1165 $this->entityType = new ContentEntityType([
1166 'id' => 'entity_test',
1167 'entity_keys' => ['id' => 'id'],
1170 $original_storage = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorage')
1171 ->disableOriginalConstructor()
1174 $original_storage->expects($this->exactly(is_null($original_storage_has_data) || !$shared_table_structure_changed ? 0 : 1))
1176 ->willReturn($original_storage_has_data);
1178 // Assert hasData() is never called on the new storage definition.
1179 $this->storage->expects($this->never())
1180 ->method('hasData');
1182 $connection = $this->getMockBuilder('Drupal\Core\Database\Connection')
1183 ->disableOriginalConstructor()
1186 $this->entityManager->expects($this->any())
1187 ->method('createHandlerInstance')
1188 ->willReturn($original_storage);
1190 $this->storageSchema = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema')
1191 ->setConstructorArgs([$this->entityManager, $this->entityType, $this->storage, $connection])
1192 ->setMethods(['installedStorageSchema', 'hasSharedTableStructureChange'])
1195 $this->storageSchema->expects($this->any())
1196 ->method('hasSharedTableStructureChange')
1197 ->with($updated_entity_type_definition, $original_entity_type_definition)
1198 ->willReturn($shared_table_structure_changed);
1200 $this->assertEquals($migration_required, $this->storageSchema->requiresEntityDataMigration($updated_entity_type_definition, $original_entity_type_definition));
1204 * Data provider for ::testRequiresEntityStorageSchemaChanges().
1206 public function providerTestRequiresEntityStorageSchemaChanges() {
1210 $updated_entity_type_definition = $this->getMock('\Drupal\Core\Entity\ContentEntityTypeInterface');
1211 $original_entity_type_definition = $this->getMock('\Drupal\Core\Entity\ContentEntityTypeInterface');
1213 $updated_entity_type_definition->expects($this->any())
1215 ->willReturn('entity_test');
1216 $updated_entity_type_definition->expects($this->any())
1219 $original_entity_type_definition->expects($this->any())
1221 ->willReturn('entity_test');
1222 $original_entity_type_definition->expects($this->any())
1226 // Storage class changes should not impact this at all, and should not be
1228 $updated = clone $updated_entity_type_definition;
1229 $original = clone $original_entity_type_definition;
1230 $updated->expects($this->never())
1231 ->method('getStorageClass');
1232 $original->expects($this->never())
1233 ->method('getStorageClass');
1235 // Case 1: No shared table changes should not require change.
1236 $cases[] = [$updated, $original, FALSE, FALSE, FALSE];
1238 // Case 2: A change in the entity schema should result in required changes.
1239 $cases[] = [$updated, $original, TRUE, TRUE, FALSE];
1241 // Case 3: Has shared table changes should result in required changes.
1242 $cases[] = [$updated, $original, TRUE, FALSE, TRUE];
1244 // Case 4: Changing translation should result in required changes.
1245 $updated = clone $updated_entity_type_definition;
1246 $updated->expects($this->once())
1247 ->method('isTranslatable')
1248 ->willReturn(FALSE);
1249 $original = clone $original_entity_type_definition;
1250 $original->expects($this->once())
1251 ->method('isTranslatable')
1253 $cases[] = [$updated, $original, TRUE, FALSE, FALSE];
1255 // Case 5: Changing revisionable should result in required changes.
1256 $updated = clone $updated_entity_type_definition;
1257 $updated->expects($this->once())
1258 ->method('isRevisionable')
1259 ->willReturn(FALSE);
1260 $original = clone $original_entity_type_definition;
1261 $original->expects($this->once())
1262 ->method('isRevisionable')
1264 $cases[] = [$updated, $original, TRUE, FALSE, FALSE];
1270 * @covers ::requiresEntityStorageSchemaChanges
1272 * @dataProvider providerTestRequiresEntityStorageSchemaChanges
1274 public function testRequiresEntityStorageSchemaChanges(ContentEntityTypeInterface $updated, ContentEntityTypeInterface $original, $requires_change, $change_schema, $change_shared_table) {
1276 $this->entityType = new ContentEntityType([
1277 'id' => 'entity_test',
1278 'entity_keys' => ['id' => 'id'],
1281 $this->setUpStorageSchema();
1282 $table_mapping = new DefaultTableMapping($this->entityType, $this->storageDefinitions);
1283 $table_mapping->setFieldNames('entity_test', array_keys($this->storageDefinitions));
1284 $table_mapping->setExtraColumns('entity_test', ['default_langcode']);
1285 $this->storage->expects($this->any())
1286 ->method('getTableMapping')
1287 ->will($this->returnValue($table_mapping));
1289 // Setup storage schema.
1290 if ($change_schema) {
1291 $this->storageSchema->expects($this->once())
1292 ->method('loadEntitySchemaData')
1298 'primary key' => ['id'],
1301 $this->storageSchema->expects($this->any())
1302 ->method('loadEntitySchemaData')
1303 ->willReturn($expected);
1306 if ($change_shared_table) {
1307 $this->storageSchema->expects($this->once())
1308 ->method('hasSharedTableNameChanges')
1312 $this->assertEquals($requires_change, $this->storageSchema->requiresEntityStorageSchemaChanges($updated, $original));
1316 * Sets up the storage schema object to test.
1318 * This uses the field definitions set in $this->storageDefinitions.
1320 * @param array $expected
1321 * (optional) An associative array describing the expected entity schema to
1322 * be created. Defaults to expecting nothing.
1324 protected function setUpStorageSchema(array $expected = []) {
1325 $this->entityManager->expects($this->any())
1326 ->method('getDefinition')
1327 ->with($this->entityType->id())
1328 ->will($this->returnValue($this->entityType));
1330 $this->entityManager->expects($this->any())
1331 ->method('getFieldStorageDefinitions')
1332 ->with($this->entityType->id())
1333 ->will($this->returnValue($this->storageDefinitions));
1335 $this->dbSchemaHandler = $this->getMockBuilder('Drupal\Core\Database\Schema')
1336 ->disableOriginalConstructor()
1340 $invocation_count = 0;
1341 $expected_table_names = array_keys($expected);
1342 $expected_table_schemas = array_values($expected);
1344 $this->dbSchemaHandler->expects($this->any())
1345 ->method('createTable')
1347 $this->callback(function ($table_name) use (&$invocation_count, $expected_table_names) {
1348 return $expected_table_names[$invocation_count] == $table_name;
1350 $this->callback(function ($table_schema) use (&$invocation_count, $expected_table_schemas) {
1351 return $expected_table_schemas[$invocation_count] == $table_schema;
1354 ->will($this->returnCallback(function () use (&$invocation_count) {
1355 $invocation_count++;
1359 $connection = $this->getMockBuilder('Drupal\Core\Database\Connection')
1360 ->disableOriginalConstructor()
1362 $connection->expects($this->any())
1364 ->will($this->returnValue($this->dbSchemaHandler));
1366 $key_value = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreInterface');
1367 $this->storageSchema = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema')
1368 ->setConstructorArgs([$this->entityManager, $this->entityType, $this->storage, $connection])
1369 ->setMethods(['installedStorageSchema', 'loadEntitySchemaData', 'hasSharedTableNameChanges', 'isTableEmpty'])
1371 $this->storageSchema
1372 ->expects($this->any())
1373 ->method('installedStorageSchema')
1374 ->will($this->returnValue($key_value));
1375 $this->storageSchema
1376 ->expects($this->any())
1377 ->method('isTableEmpty')
1378 ->willReturn(FALSE);
1382 * Sets up a field definition.
1384 * @param string $field_name
1386 * @param array $schema
1387 * The schema array of the field definition, as returned from
1388 * FieldStorageDefinitionInterface::getSchema().
1390 public function setUpStorageDefinition($field_name, array $schema) {
1391 $this->storageDefinitions[$field_name] = $this->getMock('Drupal\Tests\Core\Field\TestBaseFieldDefinitionInterface');
1392 $this->storageDefinitions[$field_name]->expects($this->any())
1393 ->method('isBaseField')
1394 ->will($this->returnValue(TRUE));
1395 // getName() is called once for each table.
1396 $this->storageDefinitions[$field_name]->expects($this->any())
1398 ->will($this->returnValue($field_name));
1399 // getSchema() is called once for each table.
1400 $this->storageDefinitions[$field_name]->expects($this->any())
1401 ->method('getSchema')
1402 ->will($this->returnValue($schema));
1403 $this->storageDefinitions[$field_name]->expects($this->any())
1404 ->method('getColumns')
1405 ->will($this->returnValue($schema['columns']));
1406 // Add property definitions.
1407 if (!empty($schema['columns'])) {
1408 $property_definitions = [];
1409 foreach ($schema['columns'] as $column => $info) {
1410 $property_definitions[$column] = $this->getMock('Drupal\Core\TypedData\DataDefinitionInterface');
1411 $property_definitions[$column]->expects($this->any())
1412 ->method('isRequired')
1413 ->will($this->returnValue(!empty($info['not null'])));
1415 $this->storageDefinitions[$field_name]->expects($this->any())
1416 ->method('getPropertyDefinitions')
1417 ->will($this->returnValue($property_definitions));
1422 * ::onEntityTypeUpdate
1424 public function testonEntityTypeUpdateWithNewIndex() {
1425 $this->entityType = $original_entity_type = new ContentEntityType([
1426 'id' => 'entity_test',
1427 'entity_keys' => ['id' => 'id'],
1430 // Add a field with a really long index.
1431 $this->setUpStorageDefinition('long_index_name', [
1433 'long_index_name' => [
1438 'long_index_name_really_long_long_name' => [['long_index_name', 10]],
1444 'description' => 'The base table for entity_test entities.',
1450 'long_index_name' => [
1452 'not null' => FALSE,
1456 'entity_test__b588603cb9' => [
1457 ['long_index_name', 10],
1463 $this->setUpStorageSchema($expected);
1465 $table_mapping = new DefaultTableMapping($this->entityType, $this->storageDefinitions);
1466 $table_mapping->setFieldNames('entity_test', array_keys($this->storageDefinitions));
1467 $table_mapping->setExtraColumns('entity_test', ['default_langcode']);
1469 $this->storage->expects($this->any())
1470 ->method('getTableMapping')
1471 ->will($this->returnValue($table_mapping));
1473 $this->storageSchema->expects($this->any())
1474 ->method('loadEntitySchemaData')
1478 // A changed index definition.
1479 'entity_test__b588603cb9' => ['longer_index_name'],
1480 // An index that has been removed.
1481 'entity_test__removed_field' => ['removed_field'],
1486 // The original indexes should be dropped before the new one is added.
1487 $this->dbSchemaHandler->expects($this->at(0))
1488 ->method('dropIndex')
1489 ->with('entity_test', 'entity_test__b588603cb9');
1490 $this->dbSchemaHandler->expects($this->at(1))
1491 ->method('dropIndex')
1492 ->with('entity_test', 'entity_test__removed_field');
1494 $this->dbSchemaHandler->expects($this->atLeastOnce())
1495 ->method('fieldExists')
1497 $this->dbSchemaHandler->expects($this->atLeastOnce())
1498 ->method('addIndex')
1499 ->with('entity_test', 'entity_test__b588603cb9', [['long_index_name', 10]], $this->callback(function ($actual_value) use ($expected) {
1500 $this->assertEquals($expected['entity_test']['indexes'], $actual_value['indexes']);
1501 $this->assertEquals($expected['entity_test']['fields'], $actual_value['fields']);
1502 // If the parameters don't match, the assertions above will throw an
1508 $this->storageSchema->onEntityTypeUpdate($this->entityType, $original_entity_type)