Helper.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Backup\Model\ResourceModel;
  7. /**
  8. * @api
  9. * @since 100.0.2
  10. */
  11. class Helper extends \Magento\Framework\DB\Helper
  12. {
  13. /**
  14. * Tables foreign key data array
  15. * [tbl_name] = array(create foreign key strings)
  16. *
  17. * @var array
  18. */
  19. protected $_foreignKeys = [];
  20. /**
  21. * Core Date
  22. *
  23. * @var \Magento\Framework\Stdlib\DateTime\DateTime
  24. */
  25. protected $_coreDate;
  26. /**
  27. * @param \Magento\Framework\App\ResourceConnection $resource
  28. * @param string $modulePrefix
  29. * @param \Magento\Framework\Stdlib\DateTime\DateTime $coreDate
  30. */
  31. public function __construct(
  32. \Magento\Framework\App\ResourceConnection $resource,
  33. $modulePrefix,
  34. \Magento\Framework\Stdlib\DateTime\DateTime $coreDate
  35. ) {
  36. parent::__construct($resource, $modulePrefix);
  37. $this->_coreDate = $coreDate;
  38. }
  39. /**
  40. * Retrieve SQL fragment for drop table
  41. *
  42. * @param string $tableName
  43. * @return string
  44. */
  45. public function getTableDropSql($tableName)
  46. {
  47. $quotedTableName = $this->getConnection()->quoteIdentifier($tableName);
  48. return sprintf('DROP TABLE IF EXISTS %s;', $quotedTableName);
  49. }
  50. /**
  51. * Retrieve foreign keys for table(s)
  52. *
  53. * @param string|null $tableName
  54. * @return string|bool
  55. */
  56. public function getTableForeignKeysSql($tableName = null)
  57. {
  58. $sql = false;
  59. if ($tableName === null) {
  60. $sql = '';
  61. foreach ($this->_foreignKeys as $table => $foreignKeys) {
  62. $sql .= $this->_buildForeignKeysAlterTableSql($table, $foreignKeys);
  63. }
  64. } elseif (isset($this->_foreignKeys[$tableName])) {
  65. $foreignKeys = $this->_foreignKeys[$tableName];
  66. $sql = $this->_buildForeignKeysAlterTableSql($tableName, $foreignKeys);
  67. }
  68. return $sql;
  69. }
  70. /**
  71. * Build sql that will add foreign keys to it
  72. *
  73. * @param string $tableName
  74. * @param array $foreignKeys
  75. * @return string
  76. */
  77. protected function _buildForeignKeysAlterTableSql($tableName, $foreignKeys)
  78. {
  79. if (!is_array($foreignKeys) || empty($foreignKeys)) {
  80. return '';
  81. }
  82. return sprintf(
  83. "ALTER TABLE %s\n %s;\n",
  84. $this->getConnection()->quoteIdentifier($tableName),
  85. join(",\n ", $foreignKeys)
  86. );
  87. }
  88. /**
  89. * Get create script for table
  90. *
  91. * @param string $tableName
  92. * @param boolean $addDropIfExists
  93. * @return string
  94. */
  95. public function getTableCreateScript($tableName, $addDropIfExists = false)
  96. {
  97. $script = '';
  98. $quotedTableName = $this->getConnection()->quoteIdentifier($tableName);
  99. if ($addDropIfExists) {
  100. $script .= 'DROP TABLE IF EXISTS ' . $quotedTableName . ";\n";
  101. }
  102. //TODO fix me
  103. $sql = 'SHOW CREATE TABLE ' . $quotedTableName;
  104. $data = $this->getConnection()->fetchRow($sql);
  105. $script .= isset($data['Create Table']) ? $data['Create Table'] . ";\n" : '';
  106. return $script;
  107. }
  108. /**
  109. * Retrieve SQL fragment for create table
  110. *
  111. * @param string $tableName
  112. * @param bool $withForeignKeys
  113. * @return string
  114. * @SuppressWarnings(PHPMD.NPathComplexity)
  115. */
  116. public function getTableCreateSql($tableName, $withForeignKeys = false)
  117. {
  118. $connection = $this->getConnection();
  119. $quotedTableName = $connection->quoteIdentifier($tableName);
  120. $query = 'SHOW CREATE TABLE ' . $quotedTableName;
  121. $row = $connection->fetchRow($query);
  122. if (!$row || !isset($row['Table']) || !isset($row['Create Table'])) {
  123. return false;
  124. }
  125. $regExp = '/,\s+CONSTRAINT `([^`]*)` FOREIGN KEY \(`([^`]*)`\) ' .
  126. 'REFERENCES `([^`]*)` \(`([^`]*)`\)' .
  127. '( ON DELETE (RESTRICT|CASCADE|SET NULL|NO ACTION))?' .
  128. '( ON UPDATE (RESTRICT|CASCADE|SET NULL|NO ACTION))?/';
  129. $matches = [];
  130. preg_match_all($regExp, $row['Create Table'], $matches, PREG_SET_ORDER);
  131. if (is_array($matches)) {
  132. foreach ($matches as $match) {
  133. $this->_foreignKeys[$tableName][] = sprintf(
  134. 'ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s%s',
  135. $connection->quoteIdentifier($match[1]),
  136. $connection->quoteIdentifier($match[2]),
  137. $connection->quoteIdentifier($match[3]),
  138. $connection->quoteIdentifier($match[4]),
  139. isset($match[5]) ? $match[5] : '',
  140. isset($match[7]) ? $match[7] : ''
  141. );
  142. }
  143. }
  144. if ($withForeignKeys) {
  145. $sql = $row['Create Table'];
  146. } else {
  147. $sql = preg_replace($regExp, '', $row['Create Table']);
  148. }
  149. return $sql . ';';
  150. }
  151. /**
  152. * Returns SQL header data, move from original resource model
  153. *
  154. * @return string
  155. */
  156. public function getHeader()
  157. {
  158. $dbConfig = $this->getConnection()->getConfig();
  159. $versionRow = $this->getConnection()->fetchRow('SHOW VARIABLES LIKE \'version\'');
  160. $hostName = !empty($dbConfig['unix_socket'])
  161. ? $dbConfig['unix_socket']
  162. : (!empty($dbConfig['host']) ? $dbConfig['host'] : 'localhost');
  163. $header = "-- Magento DB backup\n" .
  164. "--\n" .
  165. "-- Host: {$hostName} Database: {$dbConfig['dbname']}\n" .
  166. "-- ------------------------------------------------------\n" .
  167. "-- Server version: {$versionRow['Value']}\n\n" .
  168. "/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;\n" .
  169. "/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;\n" .
  170. "/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;\n" .
  171. "/*!40101 SET NAMES utf8 */;\n" .
  172. "/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;\n" .
  173. "/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;\n" .
  174. "/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;\n" .
  175. "/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;\n";
  176. return $header;
  177. }
  178. /**
  179. * Returns SQL footer data, move from original resource model
  180. *
  181. * @return string
  182. */
  183. public function getFooter()
  184. {
  185. $footer = "\n/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;\n" .
  186. "/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; \n" .
  187. "/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;\n" .
  188. "/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n" .
  189. "/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n" .
  190. "/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n" .
  191. "/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;\n" .
  192. "\n-- Dump completed on " .
  193. $this->_coreDate->gmtDate() .
  194. " GMT";
  195. return $footer;
  196. }
  197. /**
  198. * Retrieve before insert data SQL fragment
  199. *
  200. * @param string $tableName
  201. * @return string
  202. */
  203. public function getTableDataBeforeSql($tableName)
  204. {
  205. $quotedTableName = $this->getConnection()->quoteIdentifier($tableName);
  206. return "\n--\n" .
  207. "-- Dumping data for table {$quotedTableName}\n" .
  208. "--\n\n" .
  209. "LOCK TABLES {$quotedTableName} WRITE;\n" .
  210. "/*!40000 ALTER TABLE {$quotedTableName} DISABLE KEYS */;\n";
  211. }
  212. /**
  213. * Retrieve after insert data SQL fragment
  214. *
  215. * @param string $tableName
  216. * @return string
  217. */
  218. public function getTableDataAfterSql($tableName)
  219. {
  220. $quotedTableName = $this->getConnection()->quoteIdentifier($tableName);
  221. return "/*!40000 ALTER TABLE {$quotedTableName} ENABLE KEYS */;\n" . "UNLOCK TABLES;\n";
  222. }
  223. /**
  224. * Return table part data SQL insert
  225. *
  226. * @param string $tableName
  227. * @param int $count
  228. * @param int $offset
  229. * @return string
  230. */
  231. public function getPartInsertSql($tableName, $count = null, $offset = null)
  232. {
  233. $sql = null;
  234. $connection = $this->getConnection();
  235. $select = $connection->select()->from($tableName)->limit($count, $offset);
  236. $query = $connection->query($select);
  237. while (true == ($row = $query->fetch())) {
  238. if ($sql === null) {
  239. $sql = sprintf('INSERT INTO %s VALUES ', $connection->quoteIdentifier($tableName));
  240. } else {
  241. $sql .= ',';
  242. }
  243. $sql .= $this->_quoteRow($tableName, $row);
  244. }
  245. if ($sql !== null) {
  246. $sql .= ';' . "\n";
  247. }
  248. return $sql;
  249. }
  250. /**
  251. * Return table data SQL insert
  252. *
  253. * @param string $tableName
  254. * @return string
  255. */
  256. public function getInsertSql($tableName)
  257. {
  258. return $this->getPartInsertSql($tableName);
  259. }
  260. /**
  261. * Quote Table Row
  262. *
  263. * @param string $tableName
  264. * @param array $row
  265. * @return string
  266. */
  267. protected function _quoteRow($tableName, array $row)
  268. {
  269. $connection = $this->getConnection();
  270. $describe = $connection->describeTable($tableName);
  271. $dataTypes = ['bigint', 'mediumint', 'smallint', 'tinyint'];
  272. $rowData = [];
  273. foreach ($row as $key => $data) {
  274. if ($data === null) {
  275. $value = 'NULL';
  276. } elseif (in_array(strtolower($describe[$key]['DATA_TYPE']), $dataTypes)) {
  277. $value = $data;
  278. } else {
  279. $value = $connection->quoteInto('?', $data);
  280. }
  281. $rowData[] = $value;
  282. }
  283. return sprintf('(%s)', implode(',', $rowData));
  284. }
  285. /**
  286. * Prepare transaction isolation level for backup process
  287. *
  288. * @return void
  289. */
  290. public function prepareTransactionIsolationLevel()
  291. {
  292. $this->getConnection()->query('SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE');
  293. }
  294. /**
  295. * Restore transaction isolation level after backup
  296. *
  297. * @return void
  298. */
  299. public function restoreTransactionIsolationLevel()
  300. {
  301. $this->getConnection()->query('SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ');
  302. }
  303. /**
  304. * Get create script for triggers.
  305. *
  306. * @param string $tableName
  307. * @param boolean $addDropIfExists
  308. * @param boolean $stripDefiner
  309. * @return string
  310. * @since 100.2.3
  311. */
  312. public function getTableTriggersSql($tableName, $addDropIfExists = false, $stripDefiner = true)
  313. {
  314. $script = "--\n-- Triggers structure for table `{$tableName}`\n--\n";
  315. $triggers = $this->getConnection()->query('SHOW TRIGGERS LIKE \''. $tableName . '\'')->fetchAll();
  316. if (!$triggers) {
  317. return '';
  318. }
  319. foreach ($triggers as $trigger) {
  320. if ($addDropIfExists) {
  321. $script .= 'DROP TRIGGER IF EXISTS ' . $trigger['Trigger'] . ";\n";
  322. }
  323. $script .= "delimiter ;;\n";
  324. $triggerData = $this->getConnection()->query('SHOW CREATE TRIGGER '. $trigger['Trigger'])->fetch();
  325. if ($stripDefiner) {
  326. $cleanedScript = preg_replace('/DEFINER=[^\s]*/', '', $triggerData['SQL Original Statement']);
  327. $script .= $cleanedScript . "\n";
  328. } else {
  329. $script .= $triggerData['SQL Original Statement'] . "\n";
  330. }
  331. $script .= ";;\n";
  332. $script .= "delimiter ;\n";
  333. }
  334. return $script;
  335. }
  336. }