/** * Tests some possible entity table updates for a revision view. */ public function testVariousTableUpdatesForRevisionView() { // base + revision <-> base + translation + revision $this->updateEntityTypeToRevisionable(); // Multiple changes, so we have to invalidate the caches, otherwise // the second update will revert the first. $this->entityManager->clearCachedDefinitions(); list($view, $display) = $this->getUpdatedViewAndDisplay(TRUE); $this->assertEqual('entity_test_update_revision', $view->get('base_table')); $this->assertEqual('entity_test_update_revision', $display['display_options']['fields']['id']['table']); $this->assertEqual('entity_test_update_revision', $display['display_options']['fields']['name']['table']); $this->updateEntityTypeToTranslatable(); $this->entityDefinitionUpdateManager->applyUpdates(); list($view, $display) = $this->getUpdatedViewAndDisplay(TRUE); $this->assertEqual('entity_test_update_revision', $view->get('base_table')); $this->assertEqual('entity_test_update_revision', $display['display_options']['fields']['id']['table']); $this->assertEqual('entity_test_update_revision_data', $display['display_options']['fields']['name']['table']); $this->updateEntityTypeToNotTranslatable(); $this->entityDefinitionUpdateManager->applyUpdates(); list($view, $display) = $this->getUpdatedViewAndDisplay(TRUE); $this->assertEqual('entity_test_update_revision', $view->get('base_table')); $this->assertEqual('entity_test_update_revision', $display['display_options']['fields']['id']['table']); $this->assertEqual('entity_test_update_revision', $display['display_options']['fields']['name']['table']); $this->resetEntityType(); }
/** * {@inheritdoc} */ public function applyNewStorage() { // The first call is for making entity types revisionable, the second call // is for adding required fields. $this->updateManager->applyUpdates(); $this->updateManager->applyUpdates(); return $this; }
/** * Asserts the given module can be installed and uninstalled. * * @param string $module_name * The module to install and uninstall. */ protected function assertModuleInstallUninstall($module_name) { $this->enableModules([$module_name]); $this->entityDefinitionUpdateManager->applyUpdates(); $this->assertTrue($this->getModuleHandler()->moduleExists($module_name), $module_name . ' module is enabled.'); $this->getModuleInstaller()->uninstall([$module_name]); $this->entityDefinitionUpdateManager->applyUpdates(); $this->assertFalse($this->getModuleHandler()->moduleExists($module_name), $module_name . ' module is disabled.'); }
/** * Executes field storage definition updates if needed. * * @param array $entity_types * A list of entity type definitions to be processed. */ public function updateDefinitions(array $entity_types) { // Handle field storage definition creation, if needed. // @todo Generalize this code in https://www.drupal.org/node/2346013. // @todo Handle initial values in https://www.drupal.org/node/2346019. if ($this->updateManager->needsUpdates()) { foreach ($entity_types as $entity_type_id => $entity_type) { $storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id); $installed_storage_definitions = $this->entityManager->getLastInstalledFieldStorageDefinitions($entity_type_id); foreach (array_diff_key($storage_definitions, $installed_storage_definitions) as $storage_definition) { /** @var $storage_definition \Drupal\Core\Field\FieldStorageDefinitionInterface */ if ($storage_definition->getProvider() == 'content_translation') { $this->updateManager->installFieldStorageDefinition($storage_definition->getName(), $entity_type_id, 'content_translation', $storage_definition); } } } } }
/** * Check that field schema is correctly handled with long-named fields. */ function testLongNameFieldIndexes() { $this->addLongNameBaseField(); $entity_type_id = 'entity_test_update'; $entity_type = $this->entityManager->getDefinition($entity_type_id); $definitions = EntityTestUpdate::baseFieldDefinitions($entity_type); $name = 'new_long_named_entity_reference_base_field'; $this->entityDefinitionUpdateManager->installFieldStorageDefinition($name, $entity_type_id, 'entity_test', $definitions[$name]); $this->assertFalse($this->entityDefinitionUpdateManager->needsUpdates(), 'Entity and field schema data are correctly detected.'); }
/** * Starts the database update batch process. * * @param \Symfony\Component\HttpFoundation\Request $request * The current request object. */ protected function triggerBatch(Request $request) { // During the update, bring the site offline so that schema changes do not // affect visiting users. $maintenance_mode = $this->config('system.maintenance')->get('enabled'); if (isset($maintenance_mode)) { $_SESSION['maintenance_mode'] = $maintenance_mode; } if (empty($_SESSION['maintenance_mode'])) { $this->state->set('system.maintenance_mode', TRUE); } $operations = array(); // Resolve any update dependencies to determine the actual updates that will // be run and the order they will be run in. $start = $this->getModuleUpdates(); $updates = update_resolve_dependencies($start); // Store the dependencies for each update function in an array which the // batch API can pass in to the batch operation each time it is called. (We // do not store the entire update dependency array here because it is // potentially very large.) $dependency_map = array(); foreach ($updates as $function => $update) { $dependency_map[$function] = !empty($update['reverse_paths']) ? array_keys($update['reverse_paths']) : array(); } // Determine updates to be performed. foreach ($updates as $function => $update) { if ($update['allowed']) { // Set the installed version of each module so updates will start at the // correct place. (The updates are already sorted, so we can simply base // this on the first one we come across in the above foreach loop.) if (isset($start[$update['module']])) { drupal_set_installed_schema_version($update['module'], $update['number'] - 1); unset($start[$update['module']]); } $operations[] = array('update_do_one', array($update['module'], $update['number'], $dependency_map[$function])); } } // Lastly, perform entity definition updates, which will update storage // schema if needed. If module update functions need to work with specific // entity schema they should call the entity update service for the specific // update themselves. // @see \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface::applyEntityUpdate() // @see \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface::applyFieldUpdate() if ($this->entityDefinitionUpdateManager->needsUpdates()) { $operations[] = array('update_entity_definitions', array()); } $batch['operations'] = $operations; $batch += array('title' => $this->t('Updating'), 'init_message' => $this->t('Starting updates'), 'error_message' => $this->t('An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.'), 'finished' => array('\\Drupal\\system\\Controller\\DbUpdateController', 'batchFinished')); batch_set($batch); return batch_process('update.php/results', Url::fromRoute('system.db_update', array('op' => 'start'))); }
/** * Tests that entity updates are correctly reported in the status report page. */ function testStatusReport() { // Create a test entity. $entity = EntityTest::create(['name' => $this->randomString(), 'user_id' => mt_rand()]); $entity->save(); // Check that the status report initially displays no error. $this->drupalGet('admin/reports/status'); $this->assertNoRaw('Out of date'); $this->assertNoRaw('Mismatched entity and/or field definitions'); // Enable an entity update and check that we have a dedicated status report // item. $this->container->get('state')->set('entity_test.remove_name_field', TRUE); $this->drupalGet('admin/reports/status'); $this->assertNoRaw('Out of date'); $this->assertRaw('Mismatched entity and/or field definitions'); // Enable a db update and check that now the entity update status report // item is no longer displayed. We assume an update function will fix the // mismatch. $this->enableUpdates('entity_test', 'status_report', 8001); $this->drupalGet('admin/reports/status'); $this->assertRaw('Out of date'); $this->assertRaw('Mismatched entity and/or field definitions'); // Apply db updates and check that entity updates were not applied. $this->applyUpdates(); $this->drupalGet('admin/reports/status'); $this->assertNoRaw('Out of date'); $this->assertRaw('Mismatched entity and/or field definitions'); // Check that en exception would be triggered when trying to apply them with // existing data. $message = 'Entity updates cannot run if entity data exists.'; try { $this->updatesManager->applyUpdates(); $this->fail($message); } catch (FieldStorageDefinitionUpdateForbiddenException $e) { $this->pass($message); } // Check the status report is the same after trying to apply updates. $this->drupalGet('admin/reports/status'); $this->assertNoRaw('Out of date'); $this->assertRaw('Mismatched entity and/or field definitions'); // Delete entity data, enable a new update, run updates again and check that // entity updates were not applied even when no data exists. $entity->delete(); $this->enableUpdates('entity_test', 'status_report', 8002); $this->applyUpdates(); $this->drupalGet('admin/reports/status'); $this->assertNoRaw('Out of date'); $this->assertRaw('Mismatched entity and/or field definitions'); }
/** * Tests updating entity schema and creating a revisionable base field. * * This tests updating entity schema and creating a revisionable base field * at the same time when there are no existing entities. */ public function testEntityTypeSchemaUpdateAndRevisionableBaseFieldCreateWithoutData() { $this->updateEntityTypeToRevisionable(); $this->addRevisionableBaseField(); $message = 'Successfully updated entity schema and created revisionable base field at the same time.'; // Entity type updates create base fields as well, thus make sure doing both // at the same time does not lead to errors due to the base field being // created twice. try { $this->entityDefinitionUpdateManager->applyUpdates(); $this->pass($message); } catch (\Exception $e) { $this->fail($message); throw $e; } }
/** * Tests ::applyEntityUpdate() and ::applyFieldUpdate(). */ public function testSingleActionCalls() { // Ensure that the methods return FALSE when called with bogus information. $this->assertFalse($this->entityDefinitionUpdateManager->applyEntityUpdate(EntityDefinitionUpdateManagerInterface::DEFINITION_CREATED, 'foo'), 'Calling applyEntityUpdate() with a non-existent entity returns FALSE.'); $this->assertFalse($this->entityDefinitionUpdateManager->applyFieldUpdate(EntityDefinitionUpdateManagerInterface::DEFINITION_CREATED, 'foo', 'bar'), 'Calling applyFieldUpdate() with a non-existent entity returns FALSE.'); $this->assertFalse($this->entityDefinitionUpdateManager->applyFieldUpdate(EntityDefinitionUpdateManagerInterface::DEFINITION_CREATED, 'entity_test_update', 'bar'), 'Calling applyFieldUpdate() with a non-existent field returns FALSE.'); $this->assertFalse($this->entityDefinitionUpdateManager->applyEntityUpdate(EntityDefinitionUpdateManagerInterface::DEFINITION_CREATED, 'entity_test_update'), 'Calling applyEntityUpdate() with an $op that is not applicable to the entity type returns FALSE.'); $this->assertFalse($this->entityDefinitionUpdateManager->applyFieldUpdate(EntityDefinitionUpdateManagerInterface::DEFINITION_DELETED, 'entity_test_update', 'new_base_field'), 'Calling applyFieldUpdate() with an $op that is not applicable to the field returns FALSE.'); // Create a new base field. $this->addRevisionableBaseField(); $this->assertTrue($this->entityDefinitionUpdateManager->applyFieldUpdate(EntityDefinitionUpdateManagerInterface::DEFINITION_CREATED, 'entity_test_update', 'new_base_field'), 'Calling applyFieldUpdate() correctly returns TRUE.'); $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."); // Make the entity type revisionable. $this->updateEntityTypeToRevisionable(); $this->assertTrue($this->entityDefinitionUpdateManager->applyEntityUpdate(EntityDefinitionUpdateManagerInterface::DEFINITION_UPDATED, 'entity_test_update'), 'Calling applyEntityUpdate() correctly returns TRUE.'); $this->assertTrue($this->database->schema()->tableExists('entity_test_update_revision'), "The 'entity_test_update_revision' table has been created."); }
/** * Ensures that a new entity level index is created when data exists. * * @see Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema::onEntityTypeUpdate */ public function testCreateIndexUsingEntityStorageSchemaWithData() { // Save an entity. $name = $this->randomString(); $storage = $this->entityManager->getStorage('entity_test_update'); $entity = $storage->create(array('name' => $name)); $entity->save(); // Create an index. $indexes = array('entity_test_update__type_index' => array('type')); $this->state->set('entity_test_update.additional_entity_indexes', $indexes); $this->entityDefinitionUpdateManager->applyUpdates(); $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."); // Check index size in for MySQL. if (Database::getConnection()->driver() == 'mysql') { $result = Database::getConnection()->query('SHOW INDEX FROM {entity_test_update} WHERE key_name = \'entity_test_update__type_index\' and column_name = \'type\'')->fetchObject(); $this->assertEqual(191, $result->Sub_part, 'The index length has been restricted to 191 characters for UTF8MB4 compatibility.'); } }
/** * Tests creating a multi-field index when there are existing entities. */ public function testEntityIndexCreateWithData() { // Save an entity. $name = $this->randomString(); $entity = $this->entityManager->getStorage('entity_test_update')->create(array('name' => $name)); $entity->save(); // Add an entity index, run the update. For now, it's expected to throw an // exception. // @todo Improve SqlContentEntityStorageSchema::requiresEntityDataMigration() // to return FALSE when only index changes are required, so that it can be // applied on top of existing data: https://www.drupal.org/node/2340993. $this->addEntityIndex(); try { $this->entityDefinitionUpdateManager->applyUpdates(); $this->fail('EntityStorageException thrown when trying to apply an update that requires data migration.'); } catch (EntityStorageException $e) { $this->pass('EntityStorageException thrown when trying to apply an update that requires data migration.'); } }
/** * Tests updating a base field when it has existing data. */ public function testBaseFieldEntityKeyUpdateWithExistingData() { // Add the base field and run the update. $this->addBaseField(); $this->entityDefinitionUpdateManager->applyUpdates(); // Save an entity with the base field populated. $this->entityManager->getStorage('entity_test_update')->create(['new_base_field' => $this->randomString()])->save(); // Save an entity with the base field not populated. /** @var \Drupal\entity_test\Entity\EntityTestUpdate $entity */ $entity = $this->entityManager->getStorage('entity_test_update')->create(); $entity->save(); // Promote the base field to an entity key. This will trigger the addition // of a NOT NULL constraint. $this->makeBaseFieldEntityKey(); // Try to apply the update and verify they fail since we have a NULL value. $message = 'An error occurs when trying to enabling NOT NULL constraints with NULL data.'; try { $this->entityDefinitionUpdateManager->applyUpdates(); $this->fail($message); } catch (EntityStorageException $e) { $this->pass($message); } // Check that the update is correctly applied when no NULL data is left. $entity->set('new_base_field', $this->randomString()); $entity->save(); $this->entityDefinitionUpdateManager->applyUpdates(); $this->pass('The update is correctly performed when no NULL data exists.'); // Check that the update actually applied a NOT NULL constraint. $entity->set('new_base_field', NULL); $message = 'The NOT NULL constraint was correctly applied.'; try { $entity->save(); $this->fail($message); } catch (EntityStorageException $e) { $this->pass($message); } }