3 namespace Drupal\KernelTests\Core\Entity;
5 use Drupal\Component\Plugin\Exception\PluginNotFoundException;
6 use Drupal\Core\Database\Database;
7 use Drupal\Core\Database\DatabaseExceptionWrapper;
8 use Drupal\Core\Database\IntegrityConstraintViolationException;
9 use Drupal\Core\Entity\ContentEntityType;
10 use Drupal\Core\Entity\EntityStorageException;
11 use Drupal\Core\Entity\EntityTypeEvents;
12 use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
13 use Drupal\Core\Field\BaseFieldDefinition;
14 use Drupal\Core\Field\FieldStorageDefinitionEvents;
15 use Drupal\Core\Language\LanguageInterface;
16 use Drupal\entity_test_update\Entity\EntityTestUpdate;
17 use Drupal\system\Tests\Entity\EntityDefinitionTestTrait;
20 * Tests EntityDefinitionUpdateManager functionality.
24 class EntityDefinitionUpdateTest extends EntityKernelTestBase {
26 use EntityDefinitionTestTrait;
29 * The entity definition update manager.
31 * @var \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface
33 protected $entityDefinitionUpdateManager;
36 * The database connection.
38 * @var \Drupal\Core\Database\Connection
47 public static $modules = ['entity_test_update'];
52 protected function setUp() {
54 $this->entityDefinitionUpdateManager = $this->container->get('entity.definition_update_manager');
55 $this->database = $this->container->get('database');
57 // Install every entity type's schema that wasn't installed in the parent
59 foreach (array_diff_key($this->entityManager->getDefinitions(), array_flip(['user', 'entity_test'])) as $entity_type_id => $entity_type) {
60 $this->installEntitySchema($entity_type_id);
65 * Tests that new entity type definitions are correctly handled.
67 public function testNewEntityType() {
68 $entity_type_id = 'entity_test_new';
69 $schema = $this->database->schema();
71 // Check that the "entity_test_new" is not defined.
72 $entity_types = $this->entityManager->getDefinitions();
73 $this->assertFalse(isset($entity_types[$entity_type_id]), 'The "entity_test_new" entity type does not exist.');
74 $this->assertFalse($schema->tableExists($entity_type_id), 'Schema for the "entity_test_new" entity type does not exist.');
76 // Check that the "entity_test_new" is now defined and the related schema
78 $this->enableNewEntityType();
79 $entity_types = $this->entityManager->getDefinitions();
80 $this->assertTrue(isset($entity_types[$entity_type_id]), 'The "entity_test_new" entity type exists.');
81 $this->assertTrue($schema->tableExists($entity_type_id), 'Schema for the "entity_test_new" entity type has been created.');
85 * Tests when no definition update is needed.
87 public function testNoUpdates() {
88 // Ensure that the definition update manager reports no updates.
89 $this->assertFalse($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that no updates are needed.');
90 $this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), [], 'EntityDefinitionUpdateManager reports an empty change summary.');
92 // Ensure that applyUpdates() runs without error (it's not expected to do
93 // anything when there aren't updates).
94 $this->entityDefinitionUpdateManager->applyUpdates();
98 * Tests updating entity schema when there are no existing entities.
100 public function testEntityTypeUpdateWithoutData() {
101 // The 'entity_test_update' entity type starts out non-revisionable, so
102 // ensure the revision table hasn't been created during setUp().
103 $this->assertFalse($this->database->schema()->tableExists('entity_test_update_revision'), 'Revision table not created for entity_test_update.');
105 // Update it to be revisionable and ensure the definition update manager
106 // reports that an update is needed.
107 $this->updateEntityTypeToRevisionable();
108 $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
110 'entity_test_update' => [
111 t('The %entity_type entity type needs to be updated.', ['%entity_type' => $this->entityManager->getDefinition('entity_test_update')->getLabel()]),
112 // The revision key is now defined, so the revision field needs to be
114 t('The %field_name field needs to be installed.', ['%field_name' => 'Revision ID']),
117 $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected); //, 'EntityDefinitionUpdateManager reports the expected change summary.');
119 // Run the update and ensure the revision table is created.
120 $this->entityDefinitionUpdateManager->applyUpdates();
121 $this->assertTrue($this->database->schema()->tableExists('entity_test_update_revision'), 'Revision table created for entity_test_update.');
125 * Tests updating entity schema when there are existing entities.
127 public function testEntityTypeUpdateWithData() {
129 $this->entityManager->getStorage('entity_test_update')->create()->save();
131 // Update the entity type to be revisionable and try to apply the update.
132 // It's expected to throw an exception.
133 $this->updateEntityTypeToRevisionable();
135 $this->entityDefinitionUpdateManager->applyUpdates();
136 $this->fail('EntityStorageException thrown when trying to apply an update that requires data migration.');
138 catch (EntityStorageException $e) {
139 $this->pass('EntityStorageException thrown when trying to apply an update that requires data migration.');
144 * Tests creating, updating, and deleting a base field if no entities exist.
146 public function testBaseFieldCreateUpdateDeleteWithoutData() {
147 // Add a base field, ensure the update manager reports it, and the update
148 // creates its schema.
149 $this->addBaseField();
150 $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
152 'entity_test_update' => [
153 t('The %field_name field needs to be installed.', ['%field_name' => t('A new base field')]),
156 $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
157 $this->entityDefinitionUpdateManager->applyUpdates();
158 $this->assertTrue($this->database->schema()->fieldExists('entity_test_update', 'new_base_field'), 'Column created in shared table for new_base_field.');
160 // Add an index on the base field, ensure the update manager reports it,
161 // and the update creates it.
162 $this->addBaseFieldIndex();
163 $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
165 'entity_test_update' => [
166 t('The %field_name field needs to be updated.', ['%field_name' => t('A new base field')]),
169 $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
170 $this->entityDefinitionUpdateManager->applyUpdates();
171 $this->assertTrue($this->database->schema()->indexExists('entity_test_update', 'entity_test_update_field__new_base_field'), 'Index created.');
173 // Remove the above index, ensure the update manager reports it, and the
174 // update deletes it.
175 $this->removeBaseFieldIndex();
176 $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
178 'entity_test_update' => [
179 t('The %field_name field needs to be updated.', ['%field_name' => t('A new base field')]),
182 $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
183 $this->entityDefinitionUpdateManager->applyUpdates();
184 $this->assertFalse($this->database->schema()->indexExists('entity_test_update', 'entity_test_update_field__new_base_field'), 'Index deleted.');
186 // Update the type of the base field from 'string' to 'text', ensure the
187 // update manager reports it, and the update adjusts the schema
189 $this->modifyBaseField();
190 $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
192 'entity_test_update' => [
193 t('The %field_name field needs to be updated.', ['%field_name' => t('A new base field')]),
196 $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
197 $this->entityDefinitionUpdateManager->applyUpdates();
198 $this->assertFalse($this->database->schema()->fieldExists('entity_test_update', 'new_base_field'), 'Original column deleted in shared table for new_base_field.');
199 $this->assertTrue($this->database->schema()->fieldExists('entity_test_update', 'new_base_field__value'), 'Value column created in shared table for new_base_field.');
200 $this->assertTrue($this->database->schema()->fieldExists('entity_test_update', 'new_base_field__format'), 'Format column created in shared table for new_base_field.');
202 // Remove the base field, ensure the update manager reports it, and the
203 // update deletes the schema.
204 $this->removeBaseField();
205 $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
207 'entity_test_update' => [
208 t('The %field_name field needs to be uninstalled.', ['%field_name' => t('A new base field')]),
211 $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
212 $this->entityDefinitionUpdateManager->applyUpdates();
213 $this->assertFalse($this->database->schema()->fieldExists('entity_test_update', 'new_base_field_value'), 'Value column deleted from shared table for new_base_field.');
214 $this->assertFalse($this->database->schema()->fieldExists('entity_test_update', 'new_base_field_format'), 'Format column deleted from shared table for new_base_field.');
218 * Tests creating, updating, and deleting a bundle field if no entities exist.
220 public function testBundleFieldCreateUpdateDeleteWithoutData() {
221 // Add a bundle field, ensure the update manager reports it, and the update
222 // creates its schema.
223 $this->addBundleField();
224 $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
226 'entity_test_update' => [
227 t('The %field_name field needs to be installed.', ['%field_name' => t('A new bundle field')]),
230 $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
231 $this->entityDefinitionUpdateManager->applyUpdates();
232 $this->assertTrue($this->database->schema()->tableExists('entity_test_update__new_bundle_field'), 'Dedicated table created for new_bundle_field.');
234 // Update the type of the base field from 'string' to 'text', ensure the
235 // update manager reports it, and the update adjusts the schema
237 $this->modifyBundleField();
238 $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
240 'entity_test_update' => [
241 t('The %field_name field needs to be updated.', ['%field_name' => t('A new bundle field')]),
244 $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
245 $this->entityDefinitionUpdateManager->applyUpdates();
246 $this->assertTrue($this->database->schema()->fieldExists('entity_test_update__new_bundle_field', 'new_bundle_field_format'), 'Format column created in dedicated table for new_base_field.');
248 // Remove the bundle field, ensure the update manager reports it, and the
249 // update deletes the schema.
250 $this->removeBundleField();
251 $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
253 'entity_test_update' => [
254 t('The %field_name field needs to be uninstalled.', ['%field_name' => t('A new bundle field')]),
257 $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
258 $this->entityDefinitionUpdateManager->applyUpdates();
259 $this->assertFalse($this->database->schema()->tableExists('entity_test_update__new_bundle_field'), 'Dedicated table deleted for new_bundle_field.');
263 * Tests creating and deleting a base field if entities exist.
265 * This tests deletion when there are existing entities, but not existing data
266 * for the field being deleted.
268 * @see testBaseFieldDeleteWithExistingData()
270 public function testBaseFieldCreateDeleteWithExistingEntities() {
272 $name = $this->randomString();
273 $storage = $this->entityManager->getStorage('entity_test_update');
274 $entity = $storage->create(['name' => $name]);
277 // Add a base field and run the update. Ensure the base field's column is
278 // created and the prior saved entity data is still there.
279 $this->addBaseField();
280 $this->entityDefinitionUpdateManager->applyUpdates();
281 $schema_handler = $this->database->schema();
282 $this->assertTrue($schema_handler->fieldExists('entity_test_update', 'new_base_field'), 'Column created in shared table for new_base_field.');
283 $entity = $this->entityManager->getStorage('entity_test_update')->load($entity->id());
284 $this->assertIdentical($entity->name->value, $name, 'Entity data preserved during field creation.');
286 // Remove the base field and run the update. Ensure the base field's column
287 // is deleted and the prior saved entity data is still there.
288 $this->removeBaseField();
289 $this->entityDefinitionUpdateManager->applyUpdates();
290 $this->assertFalse($schema_handler->fieldExists('entity_test_update', 'new_base_field'), 'Column deleted from shared table for new_base_field.');
291 $entity = $this->entityManager->getStorage('entity_test_update')->load($entity->id());
292 $this->assertIdentical($entity->name->value, $name, 'Entity data preserved during field deletion.');
294 // Add a base field with a required property and run the update. Ensure
295 // 'not null' is not applied and thus no exception is thrown.
296 $this->addBaseField('shape_required');
297 $this->entityDefinitionUpdateManager->applyUpdates();
298 $assert = $schema_handler->fieldExists('entity_test_update', 'new_base_field__shape') && $schema_handler->fieldExists('entity_test_update', 'new_base_field__color');
299 $this->assertTrue($assert, 'Columns created in shared table for new_base_field.');
301 // Recreate the field after emptying the base table and check that its
302 // columns are not 'not null'.
303 // @todo Revisit this test when allowing for required storage field
304 // definitions. See https://www.drupal.org/node/2390495.
306 $this->removeBaseField();
307 $this->entityDefinitionUpdateManager->applyUpdates();
308 $assert = !$schema_handler->fieldExists('entity_test_update', 'new_base_field__shape') && !$schema_handler->fieldExists('entity_test_update', 'new_base_field__color');
309 $this->assert($assert, 'Columns removed from the shared table for new_base_field.');
310 $this->addBaseField('shape_required');
311 $this->entityDefinitionUpdateManager->applyUpdates();
312 $assert = $schema_handler->fieldExists('entity_test_update', 'new_base_field__shape') && $schema_handler->fieldExists('entity_test_update', 'new_base_field__color');
313 $this->assertTrue($assert, 'Columns created again in shared table for new_base_field.');
314 $entity = $storage->create(['name' => $name]);
316 $this->pass('The new_base_field columns are still nullable');
320 * Tests creating and deleting a bundle field if entities exist.
322 * This tests deletion when there are existing entities, but not existing data
323 * for the field being deleted.
325 * @see testBundleFieldDeleteWithExistingData()
327 public function testBundleFieldCreateDeleteWithExistingEntities() {
329 $name = $this->randomString();
330 $storage = $this->entityManager->getStorage('entity_test_update');
331 $entity = $storage->create(['name' => $name]);
334 // Add a bundle field and run the update. Ensure the bundle field's table
335 // is created and the prior saved entity data is still there.
336 $this->addBundleField();
337 $this->entityDefinitionUpdateManager->applyUpdates();
338 $schema_handler = $this->database->schema();
339 $this->assertTrue($schema_handler->tableExists('entity_test_update__new_bundle_field'), 'Dedicated table created for new_bundle_field.');
340 $entity = $this->entityManager->getStorage('entity_test_update')->load($entity->id());
341 $this->assertIdentical($entity->name->value, $name, 'Entity data preserved during field creation.');
343 // Remove the base field and run the update. Ensure the bundle field's
344 // table is deleted and the prior saved entity data is still there.
345 $this->removeBundleField();
346 $this->entityDefinitionUpdateManager->applyUpdates();
347 $this->assertFalse($schema_handler->tableExists('entity_test_update__new_bundle_field'), 'Dedicated table deleted for new_bundle_field.');
348 $entity = $this->entityManager->getStorage('entity_test_update')->load($entity->id());
349 $this->assertIdentical($entity->name->value, $name, 'Entity data preserved during field deletion.');
351 // Test that required columns are created as 'not null'.
352 $this->addBundleField('shape_required');
353 $this->entityDefinitionUpdateManager->applyUpdates();
354 $message = 'The new_bundle_field_shape column is not nullable.';
356 'bundle' => $entity->bundle(),
358 'entity_id' => $entity->id(),
359 'revision_id' => $entity->id(),
360 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
362 'new_bundle_field_color' => $this->randomString(),
365 // Try to insert a record without providing a value for the 'not null'
366 // column. This should fail.
367 $this->database->insert('entity_test_update__new_bundle_field')
370 $this->fail($message);
372 catch (\RuntimeException $e) {
373 if ($e instanceof DatabaseExceptionWrapper || $e instanceof IntegrityConstraintViolationException) {
374 // Now provide a value for the 'not null' column. This is expected to
376 $values['new_bundle_field_shape'] = $this->randomString();
377 $this->database->insert('entity_test_update__new_bundle_field')
380 $this->pass($message);
390 * Tests deleting a base field when it has existing data.
392 public function testBaseFieldDeleteWithExistingData() {
393 // Add the base field and run the update.
394 $this->addBaseField();
395 $this->entityDefinitionUpdateManager->applyUpdates();
397 // Save an entity with the base field populated.
398 $this->entityManager->getStorage('entity_test_update')->create(['new_base_field' => 'foo'])->save();
400 // Remove the base field and apply updates. It's expected to throw an
402 // @todo Revisit that expectation once purging is implemented for
403 // all fields: https://www.drupal.org/node/2282119.
404 $this->removeBaseField();
406 $this->entityDefinitionUpdateManager->applyUpdates();
407 $this->fail('FieldStorageDefinitionUpdateForbiddenException thrown when trying to apply an update that deletes a non-purgeable field with data.');
409 catch (FieldStorageDefinitionUpdateForbiddenException $e) {
410 $this->pass('FieldStorageDefinitionUpdateForbiddenException thrown when trying to apply an update that deletes a non-purgeable field with data.');
415 * Tests deleting a bundle field when it has existing data.
417 public function testBundleFieldDeleteWithExistingData() {
418 // Add the bundle field and run the update.
419 $this->addBundleField();
420 $this->entityDefinitionUpdateManager->applyUpdates();
422 // Save an entity with the bundle field populated.
423 entity_test_create_bundle('custom');
424 $this->entityManager->getStorage('entity_test_update')->create(['type' => 'test_bundle', 'new_bundle_field' => 'foo'])->save();
426 // Remove the bundle field and apply updates. It's expected to throw an
428 // @todo Revisit that expectation once purging is implemented for
429 // all fields: https://www.drupal.org/node/2282119.
430 $this->removeBundleField();
432 $this->entityDefinitionUpdateManager->applyUpdates();
433 $this->fail('FieldStorageDefinitionUpdateForbiddenException thrown when trying to apply an update that deletes a non-purgeable field with data.');
435 catch (FieldStorageDefinitionUpdateForbiddenException $e) {
436 $this->pass('FieldStorageDefinitionUpdateForbiddenException thrown when trying to apply an update that deletes a non-purgeable field with data.');
441 * Tests updating a base field when it has existing data.
443 public function testBaseFieldUpdateWithExistingData() {
444 // Add the base field and run the update.
445 $this->addBaseField();
446 $this->entityDefinitionUpdateManager->applyUpdates();
448 // Save an entity with the base field populated.
449 $this->entityManager->getStorage('entity_test_update')->create(['new_base_field' => 'foo'])->save();
451 // Change the field's field type and apply updates. It's expected to
452 // throw an exception.
453 $this->modifyBaseField();
455 $this->entityDefinitionUpdateManager->applyUpdates();
456 $this->fail('FieldStorageDefinitionUpdateForbiddenException thrown when trying to update a field schema that has data.');
458 catch (FieldStorageDefinitionUpdateForbiddenException $e) {
459 $this->pass('FieldStorageDefinitionUpdateForbiddenException thrown when trying to update a field schema that has data.');
464 * Tests updating a bundle field when it has existing data.
466 public function testBundleFieldUpdateWithExistingData() {
467 // Add the bundle field and run the update.
468 $this->addBundleField();
469 $this->entityDefinitionUpdateManager->applyUpdates();
471 // Save an entity with the bundle field populated.
472 entity_test_create_bundle('custom');
473 $this->entityManager->getStorage('entity_test_update')->create(['type' => 'test_bundle', 'new_bundle_field' => 'foo'])->save();
475 // Change the field's field type and apply updates. It's expected to
476 // throw an exception.
477 $this->modifyBundleField();
479 $this->entityDefinitionUpdateManager->applyUpdates();
480 $this->fail('FieldStorageDefinitionUpdateForbiddenException thrown when trying to update a field schema that has data.');
482 catch (FieldStorageDefinitionUpdateForbiddenException $e) {
483 $this->pass('FieldStorageDefinitionUpdateForbiddenException thrown when trying to update a field schema that has data.');
488 * Tests creating and deleting a multi-field index when there are no existing entities.
490 public function testEntityIndexCreateDeleteWithoutData() {
491 // Add an entity index and ensure the update manager reports that as an
492 // update to the entity type.
493 $this->addEntityIndex();
494 $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
496 'entity_test_update' => [
497 t('The %entity_type entity type needs to be updated.', ['%entity_type' => $this->entityManager->getDefinition('entity_test_update')->getLabel()]),
500 $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
502 // Run the update and ensure the new index is created.
503 $this->entityDefinitionUpdateManager->applyUpdates();
504 $this->assertTrue($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__new_index'), 'Index created.');
506 // Remove the index and ensure the update manager reports that as an
507 // update to the entity type.
508 $this->removeEntityIndex();
509 $this->assertTrue($this->entityDefinitionUpdateManager->needsUpdates(), 'EntityDefinitionUpdateManager reports that updates are needed.');
511 'entity_test_update' => [
512 t('The %entity_type entity type needs to be updated.', ['%entity_type' => $this->entityManager->getDefinition('entity_test_update')->getLabel()]),
515 $this->assertEqual($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
517 // Run the update and ensure the index is deleted.
518 $this->entityDefinitionUpdateManager->applyUpdates();
519 $this->assertFalse($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__new_index'), 'Index deleted.');
521 // Test that composite indexes are handled correctly when dropping and
522 // re-creating one of their columns.
523 $this->addEntityIndex();
524 $this->entityDefinitionUpdateManager->applyUpdates();
525 $storage_definition = $this->entityDefinitionUpdateManager->getFieldStorageDefinition('name', 'entity_test_update');
526 $this->entityDefinitionUpdateManager->updateFieldStorageDefinition($storage_definition);
527 $this->assertTrue($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__new_index'), 'Index created.');
528 $this->entityDefinitionUpdateManager->uninstallFieldStorageDefinition($storage_definition);
529 $this->assertFalse($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__new_index'), 'Index deleted.');
530 $this->entityDefinitionUpdateManager->installFieldStorageDefinition('name', 'entity_test_update', 'entity_test', $storage_definition);
531 $this->assertTrue($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__new_index'), 'Index created again.');
535 * Tests creating a multi-field index when there are existing entities.
537 public function testEntityIndexCreateWithData() {
539 $name = $this->randomString();
540 $entity = $this->entityManager->getStorage('entity_test_update')->create(['name' => $name]);
543 // Add an entity index, run the update. Ensure that the index is created
544 // despite having data.
545 $this->addEntityIndex();
546 $this->entityDefinitionUpdateManager->applyUpdates();
547 $this->assertTrue($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__new_index'), 'Index added.');
551 * Tests entity type and field storage definition events.
553 public function testDefinitionEvents() {
554 /** @var \Drupal\entity_test\EntityTestDefinitionSubscriber $event_subscriber */
555 $event_subscriber = $this->container->get('entity_test.definition.subscriber');
556 $event_subscriber->enableEventTracking();
558 // Test field storage definition events.
559 $storage_definition = current($this->entityManager->getFieldStorageDefinitions('entity_test_rev'));
560 $this->assertFalse($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::DELETE), 'Entity type delete was not dispatched yet.');
561 $this->entityManager->onFieldStorageDefinitionDelete($storage_definition);
562 $this->assertTrue($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::DELETE), 'Entity type delete event successfully dispatched.');
563 $this->assertFalse($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::CREATE), 'Entity type create was not dispatched yet.');
564 $this->entityManager->onFieldStorageDefinitionCreate($storage_definition);
565 $this->assertTrue($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::CREATE), 'Entity type create event successfully dispatched.');
566 $this->assertFalse($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::UPDATE), 'Entity type update was not dispatched yet.');
567 $this->entityManager->onFieldStorageDefinitionUpdate($storage_definition, $storage_definition);
568 $this->assertTrue($event_subscriber->hasEventFired(FieldStorageDefinitionEvents::UPDATE), 'Entity type update event successfully dispatched.');
570 // Test entity type events.
571 $entity_type = $this->entityManager->getDefinition('entity_test_rev');
572 $this->assertFalse($event_subscriber->hasEventFired(EntityTypeEvents::CREATE), 'Entity type create was not dispatched yet.');
573 $this->entityManager->onEntityTypeCreate($entity_type);
574 $this->assertTrue($event_subscriber->hasEventFired(EntityTypeEvents::CREATE), 'Entity type create event successfully dispatched.');
575 $this->assertFalse($event_subscriber->hasEventFired(EntityTypeEvents::UPDATE), 'Entity type update was not dispatched yet.');
576 $this->entityManager->onEntityTypeUpdate($entity_type, $entity_type);
577 $this->assertTrue($event_subscriber->hasEventFired(EntityTypeEvents::UPDATE), 'Entity type update event successfully dispatched.');
578 $this->assertFalse($event_subscriber->hasEventFired(EntityTypeEvents::DELETE), 'Entity type delete was not dispatched yet.');
579 $this->entityManager->onEntityTypeDelete($entity_type);
580 $this->assertTrue($event_subscriber->hasEventFired(EntityTypeEvents::DELETE), 'Entity type delete event successfully dispatched.');
584 * Tests updating entity schema and creating a base field.
586 * This tests updating entity schema and creating a base field at the same
587 * time when there are no existing entities.
589 public function testEntityTypeSchemaUpdateAndBaseFieldCreateWithoutData() {
590 $this->updateEntityTypeToRevisionable();
591 $this->addBaseField();
592 $message = 'Successfully updated entity schema and created base field at the same time.';
593 // Entity type updates create base fields as well, thus make sure doing both
594 // at the same time does not lead to errors due to the base field being
597 $this->entityDefinitionUpdateManager->applyUpdates();
598 $this->pass($message);
600 catch (\Exception $e) {
601 $this->fail($message);
607 * Tests updating entity schema and creating a revisionable base field.
609 * This tests updating entity schema and creating a revisionable base field
610 * at the same time when there are no existing entities.
612 public function testEntityTypeSchemaUpdateAndRevisionableBaseFieldCreateWithoutData() {
613 $this->updateEntityTypeToRevisionable();
614 $this->addRevisionableBaseField();
615 $message = 'Successfully updated entity schema and created revisionable base field at the same time.';
616 // Entity type updates create base fields as well, thus make sure doing both
617 // at the same time does not lead to errors due to the base field being
620 $this->entityDefinitionUpdateManager->applyUpdates();
621 $this->pass($message);
623 catch (\Exception $e) {
624 $this->fail($message);
630 * Tests applying single updates.
632 public function testSingleActionCalls() {
633 $db_schema = $this->database->schema();
635 // Ensure that a non-existing entity type cannot be installed.
636 $message = 'A non-existing entity type cannot be installed';
638 $this->entityDefinitionUpdateManager->installEntityType(new ContentEntityType(['id' => 'foo']));
639 $this->fail($message);
641 catch (PluginNotFoundException $e) {
642 $this->pass($message);
645 // Ensure that a field cannot be installed on non-existing entity type.
646 $message = 'A field cannot be installed on a non-existing entity type';
648 $storage_definition = BaseFieldDefinition::create('string')
649 ->setLabel(t('A new revisionable base field'))
650 ->setRevisionable(TRUE);
651 $this->entityDefinitionUpdateManager->installFieldStorageDefinition('bar', 'foo', 'entity_test', $storage_definition);
652 $this->fail($message);
654 catch (PluginNotFoundException $e) {
655 $this->pass($message);
658 // Ensure that a non-existing field cannot be installed.
659 $storage_definition = BaseFieldDefinition::create('string')
660 ->setLabel(t('A new revisionable base field'))
661 ->setRevisionable(TRUE);
662 $this->entityDefinitionUpdateManager->installFieldStorageDefinition('bar', 'entity_test_update', 'entity_test', $storage_definition);
663 $this->assertFalse($db_schema->fieldExists('entity_test_update', 'bar'), "A non-existing field cannot be installed.");
665 // Ensure that installing an existing entity type is a no-op.
666 $entity_type = $this->entityDefinitionUpdateManager->getEntityType('entity_test_update');
667 $this->entityDefinitionUpdateManager->installEntityType($entity_type);
668 $this->assertTrue($db_schema->tableExists('entity_test_update'), 'Installing an existing entity type is a no-op');
670 // Create a new base field.
671 $this->addRevisionableBaseField();
672 $storage_definition = BaseFieldDefinition::create('string')
673 ->setLabel(t('A new revisionable base field'))
674 ->setRevisionable(TRUE);
675 $this->assertFalse($db_schema->fieldExists('entity_test_update', 'new_base_field'), "New field 'new_base_field' does not exist before applying the update.");
676 $this->entityDefinitionUpdateManager->installFieldStorageDefinition('new_base_field', 'entity_test_update', 'entity_test', $storage_definition);
677 $this->assertTrue($db_schema->fieldExists('entity_test_update', 'new_base_field'), "New field 'new_base_field' has been created on the 'entity_test_update' table.");
679 // Ensure that installing an existing field is a no-op.
680 $this->entityDefinitionUpdateManager->installFieldStorageDefinition('new_base_field', 'entity_test_update', 'entity_test', $storage_definition);
681 $this->assertTrue($db_schema->fieldExists('entity_test_update', 'new_base_field'), 'Installing an existing field is a no-op');
683 // Update an existing field schema.
684 $this->modifyBaseField();
685 $storage_definition = BaseFieldDefinition::create('text')
686 ->setName('new_base_field')
687 ->setTargetEntityTypeId('entity_test_update')
688 ->setLabel(t('A new revisionable base field'))
689 ->setRevisionable(TRUE);
690 $this->entityDefinitionUpdateManager->updateFieldStorageDefinition($storage_definition);
691 $this->assertFalse($db_schema->fieldExists('entity_test_update', 'new_base_field'), "Previous schema for 'new_base_field' no longer exists.");
693 $db_schema->fieldExists('entity_test_update', 'new_base_field__value') && $db_schema->fieldExists('entity_test_update', 'new_base_field__format'),
694 "New schema for 'new_base_field' has been created."
697 // Drop an existing field schema.
698 $this->entityDefinitionUpdateManager->uninstallFieldStorageDefinition($storage_definition);
700 $db_schema->fieldExists('entity_test_update', 'new_base_field__value') || $db_schema->fieldExists('entity_test_update', 'new_base_field__format'),
701 "The schema for 'new_base_field' has been dropped."
704 // Make the entity type revisionable.
705 $this->updateEntityTypeToRevisionable();
706 $this->assertFalse($db_schema->tableExists('entity_test_update_revision'), "The 'entity_test_update_revision' does not exist before applying the update.");
707 $entity_type = $this->entityDefinitionUpdateManager->getEntityType('entity_test_update');
708 $keys = $entity_type->getKeys();
709 $keys['revision'] = 'revision_id';
710 $entity_type->set('entity_keys', $keys);
711 $this->entityDefinitionUpdateManager->updateEntityType($entity_type);
712 $this->assertTrue($db_schema->tableExists('entity_test_update_revision'), "The 'entity_test_update_revision' table has been created.");
716 * Ensures that a new field and index on a shared table are created.
718 * @see Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema::createSharedTableSchema
720 public function testCreateFieldAndIndexOnSharedTable() {
721 $this->addBaseField();
722 $this->addBaseFieldIndex();
723 $this->entityDefinitionUpdateManager->applyUpdates();
724 $this->assertTrue($this->database->schema()->fieldExists('entity_test_update', 'new_base_field'), "New field 'new_base_field' has been created on the 'entity_test_update' table.");
725 $this->assertTrue($this->database->schema()->indexExists('entity_test_update', 'entity_test_update_field__new_base_field'), "New index 'entity_test_update_field__new_base_field' has been created on the 'entity_test_update' table.");
726 // Check index size in for MySQL.
727 if (Database::getConnection()->driver() == 'mysql') {
728 $result = Database::getConnection()->query('SHOW INDEX FROM {entity_test_update} WHERE key_name = \'entity_test_update_field__new_base_field\' and column_name = \'new_base_field\'')->fetchObject();
729 $this->assertEqual(191, $result->Sub_part, 'The index length has been restricted to 191 characters for UTF8MB4 compatibility.');
734 * Ensures that a new entity level index is created when data exists.
736 * @see Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema::onEntityTypeUpdate
738 public function testCreateIndexUsingEntityStorageSchemaWithData() {
740 $name = $this->randomString();
741 $storage = $this->entityManager->getStorage('entity_test_update');
742 $entity = $storage->create(['name' => $name]);
747 'entity_test_update__type_index' => ['type'],
749 $this->state->set('entity_test_update.additional_entity_indexes', $indexes);
750 $this->entityDefinitionUpdateManager->applyUpdates();
751 $this->assertTrue($this->database->schema()->indexExists('entity_test_update', 'entity_test_update__type_index'), "New index 'entity_test_update__type_index' has been created on the 'entity_test_update' table.");
752 // Check index size in for MySQL.
753 if (Database::getConnection()->driver() == 'mysql') {
754 $result = Database::getConnection()->query('SHOW INDEX FROM {entity_test_update} WHERE key_name = \'entity_test_update__type_index\' and column_name = \'type\'')->fetchObject();
755 $this->assertEqual(191, $result->Sub_part, 'The index length has been restricted to 191 characters for UTF8MB4 compatibility.');
760 * Tests updating a base field when it has existing data.
762 public function testBaseFieldEntityKeyUpdateWithExistingData() {
763 // Add the base field and run the update.
764 $this->addBaseField();
765 $this->entityDefinitionUpdateManager->applyUpdates();
767 // Save an entity with the base field populated.
768 $this->entityManager->getStorage('entity_test_update')->create(['new_base_field' => $this->randomString()])->save();
770 // Save an entity with the base field not populated.
771 /** @var \Drupal\entity_test\Entity\EntityTestUpdate $entity */
772 $entity = $this->entityManager->getStorage('entity_test_update')->create();
775 // Promote the base field to an entity key. This will trigger the addition
776 // of a NOT NULL constraint.
777 $this->makeBaseFieldEntityKey();
779 // Try to apply the update and verify they fail since we have a NULL value.
780 $message = 'An error occurs when trying to enabling NOT NULL constraints with NULL data.';
782 $this->entityDefinitionUpdateManager->applyUpdates();
783 $this->fail($message);
785 catch (EntityStorageException $e) {
786 $this->pass($message);
789 // Check that the update is correctly applied when no NULL data is left.
790 $entity->set('new_base_field', $this->randomString());
792 $this->entityDefinitionUpdateManager->applyUpdates();
793 $this->pass('The update is correctly performed when no NULL data exists.');
795 // Check that the update actually applied a NOT NULL constraint.
796 $entity->set('new_base_field', NULL);
797 $message = 'The NOT NULL constraint was correctly applied.';
800 $this->fail($message);
802 catch (EntityStorageException $e) {
803 $this->pass($message);
808 * Check that field schema is correctly handled with long-named fields.
810 public function testLongNameFieldIndexes() {
811 $this->addLongNameBaseField();
812 $entity_type_id = 'entity_test_update';
813 $entity_type = $this->entityManager->getDefinition($entity_type_id);
814 $definitions = EntityTestUpdate::baseFieldDefinitions($entity_type);
815 $name = 'new_long_named_entity_reference_base_field';
816 $this->entityDefinitionUpdateManager->installFieldStorageDefinition($name, $entity_type_id, 'entity_test', $definitions[$name]);
817 $this->assertFalse($this->entityDefinitionUpdateManager->needsUpdates(), 'Entity and field schema data are correctly detected.');