SqlContentEntityStorageSchemaConverter.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  1. <?php
  2. namespace Drupal\Core\Entity\Sql;
  3. use Drupal\Core\Database\Connection;
  4. use Drupal\Core\Entity\ContentEntityTypeInterface;
  5. use Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface;
  6. use Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface;
  7. use Drupal\Core\Entity\EntityStorageException;
  8. use Drupal\Core\Entity\EntityTypeManagerInterface;
  9. use Drupal\Core\Field\BaseFieldDefinition;
  10. use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
  11. use Drupal\Core\Site\Settings;
  12. use Drupal\Core\StringTranslation\TranslatableMarkup;
  13. /**
  14. * Defines a schema converter for entity types with existing data.
  15. *
  16. * For now, this can only be used to convert an entity type from
  17. * non-revisionable to revisionable, however, it should be expanded so it can
  18. * also handle converting an entity type to be translatable.
  19. */
  20. class SqlContentEntityStorageSchemaConverter {
  21. /**
  22. * The entity type ID this schema converter is responsible for.
  23. *
  24. * @var string
  25. */
  26. protected $entityTypeId;
  27. /**
  28. * The entity type manager.
  29. *
  30. * @var \Drupal\Core\Entity\EntityTypeManagerInterface
  31. */
  32. protected $entityTypeManager;
  33. /**
  34. * The entity definition update manager service.
  35. *
  36. * @var \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface
  37. */
  38. protected $entityDefinitionUpdateManager;
  39. /**
  40. * The last installed schema repository service.
  41. *
  42. * @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface
  43. */
  44. protected $lastInstalledSchemaRepository;
  45. /**
  46. * The key-value collection for tracking installed storage schema.
  47. *
  48. * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
  49. */
  50. protected $installedStorageSchema;
  51. /**
  52. * The database connection.
  53. *
  54. * @var \Drupal\Core\Database\Connection
  55. */
  56. protected $database;
  57. /**
  58. * SqlContentEntityStorageSchemaConverter constructor.
  59. *
  60. * @param string $entity_type_id
  61. * The ID of the entity type.
  62. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
  63. * The entity type manager.
  64. * @param \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface $entity_definition_update_manager
  65. * Entity definition update manager service.
  66. * @param \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $last_installed_schema_repository
  67. * Last installed schema repository service.
  68. * @param \Drupal\Core\Database\Connection $database
  69. * Database connection.
  70. */
  71. public function __construct($entity_type_id, EntityTypeManagerInterface $entity_type_manager, EntityDefinitionUpdateManagerInterface $entity_definition_update_manager, EntityLastInstalledSchemaRepositoryInterface $last_installed_schema_repository, KeyValueStoreInterface $installed_storage_schema, Connection $database) {
  72. $this->entityTypeId = $entity_type_id;
  73. $this->entityTypeManager = $entity_type_manager;
  74. $this->entityDefinitionUpdateManager = $entity_definition_update_manager;
  75. $this->lastInstalledSchemaRepository = $last_installed_schema_repository;
  76. $this->installedStorageSchema = $installed_storage_schema;
  77. $this->database = $database;
  78. }
  79. /**
  80. * Converts an entity type with existing data to be revisionable.
  81. *
  82. * This process does the following tasks:
  83. * - creates the schema from scratch with the new revisionable entity type
  84. * definition (i.e. the current definition of the entity type from code)
  85. * using temporary table names;
  86. * - loads the initial entity data by using the last installed entity and
  87. * field storage definitions;
  88. * - saves the entity data to the temporary tables;
  89. * - at the end of the process:
  90. * - deletes the original tables and replaces them with the temporary ones
  91. * that hold the new (revisionable) entity data;
  92. * - updates the installed entity schema data;
  93. * - updates the entity type definition in order to trigger the
  94. * \Drupal\Core\Entity\EntityTypeEvents::UPDATE event;
  95. * - updates the field storage definitions in order to mark the
  96. * revisionable ones as such.
  97. *
  98. * In case of an error during the entity save process, the temporary tables
  99. * are deleted and the original entity type and field storage definitions are
  100. * restored.
  101. *
  102. * @param array $sandbox
  103. * The sandbox array from a hook_update_N() implementation.
  104. * @param string[] $fields_to_update
  105. * (optional) An array of field names that should be converted to be
  106. * revisionable. Note that the 'langcode' field, if present, is updated
  107. * automatically. Defaults to an empty array.
  108. *
  109. * @throws \Exception
  110. * Re-throws any exception raised during the update process.
  111. */
  112. public function convertToRevisionable(array &$sandbox, array $fields_to_update = []) {
  113. // If 'progress' is not set, then this will be the first run of the batch.
  114. if (!isset($sandbox['progress'])) {
  115. // Store the original entity type and field definitions in the $sandbox
  116. // array so we can use them later in the update process.
  117. $this->collectOriginalDefinitions($sandbox);
  118. // Create a temporary environment in which the new data will be stored.
  119. $this->createTemporaryDefinitions($sandbox, $fields_to_update);
  120. // Create the updated entity schema using temporary tables.
  121. /** @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage $storage */
  122. $storage = $this->entityTypeManager->getStorage($this->entityTypeId);
  123. $storage->setTemporary(TRUE);
  124. $storage->setEntityType($sandbox['temporary_entity_type']);
  125. $storage->onEntityTypeCreate($sandbox['temporary_entity_type']);
  126. }
  127. // Copy over the existing data to the new temporary tables.
  128. $this->copyData($sandbox);
  129. // If the data copying has finished successfully, we can drop the temporary
  130. // tables and call the appropriate update mechanisms.
  131. if ($sandbox['#finished'] == 1) {
  132. $this->entityTypeManager->useCaches(FALSE);
  133. $actual_entity_type = $this->entityTypeManager->getDefinition($this->entityTypeId);
  134. // Rename the original tables so we can put them back in place in case
  135. // anything goes wrong.
  136. foreach ($sandbox['original_table_mapping']->getTableNames() as $table_name) {
  137. $old_table_name = TemporaryTableMapping::getTempTableName($table_name, 'old_');
  138. $this->database->schema()->renameTable($table_name, $old_table_name);
  139. }
  140. // Put the new tables in place and update the entity type and field
  141. // storage definitions.
  142. try {
  143. $storage = $this->entityTypeManager->getStorage($this->entityTypeId);
  144. $storage->setEntityType($actual_entity_type);
  145. $storage->setTemporary(FALSE);
  146. $actual_table_names = $storage->getTableMapping()->getTableNames();
  147. $table_name_mapping = [];
  148. foreach ($actual_table_names as $new_table_name) {
  149. $temp_table_name = TemporaryTableMapping::getTempTableName($new_table_name);
  150. $table_name_mapping[$temp_table_name] = $new_table_name;
  151. $this->database->schema()->renameTable($temp_table_name, $new_table_name);
  152. }
  153. // Rename the tables in the cached entity schema data.
  154. $entity_schema_data = $this->installedStorageSchema->get($this->entityTypeId . '.entity_schema_data', []);
  155. foreach ($entity_schema_data as $temp_table_name => $schema) {
  156. if (isset($table_name_mapping[$temp_table_name])) {
  157. $entity_schema_data[$table_name_mapping[$temp_table_name]] = $schema;
  158. unset($entity_schema_data[$temp_table_name]);
  159. }
  160. }
  161. $this->installedStorageSchema->set($this->entityTypeId . '.entity_schema_data', $entity_schema_data);
  162. // Rename the tables in the cached field schema data.
  163. foreach ($sandbox['updated_storage_definitions'] as $storage_definition) {
  164. $field_schema_data = $this->installedStorageSchema->get($this->entityTypeId . '.field_schema_data.' . $storage_definition->getName(), []);
  165. foreach ($field_schema_data as $temp_table_name => $schema) {
  166. if (isset($table_name_mapping[$temp_table_name])) {
  167. $field_schema_data[$table_name_mapping[$temp_table_name]] = $schema;
  168. unset($field_schema_data[$temp_table_name]);
  169. }
  170. }
  171. $this->installedStorageSchema->set($this->entityTypeId . '.field_schema_data.' . $storage_definition->getName(), $field_schema_data);
  172. }
  173. // Instruct the entity schema handler that data migration has been
  174. // handled already and update the entity type.
  175. $actual_entity_type->set('requires_data_migration', FALSE);
  176. $this->entityDefinitionUpdateManager->updateEntityType($actual_entity_type);
  177. // Update the field storage definitions.
  178. $this->updateFieldStorageDefinitionsToRevisionable($actual_entity_type, $sandbox['original_storage_definitions'], $fields_to_update);
  179. }
  180. catch (\Exception $e) {
  181. // Something went wrong, bring back the original tables.
  182. foreach ($sandbox['original_table_mapping']->getTableNames() as $table_name) {
  183. // We are in the 'original data recovery' phase, so we need to be sure
  184. // that the initial tables can be properly restored.
  185. if ($this->database->schema()->tableExists($table_name)) {
  186. $this->database->schema()->dropTable($table_name);
  187. }
  188. $old_table_name = TemporaryTableMapping::getTempTableName($table_name, 'old_');
  189. $this->database->schema()->renameTable($old_table_name, $table_name);
  190. }
  191. // Re-throw the original exception.
  192. throw $e;
  193. }
  194. // At this point the update process either finished successfully or any
  195. // error has been handled already, so we can drop the backup entity
  196. // tables.
  197. foreach ($sandbox['original_table_mapping']->getTableNames() as $table_name) {
  198. $old_table_name = TemporaryTableMapping::getTempTableName($table_name, 'old_');
  199. $this->database->schema()->dropTable($old_table_name);
  200. }
  201. }
  202. }
  203. /**
  204. * Loads entities from the original storage and saves them to a temporary one.
  205. *
  206. * @param array &$sandbox
  207. * The sandbox array from a hook_update_N() implementation.
  208. *
  209. * @throws \Drupal\Core\Entity\EntityStorageException
  210. * Thrown in case of an error during the entity save process.
  211. */
  212. protected function copyData(array &$sandbox) {
  213. /** @var \Drupal\Core\Entity\Sql\TemporaryTableMapping $temporary_table_mapping */
  214. $temporary_table_mapping = $sandbox['temporary_table_mapping'];
  215. $temporary_entity_type = $sandbox['temporary_entity_type'];
  216. $original_table_mapping = $sandbox['original_table_mapping'];
  217. $original_entity_type = $sandbox['original_entity_type'];
  218. $original_base_table = $original_entity_type->getBaseTable();
  219. $revision_id_key = $temporary_entity_type->getKey('revision');
  220. $revision_default_key = $temporary_entity_type->getRevisionMetadataKey('revision_default');
  221. $revision_translation_affected_key = $temporary_entity_type->getKey('revision_translation_affected');
  222. // If 'progress' is not set, then this will be the first run of the batch.
  223. if (!isset($sandbox['progress'])) {
  224. $sandbox['progress'] = 0;
  225. $sandbox['current_id'] = 0;
  226. $sandbox['max'] = $this->database->select($original_base_table)
  227. ->countQuery()
  228. ->execute()
  229. ->fetchField();
  230. }
  231. $id = $original_entity_type->getKey('id');
  232. // Define the step size.
  233. $step_size = Settings::get('entity_update_batch_size', 50);
  234. // Get the next entity IDs to migrate.
  235. $entity_ids = $this->database->select($original_base_table)
  236. ->fields($original_base_table, [$id])
  237. ->condition($id, $sandbox['current_id'], '>')
  238. ->orderBy($id, 'ASC')
  239. ->range(0, $step_size)
  240. ->execute()
  241. ->fetchAllKeyed(0, 0);
  242. /** @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage $storage */
  243. $storage = $this->entityTypeManager->getStorage($temporary_entity_type->id());
  244. $storage->setEntityType($original_entity_type);
  245. $storage->setTableMapping($original_table_mapping);
  246. $entities = $storage->loadMultiple($entity_ids);
  247. // Now inject the temporary entity type definition and table mapping in the
  248. // storage and re-save the entities.
  249. $storage->setEntityType($temporary_entity_type);
  250. $storage->setTableMapping($temporary_table_mapping);
  251. foreach ($entities as $entity_id => $entity) {
  252. try {
  253. // Set the revision ID to be same as the entity ID.
  254. $entity->set($revision_id_key, $entity_id);
  255. // We had no revisions so far, so the existing data belongs to the
  256. // default revision now.
  257. $entity->set($revision_default_key, TRUE);
  258. // Set the 'revision_translation_affected' flag to TRUE to match the
  259. // previous API return value: if the field was not defined the value
  260. // returned was always TRUE.
  261. if ($temporary_entity_type->isTranslatable()) {
  262. $entity->set($revision_translation_affected_key, TRUE);
  263. }
  264. // Treat the entity as new in order to make the storage do an INSERT
  265. // rather than an UPDATE.
  266. $entity->enforceIsNew(TRUE);
  267. // Finally, save the entity in the temporary storage.
  268. $storage->save($entity);
  269. }
  270. catch (\Exception $e) {
  271. // In case of an error during the save process, we need to roll back the
  272. // original entity type and field storage definitions and clean up the
  273. // temporary tables.
  274. $this->restoreOriginalDefinitions($sandbox);
  275. foreach ($temporary_table_mapping->getTableNames() as $table_name) {
  276. $this->database->schema()->dropTable($table_name);
  277. }
  278. // Re-throw the original exception with a helpful message.
  279. throw new EntityStorageException("The entity update process failed while processing the entity {$original_entity_type->id()}:$entity_id.", $e->getCode(), $e);
  280. }
  281. $sandbox['progress']++;
  282. $sandbox['current_id'] = $entity_id;
  283. }
  284. // If we're not in maintenance mode, the number of entities could change at
  285. // any time so make sure that we always use the latest record count.
  286. $sandbox['max'] = $this->database->select($original_base_table)
  287. ->countQuery()
  288. ->execute()
  289. ->fetchField();
  290. $sandbox['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['progress'] / $sandbox['max']);
  291. }
  292. /**
  293. * Updates field definitions to be revisionable.
  294. *
  295. * @param \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type
  296. * A content entity type definition.
  297. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface[] $storage_definitions
  298. * An array of field storage definitions.
  299. * @param array $fields_to_update
  300. * (optional) An array of field names for which to enable revision support.
  301. * Defaults to an empty array.
  302. * @param bool $update_cached_definitions
  303. * (optional) Whether to update the cached field storage definitions in the
  304. * entity definition update manager. Defaults to TRUE.
  305. *
  306. * @return \Drupal\Core\Field\FieldStorageDefinitionInterface[]
  307. * An array of updated field storage definitions.
  308. */
  309. protected function updateFieldStorageDefinitionsToRevisionable(ContentEntityTypeInterface $entity_type, array $storage_definitions, array $fields_to_update = [], $update_cached_definitions = TRUE) {
  310. $updated_storage_definitions = array_map(function ($storage_definition) {
  311. return clone $storage_definition;
  312. }, $storage_definitions);
  313. // Update the 'langcode' field manually, as it is configured in the base
  314. // content entity field definitions.
  315. if ($entity_type->hasKey('langcode')) {
  316. $fields_to_update = array_merge([$entity_type->getKey('langcode')], $fields_to_update);
  317. }
  318. foreach ($fields_to_update as $field_name) {
  319. if (!$updated_storage_definitions[$field_name]->isRevisionable()) {
  320. $updated_storage_definitions[$field_name]->setRevisionable(TRUE);
  321. if ($update_cached_definitions) {
  322. $this->entityDefinitionUpdateManager->updateFieldStorageDefinition($updated_storage_definitions[$field_name]);
  323. }
  324. }
  325. }
  326. // Add the revision ID field.
  327. $revision_field = BaseFieldDefinition::create('integer')
  328. ->setName($entity_type->getKey('revision'))
  329. ->setTargetEntityTypeId($entity_type->id())
  330. ->setTargetBundle(NULL)
  331. ->setLabel(new TranslatableMarkup('Revision ID'))
  332. ->setReadOnly(TRUE)
  333. ->setSetting('unsigned', TRUE);
  334. if ($update_cached_definitions) {
  335. $this->entityDefinitionUpdateManager->installFieldStorageDefinition($revision_field->getName(), $entity_type->id(), $entity_type->getProvider(), $revision_field);
  336. }
  337. $updated_storage_definitions[$entity_type->getKey('revision')] = $revision_field;
  338. // Add the default revision flag field.
  339. $field_name = $entity_type->getRevisionMetadataKey('revision_default');
  340. $storage_definition = BaseFieldDefinition::create('boolean')
  341. ->setName($field_name)
  342. ->setTargetEntityTypeId($entity_type->id())
  343. ->setTargetBundle(NULL)
  344. ->setLabel(t('Default revision'))
  345. ->setDescription(t('A flag indicating whether this was a default revision when it was saved.'))
  346. ->setStorageRequired(TRUE)
  347. ->setTranslatable(FALSE)
  348. ->setRevisionable(TRUE);
  349. if ($update_cached_definitions) {
  350. $this->entityDefinitionUpdateManager->installFieldStorageDefinition($field_name, $entity_type->id(), $entity_type->getProvider(), $storage_definition);
  351. }
  352. $updated_storage_definitions[$field_name] = $storage_definition;
  353. // Add the 'revision_translation_affected' field if needed.
  354. if ($entity_type->isTranslatable()) {
  355. $revision_translation_affected_field = BaseFieldDefinition::create('boolean')
  356. ->setName($entity_type->getKey('revision_translation_affected'))
  357. ->setTargetEntityTypeId($entity_type->id())
  358. ->setTargetBundle(NULL)
  359. ->setLabel(new TranslatableMarkup('Revision translation affected'))
  360. ->setDescription(new TranslatableMarkup('Indicates if the last edit of a translation belongs to current revision.'))
  361. ->setReadOnly(TRUE)
  362. ->setRevisionable(TRUE)
  363. ->setTranslatable(TRUE);
  364. if ($update_cached_definitions) {
  365. $this->entityDefinitionUpdateManager->installFieldStorageDefinition($revision_translation_affected_field->getName(), $entity_type->id(), $entity_type->getProvider(), $revision_translation_affected_field);
  366. }
  367. $updated_storage_definitions[$entity_type->getKey('revision_translation_affected')] = $revision_translation_affected_field;
  368. }
  369. return $updated_storage_definitions;
  370. }
  371. /**
  372. * Collects the original definitions of an entity type and its fields.
  373. *
  374. * @param array &$sandbox
  375. * A sandbox array from a hook_update_N() implementation.
  376. */
  377. protected function collectOriginalDefinitions(array &$sandbox) {
  378. $original_entity_type = $this->lastInstalledSchemaRepository->getLastInstalledDefinition($this->entityTypeId);
  379. $original_storage_definitions = $this->lastInstalledSchemaRepository->getLastInstalledFieldStorageDefinitions($this->entityTypeId);
  380. /** @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage $storage */
  381. $storage = $this->entityTypeManager->getStorage($this->entityTypeId);
  382. $storage->setEntityType($original_entity_type);
  383. $original_table_mapping = $storage->getTableMapping($original_storage_definitions);
  384. $sandbox['original_entity_type'] = $original_entity_type;
  385. $sandbox['original_storage_definitions'] = $original_storage_definitions;
  386. $sandbox['original_table_mapping'] = $original_table_mapping;
  387. $sandbox['original_entity_schema_data'] = $this->installedStorageSchema->get($this->entityTypeId . '.entity_schema_data', []);
  388. foreach ($original_storage_definitions as $storage_definition) {
  389. $sandbox['original_field_schema_data'][$storage_definition->getName()] = $this->installedStorageSchema->get($this->entityTypeId . '.field_schema_data.' . $storage_definition->getName(), []);
  390. }
  391. }
  392. /**
  393. * Restores the entity type, field storage definitions and their schema data.
  394. *
  395. * @param array $sandbox
  396. * The sandbox array from a hook_update_N() implementation.
  397. */
  398. protected function restoreOriginalDefinitions(array $sandbox) {
  399. $original_entity_type = $sandbox['original_entity_type'];
  400. $original_storage_definitions = $sandbox['original_storage_definitions'];
  401. $original_entity_schema_data = $sandbox['original_entity_schema_data'];
  402. $original_field_schema_data = $sandbox['original_field_schema_data'];
  403. $this->lastInstalledSchemaRepository->setLastInstalledDefinition($original_entity_type);
  404. $this->lastInstalledSchemaRepository->setLastInstalledFieldStorageDefinitions($original_entity_type->id(), $original_storage_definitions);
  405. $this->installedStorageSchema->set($original_entity_type->id() . '.entity_schema_data', $original_entity_schema_data);
  406. foreach ($original_field_schema_data as $field_name => $field_schema_data) {
  407. $this->installedStorageSchema->set($original_entity_type->id() . '.field_schema_data.' . $field_name, $field_schema_data);
  408. }
  409. }
  410. /**
  411. * Creates temporary entity type, field storage and table mapping objects.
  412. *
  413. * @param array &$sandbox
  414. * A sandbox array from a hook_update_N() implementation.
  415. * @param string[] $fields_to_update
  416. * (optional) An array of field names that should be converted to be
  417. * revisionable. Note that the 'langcode' field, if present, is updated
  418. * automatically. Defaults to an empty array.
  419. */
  420. protected function createTemporaryDefinitions(array &$sandbox, array $fields_to_update) {
  421. // Make sure to get the latest entity type definition from code.
  422. $this->entityTypeManager->useCaches(FALSE);
  423. $actual_entity_type = $this->entityTypeManager->getDefinition($this->entityTypeId);
  424. $temporary_entity_type = clone $actual_entity_type;
  425. $temporary_entity_type->set('base_table', TemporaryTableMapping::getTempTableName($temporary_entity_type->getBaseTable()));
  426. $temporary_entity_type->set('revision_table', TemporaryTableMapping::getTempTableName($temporary_entity_type->getRevisionTable()));
  427. if ($temporary_entity_type->isTranslatable()) {
  428. $temporary_entity_type->set('data_table', TemporaryTableMapping::getTempTableName($temporary_entity_type->getDataTable()));
  429. $temporary_entity_type->set('revision_data_table', TemporaryTableMapping::getTempTableName($temporary_entity_type->getRevisionDataTable()));
  430. }
  431. /** @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage $storage */
  432. $storage = $this->entityTypeManager->getStorage($this->entityTypeId);
  433. $storage->setTemporary(TRUE);
  434. $storage->setEntityType($temporary_entity_type);
  435. $updated_storage_definitions = $this->updateFieldStorageDefinitionsToRevisionable($temporary_entity_type, $sandbox['original_storage_definitions'], $fields_to_update, FALSE);
  436. $temporary_table_mapping = $storage->getTableMapping($updated_storage_definitions);
  437. $sandbox['temporary_entity_type'] = $temporary_entity_type;
  438. $sandbox['temporary_table_mapping'] = $temporary_table_mapping;
  439. $sandbox['updated_storage_definitions'] = $updated_storage_definitions;
  440. }
  441. }