PageRenderTime 47ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/core/modules/migrate/src/Plugin/migrate/source/SqlBase.php

https://gitlab.com/guillaumev/alkarama
PHP | 342 lines | 176 code | 31 blank | 135 comment | 36 complexity | 794f56907cefbee82d20484e34fd7023 MD5 | raw file
  1. <?php
  2. namespace Drupal\migrate\Plugin\migrate\source;
  3. use Drupal\Core\Database\Database;
  4. use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
  5. use Drupal\Core\State\StateInterface;
  6. use Drupal\migrate\MigrateException;
  7. use Drupal\migrate\Plugin\MigrationInterface;
  8. use Drupal\migrate\Plugin\migrate\id_map\Sql;
  9. use Drupal\migrate\Plugin\MigrateIdMapInterface;
  10. use Symfony\Component\DependencyInjection\ContainerInterface;
  11. /**
  12. * Sources whose data may be fetched via DBTNG.
  13. *
  14. * By default, an existing database connection with key 'migrate' and target
  15. * 'default' is used. These may be overridden with explicit 'key' and/or
  16. * 'target' configuration keys. In addition, if the configuration key 'database'
  17. * is present, it is used as a database connection information array to define
  18. * the connection.
  19. */
  20. abstract class SqlBase extends SourcePluginBase implements ContainerFactoryPluginInterface {
  21. /**
  22. * The query string.
  23. *
  24. * @var \Drupal\Core\Database\Query\SelectInterface
  25. */
  26. protected $query;
  27. /**
  28. * The database object.
  29. *
  30. * @var \Drupal\Core\Database\Connection
  31. */
  32. protected $database;
  33. /**
  34. * State service for retrieving database info.
  35. *
  36. * @var \Drupal\Core\State\StateInterface
  37. */
  38. protected $state;
  39. /**
  40. * The count of the number of batches run.
  41. *
  42. * @var int
  43. */
  44. protected $batch = 0;
  45. /**
  46. * Number of records to fetch from the database during each batch.
  47. *
  48. * A value of zero indicates no batching is to be done.
  49. *
  50. * @var int
  51. */
  52. protected $batchSize = 0;
  53. /**
  54. * {@inheritdoc}
  55. */
  56. public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, StateInterface $state) {
  57. parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
  58. $this->state = $state;
  59. }
  60. /**
  61. * {@inheritdoc}
  62. */
  63. public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
  64. return new static(
  65. $configuration,
  66. $plugin_id,
  67. $plugin_definition,
  68. $migration,
  69. $container->get('state')
  70. );
  71. }
  72. /**
  73. * Prints the query string when the object is used as a string.
  74. *
  75. * @return string
  76. * The query string.
  77. */
  78. public function __toString() {
  79. return (string) $this->query();
  80. }
  81. /**
  82. * Gets the database connection object.
  83. *
  84. * @return \Drupal\Core\Database\Connection
  85. * The database connection.
  86. */
  87. public function getDatabase() {
  88. if (!isset($this->database)) {
  89. // See if the database info is in state - if not, fallback to
  90. // configuration.
  91. if (isset($this->configuration['database_state_key'])) {
  92. $this->database = $this->setUpDatabase($this->state->get($this->configuration['database_state_key']));
  93. }
  94. elseif (($fallback_state_key = $this->state->get('migrate.fallback_state_key'))) {
  95. $this->database = $this->setUpDatabase($this->state->get($fallback_state_key));
  96. }
  97. else {
  98. $this->database = $this->setUpDatabase($this->configuration);
  99. }
  100. }
  101. return $this->database;
  102. }
  103. /**
  104. * Gets a connection to the referenced database.
  105. *
  106. * This method will add the database connection if necessary.
  107. *
  108. * @param array $database_info
  109. * Configuration for the source database connection. The keys are:
  110. * 'key' - The database connection key.
  111. * 'target' - The database connection target.
  112. * 'database' - Database configuration array as accepted by
  113. * Database::addConnectionInfo.
  114. *
  115. * @return \Drupal\Core\Database\Connection
  116. * The connection to use for this plugin's queries.
  117. */
  118. protected function setUpDatabase(array $database_info) {
  119. if (isset($database_info['key'])) {
  120. $key = $database_info['key'];
  121. }
  122. else {
  123. $key = 'migrate';
  124. }
  125. if (isset($database_info['target'])) {
  126. $target = $database_info['target'];
  127. }
  128. else {
  129. $target = 'default';
  130. }
  131. if (isset($database_info['database'])) {
  132. Database::addConnectionInfo($key, $target, $database_info['database']);
  133. }
  134. return Database::getConnection($target, $key);
  135. }
  136. /**
  137. * Wrapper for database select.
  138. */
  139. protected function select($table, $alias = NULL, array $options = array()) {
  140. $options['fetch'] = \PDO::FETCH_ASSOC;
  141. return $this->getDatabase()->select($table, $alias, $options);
  142. }
  143. /**
  144. * Adds tags and metadata to the query.
  145. *
  146. * @return \Drupal\Core\Database\Query\SelectInterface
  147. * The query with additional tags and metadata.
  148. */
  149. protected function prepareQuery() {
  150. $this->query = clone $this->query();
  151. $this->query->addTag('migrate');
  152. $this->query->addTag('migrate_' . $this->migration->id());
  153. $this->query->addMetaData('migration', $this->migration);
  154. return $this->query;
  155. }
  156. /**
  157. * Implementation of MigrateSource::performRewind().
  158. *
  159. * We could simply execute the query and be functionally correct, but
  160. * we will take advantage of the PDO-based API to optimize the query up-front.
  161. */
  162. protected function initializeIterator() {
  163. // Initialize the batch size.
  164. if ($this->batchSize == 0 && isset($this->configuration['batch_size'])) {
  165. // Valid batch sizes are integers >= 0.
  166. if (is_int($this->configuration['batch_size']) && ($this->configuration['batch_size']) >= 0) {
  167. $this->batchSize = $this->configuration['batch_size'];
  168. }
  169. else {
  170. throw new MigrateException("batch_size must be greater than or equal to zero");
  171. }
  172. }
  173. // If a batch has run the query is already setup.
  174. if ($this->batch == 0) {
  175. $this->prepareQuery();
  176. // Get the key values, for potential use in joining to the map table.
  177. $keys = array();
  178. // The rules for determining what conditions to add to the query are as
  179. // follows (applying first applicable rule):
  180. // 1. If the map is joinable, join it. We will want to accept all rows
  181. // which are either not in the map, or marked in the map as NEEDS_UPDATE.
  182. // Note that if high water fields are in play, we want to accept all rows
  183. // above the high water mark in addition to those selected by the map
  184. // conditions, so we need to OR them together (but AND with any existing
  185. // conditions in the query). So, ultimately the SQL condition will look
  186. // like (original conditions) AND (map IS NULL OR map needs update
  187. // OR above high water).
  188. $conditions = $this->query->orConditionGroup();
  189. $condition_added = FALSE;
  190. if (empty($this->configuration['ignore_map']) && $this->mapJoinable()) {
  191. // Build the join to the map table. Because the source key could have
  192. // multiple fields, we need to build things up.
  193. $count = 1;
  194. $map_join = '';
  195. $delimiter = '';
  196. foreach ($this->getIds() as $field_name => $field_schema) {
  197. if (isset($field_schema['alias'])) {
  198. $field_name = $field_schema['alias'] . '.' . $this->query->escapeField($field_name);
  199. }
  200. $map_join .= "$delimiter$field_name = map.sourceid" . $count++;
  201. $delimiter = ' AND ';
  202. }
  203. $alias = $this->query->leftJoin($this->migration->getIdMap()
  204. ->getQualifiedMapTableName(), 'map', $map_join);
  205. $conditions->isNull($alias . '.sourceid1');
  206. $conditions->condition($alias . '.source_row_status', MigrateIdMapInterface::STATUS_NEEDS_UPDATE);
  207. $condition_added = TRUE;
  208. // And as long as we have the map table, add its data to the row.
  209. $n = count($this->getIds());
  210. for ($count = 1; $count <= $n; $count++) {
  211. $map_key = 'sourceid' . $count;
  212. $this->query->addField($alias, $map_key, "migrate_map_$map_key");
  213. }
  214. if ($n = count($this->migration->getDestinationIds())) {
  215. for ($count = 1; $count <= $n; $count++) {
  216. $map_key = 'destid' . $count++;
  217. $this->query->addField($alias, $map_key, "migrate_map_$map_key");
  218. }
  219. }
  220. $this->query->addField($alias, 'source_row_status', 'migrate_map_source_row_status');
  221. }
  222. // 2. If we are using high water marks, also include rows above the mark.
  223. // But, include all rows if the high water mark is not set.
  224. if ($this->getHighWaterProperty() && ($high_water = $this->getHighWater()) !== '') {
  225. $high_water_field = $this->getHighWaterField();
  226. $conditions->condition($high_water_field, $high_water, '>');
  227. $this->query->orderBy($high_water_field);
  228. }
  229. if ($condition_added) {
  230. $this->query->condition($conditions);
  231. }
  232. }
  233. // Download data in batches for performance.
  234. if (($this->batchSize > 0)) {
  235. $this->query->range($this->batch * $this->batchSize, $this->batchSize);
  236. }
  237. return new \IteratorIterator($this->query->execute());
  238. }
  239. /**
  240. * Position the iterator to the following row.
  241. */
  242. protected function fetchNextRow() {
  243. $this->getIterator()->next();
  244. // We might be out of data entirely, or just out of data in the current
  245. // batch. Attempt to fetch the next batch and see.
  246. if ($this->batchSize > 0 && !$this->getIterator()->valid()) {
  247. $this->fetchNextBatch();
  248. }
  249. }
  250. /**
  251. * Prepares query for the next set of data from the source database.
  252. */
  253. protected function fetchNextBatch() {
  254. $this->batch++;
  255. unset($this->iterator);
  256. $this->getIterator()->rewind();
  257. }
  258. /**
  259. * @return \Drupal\Core\Database\Query\SelectInterface
  260. */
  261. abstract public function query();
  262. /**
  263. * {@inheritdoc}
  264. */
  265. public function count() {
  266. return $this->query()->countQuery()->execute()->fetchField();
  267. }
  268. /**
  269. * Checks if we can join against the map table.
  270. *
  271. * This function specifically catches issues when we're migrating with
  272. * unique sets of credentials for the source and destination database.
  273. *
  274. * @return bool
  275. * TRUE if we can join against the map table otherwise FALSE.
  276. */
  277. protected function mapJoinable() {
  278. if (!$this->getIds()) {
  279. return FALSE;
  280. }
  281. // With batching, we want a later batch to return the same rows that would
  282. // have been returned at the same point within a monolithic query. If we
  283. // join to the map table, the first batch is writing to the map table and
  284. // thus affecting the results of subsequent batches. To be safe, we avoid
  285. // joining to the map table when batching.
  286. if ($this->batchSize > 0) {
  287. return FALSE;
  288. }
  289. $id_map = $this->migration->getIdMap();
  290. if (!$id_map instanceof Sql) {
  291. return FALSE;
  292. }
  293. $id_map_database_options = $id_map->getDatabase()->getConnectionOptions();
  294. $source_database_options = $this->getDatabase()->getConnectionOptions();
  295. // Special handling for sqlite which deals with files.
  296. if ($id_map_database_options['driver'] === 'sqlite' &&
  297. $source_database_options['driver'] === 'sqlite' &&
  298. $id_map_database_options['database'] != $source_database_options['database']
  299. ) {
  300. return FALSE;
  301. }
  302. foreach (array('username', 'password', 'host', 'port', 'namespace', 'driver') as $key) {
  303. if (isset($source_database_options[$key])) {
  304. if ($id_map_database_options[$key] != $source_database_options[$key]) {
  305. return FALSE;
  306. }
  307. }
  308. }
  309. return TRUE;
  310. }
  311. }