Custom MigrateMap to sync by field or property, e.g. SKU Existing situation: MigrateSQLMap

Normally with migrate API, a migration will use MigrateSQLMap to identify imported rows with existing entities.

This means: A database table named like migrate_map_* will be created for each migration. The * part of the table name is based on the migration machine name. This table associates primary key of the source data (e.g. a primary key column of a csv file) to entity ids.

Whenever a row is to be imported, Migrate API will look into the respective migrate_map_* table, and check if a row with this id was already imported, and if the imported entity still exists. If it does, it will update this existing entity, and update the value last_imported in the migrate map table. If it does not, it will create a new entity, and create a new entry in the migrate_map_* table.

What I want instead: Map to existing entities.

Now what if instead of creating new entities, I would like to update existing entities which were previously created with another mechanism, unknown to Migrate API? E.g. these might have been created manually, or with a home-grown migration script.

In my case, these entities are commerce products, which were created with a custom script which is to be replaced with Migrate API.

The idea is:


Compute an SKU from the imported csv row.
Find out if a product with this SKU already exists.


If yes, update.
If no, create a new product.

If possible, save the import timestamp somewhere.


Solutions I can think of so  far

I can imagine two possible solutions.


A custom migrate map class SkuMigrateMap extends MigrateMap.
Seeing how many abstract methods MigrateMap has, this could be quite a lot of work.
Use MigrateSQLMap, but prefill the table based on existing products.


Question

Is there an existing or recommended solution for this somewhere?
The map class doesn't (and shouldn't) know anything about either your source or your destination - its job is simply tracking the relationships between their keys.

I've done this in the migration's prepareRow(), doing an explicit lookup for a matching destination object, and if found saving that destination ID to the map and returning FALSE to prevent further processing. Look at how migrate_d2d maps user accounts based on email:

// On initial import, if this email address already exists, just map to the
// existing uid and don't allow it to be deleted. When updating, we can
// take the existing uid from the map instead of querying for it. Make sure
// when updating an imported user that we don't apply this logic.
if (empty($row-&gt;migrate_map_destid1)) {
  $new_uid = db_select('users', 'u')
             -&gt;fields('u', array('uid'))
             -&gt;condition('mail', $row-&gt;mail)
             -&gt;execute()
             -&gt;fetchField();
  // If we haven't mapped the admin account based on email address, map it
  // directly to the destination admin account.
  if (!$new_uid &amp;&amp; $row-&gt;uid == 1) {
    $new_uid = 1;
  }
}
elseif ($row-&gt;migrate_map_needs_update == MigrateMap::STATUS_IGNORED &amp;&amp;
        $row-&gt;migrate_map_rollback_action == MigrateMap::ROLLBACK_PRESERVE) {
  $new_uid = $row-&gt;migrate_map_destid1;
}

if (!empty($new_uid)) {
  $hash = isset($row-&gt;migrate_map_hash) ? $row-&gt;migrate_map_hash : NULL;
  $this-&gt;map-&gt;saveIDMapping($row, array($new_uid), MigrateMap::STATUS_IGNORED,
    MigrateMap::ROLLBACK_PRESERVE, $hash);
  $this-&gt;rollbackAction = MigrateMap::ROLLBACK_PRESERVE;
  return FALSE;
}