PageRenderTime 55ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/app/code/core/Mage/Core/Model/Resource/Setup/Query/Modifier.php

https://bitbucket.org/sevenly/magento-ce
PHP | 349 lines | 188 code | 39 blank | 122 comment | 47 complexity | 5a5e0eccbb0580f3710874b463d8358d MD5 | raw file
  1. <?php
  2. /**
  3. * Magento
  4. *
  5. * NOTICE OF LICENSE
  6. *
  7. * This source file is subject to the Open Software License (OSL 3.0)
  8. * that is bundled with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://opensource.org/licenses/osl-3.0.php
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@magentocommerce.com so we can send you a copy immediately.
  14. *
  15. * DISCLAIMER
  16. *
  17. * Do not edit or add to this file if you wish to upgrade Magento to newer
  18. * versions in the future. If you wish to customize Magento for your
  19. * needs please refer to http://www.magentocommerce.com for more information.
  20. *
  21. * @category Mage
  22. * @package Mage_Core
  23. * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com)
  24. * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  25. */
  26. /**
  27. * Modifier of queries, developed for backwards compatibility on MySQL,
  28. * while creating foreign keys
  29. *
  30. * @category Mage
  31. * @package Mage_Core
  32. * @author Magento Core Team <core@magentocommerce.com>
  33. */
  34. class Mage_Core_Model_Resource_Setup_Query_Modifier
  35. {
  36. /**
  37. * MySQL adapter instance
  38. *
  39. * @var Varien_Db_Adapter_Pdo_Mysql
  40. */
  41. protected $_adapter;
  42. /**
  43. * Types of column we process for foreign keys
  44. *
  45. * @var array
  46. */
  47. protected $_processedTypes = array('tinyint', 'smallint', 'mediumint', 'int', 'longint');
  48. /**
  49. * Inits query modifier
  50. *
  51. * @param $adapter Varien_Db_Adapter_Pdo_Mysql
  52. * @return void
  53. */
  54. public function __construct($args)
  55. {
  56. $this->_adapter = $args[0];
  57. }
  58. /**
  59. * Returns column definition from CREATE TABLE sql
  60. *
  61. * @param string $sql
  62. * @param string $column
  63. * @return array
  64. */
  65. protected function _getColumnDefinitionFromSql($sql, $column)
  66. {
  67. $result = null;
  68. foreach ($this->_processedTypes as $type) {
  69. $pattern = '/\s([^\s]+)\s+' . $type . '[^\s]*(\s+[^,]+)/i';
  70. if (!preg_match_all($pattern, $sql, $matches, PREG_SET_ORDER)) {
  71. continue;
  72. }
  73. foreach ($matches as $match) {
  74. $gotColumn = $this->_prepareIdentifier($match[1]);
  75. if ($gotColumn != $column) {
  76. continue;
  77. }
  78. $definition = $match[2];
  79. $unsigned = preg_match('/\sUNSIGNED/i', $definition) > 0;
  80. $result = array(
  81. 'type' => $type,
  82. 'unsigned' => $unsigned
  83. );
  84. break;
  85. }
  86. if ($result) {
  87. break;
  88. }
  89. }
  90. return $result;
  91. }
  92. /**
  93. * Replaces first occurence of $needle in a $haystack
  94. *
  95. * @param string $haystack
  96. * @param string $needle
  97. * @param array $replacement
  98. * @param bool $caseInsensitive
  99. * @return string
  100. */
  101. protected function _firstReplace($haystack, $needle, $replacement, $caseInsensitive = false)
  102. {
  103. $pos = $caseInsensitive ? stripos($haystack, $needle) : strpos($haystack, $needle);
  104. if ($pos === false) {
  105. return $haystack;
  106. }
  107. return substr($haystack, 0, $pos) . $replacement . substr($haystack, $pos + strlen($needle));
  108. }
  109. /**
  110. * Fixes column definition in CREATE TABLE sql to match defintion of column it's set to
  111. *
  112. * @param string $sql
  113. * @param string $column
  114. * @param array $refColumnDefinition
  115. * @return Mage_Core_Model_Resource_Setup_Query_Modifier
  116. */
  117. protected function _fixColumnDefinitionInSql(&$sql, $column, $refColumnDefinition)
  118. {
  119. $pos = stripos($sql, "`{$column}`"); // First try to find column directly recorded
  120. if ($pos === false) {
  121. $pattern = '/[`\s]' . preg_quote($column, '/') . '[`\s]/i';
  122. if (!preg_match($pattern, $sql, $matches)) {
  123. return $this;
  124. }
  125. $columnEntry = $matches[0];
  126. $pos = strpos($sql, $columnEntry);
  127. if ($pos === false) {
  128. return $this;
  129. }
  130. }
  131. $startSql = substr($sql, 0, $pos);
  132. $restSql = substr($sql, $pos);
  133. // Column type definition
  134. $columnDefinition = $this->_getColumnDefinitionFromSql($sql, $column);
  135. if (!$columnDefinition) {
  136. return $this;
  137. }
  138. // Find pattern for type defintion
  139. $pattern = '/\s*([^\s]+)\s+(' . $columnDefinition['type'] . '[^\s]*)\s+([^,]+)/i';
  140. if (!preg_match($pattern, $restSql, $matches)) {
  141. return $this;
  142. }
  143. // Replace defined type with needed type
  144. $typeDefined = $matches[2];
  145. $typeNeeded = $refColumnDefinition['type'];
  146. if ($refColumnDefinition['unsigned'] && !$columnDefinition['unsigned']) {
  147. $typeNeeded .= ' unsigned';
  148. }
  149. $restSql = $this->_firstReplace($restSql, $typeDefined, $typeNeeded);
  150. if (!$refColumnDefinition['unsigned'] && ($columnDefinition['unsigned'])) {
  151. $restSql = $this->_firstReplace($restSql, 'unsigned', '', true);
  152. }
  153. // Compose SQL back
  154. $sql = $startSql . $restSql;
  155. return $this;
  156. }
  157. /**
  158. * Fixes column definition in already existing table, so outgoing foreign key will be successfully set
  159. *
  160. * @param string $sql
  161. * @param string $column
  162. * @param array $refColumnDefinition
  163. * @return Mage_Core_Model_Resource_Setup_Query_Modifier
  164. */
  165. protected function _fixColumnDefinitionInTable($table, $column, $refColumnDefinition)
  166. {
  167. $description = $this->_adapter->fetchAll('DESCRIBE ' . $table);
  168. foreach ($description as $columnData) {
  169. $columnName = $this->_prepareIdentifier($columnData['Field']);
  170. if ($columnName != $column) {
  171. continue;
  172. }
  173. $definition = $refColumnDefinition['type'];
  174. if ($refColumnDefinition['unsigned']) {
  175. $definition .= ' UNSIGNED';
  176. }
  177. if ($columnData['Null'] == 'YES') {
  178. $definition .= ' NULL';
  179. } else {
  180. $definition .= ' NOT NULL';
  181. }
  182. if ($columnData['Default']) {
  183. $definition .= ' DEFAULT ' . $columnData['Default'];
  184. }
  185. if ($columnData['Extra']) {
  186. $definition .= ' ' . $columnData['Extra'];
  187. }
  188. $query = 'ALTER TABLE ' . $table . ' MODIFY COLUMN ' . $column . ' ' . $definition;
  189. $this->_adapter->query($query);
  190. }
  191. return $this;
  192. }
  193. /**
  194. * Returns column definition from already existing table
  195. *
  196. * @param string $sql
  197. * @param string $column
  198. * @return array|null
  199. */
  200. protected function _getColumnDefinitionFromTable($table, $column)
  201. {
  202. $description = $this->_adapter->describeTable($table);
  203. if (!isset($description[$column])) {
  204. return null;
  205. }
  206. return array(
  207. 'type' => $this->_prepareIdentifier($description[$column]['DATA_TYPE']),
  208. 'unsigned' => (bool) $description[$column]['UNSIGNED']
  209. );
  210. }
  211. /**
  212. * Returns whether table exists
  213. *
  214. * @param string $table
  215. * @return bool
  216. */
  217. protected function _tableExists($table)
  218. {
  219. $rows = $this->_adapter->fetchAll('SHOW TABLES');
  220. foreach ($rows as $row) {
  221. $tableFound = strtolower(current($row));
  222. if ($table == $tableFound) {
  223. return true;
  224. }
  225. }
  226. return false;
  227. }
  228. /**
  229. * Trims and lowercases identifier, to make common view of all of them
  230. *
  231. * @param string $identifier
  232. * @return string
  233. */
  234. protected function _prepareIdentifier($identifier)
  235. {
  236. return strtolower(trim($identifier, "`\n\r\t"));
  237. }
  238. /**
  239. * Processes query, modifies targeted columns to fit foreign keys restrictions
  240. *
  241. * @param string $sql
  242. * @param array $bind
  243. * @return Mage_Core_Model_Resource_Setup_Query_Modifier
  244. */
  245. public function processQuery(&$sql, &$bind)
  246. {
  247. // Quick test to skip queries without foreign keys
  248. if (!stripos($sql, 'foreign')) {
  249. return $this;
  250. }
  251. // Find foreign keys set
  252. $pattern = '/CONSTRAINT\s+[^\s]+\s+FOREIGN\s+KEY[^(]+\\(([^),]+)\\)\s+REFERENCES\s+([^\s.]+)\s+\\(([^)]+)\\)/i';
  253. if (!preg_match_all($pattern, $sql, $matchesFk, PREG_SET_ORDER)) {
  254. return $this;
  255. }
  256. // Get current table name
  257. if (!preg_match('/\s*(CREATE|ALTER)\s+TABLE\s+([^\s.]+)/i', $sql, $match)) {
  258. return $this;
  259. }
  260. $operation = $this->_prepareIdentifier($match[1]);
  261. $table = $this->_prepareIdentifier($match[2]);
  262. // Process all
  263. foreach ($matchesFk as $match) {
  264. $column = $this->_prepareIdentifier($match[1]);
  265. $refTable = $this->_prepareIdentifier($match[2]);
  266. $refColumn = $this->_prepareIdentifier($match[3]);
  267. // Check tables existance
  268. if (($operation != 'create') && !($this->_tableExists($table))) {
  269. continue;
  270. }
  271. if (!$this->_tableExists($refTable)) {
  272. continue;
  273. }
  274. // Self references are out of our fix scope
  275. if ($refTable == $table) {
  276. continue;
  277. }
  278. // Extract column type
  279. if ($operation == 'create') {
  280. $columnDefinition = $this->_getColumnDefinitionFromSql($sql, $column);
  281. } else {
  282. $columnDefinition = $this->_getColumnDefinitionFromTable($table, $column);
  283. }
  284. // We fix only int columns
  285. if (!$columnDefinition || !in_array($columnDefinition['type'], $this->_processedTypes)) {
  286. continue;
  287. }
  288. // Extract referenced column type
  289. $refColumnDefinition = $this->_getColumnDefinitionFromTable($refTable, $refColumn);
  290. if (!$refColumnDefinition) {
  291. continue;
  292. }
  293. // We fix only int columns
  294. if (!$refColumnDefinition || !in_array($refColumnDefinition['type'], $this->_processedTypes)) {
  295. continue;
  296. }
  297. // Whether we need to fix
  298. if ($refColumnDefinition == $columnDefinition) {
  299. continue;
  300. }
  301. // Fix column to be the same type as referenced one
  302. if ($operation == 'create') {
  303. $this->_fixColumnDefinitionInSql($sql, $column, $refColumnDefinition);
  304. } else {
  305. $this->_fixColumnDefinitionInTable($table, $column, $refColumnDefinition);
  306. }
  307. }
  308. return $this;
  309. }
  310. }