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..c760f59 100644 --- a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php +++ b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php @@ -34,6 +34,11 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP const SOURCE_IDS_HASH = 'source_ids_hash'; /** + * Suffix to append to ID properties to avoid SQL reserved words. + */ + const ID_SUFFIX = '_x'; + + /** * An event dispatcher instance to use for map events. * * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface @@ -461,9 +466,20 @@ protected function getFieldSchema(array $id_definition) { * {@inheritdoc} */ public function getRowBySource(array $source_id_values) { - $query = $this->getDatabase()->select($this->mapTableName(), 'map') - ->fields('map'); + $query = $this->getDatabase()->select($this->mapTableName(), 'map'); $query->condition(static::SOURCE_IDS_HASH, $this->getSourceIDsHash($source_id_values)); + foreach ($this->sourceIdFields() as $field_name => $source_id) { + $query->addField('map', $source_id, $field_name . static::ID_SUFFIX); + $query->condition("map.$source_id", $source_id_values[$field_name]); + } + foreach ($this->destinationIdFields() as $field_name => $destination_id) { + $query->addField('map', $destination_id, $field_name . static::ID_SUFFIX); + } + $query->addField('map', 'source_ids_hash'); + $query->addField('map', 'source_row_status'); + $query->addField('map', 'rollback_action'); + $query->addField('map', 'last_imported'); + $query->addField('map', 'hash'); $result = $query->execute(); return $result->fetchAssoc(); } @@ -472,11 +488,19 @@ public function getRowBySource(array $source_id_values) { * {@inheritdoc} */ public function getRowByDestination(array $destination_id_values) { - $query = $this->getDatabase()->select($this->mapTableName(), 'map') - ->fields('map'); - foreach ($this->destinationIdFields() as $field_name => $destination_id) { - $query->condition("map.$destination_id", $destination_id_values[$field_name], '='); + $query = $this->getDatabase()->select($this->mapTableName(), 'map'); + foreach ($this->sourceIdFields() as $field_name => $source_id) { + $query->addField('map', $source_id, $field_name . static::ID_SUFFIX); } + foreach ($this->destinationIdFields() as $field_name => $destination_id) { + $query->addField('map', $destination_id, $field_name . static::ID_SUFFIX); + $query->condition("map.$destination_id", $destination_id_values[$field_name]); + } + $query->addField('map', 'source_ids_hash'); + $query->addField('map', 'source_row_status'); + $query->addField('map', 'rollback_action'); + $query->addField('map', 'last_imported'); + $query->addField('map', 'hash'); $result = $query->execute(); return $result->fetchAssoc(); } diff --git a/core/modules/migrate/tests/src/Kernel/MigrateRollbackTest.php b/core/modules/migrate/tests/src/Kernel/MigrateRollbackTest.php index 9ec0c65..fa959d0 100644 --- a/core/modules/migrate/tests/src/Kernel/MigrateRollbackTest.php +++ b/core/modules/migrate/tests/src/Kernel/MigrateRollbackTest.php @@ -4,6 +4,7 @@ use Drupal\migrate\MigrateExecutable; use Drupal\migrate\Plugin\MigrateIdMapInterface; +use Drupal\migrate\Plugin\migrate\id_map\Sql; use Drupal\migrate\Row; use Drupal\taxonomy\Entity\Term; use Drupal\taxonomy\Entity\Vocabulary; @@ -72,7 +73,7 @@ public function testRollback() { $vocabulary = Vocabulary::load($row['id']); $this->assertTrue($vocabulary); $map_row = $vocabulary_id_map->getRowBySource(['id' => $row['id']]); - $this->assertNotNull($map_row['destid1']); + $this->assertNotNull($map_row['vid' . SQL::ID_SUFFIX]); } // We use taxonomy terms to demonstrate importing and rolling back content @@ -117,7 +118,7 @@ public function testRollback() { $preserved_term_ids[] = 2; $map_row = $term_id_map->getRowBySource(['id' => 2]); $dummy_row = new Row(['id' => 2], $ids); - $term_id_map->saveIdMapping($dummy_row, [$map_row['destid1']], + $term_id_map->saveIdMapping($dummy_row, [$map_row['id' . SQL::ID_SUFFIX]], $map_row['source_row_status'], MigrateIdMapInterface::ROLLBACK_PRESERVE); foreach ($term_data_rows as $row) { @@ -125,7 +126,7 @@ public function testRollback() { $term = Term::load($row['id']); $this->assertTrue($term); $map_row = $term_id_map->getRowBySource(['id' => $row['id']]); - $this->assertNotNull($map_row['destid1']); + $this->assertNotNull($map_row['id' . SQL::ID_SUFFIX]); } // Rollback and verify the entities are gone. diff --git a/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php b/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php index f0e7832..e6d4957 100644 --- a/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php +++ b/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php @@ -6,6 +6,7 @@ use Drupal\migrate\Plugin\MigrationInterface; use Drupal\migrate\MigrateException; use Drupal\migrate\Plugin\MigrateIdMapInterface; +use Drupal\migrate\Plugin\migrate\id_map\Sql; use Drupal\migrate\Row; /** @@ -127,12 +128,14 @@ protected function getIdMap() { * An associative array with the following keys: * - source_row_status * - rollback_action + * - last_imported * - hash */ protected function idMapDefaults() { $defaults = array( 'source_row_status' => MigrateIdMapInterface::STATUS_IMPORTED, 'rollback_action' => MigrateIdMapInterface::ROLLBACK_DELETE, + 'last_imported' => 0, 'hash' => '', ); // By default, the PDO SQLite driver strongly prefers to return strings @@ -144,6 +147,7 @@ protected function idMapDefaults() { if ($this->database->driver() == 'sqlite') { $defaults['source_row_status'] = (string) $defaults['source_row_status']; $defaults['rollback_action'] = (string) $defaults['rollback_action']; + $defaults['last_imported'] = (string) 0; } return $defaults; } @@ -258,6 +262,25 @@ public function testGetRowsNeedingUpdate() { // Assert the row matches its original source. $source_id = $expected_results[MigrateIdMapInterface::STATUS_NEEDS_UPDATE]['sourceid1']; $test_row = $id_map->getRowBySource(['source_id_property' => $source_id]); + // Convert names + foreach ($test_row as $key => $value) { + switch ($key) { + case 'source_id_property' . SQL::ID_SUFFIX; + $key = 'sourceid1'; + break; + case 'destination_id_property' . SQL::ID_SUFFIX: + $key = 'destid1'; + break; + } + $expected[$key] = $value; + } + // Sort expected to the same order as $result_row. + $test_row = []; + foreach ($row_needing_update[0] as $key => $value) { + if (isset($expected[$key]) || array_key_exists($key, $expected)) { + $test_row[$key] = $expected[$key]; + } + } // $row_needing_update is an array of objects returned from the database, // but $test_row is an array, so the cast is necessary. $this->assertSame($test_row, (array) $row_needing_update[0]); @@ -345,25 +368,64 @@ public function testMessageSave() { */ public function testGetRowBySource() { $this->getDatabase([]); + $this->sourceIds = [ + 'source_id_property1' => [ + 'type' => 'string', + ], + 'source_id_property2' => [ + 'type' => 'string', + ], + ]; $row = [ 'sourceid1' => 'source_id_value_1', 'sourceid2' => 'source_id_value_2', - 'source_ids_hash' => $this->getIdMap()->getSourceIDsHash(['source_id_property' => 'source_id_value_1']), + 'source_ids_hash' => $this->getIdMap()->getSourceIDsHash( + [ + 'source_id_property1' => 'source_id_value_1', + 'source_id_property2' => 'source_id_value_2' + ]), 'destid1' => 'destination_id_value_1', ] + $this->idMapDefaults(); $this->saveMap($row); $row = [ 'sourceid1' => 'source_id_value_3', 'sourceid2' => 'source_id_value_4', - 'source_ids_hash' => $this->getIdMap()->getSourceIDsHash(['source_id_property' => 'source_id_value_3', 'sourceid2' => 'source_id_value_4']), + 'source_ids_hash' => $this->getIdMap()->getSourceIDsHash( + [ + 'source_id_property1' => 'source_id_value_3', + 'source_id_property2' => 'source_id_value_4' + ]), 'destid1' => 'destination_id_value_2', ] + $this->idMapDefaults(); $this->saveMap($row); - $source_id_values = ['source_id_property' => $row['sourceid1'], 'sourceid2' => $row['sourceid2']]; + $expected = [ + 'source_id_property1' . SQL::ID_SUFFIX => 'source_id_value_3', + 'source_id_property2' . SQL::ID_SUFFIX => 'source_id_value_4', + 'source_ids_hash' => $this->getIdMap()->getSourceIDsHash( + [ + 'source_id_property1' => 'source_id_value_3', + 'source_id_property2' => 'source_id_value_4' + ]), + 'destination_id_property' . SQL::ID_SUFFIX => 'destination_id_value_2', + ] + $this->idMapDefaults(); + $source_id_values = [ + 'source_id_property1' => $row['sourceid1'], + 'source_id_property2' => $row['sourceid2'] + ]; $id_map = $this->getIdMap(); $result_row = $id_map->getRowBySource($source_id_values); - $this->assertSame($row, $result_row); - $source_id_values = ['source_id_property' => 'missing_value_1', 'sourceid2' => 'missing_value_2']; + + // Sort expected to the same order as $result_row. + foreach ($result_row as $key => $value) { + if (isset($expected[$key]) || array_key_exists($key, $expected)) { + $expected_results[$key] = $expected[$key]; + } + } + $this->assertSame($expected_results, $result_row); + $source_id_values = [ + 'source_id_property1' => 'missing_value_1', + 'source_id_property2' => 'missing_value_2' + ]; $result_row = $id_map->getRowBySource($source_id_values); $this->assertFalse($result_row); } @@ -434,24 +496,57 @@ public function testLookupDestinationIdMapping($num_source_fields, $num_destinat * Tests the getRowByDestination method. */ public function testGetRowByDestination() { + $this->getDatabase([]); + $this->sourceIds = [ + 'source_id_property1' => [ + 'type' => 'string', + ], + 'source_id_property2' => [ + 'type' => 'string', + ], + ]; $row = [ 'sourceid1' => 'source_id_value_1', 'sourceid2' => 'source_id_value_2', - 'source_ids_hash' => $this->getIdMap()->getSourceIDsHash(['source_id_property' => 'source_id_value_1']), + 'source_ids_hash' => $this->getIdMap()->getSourceIDsHash( + [ + 'source_id_property1' => 'source_id_value_1', + 'source_id_property2' => 'source_id_value_2' + ]), 'destid1' => 'destination_id_value_1', ] + $this->idMapDefaults(); $this->saveMap($row); $row = [ 'sourceid1' => 'source_id_value_3', 'sourceid2' => 'source_id_value_4', - 'source_ids_hash' => $this->getIdMap()->getSourceIDsHash(['source_id_property' => 'source_id_value_3']), + 'source_ids_hash' => $this->getIdMap()->getSourceIDsHash( + [ + 'source_id_property1' => 'source_id_value_3', + 'source_id_property2' => 'source_id_value_4' + ]), 'destid1' => 'destination_id_value_2', - ] + $this->idMapDefaults(); + ] + $this->idMapDefaults(); $this->saveMap($row); + $expected = [ + 'source_id_property1' . SQL::ID_SUFFIX => 'source_id_value_3', + 'source_id_property2' . SQL::ID_SUFFIX => 'source_id_value_4', + 'source_ids_hash' => $this->getIdMap()->getSourceIDsHash( + [ + 'source_id_property1' => 'source_id_value_3', + 'source_id_property2' => 'source_id_value_4' + ]), + 'destination_id_property' . SQL::ID_SUFFIX => 'destination_id_value_2', + ] + $this->idMapDefaults(); $dest_id_values = ['destination_id_property' => $row['destid1']]; $id_map = $this->getIdMap(); $result_row = $id_map->getRowByDestination($dest_id_values); - $this->assertSame($row, $result_row); + // Sort expected_results to the same order as $result_row. + foreach ($result_row as $key => $value) { + if (isset($expected[$key]) || array_key_exists($key, $expected)) { + $expected_results[$key] = $expected[$key]; + } + } + $this->assertSame($expected_results, $result_row); // This value does not exist. $dest_id_values = ['destination_id_property' => 'invalid_destination_id_property']; $id_map = $this->getIdMap(); diff --git a/core/modules/taxonomy/tests/src/Kernel/Migrate/MigrateTaxonomyTermStubTest.php b/core/modules/taxonomy/tests/src/Kernel/Migrate/MigrateTaxonomyTermStubTest.php index 2da51e4..1014983 100644 --- a/core/modules/taxonomy/tests/src/Kernel/Migrate/MigrateTaxonomyTermStubTest.php +++ b/core/modules/taxonomy/tests/src/Kernel/Migrate/MigrateTaxonomyTermStubTest.php @@ -72,7 +72,7 @@ public function testStubWithWeightMapping() { $migration = $this->getMigration('taxonomy_term_stub_test'); $term_executable = new MigrateExecutable($migration, $this); $term_executable->import(); - $this->assertTrue($migration->getIdMap()->getRowBySource(['2']), 'Stub row exists in the ID map table'); + $this->assertTrue($migration->getIdMap()->getRowBySource(['id' => '2']), 'Stub row exists in the ID map table'); // Load the referenced term, which should exist as a stub. /** @var \Drupal\Core\Entity\ContentEntityBase $stub_entity */