TemporaryTableService.php 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\DB;
  7. use Magento\Framework\DB\Adapter\AdapterInterface;
  8. /**
  9. * Class TemporaryTableService creates a temporary table in mysql from a Magento\Framework\DB\Select.
  10. * Use this class to create an index with that you want to query later for quick data access
  11. *
  12. * @api
  13. * @since 100.1.8
  14. */
  15. class TemporaryTableService
  16. {
  17. const INDEX_METHOD_HASH = 'HASH';
  18. const DB_ENGINE_INNODB = 'INNODB';
  19. /**
  20. * @var string[]
  21. */
  22. private $allowedIndexMethods;
  23. /**
  24. * @var string[]
  25. */
  26. private $allowedEngines;
  27. /**
  28. * @var \Magento\Framework\Math\Random
  29. */
  30. private $random;
  31. /**
  32. * @var AdapterInterface[]
  33. */
  34. private $createdTableAdapters = [];
  35. /**
  36. * @param \Magento\Framework\Math\Random $random
  37. * @param string[] $allowedIndexMethods
  38. * @param string[] $allowedEngines
  39. */
  40. public function __construct(
  41. \Magento\Framework\Math\Random $random,
  42. $allowedIndexMethods = [],
  43. $allowedEngines = []
  44. ) {
  45. $this->random = $random;
  46. $this->allowedIndexMethods = $allowedIndexMethods;
  47. $this->allowedEngines = $allowedEngines;
  48. }
  49. /**
  50. * Creates a temporary table from select removing duplicate rows if you have a union in your select
  51. * This method should always be paired with dropTable to ensure cleanup
  52. * Make sure you index your data so you can query it fast
  53. * You can choose from memory or file table and provide indexes to ensure fast data query
  54. *
  55. * Example: createFromSelect(
  56. * $selectObject,
  57. * $this->resourceConnection->getConnection(),
  58. * [
  59. * 'PRIMARY' => ['primary_id'],
  60. * 'some_single_field_index' => ['field'],
  61. * 'UNQ_some_multiple_field_index' => ['field1', 'field2'],
  62. * ]
  63. * )
  64. * Note that indexes names with UNQ_ prefix, will be created as unique
  65. *
  66. * @param Select $select
  67. * @param AdapterInterface $adapter
  68. * @param array $indexes
  69. * @param string $indexMethod
  70. * @param string $dbEngine
  71. * @return string
  72. * @throws \InvalidArgumentException
  73. * @since 100.1.8
  74. */
  75. public function createFromSelect(
  76. Select $select,
  77. AdapterInterface $adapter,
  78. array $indexes = [],
  79. $indexMethod = self::INDEX_METHOD_HASH,
  80. $dbEngine = self::DB_ENGINE_INNODB
  81. ) {
  82. if (!in_array($indexMethod, $this->allowedIndexMethods)) {
  83. throw new \InvalidArgumentException(
  84. sprintf('indexMethod must be one of %s', implode(',', $this->allowedIndexMethods))
  85. );
  86. }
  87. if (!in_array($dbEngine, $this->allowedEngines)) {
  88. throw new \InvalidArgumentException(
  89. sprintf('dbEngine must be one of %s', implode(',', $this->allowedEngines))
  90. );
  91. }
  92. $name = $this->random->getUniqueHash('tmp_select_');
  93. $indexStatements = [];
  94. foreach ($indexes as $indexName => $columns) {
  95. $renderedColumns = implode(',', array_map([$adapter, 'quoteIdentifier'], $columns));
  96. $indexType = sprintf(
  97. 'INDEX %s USING %s',
  98. $adapter->quoteIdentifier($indexName),
  99. $indexMethod
  100. );
  101. if ($indexName === 'PRIMARY') {
  102. $indexType = 'PRIMARY KEY';
  103. } elseif (strpos($indexName, 'UNQ_') === 0) {
  104. $indexType = sprintf('UNIQUE %s', $adapter->quoteIdentifier($indexName));
  105. }
  106. $indexStatements[] = sprintf('%s(%s)', $indexType, $renderedColumns);
  107. }
  108. $statement = sprintf(
  109. 'CREATE TEMPORARY TABLE %s %s ENGINE=%s IGNORE (%s)',
  110. $adapter->quoteIdentifier($name),
  111. $indexStatements ? '(' . implode(',', $indexStatements) . ')' : '',
  112. $adapter->quoteIdentifier($dbEngine),
  113. "{$select}"
  114. );
  115. $adapter->query(
  116. $statement,
  117. $select->getBind()
  118. );
  119. $this->createdTableAdapters[$name] = $adapter;
  120. return $name;
  121. }
  122. /**
  123. * Method used to drop a table by name
  124. * This class will hold all temporary table names in createdTableAdapters array
  125. * so we can dispose them once we're finished
  126. *
  127. * Example: dropTable($name)
  128. * where $name is a variable that holds the name for a previously created temporary table
  129. * by using "createFromSelect" method
  130. *
  131. * @param string $name
  132. * @return bool
  133. * @since 100.1.8
  134. */
  135. public function dropTable($name)
  136. {
  137. if (!empty($this->createdTableAdapters)) {
  138. if (isset($this->createdTableAdapters[$name]) && !empty($name)) {
  139. $adapter = $this->createdTableAdapters[$name];
  140. $adapter->dropTemporaryTable($name);
  141. unset($this->createdTableAdapters[$name]);
  142. return true;
  143. }
  144. }
  145. return false;
  146. }
  147. }