diff --git a/includes/map.inc b/includes/map.inc index 913ec72..ed76db5 100644 --- a/includes/map.inc +++ b/includes/map.inc @@ -19,6 +19,13 @@ abstract class MigrateMap implements Iterator { const STATUS_FAILED = 3; /** + * Codes reflecting how to handle the destination item on rollback. + * + */ + const ROLLBACK_DELETE = 0; + const ROLLBACK_PRESERVE = 1; + + /** * Arrays of key fields for the source and destination. Array keys are the * field names - array values are specific to the concrete map class. * @@ -50,9 +57,10 @@ abstract class MigrateMap implements Iterator { * @param $source_row * @param $dest_ids * @param $status + * @param $rollback_action */ abstract public function saveIDMapping(stdClass $source_row, array $dest_ids, - $status = MigrateMap::STATUS_IMPORTED); + $status = MigrateMap::STATUS_IMPORTED, $rollback_action = MigrateMap::ROLLBACK_DELETE); /** * Record a message related to a source record diff --git a/includes/migration.inc b/includes/migration.inc index cad3697..131da9a 100644 --- a/includes/migration.inc +++ b/includes/migration.inc @@ -69,6 +69,21 @@ abstract class Migration extends MigrationBase { public $needsUpdate = MigrateMap::STATUS_IMPORTED; /** + * The default rollback action for this migration. Can be overridden on + * a per-row basis by setting $row->rollbackAction in prepareRow(). + * + * @var int + */ + protected $defaultRollbackAction = MigrateMap::ROLLBACK_DELETE; + + /** + * The rollback action to be saved for the current row. + * + * @var int + */ + public $rollbackAction; + + /** * Simple mappings between destination fields (keys) and source fields (values). * * @var array @@ -398,7 +413,10 @@ abstract class Migration extends MigrationBase { // Note that bulk rollback is only supported for single-column keys $sourceids[] = $this->currentSourceKey; if (!empty($destination_key->destid1)) { - $destids[] = $destination_key->destid1; + $map_row = $this->map->getRowByDestination((array)$destination_key); + if ($map_row['rollback_action'] == MigrateMap::ROLLBACK_DELETE) { + $destids[] = $destination_key->destid1; + } } $batch_count++; @@ -479,9 +497,12 @@ abstract class Migration extends MigrationBase { } } if (!$skip) { - migrate_instrument_start('destination rollback'); - $this->destination->rollback((array)$destination_key); - migrate_instrument_stop('destination rollback'); + $map_row = $this->map->getRowByDestination((array)$destination_key); + if ($map_row['rollback_action'] == MigrateMap::ROLLBACK_DELETE) { + migrate_instrument_start('destination rollback'); + $this->destination->rollback((array)$destination_key); + migrate_instrument_stop('destination rollback'); + } } } @@ -543,24 +564,28 @@ abstract class Migration extends MigrationBase { $ids = $this->destination->import($this->destinationValues, $this->sourceValues); migrate_instrument_stop('destination import'); if ($ids) { - $this->map->saveIDMapping($this->sourceValues, $ids, $this->needsUpdate); + $this->map->saveIDMapping($this->sourceValues, $ids, $this->needsUpdate, + $this->rollbackAction); $this->successes_since_feedback++; $this->total_successes++; } else { - $this->map->saveIDMapping($this->sourceValues, array(), MigrateMap::STATUS_FAILED); + $this->map->saveIDMapping($this->sourceValues, array(), + MigrateMap::STATUS_FAILED, $this->rollbackAction); $message = t('New object was not saved, no error provided'); $this->saveMessage($message); self::displayMessage($message); } } catch (MigrateException $e) { - $this->map->saveIDMapping($this->sourceValues, array(), MigrateMap::STATUS_FAILED); + $this->map->saveIDMapping($this->sourceValues, array(), + MigrateMap::STATUS_FAILED, $this->rollbackAction); $this->saveMessage($e->getMessage(), $e->getLevel()); self::displayMessage($e->getMessage()); } catch (Exception $e) { - $this->map->saveIDMapping($this->sourceValues, array(), MigrateMap::STATUS_FAILED); + $this->map->saveIDMapping($this->sourceValues, array(), + MigrateMap::STATUS_FAILED, $this->rollbackAction); $this->handleException($e); } $this->total_processed++; @@ -737,6 +762,7 @@ abstract class Migration extends MigrationBase { * TRUE to process this row, FALSE to have the source skip it. */ public function prepareRow($row) { + $this->rollbackAction = $this->defaultRollbackAction; return TRUE; } @@ -1246,7 +1272,8 @@ abstract class Migration extends MigrationBase { foreach ($map_source_key as $key => $definition) { $data_row->$key = $source_key[$i++]; } - $this->map->saveIDMapping($data_row, $destids, MigrateMap::STATUS_NEEDS_UPDATE); + $this->map->saveIDMapping($data_row, $destids, + MigrateMap::STATUS_NEEDS_UPDATE, $this->defaultRollbackAction); } } else { diff --git a/includes/source.inc b/includes/source.inc index be7c43d..fc15834 100644 --- a/includes/source.inc +++ b/includes/source.inc @@ -353,8 +353,8 @@ abstract class MigrateSource implements Iterator { migrate_instrument_stop(get_class($this->activeMigration) . ' prepareRow'); // We're explicitly skipping this row - keep track in the map table if ($return === FALSE) { - $this->activeMigration->getMap()->saveIDMapping($row, array(NULL), - MigrateMap::STATUS_IGNORED); + $this->activeMigration->getMap()->saveIDMapping($row, array(), + MigrateMap::STATUS_IGNORED, $this->activeMigration->rollbackAction); $this->numIgnored++; $this->currentRow = NULL; $this->currentKey = NULL; diff --git a/migrate.install b/migrate.install index 703a226..87d469a 100644 --- a/migrate.install +++ b/migrate.install @@ -361,3 +361,24 @@ function migrate_update_7201() { Handling files in Drupal 7 for more information', array('@doc' => 'http://drupal.org/node/1540106')); } + +/** + * Add rollback_action field to all map tables + */ +function migrate_update_7202() { + $ret = array(); + foreach (db_find_tables('migrate_map_%') as $tablename) { + if (!db_field_exists($tablename, 'rollback_action')) { + db_add_field($tablename, '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', + )); + } + } + $ret[] = t('Added rollback_action column to all map tables'); + return $ret; +} diff --git a/plugins/sources/sqlmap.inc b/plugins/sources/sqlmap.inc index bad2637..744bcb1 100644 --- a/plugins/sources/sqlmap.inc +++ b/plugins/sources/sqlmap.inc @@ -129,6 +129,14 @@ class MigrateSQLMap extends MigrateMap { 'default' => MigrateMap::STATUS_IMPORTED, 'description' => 'Indicates current status of the source row', ); + $fields['rollback_action'] = array( + 'type' => 'int', + 'size' => 'tiny', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => MigrateMap::ROLLBACK_DELETE, + 'description' => 'Flag indicating what to do for this item on rollback', + ); $fields['last_imported'] = array( 'type' => 'int', 'unsigned' => TRUE, @@ -198,14 +206,14 @@ class MigrateSQLMap extends MigrateMap { * @param array $source_id */ public function getRowByDestination(array $destination_id) { - migrate_instrument_start('mapRowByDestination'); + migrate_instrument_start('getRowByDestination'); $query = $this->connection->select($this->mapTable, 'map') ->fields('map'); foreach ($this->destinationKeyMap as $key_name) { $query = $query->condition("map.$key_name", array_shift($destination_id), '='); } $result = $query->execute(); - migrate_instrument_stop('mapRowByDestination'); + migrate_instrument_stop('getRowByDestination'); return $result->fetchAssoc(); } @@ -275,9 +283,9 @@ class MigrateSQLMap extends MigrateMap { } /** - * Called upon successful import of one record, we record a mapping from - * the source key to the destination key. Also may be called, setting the - * third parameter to NEEDS_UPDATE, to signal an existing record should be remigrated. + * Called upon import of one record, we record a mapping from the source key + * to the destination key. Also may be called, setting the third parameter to + * NEEDS_UPDATE, to signal an existing record should be remigrated. * * @param stdClass $source_row * The raw source data. We use the key map derived from the source object @@ -286,8 +294,12 @@ class MigrateSQLMap extends MigrateMap { * The destination key values. * @param int $needs_update * Status of the source row in the map. Defaults to STATUS_IMPORTED. + * @param int $rollback_action + * How to handle the destination object on rollback. Defaults to + * ROLLBACK_DELETE. */ - public function saveIDMapping(stdClass $source_row, array $dest_ids, $needs_update = MigrateMap::STATUS_IMPORTED) { + public function saveIDMapping(stdClass $source_row, array $dest_ids, + $needs_update = MigrateMap::STATUS_IMPORTED, $rollback_action = MigrateMap::ROLLBACK_DELETE) { migrate_instrument_start('saveIDMapping'); // Construct the source key $keys = array(); @@ -303,10 +315,15 @@ class MigrateSQLMap extends MigrateMap { $keys[$key_name] = $source_row->$field_name; } - $fields = array('needs_update' => (int)$needs_update); + $fields = array( + 'needs_update' => (int)$needs_update, + 'rollback_action' => (int)$rollback_action, + ); $count = 1; - foreach ($dest_ids as $dest_id) { - $fields['destid' . $count++] = $dest_id; + if (!empty($dest_ids)) { + foreach ($dest_ids as $dest_id) { + $fields['destid' . $count++] = $dest_id; + } } if ($this->trackLastImported) { $fields['last_imported'] = time();