PageRenderTime 56ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/app/code/core/Mage/Catalog/Model/Resource/Eav/Mysql4/Product/Flat/Indexer.php

https://bitbucket.org/claudiu_marginean/magento-hg-mirror
PHP | 1221 lines | 818 code | 111 blank | 292 comment | 116 complexity | 8cfb05dc444152094472c83b15cd77a8 MD5 | raw file
Possible License(s): CC-BY-SA-3.0, LGPL-2.1, GPL-2.0, WTFPL
  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_Catalog
  23. * @copyright Copyright (c) 2010 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. * Catalog Product Flat Indexer Resource Model
  28. *
  29. * @category Mage
  30. * @package Mage_Catalog
  31. * @author Magento Core Team <core@magentocommerce.com>
  32. */
  33. class Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Flat_Indexer
  34. extends Mage_Core_Model_Mysql4_Abstract
  35. {
  36. const XML_NODE_MAX_INDEX_COUNT = 'global/catalog/product/flat/max_index_count';
  37. const XML_NODE_ATTRIBUTE_NODES = 'global/catalog/product/flat/attribute_nodes';
  38. /**
  39. * Attribute codes for flat
  40. *
  41. * @var array
  42. */
  43. protected $_attributeCodes;
  44. /**
  45. * Attribute objects for flat cache
  46. *
  47. * @var array
  48. */
  49. protected $_attributes;
  50. /**
  51. * Required system attributes for preload
  52. *
  53. * @var array
  54. */
  55. protected $_systemAttributes = array('status', 'required_options', 'tax_class_id', 'weight');
  56. /**
  57. * Eav Catalog_Product Entity Type Id
  58. *
  59. * @var int
  60. */
  61. protected $_entityTypeId;
  62. /**
  63. * Flat table columns cache
  64. *
  65. * @var array
  66. */
  67. protected $_columns;
  68. /**
  69. * Flat table indexes cache
  70. *
  71. * @var array
  72. */
  73. protected $_indexes;
  74. /**
  75. * Product Type Instances cache
  76. *
  77. * @var array
  78. */
  79. protected $_productTypes;
  80. /**
  81. * Exists flat tables cache
  82. *
  83. * @var array
  84. */
  85. protected $_existsFlatTables = array();
  86. /**
  87. * Initialize connection
  88. *
  89. */
  90. protected function _construct()
  91. {
  92. $this->_init('catalog/product', 'entity_id');
  93. }
  94. /**
  95. * Rebuild Catalog Product Flat Data
  96. *
  97. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Flat_Indexer
  98. */
  99. public function rebuild($store = null)
  100. {
  101. if (is_null($store)) {
  102. foreach (Mage::app()->getStores() as $store) {
  103. $this->rebuild($store->getId());
  104. }
  105. $flag = $this->getFlatHelper()->getFlag();
  106. $flag->setIsBuild(true)->save();
  107. return $this;
  108. }
  109. $this->prepareFlatTable($store);
  110. $this->cleanNonWebsiteProducts($store);
  111. $this->updateStaticAttributes($store);
  112. $this->updateEavAttributes($store);
  113. $this->updateEventAttributes($store);
  114. $this->updateRelationProducts($store);
  115. $this->cleanRelationProducts($store);
  116. return $this;
  117. }
  118. /**
  119. * Retrieve Catalog Product Flat helper
  120. *
  121. * @return Mage_Catalog_Helper_Product_Flat
  122. */
  123. public function getFlatHelper()
  124. {
  125. return Mage::helper('catalog/product_flat');
  126. }
  127. /**
  128. * Retrieve attribute codes using for flat
  129. *
  130. * @return array
  131. */
  132. public function getAttributeCodes()
  133. {
  134. if (is_null($this->_attributeCodes)) {
  135. $attributeNodes = Mage::getConfig()
  136. ->getNode(self::XML_NODE_ATTRIBUTE_NODES)
  137. ->children();
  138. foreach ($attributeNodes as $node) {
  139. $attributes = Mage::getConfig()->getNode((string)$node)->asArray();
  140. $attributes = array_keys($attributes);
  141. $this->_systemAttributes = array_unique(array_merge($attributes, $this->_systemAttributes));
  142. }
  143. $this->_attributeCodes = array();
  144. $whereCond = array(
  145. $this->_getReadAdapter()->quoteInto('main_table.backend_type=?', 'static'),
  146. $this->_getReadAdapter()->quoteInto('additional_table.used_in_product_listing=?', 1),
  147. $this->_getReadAdapter()->quoteInto('additional_table.used_for_sort_by=?', 1),
  148. $this->_getReadAdapter()->quoteInto('main_table.attribute_code IN(?)', $this->_systemAttributes)
  149. );
  150. if ($this->getFlatHelper()->isAddFilterableAttributes()) {
  151. $whereCond[] = $this->_getReadAdapter()->quoteInto('additional_table.is_filterable>?', 0);
  152. }
  153. $select = $this->_getReadAdapter()->select()
  154. ->from(array('main_table' => $this->getTable('eav/attribute')))
  155. ->join(
  156. array('additional_table' => $this->getTable('catalog/eav_attribute')),
  157. 'additional_table.attribute_id=main_table.attribute_id'
  158. )
  159. ->where('main_table.entity_type_id=?', $this->getEntityTypeId())
  160. ->where(join(' OR ', $whereCond));
  161. $attributesData = $this->_getReadAdapter()->fetchAll($select);
  162. Mage::getSingleton('eav/config')
  163. ->importAttributesData($this->getEntityType(), $attributesData);
  164. $this->_attributeCodes = array();
  165. foreach ($attributesData as $data) {
  166. $this->_attributeCodes[$data['attribute_id']] = $data['attribute_code'];
  167. }
  168. unset($attributesData);
  169. }
  170. return $this->_attributeCodes;
  171. }
  172. /**
  173. * Retrieve entity type
  174. *
  175. * @return string
  176. */
  177. public function getEntityType()
  178. {
  179. return 'catalog_product';
  180. }
  181. /**
  182. * Retrieve Catalog Entity Type Id
  183. *
  184. * @return int
  185. */
  186. public function getEntityTypeId()
  187. {
  188. if (is_null($this->_entityTypeId)) {
  189. $this->_entityTypeId = Mage::getResourceModel('catalog/config')
  190. ->getEntityTypeId();
  191. }
  192. return $this->_entityTypeId;
  193. }
  194. /**
  195. * Retrieve attribute objects for flat
  196. *
  197. * @param bool $cache
  198. * @return array
  199. */
  200. public function getAttributes()
  201. {
  202. if (is_null($this->_attributes)) {
  203. $attributeCodes = $this->getAttributeCodes(false);
  204. $entity = Mage::getSingleton('eav/config')
  205. ->getEntityType($this->getEntityType())
  206. ->getEntity();
  207. foreach ($attributeCodes as $attributeCode) {
  208. $attribute = Mage::getSingleton('eav/config')
  209. ->getAttribute($this->getEntityType(), $attributeCode)
  210. ->setEntity($entity);
  211. try {
  212. // check if exists source and backend model.
  213. // To prevent exception when some module was disabled
  214. $attribute->usesSource() && $attribute->getSource();
  215. $attribute->getBackend();
  216. $this->_attributes[$attributeCode] = $attribute;
  217. } catch (Exception $e) {
  218. Mage::logException($e);
  219. }
  220. }
  221. }
  222. return $this->_attributes;
  223. }
  224. /**
  225. * Retrieve loaded attribute by code
  226. *
  227. * @param string $attributeCode
  228. * @return Mage_Eav_Model_Entity_Attribute
  229. */
  230. public function getAttribute($attributeCode)
  231. {
  232. $attributes = $this->getAttributes();
  233. if (!isset($attributes[$attributeCode])) {
  234. $attribute = Mage::getModel('catalog/resource_eav_attribute')
  235. ->loadByCode($this->getEntityTypeId(), $attributeCode);
  236. if (!$attribute->getId()) {
  237. Mage::throwException(Mage::helper('catalog')->__('Invalid attribute %s.', $attributeCode));
  238. }
  239. $entity = Mage::getSingleton('eav/config')
  240. ->getEntityType($this->getEntityType())
  241. ->getEntity();
  242. $attribute->setEntity($entity);
  243. return $attribute;
  244. }
  245. return $attributes[$attributeCode];
  246. }
  247. /**
  248. * Retrieve Catalog Product Flat Table name
  249. *
  250. * @param int $store
  251. * @return string
  252. */
  253. public function getFlatTableName($store)
  254. {
  255. return $this->getTable('catalog/product_flat') . '_' . $store;
  256. }
  257. /**
  258. * Retrieve catalog product flat table columns array
  259. *
  260. * @return array
  261. */
  262. public function getFlatColumns()
  263. {
  264. if (is_null($this->_columns)) {
  265. $this->_columns = array();
  266. $this->_columns['entity_id'] = array(
  267. 'type' => 'int(10)',
  268. 'unsigned' => true,
  269. 'is_null' => false,
  270. 'default' => null,
  271. 'extra' => 'auto_increment'
  272. );
  273. if ($this->getFlatHelper()->isAddChildData()) {
  274. $this->_columns['child_id'] = array(
  275. 'type' => 'int(10)',
  276. 'unsigned' => true,
  277. 'is_null' => true,
  278. 'default' => null,
  279. 'extra' => null
  280. );
  281. $this->_columns['is_child'] = array(
  282. 'type' => 'tinyint(1)',
  283. 'unsigned' => true,
  284. 'is_null' => false,
  285. 'default' => 0,
  286. 'extra' => null
  287. );
  288. }
  289. $this->_columns['attribute_set_id'] = array(
  290. 'type' => 'smallint(5)',
  291. 'unsigned' => true,
  292. 'is_null' => false,
  293. 'default' => 0,
  294. 'extra' => null
  295. );
  296. $this->_columns['type_id'] = array(
  297. 'type' => 'varchar(32)',
  298. 'unsigned' => false,
  299. 'is_null' => false,
  300. 'default' => 'simple',
  301. 'extra' => null
  302. );
  303. foreach ($this->getAttributes() as $attribute) {
  304. /* @var $attribute Mage_Eav_Model_Entity_Attribute */
  305. $columns = $attribute
  306. ->setFlatAddFilterableAttributes($this->getFlatHelper()->isAddFilterableAttributes())
  307. ->setFlatAddChildData($this->getFlatHelper()->isAddChildData())
  308. ->getFlatColumns();
  309. if (is_null($columns)) {
  310. continue;
  311. }
  312. $this->_columns = array_merge($this->_columns, $columns);
  313. }
  314. $columnsObject = new Varien_Object();
  315. $columnsObject->setColumns($this->_columns);
  316. Mage::dispatchEvent('catalog_product_flat_prepare_columns', array(
  317. 'columns' => $columnsObject
  318. ));
  319. $this->_columns = $columnsObject->getColumns();
  320. }
  321. return $this->_columns;
  322. }
  323. /**
  324. * Retrieve catalog product flat table indexes array
  325. *
  326. * @return array
  327. */
  328. public function getFlatIndexes()
  329. {
  330. if (is_null($this->_indexes)) {
  331. $this->_indexes = array();
  332. if ($this->getFlatHelper()->isAddChildData()) {
  333. $this->_indexes['PRIMARY'] = array(
  334. 'type' => 'primary',
  335. 'fields' => array('entity_id', 'child_id')
  336. );
  337. $this->_indexes['IDX_CHILD'] = array(
  338. 'type' => 'index',
  339. 'fields' => array('child_id')
  340. );
  341. $this->_indexes['IDX_IS_CHILD'] = array(
  342. 'type' => 'index',
  343. 'fields' => array('entity_id', 'is_child')
  344. );
  345. }
  346. else {
  347. $this->_indexes['PRIMARY'] = array(
  348. 'type' => 'primary',
  349. 'fields' => array('entity_id')
  350. );
  351. }
  352. $this->_indexes['IDX_TYPE_ID'] = array(
  353. 'type' => 'index',
  354. 'fields' => array('type_id')
  355. );
  356. $this->_indexes['IDX_ATRRIBUTE_SET'] = array(
  357. 'type' => 'index',
  358. 'fields' => array('attribute_set_id')
  359. );
  360. foreach ($this->getAttributes() as $attribute) {
  361. /* @var $attribute Mage_Eav_Model_Entity_Attribute */
  362. $indexes = $attribute
  363. ->setFlatAddFilterableAttributes($this->getFlatHelper()->isAddFilterableAttributes())
  364. ->setFlatAddChildData($this->getFlatHelper()->isAddChildData())
  365. ->getFlatIndexes();
  366. if (is_null($indexes)) {
  367. continue;
  368. }
  369. $this->_indexes = array_merge($this->_indexes, $indexes);
  370. }
  371. $indexesObject = new Varien_Object();
  372. $indexesObject->setIndexes($this->_indexes);
  373. Mage::dispatchEvent('catalog_product_flat_prepare_indexes', array(
  374. 'indexes' => $indexesObject
  375. ));
  376. $this->_indexes = $indexesObject->getIndexes();
  377. }
  378. return $this->_indexes;
  379. }
  380. /**
  381. * Compare Flat style with Describe style columns
  382. *
  383. * If column a different - return false
  384. *
  385. * @param array $column
  386. * @param array $describe
  387. * @return bool
  388. */
  389. protected function _compareColumnProperties($column, $describe)
  390. {
  391. $type = $column['type'];
  392. $length = null;
  393. $precision = null;
  394. $scale = null;
  395. $matches = array();
  396. if (preg_match('/^((?:var)?char)\((\d+)\)/', $type, $matches)) {
  397. $type = $matches[1];
  398. $length = $matches[2];
  399. } else if (preg_match('/^decimal\((\d+),(\d+)\)/', $type, $matches)) {
  400. $type = 'decimal';
  401. $precision = $matches[1];
  402. $scale = $matches[2];
  403. } else if (preg_match('/^float\((\d+),(\d+)\)/', $type, $matches)) {
  404. $type = 'float';
  405. $precision = $matches[1];
  406. $scale = $matches[2];
  407. } else if (preg_match('/^((?:big|medium|small|tiny)?int)\((\d+)\)?/', $type, $matches)) {
  408. $type = $matches[1];
  409. }
  410. return ($describe['DATA_TYPE'] == $type)
  411. && ($describe['DEFAULT'] == $column['default'])
  412. && ((bool)$describe['NULLABLE'] == (bool)$column['is_null'])
  413. && ((bool)$describe['UNSIGNED'] == (bool)$column['unsigned'])
  414. && ($describe['LENGTH'] == $length)
  415. && ($describe['SCALE'] == $scale)
  416. && ($describe['PRECISION'] == $precision);
  417. }
  418. /**
  419. * Retrieve column definition fragment
  420. *
  421. * Example: `field_name` smallint(5) unsigned NOT NULL default '0'
  422. *
  423. * @param string $fieldName
  424. * @param array $fieldProp
  425. * @return string
  426. */
  427. protected function _sqlColunmDefinition($fieldName, $fieldProp)
  428. {
  429. $fieldNameQuote = $this->_getWriteAdapter()->quoteIdentifier($fieldName);
  430. /**
  431. * Process the case when 'is_null' prohibits null value, and 'default' proposed to be null
  432. * It just means that default value not specified
  433. */
  434. if (false === $fieldProp['is_null'] && null === $fieldProp['default']) {
  435. $defaultValue = '';
  436. } else {
  437. $defaultValue = $fieldProp['default'] === null ? ' DEFAULT NULL' : $this->_getReadAdapter()
  438. ->quoteInto(' DEFAULT ?', $fieldProp['default']);
  439. }
  440. return "{$fieldNameQuote} {$fieldProp['type']}"
  441. . ($fieldProp['unsigned'] ? ' UNSIGNED' : '')
  442. . ($fieldProp['extra'] ? ' ' . $fieldProp['extra'] : '')
  443. . ($fieldProp['is_null'] === false ? ' NOT NULL' : '')
  444. . $defaultValue;
  445. }
  446. /**
  447. * Retrieve index definition fragment
  448. *
  449. * Example: INDEX `IDX_NAME` (`field_id`)
  450. *
  451. * @param string $indexName
  452. * @param array $indexProp
  453. * @return string
  454. */
  455. protected function _sqlIndexDefinition($indexName, $indexProp)
  456. {
  457. $fields = $indexProp['fields'];
  458. if (is_array($fields)) {
  459. $fieldSql = array();
  460. foreach ($fields as $field) {
  461. $fieldSql[] = $this->_getReadAdapter()->quoteIdentifier($field);
  462. }
  463. $fieldSql = join(',', $fieldSql);
  464. }
  465. else {
  466. $fieldSql = $this->_getReadAdapter()->quoteIdentifier($fields);
  467. }
  468. $indexNameQuote = $this->_getReadAdapter()->quoteIdentifier($indexName);
  469. switch (strtolower($indexProp['type'])) {
  470. case 'primary':
  471. $condition = 'PRIMARY KEY';
  472. break;
  473. case 'unique':
  474. $condition = 'UNIQUE ' . $indexNameQuote;
  475. break;
  476. case 'fulltext':
  477. $condition = 'FULLTEXT ' . $indexNameQuote;
  478. break;
  479. default:
  480. $condition = 'INDEX ' . $indexNameQuote;
  481. break;
  482. }
  483. return sprintf('%s (%s)', $condition, $fieldSql);
  484. }
  485. /**
  486. * Prepare flat table for store
  487. *
  488. * @param int $store
  489. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Flat_Indexer
  490. */
  491. public function prepareFlatTable($store)
  492. {
  493. $columns = $this->getFlatColumns();
  494. $indexes = $this->getFlatIndexes();
  495. $maxIndex = Mage::getConfig()->getNode(self::XML_NODE_MAX_INDEX_COUNT);
  496. if (count($indexes) > $maxIndex) {
  497. Mage::throwException(Mage::helper('catalog')->__("The Flat Catalog module has a limit of %2\$d filterable and/or sortable attributes. Currently there are %1\$d of them. Please reduce the number of filterable/sortable attributes in order to use this module.", count($indexes), $maxIndex));
  498. }
  499. $tableName = $this->getFlatTableName($store);
  500. $tableNameQuote = $this->_getWriteAdapter()->quoteIdentifier($tableName);
  501. if (!$this->_isFlatTableExists($store)) {
  502. $sql = "CREATE TABLE {$tableNameQuote} (\n";
  503. foreach ($columns as $field => $fieldProp) {
  504. $sql .= sprintf(" %s,\n",
  505. $this->_sqlColunmDefinition($field, $fieldProp));
  506. }
  507. foreach ($indexes as $indexName => $indexProp) {
  508. $sql .= sprintf(" %s,\n",
  509. $this->_sqlIndexDefinition($indexName, $indexProp));
  510. }
  511. $sql .= " CONSTRAINT `FK_CATALOG_PRODUCT_FLAT_{$store}_ENTITY` FOREIGN KEY (`entity_id`)"
  512. . " REFERENCES `{$this->getTable('catalog/product')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE";
  513. if ($this->getFlatHelper()->isAddChildData()) {
  514. $sql .= ",\n CONSTRAINT `FK_CATALOG_PRODUCT_FLAT_{$store}_CHILD` FOREIGN KEY (`child_id`)"
  515. . " REFERENCES `{$this->getTable('catalog/product')}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE";
  516. }
  517. $sql .= "\n) ENGINE=InnoDB DEFAULT CHARSET=utf8";
  518. $this->_getWriteAdapter()->query($sql);
  519. $this->_existsFlatTables[$store] = true;
  520. }
  521. else {
  522. $this->_getWriteAdapter()->resetDdlCache($tableName);
  523. $describe = $this->_getWriteAdapter()->describeTable($tableName);
  524. $indexList = $this->_getWriteAdapter()->getIndexList($tableName);
  525. $addColumns = array_diff_key($columns, $describe);
  526. $dropColumns = array_diff_key($describe, $columns);
  527. $modifyColumns = array();
  528. $addIndexes = array_diff_key($indexes, $indexList);
  529. $dropIndexes = array_diff_key($indexList, $indexes);
  530. $addConstraints = array();
  531. if (!$this->getFlatHelper()->isAddChildData() && isset($describe['is_child'])) {
  532. $this->_getWriteAdapter()->delete($tableName, 'is_child=1');
  533. $this->_getWriteAdapter()->dropForeignKey($tableName, "FK_CATALOG_PRODUCT_FLAT_{$store}_CHILD");
  534. }
  535. if ($this->getFlatHelper()->isAddChildData() && !isset($describe['is_child'])) {
  536. $this->_getWriteAdapter()->truncate($tableName);
  537. $dropIndexes['PRIMARY'] = $indexList['PRIMARY'];
  538. $addIndexes['PRIMARY'] = $indexes['PRIMARY'];
  539. $addConstraints["FK_CATALOG_PRODUCT_FLAT_{$store}_CHILD"] = array(
  540. 'table_index' => 'child_id',
  541. 'ref_table' => $this->getTable('catalog/product'),
  542. 'ref_index' => 'entity_id',
  543. 'on_update' => 'CASCADE',
  544. 'on_delete' => 'CASCADE'
  545. );
  546. }
  547. foreach ($columns as $field => $fieldProp) {
  548. if (isset($describe[$field])
  549. && !$this->_compareColumnProperties($fieldProp, $describe[$field])) {
  550. $modifyColumns[$field] = $fieldProp;
  551. }
  552. }
  553. foreach ($indexList as $indexName => $indexProp) {
  554. if (isset($indexes[$indexName]) && ($indexes[$indexName]['type'] != $indexProp['type'])) {
  555. $dropIndexes[$indexName] = $indexProp;
  556. $addIndexes[$indexName] = $indexes[$indexName];
  557. }
  558. }
  559. if ($addColumns or $dropColumns or $modifyColumns or $addIndexes or $dropIndexes) {
  560. $sql = "ALTER TABLE {$tableNameQuote}";
  561. // drop columns
  562. foreach ($dropColumns as $columnName => $columnProp) {
  563. $columnNameQuote = $this->_getWriteAdapter()->quoteIdentifier($columnName);
  564. $sql .= " DROP COLUMN {$columnNameQuote},";
  565. }
  566. // drop indexes
  567. foreach ($dropIndexes as $indexName => $indexProp) {
  568. if ($indexName == 'PRIMARY') {
  569. $sql .= " DROP PRIMARY KEY,";
  570. }
  571. else {
  572. $indexNameQuote = $this->_getWriteAdapter()->quoteIdentifier($indexName);
  573. $sql .= " DROP INDEX {$indexNameQuote},";
  574. }
  575. }
  576. // modify colunm
  577. foreach ($modifyColumns as $columnName => $columnProp) {
  578. $sql .= sprintf(' MODIFY COLUMN %s,',
  579. $this->_sqlColunmDefinition($columnName, $columnProp));
  580. }
  581. // add columns
  582. foreach ($addColumns as $columnName => $columnProp) {
  583. $sql .= sprintf(' ADD COLUMN %s',
  584. $this->_sqlColunmDefinition($columnName, $columnProp));
  585. $afterColumn = $this->_arrayPrevKey($columns, $columnName);
  586. if ($afterColumn) {
  587. $sql .= ' AFTER ' . $this->_getWriteAdapter()->quoteIdentifier($afterColumn);
  588. }
  589. $sql .= ',';
  590. }
  591. // add indexes
  592. foreach ($addIndexes as $indexName => $indexProp) {
  593. $sql .= sprintf(' ADD %s,',
  594. $this->_sqlIndexDefinition($indexName, $indexProp));
  595. }
  596. $sql = rtrim($sql, ",");
  597. $this->_getWriteAdapter()->query($sql);
  598. }
  599. foreach ($addConstraints as $constraintName => $constraintProp) {
  600. $this->_getWriteAdapter()->addConstraint(
  601. $constraintName,
  602. $tableName,
  603. $constraintProp['table_index'],
  604. $constraintProp['ref_table'],
  605. $constraintProp['ref_index'],
  606. $constraintProp['on_delete'],
  607. $constraintProp['on_update']
  608. );
  609. }
  610. }
  611. return $this;
  612. }
  613. /**
  614. * Add or Update static attributes
  615. *
  616. * @param int $store
  617. * @param int|array $productIds update only product(s)
  618. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Flat_Indexer
  619. */
  620. public function updateStaticAttributes($store, $productIds = null)
  621. {
  622. if (!$this->_isFlatTableExists($store)) {
  623. return $this;
  624. }
  625. $website = Mage::app()->getStore($store)->getWebsite()->getId();
  626. $status = $this->getAttribute('status');
  627. /* @var $status Mage_Eav_Model_Entity_Attribute */
  628. $fieldList = array('entity_id', 'type_id', 'attribute_set_id');
  629. $colsList = array('entity_id', 'type_id', 'attribute_set_id');
  630. if ($this->getFlatHelper()->isAddChildData()) {
  631. $fieldList = array_merge($fieldList, array('child_id', 'is_child'));
  632. $isChild = new Zend_Db_Expr('0');
  633. $colsList = array_merge($colsList, array('entity_id', $isChild));
  634. }
  635. $columns = $this->getFlatColumns();
  636. $select = $this->_getWriteAdapter()->select()
  637. ->from(
  638. array('e' => $this->getTable('catalog/product')),
  639. $colsList)
  640. ->join(
  641. array('wp' => $this->getTable('catalog/product_website')),
  642. "`e`.`entity_id`=`wp`.`product_id` AND `wp`.`website_id`={$website}",
  643. array())
  644. ->joinLeft(
  645. array('t1' => $status->getBackend()->getTable()),
  646. "`e`.`entity_id`=`t1`.`entity_id`",
  647. array())
  648. ->joinLeft(
  649. array('t2' => $status->getBackend()->getTable()),
  650. "t2.entity_id = t1.entity_id"
  651. . " AND t1.entity_type_id = t2.entity_type_id"
  652. . " AND t1.attribute_id = t2.attribute_id"
  653. . " AND t2.store_id = {$store}",
  654. array())
  655. ->where("t1.entity_type_id=?", $status->getEntityTypeId())
  656. ->where("t1.attribute_id=?", $status->getId())
  657. ->where("t1.store_id=?", 0)
  658. ->where("IF(`t2`.`value_id`>0, `t2`.`value`, `t1`.`value`)=?", Mage_Catalog_Model_Product_Status::STATUS_ENABLED);
  659. foreach ($this->getAttributes() as $attributeCode => $attribute) {
  660. /* @var $attribute Mage_Eav_Model_Entity_Attribute */
  661. if ($attribute->getBackend()->getType() == 'static') {
  662. if (!isset($columns[$attributeCode])) {
  663. continue;
  664. }
  665. $fieldList[] = $attributeCode;
  666. $select->columns($attributeCode, 'e');
  667. }
  668. }
  669. if (!is_null($productIds)) {
  670. $select->where('e.entity_id IN(?)', $productIds);
  671. }
  672. $sql = $select->insertFromSelect($this->getFlatTableName($store), $fieldList);
  673. $this->_getWriteAdapter()->query($sql);
  674. return $this;
  675. }
  676. /**
  677. * Remove non website products
  678. *
  679. * @param int $store
  680. * @param int|array $productIds
  681. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Flat_Indexer
  682. */
  683. public function cleanNonWebsiteProducts($store, $productIds = null)
  684. {
  685. $website = Mage::app()->getStore($store)->getWebsite()->getId();
  686. $joinCond = "`e`.`entity_id`=`wp`.`product_id` AND `wp`.`website_id`={$website}";
  687. if ($this->getFlatHelper()->isAddChildData()) {
  688. $joinCond .= " AND `e`.`child_id`=`wp`.`product_id`";
  689. }
  690. $select = $this->_getWriteAdapter()->select()
  691. ->from(
  692. array('e' => $this->getFlatTableName($store)),
  693. null)
  694. ->joinLeft(
  695. array('wp' => $this->getTable('catalog/product_website')),
  696. $joinCond,
  697. array());
  698. if (!is_null($productIds)) {
  699. $cond = array(
  700. $this->_getWriteAdapter()->quoteInto('e.entity_id IN(?)', $productIds)
  701. );
  702. if ($this->getFlatHelper()->isAddChildData()) {
  703. $cond[] = $this->_getWriteAdapter()->quoteInto('e.child_id IN(?)', $productIds);
  704. }
  705. $select->where(join(' OR ', $cond));
  706. }
  707. $sql = $select->deleteFromSelect('e');
  708. $this->_getWriteAdapter()->query($sql);
  709. return $this;
  710. }
  711. /**
  712. * Update attribute flat data
  713. *
  714. * @param Mage_Eav_Model_Entity_Attribute $attribute
  715. * @param int $store
  716. * @param int|array $productIds update only product(s)
  717. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Flat_Indexer
  718. */
  719. public function updateAttribute($attribute, $store, $productIds = null)
  720. {
  721. if (!$this->_isFlatTableExists($store)) {
  722. return $this;
  723. }
  724. $describe = $this->_getWriteAdapter()->describeTable($this->getFlatTableName($store));
  725. if ($attribute->getBackend()->getType() == 'static') {
  726. if (!isset($describe[$attribute->getAttributeCode()])) {
  727. return $this;
  728. }
  729. $select = $this->_getWriteAdapter()->select()
  730. ->join(
  731. array('main_table' => $this->getTable('catalog/product')),
  732. 'main_table.entity_id=e.entity_id ',
  733. array($attribute->getAttributeCode() => 'main_table.' . $attribute->getAttributeCode())
  734. );
  735. if ($this->getFlatHelper()->isAddChildData()) {
  736. $select->where("e.is_child=?", 0);
  737. }
  738. if (!is_null($productIds)) {
  739. $select->where('main_table.entity_id IN(?)', $productIds);
  740. }
  741. $sql = $select->crossUpdateFromSelect(array('e' => $this->getFlatTableName($store)));
  742. $this->_getWriteAdapter()->query($sql);
  743. }
  744. else {
  745. $columns = $attribute->getFlatColumns();
  746. if (!$columns) {
  747. return $this;
  748. }
  749. foreach (array_keys($columns) as $columnName) {
  750. if (!isset($describe[$columnName])) {
  751. return $this;
  752. }
  753. }
  754. $select = $attribute->getFlatUpdateSelect($store);
  755. if ($select instanceof Varien_Db_Select) {
  756. if (!is_null($productIds)) {
  757. $select->where('e.entity_id IN(?)', $productIds);
  758. }
  759. $sql = $select->crossUpdateFromSelect(array('e' => $this->getFlatTableName($store)));
  760. $this->_getWriteAdapter()->query($sql);
  761. }
  762. }
  763. return $this;
  764. }
  765. /**
  766. * Update non static EAV attributes flat data
  767. *
  768. * @param int $store
  769. * @param int|array $productIds update only product(s)
  770. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Flat_Indexer
  771. */
  772. public function updateEavAttributes($store, $productIds = null)
  773. {
  774. if (!$this->_isFlatTableExists($store)) {
  775. return $this;
  776. }
  777. foreach ($this->getAttributes() as $attribute) {
  778. /* @var $attribute Mage_Eav_Model_Entity_Attribute */
  779. if ($attribute->getBackend()->getType() != 'static') {
  780. $this->updateAttribute($attribute, $store, $productIds);
  781. }
  782. }
  783. return $this;
  784. }
  785. /**
  786. * Update events observer attributes
  787. *
  788. * @param int $store
  789. * @return Mage_Catalog_Model_Product_Flat_Indexer
  790. */
  791. public function updateEventAttributes($store = null)
  792. {
  793. Mage::dispatchEvent('catalog_product_flat_rebuild', array(
  794. 'store_id' => $store,
  795. 'table' => $this->getFlatTableName($store)
  796. ));
  797. }
  798. /**
  799. * Retrieve Product Type Instances
  800. * as key - type code, value - instance model
  801. *
  802. * @return array
  803. */
  804. public function getProductTypeInstances()
  805. {
  806. if (is_null($this->_productTypes)) {
  807. $this->_productTypes = array();
  808. $productEmulator = new Varien_Object();
  809. foreach (array_keys(Mage_Catalog_Model_Product_Type::getTypes()) as $typeId) {
  810. $productEmulator->setTypeId($typeId);
  811. $this->_productTypes[$typeId] = Mage::getSingleton('catalog/product_type')
  812. ->factory($productEmulator);
  813. }
  814. }
  815. return $this->_productTypes;
  816. }
  817. /**
  818. * Update relation products
  819. *
  820. * @param int $store
  821. * @param int|array $productIds Update child product(s) only
  822. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Flat_Indexer
  823. */
  824. public function updateRelationProducts($store, $productIds = null)
  825. {
  826. if (!$this->getFlatHelper()->isAddChildData()) {
  827. return $this;
  828. }
  829. if (!$this->_isFlatTableExists($store)) {
  830. return $this;
  831. }
  832. foreach ($this->getProductTypeInstances() as $typeInstance) {
  833. if (!$typeInstance->isComposite()) {
  834. continue;
  835. }
  836. $relation = $typeInstance->getRelationInfo();
  837. if ($relation
  838. and $relation->getTable()
  839. and $relation->getParentFieldName()
  840. and $relation->getChildFieldName()
  841. ) {
  842. $columns = $this->getFlatColumns();
  843. $fieldList = array_keys($columns);
  844. unset($columns['entity_id']);
  845. unset($columns['child_id']);
  846. unset($columns['is_child']);
  847. $select = $this->_getWriteAdapter()->select()
  848. ->from(
  849. array('t' => $this->getTable($relation->getTable())),
  850. array($relation->getParentFieldName(), $relation->getChildFieldName(), new Zend_Db_Expr('1')))
  851. ->join(
  852. array('e' => $this->getFlatTableName($store)),
  853. "`e`.`entity_id`=`t`.`{$relation->getChildFieldName()}`",
  854. array_keys($columns)
  855. );
  856. if (!is_null($relation->getWhere())) {
  857. $select->where($relation->getWhere());
  858. }
  859. if (!is_null($productIds)) {
  860. $cond = array(
  861. $this->_getWriteAdapter()->quoteInto("{$relation->getChildFieldName()} IN(?)", $productIds),
  862. $this->_getWriteAdapter()->quoteInto("{$relation->getParentFieldName()} IN(?)", $productIds)
  863. );
  864. $select->where(join(' OR ', $cond));
  865. }
  866. $sql = $select->insertFromSelect($this->getFlatTableName($store), $fieldList);
  867. $this->_getWriteAdapter()->query($sql);
  868. }
  869. }
  870. return $this;
  871. }
  872. /**
  873. * Update children data from parent
  874. *
  875. * @param int $store
  876. * @param int|array $productIds
  877. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Flat_Indexer
  878. */
  879. public function updateChildrenDataFromParent($store, $productIds = null)
  880. {
  881. if (!$this->getFlatHelper()->isAddChildData()) {
  882. return $this;
  883. }
  884. if (!$this->_isFlatTableExists($store)) {
  885. return $this;
  886. }
  887. $select = $this->_getWriteAdapter()->select();
  888. foreach (array_keys($this->getFlatColumns()) as $columnName) {
  889. if ($columnName == 'entity_id' || $columnName == 'child_id' || $columnName == 'is_child') {
  890. continue;
  891. }
  892. $select->columns(array($columnName => new Zend_Db_Expr('`t1`.`'. $columnName.'`')));
  893. }
  894. $select
  895. ->joinLeft(
  896. array('t1' => $this->getFlatTableName($store)),
  897. "`t2`.`child_id`=`t1`.`entity_id` AND `t1`.`is_child`=0",
  898. array())
  899. ->where('t2.is_child=1');
  900. if (!is_null($productIds)) {
  901. $select->where('t2.child_id IN(?)', $productIds);
  902. }
  903. $sql = $select->crossUpdateFromSelect(array('t2' => $this->getFlatTableName($store)));
  904. $this->_getWriteAdapter()->query($sql);
  905. return $this;
  906. }
  907. /**
  908. * Clean unused relation products
  909. *
  910. * @param int $store
  911. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Flat_Indexer
  912. */
  913. public function cleanRelationProducts($store)
  914. {
  915. if (!$this->getFlatHelper()->isAddChildData()) {
  916. return $this;
  917. }
  918. foreach ($this->getProductTypeInstances() as $typeInstance) {
  919. if (!$typeInstance->isComposite()) {
  920. continue;
  921. }
  922. $relation = $typeInstance->getRelationInfo();
  923. if ($relation
  924. and $relation->getTable()
  925. and $relation->getParentFieldName()
  926. and $relation->getChildFieldName()
  927. ) {
  928. $select = $this->_getWriteAdapter()->select()
  929. ->distinct(true)
  930. ->from(
  931. $this->getTable($relation->getTable()),
  932. "{$relation->getParentFieldName()}"
  933. )
  934. ;
  935. $joinLeftCond = null;
  936. if (!is_null($relation->getWhere())) {
  937. $select->where($relation->getWhere());
  938. $joinLeftCond = ' AND ' . $relation->getWhere();
  939. }
  940. $entitySelect = new Zend_Db_Expr($select->__toString());
  941. $select = $this->_getWriteAdapter()->select()
  942. ->from(
  943. array('e' => $this->getFlatTableName($store)),
  944. null
  945. )
  946. ->joinLeft(
  947. array('t' => $this->getTable($relation->getTable())),
  948. "e.entity_id=t.{$relation->getParentFieldName()} AND e.child_id=t.{$relation->getChildFieldName()}"
  949. . $joinLeftCond,
  950. array())
  951. ->where("e.is_child=?", 1)
  952. ->where("e.entity_id IN(?)", $entitySelect)
  953. ->where("t.{$relation->getChildFieldName()} IS NULL");
  954. $sql = $select->deleteFromSelect('e');
  955. $this->_getWriteAdapter()->query($sql);
  956. }
  957. }
  958. return $this;
  959. }
  960. /**
  961. * Remove product data from flat
  962. *
  963. * @param int|array $productIds
  964. * @param int $store
  965. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Flat_Indexer
  966. */
  967. public function removeProduct($productIds, $store)
  968. {
  969. if (!$this->_isFlatTableExists($store)) {
  970. return $this;
  971. }
  972. $cond = array(
  973. $this->_getWriteAdapter()->quoteInto('entity_id IN(?)', $productIds)
  974. );
  975. if ($this->getFlatHelper()->isAddChildData()) {
  976. $cond[] = $this->_getWriteAdapter()->quoteInto('child_id IN(?)', $productIds);
  977. }
  978. $cond = join(' OR ', $cond);
  979. $this->_getWriteAdapter()->delete($this->getFlatTableName($store), $cond);
  980. return $this;
  981. }
  982. /**
  983. * Remove children from parent product
  984. *
  985. * @param int|array $productIds
  986. * @param int $store
  987. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Flat_Indexer
  988. */
  989. public function removeProductChildren($productIds, $store)
  990. {
  991. if (!$this->getFlatHelper()->isAddChildData()) {
  992. return $this;
  993. }
  994. $cond = array(
  995. $this->_getWriteAdapter()->quoteInto('entity_id IN(?)', $productIds),
  996. $this->_getWriteAdapter()->quoteInto('is_child=?', 1),
  997. );
  998. $this->_getWriteAdapter()->delete($this->getFlatTableName($store), $cond);
  999. return $this;
  1000. }
  1001. /**
  1002. * Update flat data for product
  1003. *
  1004. * @param int|array $productIds
  1005. * @param int $store
  1006. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Flat_Indexer
  1007. */
  1008. public function updateProduct($productIds, $store)
  1009. {
  1010. if (!$this->_isFlatTableExists($store)) {
  1011. return $this;
  1012. }
  1013. $this->saveProduct($productIds, $store);
  1014. Mage::dispatchEvent('catalog_product_flat_update_product', array(
  1015. 'store_id' => $store,
  1016. 'table' => $this->getFlatTableName($store),
  1017. 'product_ids' => $productIds
  1018. ));
  1019. return $this;
  1020. }
  1021. /**
  1022. * Save product(s) data for store
  1023. *
  1024. * @param int|array $productIds
  1025. * @param int $store
  1026. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Flat_Indexer
  1027. */
  1028. public function saveProduct($productIds, $store)
  1029. {
  1030. if (!$this->_isFlatTableExists($store)) {
  1031. return $this;
  1032. }
  1033. $this->updateStaticAttributes($store, $productIds);
  1034. $this->updateEavAttributes($store, $productIds);
  1035. return $this;
  1036. }
  1037. /**
  1038. * Delete flat table process
  1039. *
  1040. * @param int $store
  1041. * @return Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Flat_Indexer
  1042. */
  1043. public function deleteFlatTable($store)
  1044. {
  1045. if ($this->_isFlatTableExists($store)) {
  1046. $tableName = $this->_getWriteAdapter()->quoteIdentifier($this->getFlatTableName($store));
  1047. $sql = sprintf('DROP TABLE IF EXISTS %s', $tableName);
  1048. $this->_getWriteAdapter()->query($sql);
  1049. }
  1050. return $this;
  1051. }
  1052. /**
  1053. * Check is flat table for store exists
  1054. *
  1055. * @param int $store
  1056. * @return bool
  1057. */
  1058. protected function _isFlatTableExists($store)
  1059. {
  1060. if (!isset($this->_existsFlatTables[$store])) {
  1061. $tableName = $this->getFlatTableName($store);
  1062. $tableExistsSql = $this->_getWriteAdapter()
  1063. ->quoteInto("SHOW TABLE STATUS LIKE ?", $tableName);
  1064. if ($this->_getWriteAdapter()->fetchRow($tableExistsSql)) {
  1065. $this->_existsFlatTables[$store] = true;
  1066. } else {
  1067. $this->_existsFlatTables[$store] = false;
  1068. }
  1069. }
  1070. return $this->_existsFlatTables[$store];
  1071. }
  1072. /**
  1073. * Retrieve previous key from array by key
  1074. *
  1075. * @param array $array
  1076. * @param mixed $key
  1077. * @return mixed
  1078. */
  1079. protected function _arrayPrevKey(array $array, $key)
  1080. {
  1081. $prev = false;
  1082. foreach (array_keys($array) as $k) {
  1083. if ($k == $key) {
  1084. return $prev;
  1085. }
  1086. $prev = $k;
  1087. }
  1088. return false;
  1089. }
  1090. /**
  1091. * Retrieve next key from array by key
  1092. *
  1093. * @param array $array
  1094. * @param mixed $key
  1095. * @return mixed
  1096. */
  1097. protected function _arrayNextKey(array $array, $key)
  1098. {
  1099. $next = false;
  1100. foreach (array_keys($array) as $k) {
  1101. if ($next === true) {
  1102. return $k;
  1103. }
  1104. if ($k == $key) {
  1105. $next = true;
  1106. }
  1107. }
  1108. return false;
  1109. }
  1110. }