/** * {@inheritdoc} */ public function rollback() { // Only begin the rollback operation if the migration is currently idle. if ($this->migration->getStatus() !== MigrationInterface::STATUS_IDLE) { $this->message->display($this->t('Migration @id is busy with another operation: @status', ['@id' => $this->migration->id(), '@status' => $this->t($this->migration->getStatusLabel())]), 'error'); return MigrationInterface::RESULT_FAILED; } // Announce that rollback is about to happen. $this->getEventDispatcher()->dispatch(MigrateEvents::PRE_ROLLBACK, new MigrateRollbackEvent($this->migration)); // Optimistically assume things are going to work out; if not, $return will be // updated to some other status. $return = MigrationInterface::RESULT_COMPLETED; $this->migration->setStatus(MigrationInterface::STATUS_ROLLING_BACK); $id_map = $this->migration->getIdMap(); $destination = $this->migration->getDestinationPlugin(); // Loop through each row in the map, and try to roll it back. foreach ($id_map as $map_row) { $destination_key = $id_map->currentDestination(); if ($destination_key) { $map_row = $id_map->getRowByDestination($destination_key); if ($map_row['rollback_action'] == MigrateIdMapInterface::ROLLBACK_DELETE) { $this->getEventDispatcher()->dispatch(MigrateEvents::PRE_ROW_DELETE, new MigrateRowDeleteEvent($this->migration, $destination_key)); $destination->rollback($destination_key); $this->getEventDispatcher()->dispatch(MigrateEvents::POST_ROW_DELETE, new MigrateRowDeleteEvent($this->migration, $destination_key)); } // We're now done with this row, so remove it from the map. $id_map->deleteDestination($destination_key); } else { // If there is no destination key the import probably failed and we can // remove the row without further action. $source_key = $id_map->currentSource(); $id_map->delete($source_key); } // Check for memory exhaustion. if (($return = $this->checkStatus()) != MigrationInterface::RESULT_COMPLETED) { break; } // If anyone has requested we stop, return the requested result. if ($this->migration->getStatus() == MigrationInterface::STATUS_STOPPING) { $return = $this->migration->getInterruptionResult(); $this->migration->clearInterruptionResult(); break; } } // If rollback completed successfully, reset the high water mark. if ($return == MigrationInterface::RESULT_COMPLETED) { $this->migration->saveHighWater(NULL); } // Notify modules that rollback attempt was complete. $this->getEventDispatcher()->dispatch(MigrateEvents::POST_ROLLBACK, new MigrateRollbackEvent($this->migration)); $this->migration->setStatus(MigrationInterface::STATUS_IDLE); return $return; }
/** * Create the map and message tables if they don't already exist. */ protected function ensureTables() { if (!$this->getDatabase()->schema()->tableExists($this->mapTableName)) { // Generate appropriate schema info for the map and message tables, // and map from the source field names to the map/msg field names. $count = 1; $source_id_schema = array(); foreach ($this->migration->getSourcePlugin()->getIds() as $id_definition) { $mapkey = 'sourceid' . $count++; $source_id_schema[$mapkey] = $this->getFieldSchema($id_definition); $source_id_schema[$mapkey]['not null'] = TRUE; } $source_ids_hash[static::SOURCE_IDS_HASH] = array('type' => 'varchar', 'length' => '64', 'not null' => TRUE, 'description' => 'Hash of source ids. Used as primary key'); $fields = $source_ids_hash + $source_id_schema; // Add destination identifiers to map table. // @todo How do we discover the destination schema? $count = 1; foreach ($this->migration->getDestinationPlugin()->getIds() as $id_definition) { // Allow dest identifier fields to be NULL (for IGNORED/FAILED cases). $mapkey = 'destid' . $count++; $fields[$mapkey] = $this->getFieldSchema($id_definition); $fields[$mapkey]['not null'] = FALSE; } $fields['source_row_status'] = array('type' => 'int', 'size' => 'tiny', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => MigrateIdMapInterface::STATUS_IMPORTED, 'description' => 'Indicates current status of the source row'); $fields['rollback_action'] = array('type' => 'int', 'size' => 'tiny', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => MigrateIdMapInterface::ROLLBACK_DELETE, 'description' => 'Flag indicating what to do for this item on rollback'); $fields['last_imported'] = array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0, 'description' => 'UNIX timestamp of the last time this row was imported'); $fields['hash'] = array('type' => 'varchar', 'length' => '64', 'not null' => FALSE, 'description' => 'Hash of source row data, for detecting changes'); $schema = array('description' => 'Mappings from source identifier value(s) to destination identifier value(s).', 'fields' => $fields, 'primary key' => array(static::SOURCE_IDS_HASH)); $this->getDatabase()->schema()->createTable($this->mapTableName, $schema); // Now do the message table. if (!$this->getDatabase()->schema()->tableExists($this->messageTableName())) { $fields = array(); $fields['msgid'] = array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE); $fields += $source_ids_hash; $fields['level'] = array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 1); $fields['message'] = array('type' => 'text', 'size' => 'medium', 'not null' => TRUE); $schema = array('description' => 'Messages generated during a migration process', 'fields' => $fields, 'primary key' => array('msgid')); $this->getDatabase()->schema()->createTable($this->messageTableName(), $schema); } } else { // Add any missing columns to the map table. if (!$this->getDatabase()->schema()->fieldExists($this->mapTableName, 'rollback_action')) { $this->getDatabase()->schema()->addField($this->mapTableName, 'rollback_action', array('type' => 'int', 'size' => 'tiny', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0, 'description' => 'Flag indicating what to do for this item on rollback')); } if (!$this->getDatabase()->schema()->fieldExists($this->mapTableName, 'hash')) { $this->getDatabase()->schema()->addField($this->mapTableName, 'hash', array('type' => 'varchar', 'length' => '64', 'not null' => FALSE, 'description' => 'Hash of source row data, for detecting changes')); } if (!$this->getDatabase()->schema()->fieldExists($this->mapTableName, static::SOURCE_IDS_HASH)) { $this->getDatabase()->schema()->addField($this->mapTableName, static::SOURCE_IDS_HASH, array('type' => 'varchar', 'length' => '64', 'not null' => TRUE, 'description' => 'Hash of source ids. Used as primary key')); } } }