The migration lookup plugin can be used in two ways:

  uid:
    plugin: migration_lookup
    migration: users
    source: author

There's a single source key, and its value is taken from the 'author' value in the row.

  uid:
    plugin: migration_lookup
      migration:
        - users
        - members
      source_ids:
        users:
          - author
        members:
          - id

The plugin will try multiple migrations in turn, and for each migration to lookup, the source is specified for each one.

However, migrations often have more than one source key.

Because of how lookupDestinationIds() works, the key used by the lookup plugin is assumed to be the first one:

   * @param array $source_id_values
   *   The source identifier keyed values of the records, e.g. ['nid' => 5].
   *   If unkeyed, the first count($source_id_values) keys will be assumed.

What's missing is the ability to specify specific source keys. E.g. the migration to use as a lookup might have keys alpha, beta, gamma, and in migration needs to look up using the beta key.

Issue fork drupal-2961428

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

joachim created an issue. See original summary.

quietone’s picture

Not sure about this. There are two instances in core where multiple source ids are used in a migration, both in d7_field_formatter_settings.

      plugin: migration_lookup
      migration: d7_field
      source:
        - field_name
        - entity_type

and

      plugin: migration_lookup
      migration: d7_view_modes
      source:
        - entity_type
        - view_mode

Based on that, having multiple source ids is available.

That still leaves the second point, doing a lookup using a subset of the keys. I'm curious, what is the use case where this is needed?

joachim’s picture

      source:
        - field_name
        - entity_type

That's could do to be documented!

The use case is that I need a reference field value, but I don't have the entity ID for those items in the source.

More specifically, I had Commerce License entities, which don't exist in my source D6 DB and so are migrated from custom tables joined to the Ubercart order_product table.

When I migrate Order Items (from the order_product table), I have the order_product ID to use to get a lookup to the migrated License. That's a source key in the License migration, but not the first key.

joachim’s picture

It occurs to me that if a source migration has keys alpha, beta, gamma, and you want to look up with the gamma key, this might work:

      plugin: migration_lookup
      migration: d7_field
      source:
        - constants/empty
        - constants/empty
        - actual_value

where constants/empty is defined as a constant in the source section of the YAML that's either '' or NULL -- depending on what works with the MigrationLookup plugin. Would that actually work to mean those keys are to be ignored, or would the lookup query for a literal NULL or ''?

If it does work, it's still very fiddly though, and would need documenting.

joachim’s picture

> When I migrate Order Items (from the order_product table), I have the order_product ID to use to get a lookup to the migrated License. That's a source key in the License migration, but not the first key.

To expand on my use case in reply to your question on slack:

IIRC my source keys in my license migration are:

- the source custom table's primary key - this is enough to be unique for this migration, but other dependent migrations won't have this ID to query by
- OG group node ID (which the license grants membership of) - mostly used for checking the data after import. not unique
- User ID (together with the group node ID forms a unique key)
- Commerce order item ID - this is also enough to be unique for this migration

And my dependent migration is doing Commerce Order items. The order items have a reference field to the license. I specifically added the order item ID key to the licenses migration so I could use it in the lookup, as otherwise I'd need some fiddly queries to get the other source key values.

joachim’s picture

Here's another use case for this, in Commerce Migrate's Ubercart D6 migrations:

We have a billing profile migration which uses orders as its source, but queries to get only the most recent order for each user. So not every souce order will produce a destination billing profile.

When it comes to the order migration, there is a lookup to find the value for the entity reference field that points to the billing profile.

Currently, there is a bug because this is using the order ID for the lookup -- but not every source order ID has a destination billing profile ID, so the lookup will often fail. (#2969268: migrated orders can fail to get a billing profile).

One way to fix this which would be fairly elegant would be to use the order's uid as the lookup value. But from a data structure point of view, it makes sense to keep the billing profile migration's sourceid1 as the source order ID, since that is the primary key of the source table.

So for the lookup, we would add a 2nd source ID to the migration map, which would be the uid.

We'd then want the order migration's billing_profile field to do a migration lookup to the billing_profile migration, using the uid value as the source...

And that's where this bug comes in -- we can't do that.

joachim’s picture

Assigned: Unassigned » joachim

This is actually *really* close to being doable. Doing a lookup query on the map with named source IDs is supported, as the docs for MigrateIdMapInterface::lookupDestinationIds() say:

   * This can look up a subset of source keys if only some are provided, and
   * will return all destination keys that match.

and the code for the Sql map implementation clearly supports it:

    foreach ($this->sourceIdFields() as $field_name => $db_field) {
      if ($is_associative) {
        // Associative $source_id_values can have fields out of order.
        if (isset($source_id_values[$field_name])) {
          $conditions[$db_field] = $source_id_values[$field_name];
          unset($source_id_values[$field_name]);
        }
      }

However, if I do this in my migration:

  my_destination:
    plugin: migration_lookup
    migration:
      - my_lookup_migration
    source:
      my_key_name: my_source_field

then the $value received by MigrationLookup::transform() is merely a numeric array -- the 'my_key_name' part is lost.

Losing that key though is fundamental to how Migrate works -- it's done in MigrateExecutable. So I don't think we can change that.

I'm having a look to see whether the lookup plugin could be tweaked so the source_ids configuration has keys...

joachim’s picture

Assigned: joachim » Unassigned
Status: Active » Needs review
StatusFileSize
new2.32 KB

Figured it out.

Here's a patch.

joachim’s picture

Hmm the docs should be tweaked to say that you can use 'source_ids' without 'migrations', just to specify the IDs.

Status: Needs review » Needs work
joachim’s picture

joachim’s picture

Status: Needs work » Needs review

No, that fixed something else. It still doesn't allow you to specify *which* of the migration's source keys to use. So with this example:

  public function getIds() {
    return [
      'type' => [
        'type' => 'integer',
        'alias' => 'cm',
      ],
      'id' => [
        'type' => 'integer',
        'alias' => 'cm',
      ],
    ];

I can't specify in my migration that I will be passing in the value for the 'id' source key only.

That's because of how this code works:

      if (isset($this->configuration['source_ids'][$migration_id])) {
        $configuration = ['source' => $this->configuration['source_ids'][$migration_id]];
        $value = $this->processPluginManager
          ->createInstance('get', $configuration, $this->migration)
          ->transform(NULL, $migrate_executable, $row, $destination_property);
      }

Here $value will always be a numeric array -- that what Get::transform returns.

      if ($destination_ids = $migration->getIdMap()->lookupDestinationId($source_id_values[$migration_id])) {

So we can never pass keys to lookupDestinationId() to tell it which IDs we are passing values for.

tacituseu’s picture

I think I get it now, it's about allowing lookup with using only a subset of keys defined in getIds().

joachim’s picture

> it's about allowing lookup with using only a subset of keys defined in getIds().

Exactly.

Currently, the only kind of subset you can do is a slice starting at the beginning.

maxocub’s picture

Assigned: Unassigned » maxocub

Assinging for review

jofitz’s picture

StatusFileSize
new1.53 KB
new4 KB

Fixing errors in test.

maxocub’s picture

Assigned: maxocub » Unassigned
Status: Needs review » Needs work
  1. +++ b/core/modules/migrate/src/Plugin/migrate/process/MigrationLookup.php
    @@ -48,10 +48,16 @@
    + * It is possible to specify with source IDs to use in the lookup migration's
    

    s/with/which

  2. +++ b/core/modules/migrate/src/Plugin/migrate/process/MigrationLookup.php
    @@ -48,10 +48,16 @@
    + * map, for each lookup migration if more than one is involved. For this, use
    

    s/migration/migrations

  3. +++ b/core/modules/migrate/src/Plugin/migrate/process/MigrationLookup.php
    @@ -63,7 +69,8 @@
    - *           - id
    + *           key3: id
    + *           key1: name
    

    I think it would be good to keep the example with multiple migrations as it is (and maybe the doc too?) and add a new section about the way we can specify the keys and a new example with only one migration. I think that someone looking at the doc would maybe miss this feature seeing it used in a multiple migrations scenario.

joachim’s picture

Thanks for the test fix and the review!

> + * map, for each lookup migration if more than one is involved. For this, use

That's correct as a singular.

Other points addressed in the new patch.

masipila’s picture

Hi!

@maxocub asked me to chime in for the documentation part of this. I read through the issue, comments and the patch twice. I totally second that we need to improve the documentation on this in addition to fixing the bug itself.

The use case described in #6 is very practical. I propose we will use that as an example and write a handbook documentation page with a diagram which makes it easier to understand what we're trying to do. The API docblock of the migration_lookup process plugin should then have a @see reference to this handbook page.

I created a stub handbook page for this: https://www.drupal.org/docs/8/api/migrate-api/migrate-process-plugins/us...

Proposed next steps for documentation:

  • @joachim, could you kindly draw a picture of what we're trying to achieve in the use case you shared in #6 and add it to the handbook page?
  • One user can have multiple orders
  • But how are the relations with the billing profiles? Is it so that one user can have multiple billing profiles but only one of them is selected for each order? Or how does the data model go?
  • So if you could please draw this as a picture and then share an example YAML that can be used to achieve what we need to achieve in that use case (provided that the patch is in). That will save hours and hours from people who are working with same kind of complex data structures.

Cheers,
Markus

joachim’s picture

> @joachim, could you kindly draw a picture of what we're trying to achieve in the use case you shared in #6 and add it to the handbook page?

Sorry, I have no idea what a picture of that should look like.

masipila’s picture

@joachim ok, can you explain the relations for me and I'll try to draw the picture?

One user can have multiple orders.
But how are the relations with the billing profiles? Is it so that one user can have multiple billing profiles but only one of them is selected for each order? Or how does the data model go? What are the field names of these relations?

Cheers,
Markus

joachim’s picture

That example is maybe no longer appropriate, as there is work happening on another Commerce Migrate issue to change that migration to work differently.

It's also a rather complicated example to illustrate this functionality, which is perhaps better expressed like this:

- sometimes you want to use a lookup migration for a value, but instead of that migration's primary key, the value you have available is another field, which is also unique for that migration

brooke_heaton’s picture

This is a brilliant patch and is working for me with the following config:

process:
  field_committee_region/target_id:
    plugin: migration_lookup
    migration: region
    source_ids:
      region:
        committee_position_name: committee_position_name

Regions.php SQL source plugin:

  /**
   * {@inheritdoc}
   */
  public function getIds() {
    return [
      'committee_position_order' => [
        'type' => 'integer',
        'alias' => 'id',
      ],
      'committee_position_name' => [
        'type' => 'text',
        'alias' => 'name',
      ],
    ];
  }

In my use case, I am needing to use a string to match an incoming value to previously imported vocabulary terms. The string (committee_position_name) is the second key (sourceid2) of my Regions.php source, so I am able to set the associative key used in getIds() to match the targeted migration (migration id = region) in migration_lookup. I am migrating two fields from a legacy CMS that both include 'Region' terms but these terms are not unified by a single vocabulary, which is problematic and they really should be merged and not separate. In migrating to Drupal 8, I now have a unified and singular 'Region' vocabulary and I'm needing to match a field for a Paragraph based specifically on a string match of the Region term so that we are using a singular term for things like Region I, Region II etc. I have migrated these terms and included their legacy 'id' but I'm also including the actual term string so that I can key off it for other fields that I want to use the new 'Regions' vocabulary. In doing so, I've merged these legacy fields into a single new 'Regions' vocabulary through the migration. In the configuration above, I have a new field 'field_committee_region' that will fetch the string values from a legacy 'committee_position_name' field and find a matching region string based on the 'region' migration's second key from the migration map table.

Only local images are allowed.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

daften’s picture

Attempt at patch reroll, wasn't able to update the test completely.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

josephdpurcell’s picture

I want to report that I was reading \Drupal\migrate\Plugin\migrate\process\MigrationLookup and was entirely confused how to set multiple source IDs. Then I found this ticket to save the day!!!

The approach described in comment #2 worked for me, without needing to apply a patch. Granted, my use case is very simple.

Thank you for submitting this ticket!

I took a look at https://www.drupal.org/docs/8/api/migrate-api/migrate-process-plugins/us..., I'll make a note to update that documentation this week (if I can).

rob230’s picture

The patch in #29 has the wrong indentation and the test is wrong. I don't understand the changes made in #3004927: Create Migration Lookup and Stub services, so I can't update the test, but here's a rerolled patch for 8.8 and 8.9 without test changes.

quietone’s picture

It would really help move this along if the IS was updated and it looks like the title is no longer accurate.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

dhirendra.mishra’s picture

StatusFileSize
new3.06 KB

re-rolled it manually for 9.3.x

ludo.r’s picture

StatusFileSize
new3.41 KB

This is a re-roll for Drupal 9.2.x

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

bvoynick’s picture

#39 does not look to be updated for a variable name change in 9.2. (Inside the foreach loop, $lookup_value is now used to avoid changing the original $value permanently.)

#40 does use the newer variable name and is applying and working for me on 9.3.14.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

quicksketch’s picture

I couldn't get #40 working. Here's an updated version that works on Drupal 10/11.

bradjones1’s picture

This also needs to be an MR at this point.

acbramley’s picture

As per #47 - needs to go to an MR.

joachim’s picture

Title: migration_lookup process plugin can't use multiple or specific source keys to use for lookup » migration_lookup process plugin can't use specific subset of source keys to use for lookup

Removing

> 1. The ability to specify multiple source keys

from the IS and the title, as this is now possible:

        $lookup_value = array_values($row->getMultiple($this->configuration['source_ids'][$lookup_migration_id]));

mrinalini9 made their first commit to this issue’s fork.

joachim’s picture

Issue summary: View changes
Issue tags: -Needs issue summary update

Updated IS again.

joachim’s picture

There's a backwards compatibility & DX problem we haven't spotted.

We add support for this:

 *       source_ids:
 *         users:
 *           key3: author

but remove support for this, without keys:

 *       source_ids:
 *         users:
 *           author

That's a BC break, and it's also not good DX if you want to specify all the lookup keys.

jofitz’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll

Fixed bug in migrate process plugin.

(no longer needs reroll)

smustgrave’s picture

Status: Needs review » Needs work
Issue tags: +Needs tests, +Needs issue summary update

Think next step would be to get a test case showing the issue. Also issue summary needs some love.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.