Skip to content

Commit 089f759

Browse files
committed
MAGETWO-59088: [MySQL] Layered navigation contains filters for out of stock products
1 parent 0c6227d commit 089f759

File tree

30 files changed

+1079
-456
lines changed

30 files changed

+1079
-456
lines changed

app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/AbstractEav.php

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -197,17 +197,25 @@ protected function _prepareRelationIndexSelect($parentIds = null)
197197
)->joinLeft(
198198
['e' => $this->getTable('catalog_product_entity')],
199199
'e.' . $linkField .' = l.parent_id',
200-
['e.entity_id as parent_id']
200+
[]
201201
)->join(
202202
['cs' => $this->getTable('store')],
203203
'',
204204
[]
205205
)->join(
206206
['i' => $idxTable],
207207
'l.child_id = i.entity_id AND cs.store_id = i.store_id',
208-
['attribute_id', 'store_id', 'value']
208+
[]
209209
)->group(
210-
['parent_id', 'i.attribute_id', 'i.store_id', 'i.value']
210+
['parent_id', 'i.attribute_id', 'i.store_id', 'i.value', 'l.child_id']
211+
)->columns(
212+
[
213+
'parent_id' => 'e.entity_id',
214+
'attribute_id' => 'i.attribute_id',
215+
'store_id' => 'i.store_id',
216+
'value' => 'i.value',
217+
'source_id' => 'l.child_id'
218+
]
211219
);
212220
if ($parentIds !== null) {
213221
$select->where('e.entity_id IN(?)', $parentIds);
@@ -222,7 +230,7 @@ protected function _prepareRelationIndexSelect($parentIds = null)
222230
'select' => $select,
223231
'entity_field' => new \Zend_Db_Expr('l.parent_id'),
224232
'website_field' => new \Zend_Db_Expr('cs.website_id'),
225-
'store_field' => new \Zend_Db_Expr('cs.store_id')
233+
'store_field' => new \Zend_Db_Expr('cs.store_id'),
226234
]
227235
);
228236

app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Decimal.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ protected function _prepareIndex($entityIds = null, $attributeId = null)
8585
'pdd.attribute_id',
8686
'cs.store_id',
8787
'value' => $productValueExpression,
88+
'source_id' => 'cpe.entity_id',
8889
]
8990
);
9091

@@ -116,7 +117,7 @@ protected function _prepareIndex($entityIds = null, $attributeId = null)
116117
'select' => $select,
117118
'entity_field' => new \Zend_Db_Expr('cpe.entity_id'),
118119
'website_field' => new \Zend_Db_Expr('cs.website_id'),
119-
'store_field' => new \Zend_Db_Expr('cs.store_id')
120+
'store_field' => new \Zend_Db_Expr('cs.store_id'),
120121
]
121122
);
122123

app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php

Lines changed: 45 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ protected function _prepareSelectIndex($entityIds = null, $attributeId = null)
178178
'pid.attribute_id',
179179
'pid.store_id',
180180
'value' => $ifNullSql,
181+
'pid.entity_id',
181182
]
182183
)->where(
183184
'pid.attribute_id IN(?)',
@@ -200,7 +201,7 @@ protected function _prepareSelectIndex($entityIds = null, $attributeId = null)
200201
'select' => $select,
201202
'entity_field' => new \Zend_Db_Expr('pid.entity_id'),
202203
'website_field' => new \Zend_Db_Expr('pid.website_id'),
203-
'store_field' => new \Zend_Db_Expr('pid.store_id')
204+
'store_field' => new \Zend_Db_Expr('pid.store_id'),
204205
]
205206
);
206207
$query = $select->insertFromSelect($idxTable);
@@ -221,11 +222,7 @@ protected function _prepareMultiselectIndex($entityIds = null, $attributeId = nu
221222
$connection = $this->getConnection();
222223

223224
// prepare multiselect attributes
224-
if ($attributeId === null) {
225-
$attrIds = $this->_getIndexableAttributes(true);
226-
} else {
227-
$attrIds = [$attributeId];
228-
}
225+
$attrIds = $attributeId === null ? $this->_getIndexableAttributes(true) : [$attributeId];
229226

230227
if (!$attrIds) {
231228
return $this;
@@ -247,20 +244,20 @@ protected function _prepareMultiselectIndex($entityIds = null, $attributeId = nu
247244
$productValueExpression = $connection->getCheckSql('pvs.value_id > 0', 'pvs.value', 'pvd.value');
248245
$select = $connection->select()->from(
249246
['pvd' => $this->getTable('catalog_product_entity_varchar')],
250-
[$productIdField, 'attribute_id']
247+
[]
251248
)->join(
252249
['cs' => $this->getTable('store')],
253250
'',
254-
['store_id']
251+
[]
255252
)->joinLeft(
256253
['pvs' => $this->getTable('catalog_product_entity_varchar')],
257254
"pvs.{$productIdField} = pvd.{$productIdField} AND pvs.attribute_id = pvd.attribute_id"
258255
. ' AND pvs.store_id=cs.store_id',
259-
['value' => $productValueExpression]
256+
[]
260257
)->joinLeft(
261258
['cpe' => $this->getTable('catalog_product_entity')],
262259
"cpe.{$productIdField} = pvd.{$productIdField}",
263-
['entity_id']
260+
[]
264261
)->where(
265262
'pvd.store_id=?',
266263
$connection->getIfNullSql('pvs.store_id', \Magento\Store\Model\Store::DEFAULT_STORE_ID)
@@ -272,6 +269,14 @@ protected function _prepareMultiselectIndex($entityIds = null, $attributeId = nu
272269
$attrIds
273270
)->where(
274271
'cpe.entity_id IS NOT NULL'
272+
)->columns(
273+
[
274+
'entity_id' => 'cpe.entity_id',
275+
'attribute_id' => 'attribute_id',
276+
'store_id' => 'cs.store_id',
277+
'value' => $productValueExpression,
278+
'source_id' => 'cpe.entity_id',
279+
]
275280
);
276281

277282
$statusCond = $connection->quoteInto('=?', ProductStatus::STATUS_ENABLED);
@@ -289,30 +294,11 @@ protected function _prepareMultiselectIndex($entityIds = null, $attributeId = nu
289294
'select' => $select,
290295
'entity_field' => new \Zend_Db_Expr('cpe.entity_id'),
291296
'website_field' => new \Zend_Db_Expr('cs.website_id'),
292-
'store_field' => new \Zend_Db_Expr('cs.store_id')
297+
'store_field' => new \Zend_Db_Expr('cs.store_id'),
293298
]
294299
);
295300

296-
$i = 0;
297-
$data = [];
298-
$query = $select->query();
299-
while ($row = $query->fetch()) {
300-
$values = explode(',', $row['value']);
301-
foreach ($values as $valueId) {
302-
if (isset($options[$row['attribute_id']][$valueId])) {
303-
$data[] = [$row['entity_id'], $row['attribute_id'], $row['store_id'], $valueId];
304-
$i++;
305-
if ($i % 10000 == 0) {
306-
$this->_saveIndexData($data);
307-
$data = [];
308-
}
309-
}
310-
}
311-
}
312-
313-
$this->_saveIndexData($data);
314-
unset($options);
315-
unset($data);
301+
$this->saveDataFromSelect($select, $options);
316302

317303
return $this;
318304
}
@@ -331,7 +317,7 @@ protected function _saveIndexData(array $data)
331317
$connection = $this->getConnection();
332318
$connection->insertArray(
333319
$this->getIdxTable(),
334-
['entity_id', 'attribute_id', 'store_id', 'value'],
320+
['entity_id', 'attribute_id', 'store_id', 'value', 'source_id'],
335321
$data
336322
);
337323
return $this;
@@ -348,4 +334,31 @@ public function getIdxTable($table = null)
348334
{
349335
return $this->tableStrategy->getTableName('catalog_product_index_eav');
350336
}
337+
338+
/**
339+
* @param \Magento\Framework\DB\Select $select
340+
* @param array $options
341+
* @return void
342+
*/
343+
private function saveDataFromSelect(\Magento\Framework\DB\Select $select, array $options)
344+
{
345+
$i = 0;
346+
$data = [];
347+
$query = $select->query();
348+
while ($row = $query->fetch()) {
349+
$values = explode(',', $row['value']);
350+
foreach ($values as $valueId) {
351+
if (isset($options[$row['attribute_id']][$valueId])) {
352+
$data[] = [$row['entity_id'], $row['attribute_id'], $row['store_id'], $valueId, $row['source_id']];
353+
$i++;
354+
if ($i % 10000 == 0) {
355+
$this->_saveIndexData($data);
356+
$data = [];
357+
}
358+
}
359+
}
360+
}
361+
362+
$this->_saveIndexData($data);
363+
}
351364
}

app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ protected function prepareFinalPriceDataForType($entityIds, $type)
368368
'select' => $select,
369369
'entity_field' => new \Zend_Db_Expr('e.entity_id'),
370370
'website_field' => new \Zend_Db_Expr('cw.website_id'),
371-
'store_field' => new \Zend_Db_Expr('cs.store_id')
371+
'store_field' => new \Zend_Db_Expr('cs.store_id'),
372372
]
373373
);
374374

app/code/Magento/Catalog/Setup/InstallSchema.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class InstallSchema implements InstallSchemaInterface
1818
/**
1919
* {@inheritdoc}
2020
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
21+
* @throws \Zend_Db_Exception
2122
*/
2223
public function install(SchemaSetupInterface $setup, ModuleContextInterface $context)
2324
{
@@ -2429,7 +2430,6 @@ public function install(SchemaSetupInterface $setup, ModuleContextInterface $con
24292430
'option_id',
24302431
$installer->getTable('catalog_product_option'),
24312432
'option_id',
2432-
\Magento\Framework\DB\Ddl\Table::ACTION_CASCADE,
24332433
\Magento\Framework\DB\Ddl\Table::ACTION_CASCADE
24342434
)
24352435
->setComment(

app/code/Magento/Catalog/Setup/UpgradeSchema.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,56 @@ public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $con
6161
}
6262
}
6363

64+
if (version_compare($context->getVersion(), '2.1.2', '<')) {
65+
$this->addSourceEntityIdToProductEavIndex($setup);
66+
}
67+
6468
$setup->endSetup();
6569
}
6670

71+
/**
72+
* Add the column 'source_id' to the Product EAV index tables.
73+
* It allows to identify which entity was used to create value in the index.
74+
* It is useful to identify original entity in a composite products.
75+
*
76+
* @param SchemaSetupInterface $setup
77+
* @return void
78+
*/
79+
private function addSourceEntityIdToProductEavIndex(SchemaSetupInterface $setup)
80+
{
81+
$tables = [
82+
'catalog_product_index_eav',
83+
'catalog_product_index_eav_idx',
84+
'catalog_product_index_eav_tmp',
85+
'catalog_product_index_eav_decimal',
86+
'catalog_product_index_eav_decimal_idx',
87+
'catalog_product_index_eav_decimal_tmp',
88+
];
89+
$connection = $setup->getConnection();
90+
foreach ($tables as $tableName) {
91+
$tableName = $setup->getTable($tableName);
92+
$connection->addColumn(
93+
$tableName,
94+
'source_id',
95+
[
96+
'type' => \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER,
97+
'unsigned' => true,
98+
'nullable' => false,
99+
'default' => 0,
100+
'comment' => 'Original entity Id for attribute value',
101+
]
102+
);
103+
$connection->dropIndex($tableName, $connection->getPrimaryKeyName($tableName));
104+
$primaryKeyFields = ['entity_id', 'attribute_id', 'store_id', 'value', 'source_id'];
105+
$setup->getConnection()->addIndex(
106+
$tableName,
107+
$connection->getIndexName($tableName, $primaryKeyFields),
108+
$primaryKeyFields,
109+
\Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_PRIMARY
110+
);
111+
}
112+
}
113+
67114
/**
68115
* @param SchemaSetupInterface $setup
69116
* @return void

app/code/Magento/Catalog/etc/module.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
*/
77
-->
88
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
9-
<module name="Magento_Catalog" setup_version="2.1.1">
9+
<module name="Magento_Catalog" setup_version="2.1.2">
1010
<sequence>
1111
<module name="Magento_Eav"/>
1212
<module name="Magento_Cms"/>

app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Aggregation/DataProvider.php

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
namespace Magento\CatalogSearch\Model\Adapter\Mysql\Aggregation;
77

88
use Magento\Catalog\Model\Product;
9+
use Magento\CatalogInventory\Model\Stock;
910
use Magento\Customer\Model\Session;
1011
use Magento\Eav\Model\Config;
1112
use Magento\Framework\App\ResourceConnection;
@@ -79,7 +80,13 @@ public function getDataSet(
7980

8081
$select = $this->getSelect();
8182

82-
if ($attribute->getAttributeCode() == 'price') {
83+
$select->joinInner(
84+
['entities' => $entityIdsTable->getName()],
85+
'main_table.entity_id = entities.entity_id',
86+
[]
87+
);
88+
89+
if ($attribute->getAttributeCode() === 'price') {
8390
/** @var \Magento\Store\Model\Store $store */
8491
$store = $this->scopeResolver->getScope($currentScope);
8592
if (!$store instanceof \Magento\Store\Model\Store) {
@@ -94,19 +101,24 @@ public function getDataSet(
94101
$currentScopeId = $this->scopeResolver->getScope($currentScope)
95102
->getId();
96103
$table = $this->resource->getTableName(
97-
'catalog_product_index_eav' . ($attribute->getBackendType() == 'decimal' ? '_decimal' : '')
104+
'catalog_product_index_eav' . ($attribute->getBackendType() === 'decimal' ? '_decimal' : '')
98105
);
99-
$select->from(['main_table' => $table], ['value'])
106+
$subSelect = $select;
107+
$subSelect->from(['main_table' => $table], ['main_table.value'])
108+
->joinLeft(
109+
['stock_index' => $this->resource->getTableName('cataloginventory_stock_status')],
110+
'main_table.source_id = stock_index.product_id',
111+
[]
112+
)
100113
->where('main_table.attribute_id = ?', $attribute->getAttributeId())
101-
->where('main_table.store_id = ? ', $currentScopeId);
114+
->where('main_table.store_id = ? ', $currentScopeId)
115+
->where('stock_index.stock_status = ?', Stock::STOCK_IN_STOCK)
116+
->group(['main_table.entity_id', 'main_table.value']);
117+
$parentSelect = $this->getSelect();
118+
$parentSelect->from(['main_table' => $subSelect], ['main_table.value']);
119+
$select = $parentSelect;
102120
}
103121

104-
$select->joinInner(
105-
['entities' => $entityIdsTable->getName()],
106-
'main_table.entity_id = entities.entity_id',
107-
[]
108-
);
109-
110122
return $select;
111123
}
112124

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
/**
3+
* Copyright © 2016 Magento. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
namespace Magento\CatalogSearch\Model\Adapter\Mysql\Filter;
8+
9+
10+
use Magento\CatalogSearch\Model\Search\RequestGenerator;
11+
12+
/**
13+
* Purpose of class is to resolve table alias for Search Request filter
14+
*/
15+
class AliasResolver
16+
{
17+
/**
18+
* The suffix for stock status filter that may be added to the query beside the filter query
19+
* Used when showing of Out of Stock products is disabled.
20+
*/
21+
const STOCK_FILTER_SUFFIX = '_stock';
22+
23+
/**
24+
* @param \Magento\Framework\Search\Request\FilterInterface $filter
25+
* @return string alias of the filter in database
26+
*/
27+
public function getAlias(\Magento\Framework\Search\Request\FilterInterface $filter)
28+
{
29+
$alias = null;
30+
$field = $filter->getField();
31+
switch ($field) {
32+
case 'price':
33+
$alias = 'price_index';
34+
break;
35+
case 'category_ids':
36+
$alias = 'category_ids_index';
37+
break;
38+
default:
39+
$alias = $field . RequestGenerator::FILTER_SUFFIX;
40+
break;
41+
}
42+
return $alias;
43+
}
44+
}

0 commit comments

Comments
 (0)