diff --git a/core/modules/migrate/src/Plugin/MigrateIdMapInterface.php b/core/modules/migrate/src/Plugin/MigrateIdMapInterface.php index 58ec334..5cdc50c 100644 --- a/core/modules/migrate/src/Plugin/MigrateIdMapInterface.php +++ b/core/modules/migrate/src/Plugin/MigrateIdMapInterface.php @@ -209,11 +209,25 @@ public function lookupSourceID(array $destination_id_values); * The source identifier keyed values of the record, e.g. ['nid' => 5]. * * @return array - * The destination identifier values of the record, or NULL on failure. + * The destination identifier values of the record, or empty on failure. */ public function lookupDestinationId(array $source_id_values); /** + * Looks up the destination identifies corresponding to a source key. + * + * This can look up a subset of source keys if only some are provided, and + * will return all destination keys that match. + * + * @param array $source_id_values + * The source identifier keyed values of the records, e.g. ['nid' => 5]. + * + * @return array + * An array of arrays of destination identifier values. + */ + public function lookupDestinationIds(array $source_id_values); + + /** * Looks up the destination identifier currently being iterated. * * @return array diff --git a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php index 8269957..d1f17b8 100644 --- a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php +++ b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php @@ -517,16 +517,46 @@ public function lookupSourceID(array $destination_id_values) { * {@inheritdoc} */ public function lookupDestinationId(array $source_id_values) { + $results = $this->lookupDestinationIds($source_id_values); + return $results ? reset($results) : array(); + } + + /** + * {@inheritdoc} + */ + public function lookupDestinationIds(array $source_id_values) { if (empty($source_id_values)) { return array(); } + $query = $this->getDatabase()->select($this->mapTableName(), 'map') - ->fields('map', $this->destinationIdFields()); + ->fields('map', $this->destinationIdFields()); + + $is_associative = !isset($source_id_values[0]); + $orig_source_ids = $source_id_values; + 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])) { + $query->condition($db_field, $source_id_values[$field_name]); + unset($source_id_values[$field_name]); + } + } + else { + // For non-associative $source_id_values, we assume they're the first + // few fields. + if (empty($source_id_values)) { + break; + } + $query->condition($db_field, array_shift($source_id_values)); + } + } - $query->condition(static::SOURCE_IDS_HASH, $this->getSourceIDsHash($source_id_values)); - $result = $query->execute(); - $destination_id = $result->fetchAssoc(); - return array_values($destination_id ?: array()); + if (!empty($source_id_values)) { + throw new MigrateException("Extra unknown items in source IDs " . print_r($orig_source_ids, TRUE)); + } + + return $query->execute()->fetchAll(\PDO::FETCH_NUM); } /** diff --git a/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php b/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php index f0e7832..471fb98 100644 --- a/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php +++ b/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php @@ -431,6 +431,111 @@ public function testLookupDestinationIdMapping($num_source_fields, $num_destinat } /** + * Setup a database with the given rows. + * + * @param array $source_keys + * The source keys for the ID map table. + * @param array $dest_keys + * The destination keys for the ID map table. + * @param array $rows + * An array of source and destination value arrays for the ID map table. + * + * @return \Drupal\Tests\migrate\Unit\TestSqlIdMap + * An ID map instance for testing. + */ + protected function setupRows($source_keys, $dest_keys, $rows) { + $this->database = $this->getDatabase([]); + $this->sourceIds = array_fill_keys($source_keys, []); + $this->destinationIds = array_fill_keys($dest_keys, []); + + $db_keys = []; + foreach (array_keys($source_keys) as $i) { + $db_keys[] = 'sourceid' . ($i + 1); + } + foreach (array_keys($dest_keys) as $i) { + $db_keys[] = 'destid' . ($i + 1); + } + foreach ($rows as $row) { + $values = array_combine($db_keys, $row); + $values['source_ids_hash'] = uniqid(); + $this->saveMap($values); + } + + return $this->getIdMap(); + } + + /** + * Tests lookupDestinationIds(). + */ + public function testLookupDestinationIds() { + // Simple map with one source and one destination ID. + $id_map = $this->setupRows(['nid'], ['nid'], [ + [1, 101], + [2, 102], + [3, 103], + ]); + + // Lookup nothing, gives nothing. + $this->assertEquals([], $id_map->lookupDestinationIds([])); + // Lookup by complete non-associative list. + $this->assertEquals([[101]], $id_map->lookupDestinationIds([1])); + $this->assertEquals([[102]], $id_map->lookupDestinationIds([2])); + $this->assertEquals([], $id_map->lookupDestinationIds([99])); + // Lookup by complete associative list. + $this->assertEquals([[101]], $id_map->lookupDestinationIds(['nid' => 1])); + $this->assertEquals([[102]], $id_map->lookupDestinationIds(['nid' => 2])); + $this->assertEquals([], $id_map->lookupDestinationIds(['nid' => 99])); + + // Map with multiple source and destination IDs. + $id_map = $this->setupRows(['nid', 'language'], ['nid', 'langcode'], [ + [1, 'en', 101, 'en'], + [1, 'fr', 101, 'fr'], + [1, 'de', 101, 'de'], + [2, 'en', 102, 'en'], + ]); + + // Lookup nothing, gives nothing. + $this->assertEquals([], $id_map->lookupDestinationIds([])); + // Lookup by complete non-associative list. + $this->assertEquals([[101, 'en']], $id_map->lookupDestinationIds([1, 'en'])); + $this->assertEquals([[101, 'fr']], $id_map->lookupDestinationIds([1, 'fr'])); + $this->assertEquals([[102, 'en']], $id_map->lookupDestinationIds([2, 'en'])); + $this->assertEquals([], $id_map->lookupDestinationIds([2, 'fr'])); + $this->assertEquals([], $id_map->lookupDestinationIds([99, 'en'])); + // Lookup by complete associative list. + $this->assertEquals([[101, 'en']], $id_map->lookupDestinationIds(['nid' => 1, 'language' => 'en'])); + $this->assertEquals([[101, 'fr']], $id_map->lookupDestinationIds(['nid' => 1, 'language' => 'fr'])); + $this->assertEquals([[102, 'en']], $id_map->lookupDestinationIds(['nid' => 2, 'language' => 'en'])); + $this->assertEquals([], $id_map->lookupDestinationIds(['nid' => 2, 'language' => 'fr'])); + $this->assertEquals([], $id_map->lookupDestinationIds(['nid' => 99, 'language' => 'en'])); + // Lookup by partial non-associative list. + $this->assertEquals([[101, 'en'], [101, 'fr'], [101, 'de']], $id_map->lookupDestinationIds([1])); + $this->assertEquals([[102, 'en']], $id_map->lookupDestinationIds([2])); + $this->assertEquals([], $id_map->lookupDestinationIds([99])); + // Lookup by partial associative list. + $this->assertEquals([[101, 'en'], [101, 'fr'], [101, 'de']], $id_map->lookupDestinationIds(['nid' => 1])); + $this->assertEquals([[102, 'en']], $id_map->lookupDestinationIds(['nid' => 2])); + $this->assertEquals([], $id_map->lookupDestinationIds(['nid' => 99])); + // Out-of-order partial associative list. + $this->assertEquals([[101, 'en'], [102, 'en']], $id_map->lookupDestinationIds(['language' => 'en'])); + $this->assertEquals([[101, 'fr']], $id_map->lookupDestinationIds(['language' => 'fr'])); + $this->assertEquals([], $id_map->lookupDestinationIds(['language' => 'zh'])); + // Error conditions. + try { + $id_map->lookupDestinationIds([1, 2, 3]); + $this->fail('Too many source IDs should throw'); + } + catch (MigrateException $e) { + } + try { + $id_map->lookupDestinationIds(['nid' => 1, 'aaa' => '2']); + $this->fail('Unknown source ID key should throw'); + } + catch (MigrateException $e) { + } + } + + /** * Tests the getRowByDestination method. */ public function testGetRowByDestination() {