diff -u b/core/modules/migrate/src/Plugin/migrate/source/SqlBase.php b/core/modules/migrate/src/Plugin/migrate/source/SqlBase.php --- b/core/modules/migrate/src/Plugin/migrate/source/SqlBase.php +++ b/core/modules/migrate/src/Plugin/migrate/source/SqlBase.php @@ -8,6 +8,8 @@ namespace Drupal\migrate\Plugin\migrate\source; use Drupal\Core\Database\Database; +use Drupal\Core\Database\Query\ConditionInterface; +use Drupal\Core\Database\Query\SelectInterface; use Drupal\migrate\Entity\MigrationInterface; use Drupal\migrate\Plugin\migrate\id_map\Sql; use Drupal\migrate\Plugin\MigrateIdMapInterface; @@ -103,11 +105,8 @@ $this->query->addTag('migrate_' . $this->migration->id()); $this->query->addMetaData('migration', $this->migration); - // If an ID list is provided, filter on it. This only works for single-value - // keys, so throw an exception if the key is multi-value. + // If an ID list is provided, filter on it. if ($id_list = $this->getIdList()) { - // \Drupal\migrate\Plugin\migrate\source\SourcePluginBase::__construct() - // ensures the idlist is single long. foreach ($this->getIds() as $field => $definition) { // If a table alias was provided, prepend it in order to prevent // the possibility of 'ambiguous column' errors. @@ -117,21 +116,8 @@ $this->query->condition($field, $id_list, 'IN'); } } - - return $this->query; - } - - /** - * Implementation of MigrateSource::performRewind(). - * - * We could simply execute the query and be functionally correct, but - * we will take advantage of the PDO-based API to optimize the query up-front. - */ - protected function initializeIterator() { - $this->prepareQuery(); - $high_water_property = $this->migration->get('highWaterProperty'); - - if (!$this->getIdList()) { + else { + $conditions = $this->query->orConditionGroup(); // 1. If the map is joinable, join it. We will want to accept all rows // which are either not in the map, or marked in the map as NEEDS_UPDATE. // Note that if high water fields are in play, we want to accept all rows @@ -139,59 +125,104 @@ // conditions, so we need to OR them together (but AND with any existing // conditions in the query). So, ultimately the SQL condition will look // like (original conditions) AND (map IS NULL OR map needs update - // OR above high water). - $conditions = $this->query->orConditionGroup(); - $condition_added = FALSE; - if ($this->mapJoinable()) { - // Build the join to the map table. Because the source key could have - // multiple fields, we need to build things up. - $count = 1; - $map_join = ''; - $delimiter = ''; - foreach ($this->getIds() as $field_name => $field_schema) { - if (isset($field_schema['alias'])) { - $field_name = $field_schema['alias'] . '.' . $field_name; - } - $map_join .= "$delimiter$field_name = map.sourceid" . $count++; - $delimiter = ' AND '; - } + // OR above high water). + // + // 2. If we are using high water marks, also include rows above the mark. + // But, include all rows if the high water mark is not set. + if ($this->joinMapTable($conditions) || $this->addHighWaterProperty($conditions)) { + $this->query->condition($conditions); + } + } - $alias = $this->query->leftJoin($this->migration->getIdMap()->getQualifiedMapTableName(), 'map', $map_join); - $conditions->isNull($alias . '.sourceid1'); - $conditions->condition($alias . '.source_row_status', MigrateIdMapInterface::STATUS_NEEDS_UPDATE); - $condition_added = TRUE; + return $this->query; + } - // And as long as we have the map table, add its data to the row. - $n = count($this->getIds()); - for ($count = 1; $count <= $n; $count++) { - $map_key = 'sourceid' . $count; - $this->query->addField($alias, $map_key, "migrate_map_$map_key"); - } - if ($n = count($this->migration->getDestinationPlugin()->getIds())) { - for ($count = 1; $count <= $n; $count++) { - $map_key = 'destid' . $count++; - $this->query->addField($alias, $map_key, "migrate_map_$map_key"); - } + /** + * Joins the query on the migration's map table and filters by row status + * for a performance boost. + * + * @param \Drupal\Core\Database\Query\ConditionInterface $conditions + * The condition group on which to set the filters. + * @param \Drupal\Core\Database\Query\SelectInterface|NULL $query + * The query to alter. If not passed, defaults to $this->query. + * + * @return boolean + * TRUE if the map table was joined, FALSE otherwise. + */ + protected function joinMapTable(ConditionInterface $conditions, SelectInterface $query = NULL) { + if ($this->mapJoinable()) { + $query = $query ?: $this->query; + // Build the join to the map table. Because the source key could have + // multiple fields, we need to build things up. + $count = 1; + $map_join = ''; + $delimiter = ''; + foreach ($this->getIds() as $field_name => $field_schema) { + if (isset($field_schema['alias'])) { + $field_name = $field_schema['alias'] . '.' . $field_name; } - $this->query->addField($alias, 'source_row_status', 'migrate_map_source_row_status'); + $map_join .= "$delimiter$field_name = map.sourceid" . $count++; + $delimiter = ' AND '; } - // 2. If we are using high water marks, also include rows above the mark. - // But, include all rows if the high water mark is not set. - if (isset($high_water_property['name']) && ($high_water = $this->migration->getHighWater()) !== '') { - if (isset($high_water_property['alias'])) { - $high_water = $high_water_property['alias'] . '.' . $high_water_property['name']; - } - else { - $high_water = $high_water_property['name']; + + $alias = $query->leftJoin($this->migration->getIdMap()->getQualifiedMapTableName(), 'map', $map_join); + $conditions->isNull($alias . '.sourceid1'); + $conditions->condition($alias . '.source_row_status', MigrateIdMapInterface::STATUS_NEEDS_UPDATE); + + // And as long as we have the map table, add its data to the row. + $n = count($this->getIds()); + for ($count = 1; $count <= $n; $count++) { + $map_key = 'sourceid' . $count; + $query->addField($alias, $map_key, "migrate_map_$map_key"); + } + if ($n = count($this->migration->getDestinationPlugin()->getIds())) { + for ($count = 1; $count <= $n; $count++) { + $map_key = 'destid' . $count++; + $query->addField($alias, $map_key, "migrate_map_$map_key"); } - $conditions->condition($high_water, $high_water, '>'); - $condition_added = TRUE; } - if ($condition_added) { - $this->query->condition($conditions); + $query->addField($alias, 'source_row_status', 'migrate_map_source_row_status'); + return TRUE; + } + else { + return FALSE; + } + } + + /** + * Adds a query condition to filter on the high-water property, if defined. + * + * @param \Drupal\Core\Database\Query\ConditionInterface $conditions + * The condition group on which to add the filters. + * + * @return boolean + * TRUE if the high-water property filter was added, FALSE otherwise. + */ + protected function addHighWaterProperty(ConditionInterface $conditions) { + $high_water_property = $this->migration->get('highWaterProperty'); + if (isset($high_water_property['name']) && ($high_water = $this->migration->getHighWater()) !== '') { + if (isset($high_water_property['alias'])) { + $high_water = $high_water_property['alias'] . '.' . $high_water_property['name']; + } + else { + $high_water = $high_water_property['name']; } + $conditions->condition($high_water, $high_water, '>'); + return TRUE; } + else { + return FALSE; + } + } + /** + * Implementation of MigrateSource::performRewind(). + * + * We could simply execute the query and be functionally correct, but + * we will take advantage of the PDO-based API to optimize the query up-front. + */ + protected function initializeIterator() { + $this->prepareQuery(); return new \IteratorIterator($this->query->execute()); }