diff --git a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePanel.php b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePanel.php
index 50a3caaf68b3bd39ce1458e4883b4c1dd35c0525..538c80d9b1cf27af4ba14c911b0d0a6913c403c7 100644
--- a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePanel.php
+++ b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePanel.php
@@ -268,15 +268,12 @@ class BundlePanel extends AbstractModifier
             'arguments' => [
                 'data' => [
                     'config' => [
-                        'componentType' => 'dynamicRows',
+                        'componentType' => Container::NAME,
+                        'component' => 'Magento_Bundle/js/components/bundle-dynamic-rows',
                         'template' => 'ui/dynamic-rows/templates/collapsible',
-                        'label' => '',
                         'additionalClasses' => 'admin__field-wide',
-                        'collapsibleHeader' => true,
-                        'columnsHeader' => false,
-                        'deleteProperty' => false,
-                        'addButton' => false,
                         'dataScope' => 'data.bundle_options',
+                        'bundleSelectionsName' => 'product_bundle_container.bundle_selections'
                     ],
                 ],
             ],
@@ -318,14 +315,11 @@ class BundlePanel extends AbstractModifier
                                     'arguments' => [
                                         'data' => [
                                             'config' => [
-                                                'componentType' => DynamicRows::NAME,
-                                                'label' => '',
+                                                'componentType' => Container::NAME,
+                                                'component' => 'Magento_Bundle/js/components/bundle-dynamic-rows-grid',
                                                 'sortOrder' => 50,
                                                 'additionalClasses' => 'admin__field-wide',
-                                                'component' => 'Magento_Ui/js/dynamic-rows/dynamic-rows-grid',
                                                 'template' => 'ui/dynamic-rows/templates/default',
-                                                'columnsHeader' => false,
-                                                'columnsHeaderAfterRender' => true,
                                                 'provider' => 'product_form.product_form_data_source',
                                                 'dataProvider' => '${ $.dataScope }' . '.bundle_button_proxy',
                                                 'identificationDRProperty' => 'product_id',
@@ -343,8 +337,7 @@ class BundlePanel extends AbstractModifier
                                                     'selection_qty' => '',
                                                 ],
                                                 'links' => ['insertData' => '${ $.provider }:${ $.dataProvider }'],
-                                                'source' => 'product',
-                                                'addButton' => false,
+                                                'source' => 'product'
                                             ],
                                         ],
                                     ],
@@ -561,7 +554,7 @@ class BundlePanel extends AbstractModifier
                         'componentType' => Container::NAME,
                         'isTemplate' => true,
                         'component' => 'Magento_Ui/js/dynamic-rows/record',
-                        'is_collection' => true,
+                        'is_collection' => true
                     ],
                 ],
             ],
diff --git a/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-dynamic-rows-grid.js b/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-dynamic-rows-grid.js
new file mode 100644
index 0000000000000000000000000000000000000000..e9a924e1cffe696688fed502bc715347c8afb8d2
--- /dev/null
+++ b/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-dynamic-rows-grid.js
@@ -0,0 +1,59 @@
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+define([
+    'underscore',
+    'Magento_Ui/js/dynamic-rows/dynamic-rows-grid'
+], function (_, dynamicRowsGrid) {
+    'use strict';
+
+    return dynamicRowsGrid.extend({
+        defaults: {
+            label: '',
+            columnsHeader: false,
+            columnsHeaderAfterRender: true,
+            addButton: false
+        },
+
+        /**
+         * Initialize elements from grid
+         *
+         * @param {Array} data
+         *
+         * @returns {Object} Chainable.
+         */
+        initElements: function (data) {
+            var newData = this.getNewData(data),
+                recordIndex;
+
+            this.parsePagesData(data);
+
+            if (newData.length) {
+                if (this.insertData().length) {
+                    recordIndex = data.length - newData.length - 1;
+
+                    _.each(newData, function (newRecord) {
+                        this.processingAddChild(newRecord, ++recordIndex, newRecord[this.identificationProperty]);
+                    }, this);
+                }
+            }
+
+            return this;
+        },
+
+        /**
+         * Mapping value from grid
+         *
+         * @param {Array} data
+         */
+        mappingValue: function (data) {
+            if (_.isEmpty(data)) {
+                return;
+            }
+
+            this._super();
+        }
+    });
+});
diff --git a/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-dynamic-rows.js b/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-dynamic-rows.js
new file mode 100644
index 0000000000000000000000000000000000000000..b36d8003a399fb4af88d3e3cfab34559e2e80bea
--- /dev/null
+++ b/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-dynamic-rows.js
@@ -0,0 +1,98 @@
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+define([
+    'underscore',
+    'mageUtils',
+    'uiRegistry',
+    'Magento_Ui/js/dynamic-rows/dynamic-rows'
+], function (_, utils, registry, dynamicRows) {
+    'use strict';
+
+    return dynamicRows.extend({
+        defaults: {
+            label: '',
+            collapsibleHeader: true,
+            columnsHeader: false,
+            deleteProperty: false,
+            addButton: false
+        },
+
+        /**
+         * Set new data to dataSource,
+         * delete element
+         *
+         * @param {Array} data - record data
+         */
+        _updateData: function (data) {
+            var elems = _.clone(this.elems()),
+                path,
+                dataArr,
+                optionBaseData;
+
+            dataArr = this.recordData.splice(this.startIndex, this.recordData().length - this.startIndex);
+            dataArr.splice(0, this.pageSize);
+            elems = _.sortBy(this.elems(), function (elem) {
+                return ~~elem.index;
+            });
+
+            data.concat(dataArr).forEach(function (rec, idx) {
+                if (elems[idx]) {
+                    elems[idx].recordId = rec[this.identificationProperty];
+                }
+
+                if (!rec.position) {
+                    rec.position = this.maxPosition;
+                    this.setMaxPosition();
+                }
+
+                path = this.dataScope + '.' + this.index + '.' + (this.startIndex + idx);
+                optionBaseData = _.pick(rec, function (value) {
+                    return !_.isObject(value);
+                });
+                this.source.set(path, optionBaseData);
+                this.source.set(path + '.bundle_button_proxy', []);
+                this.source.set(path + '.bundle_selections', []);
+                this.removeBundleItemsFromOption(idx);
+                _.each(rec['bundle_selections'], function (obj, index) {
+                    this.source.set(path + '.bundle_button_proxy' + '.' + index, rec['bundle_button_proxy'][index]);
+                    this.source.set(path + '.bundle_selections' + '.' + index, obj);
+                }, this);
+            }, this);
+
+            this.elems(elems);
+        },
+
+        /**
+         *  Removes nested dynamic-rows-grid rendered records from option
+         *
+         * @param {Number|String} index - element index
+         */
+        removeBundleItemsFromOption: function (index) {
+            var bundleSelections = registry.get(this.name + '.' + index + '.' + this.bundleSelectionsName),
+                bundleSelectionsLength = (bundleSelections.elems() || []).length,
+                i;
+
+            if (bundleSelectionsLength) {
+                for (i = 0; i < bundleSelectionsLength; i++) {
+                    bundleSelections.elems()[0].destroy();
+                }
+            }
+        },
+
+        /**
+        * {@inheritdoc}
+        */
+        processingAddChild: function (ctx, index, prop) {
+            var recordIds = _.map(this.recordData(), function (rec) {
+                return parseInt(rec['record_id'], 10);
+            }),
+            maxRecordId = _.max(recordIds);
+
+            prop = maxRecordId > -1 ? maxRecordId + 1 : prop;
+            this._super(ctx, index, prop);
+        }
+    });
+});
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/AbstractEav.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/AbstractEav.php
index d9612f18c9111df4634e18cb8b9abf523643f2e5..432bc696ef7cecd822b5168e7da70a9ed1cd4fc5 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/AbstractEav.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/AbstractEav.php
@@ -197,7 +197,7 @@ abstract class AbstractEav extends \Magento\Catalog\Model\ResourceModel\Product\
         )->joinLeft(
             ['e' => $this->getTable('catalog_product_entity')],
             'e.' . $linkField .' = l.parent_id',
-            ['e.entity_id as parent_id']
+            []
         )->join(
             ['cs' => $this->getTable('store')],
             '',
@@ -205,9 +205,17 @@ abstract class AbstractEav extends \Magento\Catalog\Model\ResourceModel\Product\
         )->join(
             ['i' => $idxTable],
             'l.child_id = i.entity_id AND cs.store_id = i.store_id',
-            ['attribute_id', 'store_id', 'value']
+            []
         )->group(
-            ['parent_id', 'i.attribute_id', 'i.store_id', 'i.value']
+            ['parent_id', 'i.attribute_id', 'i.store_id', 'i.value', 'l.child_id']
+        )->columns(
+            [
+                'parent_id' => 'e.entity_id',
+                'attribute_id' => 'i.attribute_id',
+                'store_id' => 'i.store_id',
+                'value' => 'i.value',
+                'source_id' => 'l.child_id'
+            ]
         );
         if ($parentIds !== null) {
             $select->where('e.entity_id IN(?)', $parentIds);
@@ -222,7 +230,7 @@ abstract class AbstractEav extends \Magento\Catalog\Model\ResourceModel\Product\
                 'select' => $select,
                 'entity_field' => new \Zend_Db_Expr('l.parent_id'),
                 'website_field' => new \Zend_Db_Expr('cs.website_id'),
-                'store_field' => new \Zend_Db_Expr('cs.store_id')
+                'store_field' => new \Zend_Db_Expr('cs.store_id'),
             ]
         );
 
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Decimal.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Decimal.php
index e8d9889e68d59384e74cfb3fc0491a2edb8f3edb..a45d4f13a1a9a811fffb6269bf94575675c60d80 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Decimal.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Decimal.php
@@ -85,6 +85,7 @@ class Decimal extends AbstractEav
                 'pdd.attribute_id',
                 'cs.store_id',
                 'value' => $productValueExpression,
+                'source_id' => 'cpe.entity_id',
             ]
         );
 
@@ -116,7 +117,7 @@ class Decimal extends AbstractEav
                 'select' => $select,
                 'entity_field' => new \Zend_Db_Expr('cpe.entity_id'),
                 'website_field' => new \Zend_Db_Expr('cs.website_id'),
-                'store_field' => new \Zend_Db_Expr('cs.store_id')
+                'store_field' => new \Zend_Db_Expr('cs.store_id'),
             ]
         );
 
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php
index c4eda1c987192acb5dd7e5ce003fe8b9cff0bdc6..1d37c57aa8b251bc73edf6e4a06aed1789cdc476 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php
@@ -178,6 +178,7 @@ class Source extends AbstractEav
                 'pid.attribute_id',
                 'pid.store_id',
                 'value' => $ifNullSql,
+                'pid.entity_id',
             ]
         )->where(
             'pid.attribute_id IN(?)',
@@ -200,7 +201,7 @@ class Source extends AbstractEav
                 'select' => $select,
                 'entity_field' => new \Zend_Db_Expr('pid.entity_id'),
                 'website_field' => new \Zend_Db_Expr('pid.website_id'),
-                'store_field' => new \Zend_Db_Expr('pid.store_id')
+                'store_field' => new \Zend_Db_Expr('pid.store_id'),
             ]
         );
         $query = $select->insertFromSelect($idxTable);
@@ -221,11 +222,7 @@ class Source extends AbstractEav
         $connection = $this->getConnection();
 
         // prepare multiselect attributes
-        if ($attributeId === null) {
-            $attrIds = $this->_getIndexableAttributes(true);
-        } else {
-            $attrIds = [$attributeId];
-        }
+        $attrIds = $attributeId === null ? $this->_getIndexableAttributes(true) : [$attributeId];
 
         if (!$attrIds) {
             return $this;
@@ -247,20 +244,20 @@ class Source extends AbstractEav
         $productValueExpression = $connection->getCheckSql('pvs.value_id > 0', 'pvs.value', 'pvd.value');
         $select = $connection->select()->from(
             ['pvd' => $this->getTable('catalog_product_entity_varchar')],
-            [$productIdField, 'attribute_id']
+            []
         )->join(
             ['cs' => $this->getTable('store')],
             '',
-            ['store_id']
+            []
         )->joinLeft(
             ['pvs' => $this->getTable('catalog_product_entity_varchar')],
             "pvs.{$productIdField} = pvd.{$productIdField} AND pvs.attribute_id = pvd.attribute_id"
             . ' AND pvs.store_id=cs.store_id',
-            ['value' => $productValueExpression]
+            []
         )->joinLeft(
             ['cpe' => $this->getTable('catalog_product_entity')],
             "cpe.{$productIdField} = pvd.{$productIdField}",
-            ['entity_id']
+            []
         )->where(
             'pvd.store_id=?',
             $connection->getIfNullSql('pvs.store_id', \Magento\Store\Model\Store::DEFAULT_STORE_ID)
@@ -272,6 +269,14 @@ class Source extends AbstractEav
             $attrIds
         )->where(
             'cpe.entity_id IS NOT NULL'
+        )->columns(
+            [
+                'entity_id' => 'cpe.entity_id',
+                'attribute_id' => 'attribute_id',
+                'store_id' => 'cs.store_id',
+                'value' => $productValueExpression,
+                'source_id' => 'cpe.entity_id',
+            ]
         );
 
         $statusCond = $connection->quoteInto('=?', ProductStatus::STATUS_ENABLED);
@@ -289,30 +294,11 @@ class Source extends AbstractEav
                 'select' => $select,
                 'entity_field' => new \Zend_Db_Expr('cpe.entity_id'),
                 'website_field' => new \Zend_Db_Expr('cs.website_id'),
-                'store_field' => new \Zend_Db_Expr('cs.store_id')
+                'store_field' => new \Zend_Db_Expr('cs.store_id'),
             ]
         );
 
-        $i = 0;
-        $data = [];
-        $query = $select->query();
-        while ($row = $query->fetch()) {
-            $values = explode(',', $row['value']);
-            foreach ($values as $valueId) {
-                if (isset($options[$row['attribute_id']][$valueId])) {
-                    $data[] = [$row['entity_id'], $row['attribute_id'], $row['store_id'], $valueId];
-                    $i++;
-                    if ($i % 10000 == 0) {
-                        $this->_saveIndexData($data);
-                        $data = [];
-                    }
-                }
-            }
-        }
-
-        $this->_saveIndexData($data);
-        unset($options);
-        unset($data);
+        $this->saveDataFromSelect($select, $options);
 
         return $this;
     }
@@ -331,7 +317,7 @@ class Source extends AbstractEav
         $connection = $this->getConnection();
         $connection->insertArray(
             $this->getIdxTable(),
-            ['entity_id', 'attribute_id', 'store_id', 'value'],
+            ['entity_id', 'attribute_id', 'store_id', 'value', 'source_id'],
             $data
         );
         return $this;
@@ -348,4 +334,31 @@ class Source extends AbstractEav
     {
         return $this->tableStrategy->getTableName('catalog_product_index_eav');
     }
+
+    /**
+     * @param \Magento\Framework\DB\Select $select
+     * @param array $options
+     * @return void
+     */
+    private function saveDataFromSelect(\Magento\Framework\DB\Select $select, array $options)
+    {
+        $i = 0;
+        $data = [];
+        $query = $select->query();
+        while ($row = $query->fetch()) {
+            $values = explode(',', $row['value']);
+            foreach ($values as $valueId) {
+                if (isset($options[$row['attribute_id']][$valueId])) {
+                    $data[] = [$row['entity_id'], $row['attribute_id'], $row['store_id'], $valueId, $row['source_id']];
+                    $i++;
+                    if ($i % 10000 == 0) {
+                        $this->_saveIndexData($data);
+                        $data = [];
+                    }
+                }
+            }
+        }
+
+        $this->_saveIndexData($data);
+    }
 }
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php
index 289445ae2daf07c4132f6d69372c84fcfb36a066..2b979ff79fe5c498215b6b144e71897b9ca2c913 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php
@@ -368,7 +368,7 @@ class DefaultPrice extends AbstractIndexer implements PriceInterface
                 'select' => $select,
                 'entity_field' => new \Zend_Db_Expr('e.entity_id'),
                 'website_field' => new \Zend_Db_Expr('cw.website_id'),
-                'store_field' => new \Zend_Db_Expr('cs.store_id')
+                'store_field' => new \Zend_Db_Expr('cs.store_id'),
             ]
         );
 
diff --git a/app/code/Magento/Catalog/Setup/InstallSchema.php b/app/code/Magento/Catalog/Setup/InstallSchema.php
index 171e96efe9b0a1e714e1331fc2ed95a175cd58fb..a2ba6aa283f6516021f0baffaafd6e6470c060f0 100644
--- a/app/code/Magento/Catalog/Setup/InstallSchema.php
+++ b/app/code/Magento/Catalog/Setup/InstallSchema.php
@@ -18,6 +18,7 @@ class InstallSchema implements InstallSchemaInterface
     /**
      * {@inheritdoc}
      * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+     * @throws \Zend_Db_Exception
      */
     public function install(SchemaSetupInterface $setup, ModuleContextInterface $context)
     {
@@ -2429,7 +2430,6 @@ class InstallSchema implements InstallSchemaInterface
                 'option_id',
                 $installer->getTable('catalog_product_option'),
                 'option_id',
-                \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE,
                 \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE
             )
             ->setComment(
diff --git a/app/code/Magento/Catalog/Setup/UpgradeSchema.php b/app/code/Magento/Catalog/Setup/UpgradeSchema.php
index cbcce1d427bb6c91fa000bff0b4a069ab02d034f..7fc2ef7d219ba917aa59c9887749a9024bda49ce 100755
--- a/app/code/Magento/Catalog/Setup/UpgradeSchema.php
+++ b/app/code/Magento/Catalog/Setup/UpgradeSchema.php
@@ -61,9 +61,56 @@ class UpgradeSchema implements UpgradeSchemaInterface
             }
         }
 
+        if (version_compare($context->getVersion(), '2.1.2', '<')) {
+            $this->addSourceEntityIdToProductEavIndex($setup);
+        }
+
         $setup->endSetup();
     }
 
+    /**
+     * Add the column 'source_id' to the Product EAV index tables.
+     * It allows to identify which entity was used to create value in the index.
+     * It is useful to identify original entity in a composite products.
+     *
+     * @param SchemaSetupInterface $setup
+     * @return void
+     */
+    private function addSourceEntityIdToProductEavIndex(SchemaSetupInterface $setup)
+    {
+        $tables = [
+            'catalog_product_index_eav',
+            'catalog_product_index_eav_idx',
+            'catalog_product_index_eav_tmp',
+            'catalog_product_index_eav_decimal',
+            'catalog_product_index_eav_decimal_idx',
+            'catalog_product_index_eav_decimal_tmp',
+        ];
+        $connection = $setup->getConnection();
+        foreach ($tables as $tableName) {
+            $tableName = $setup->getTable($tableName);
+            $connection->addColumn(
+                $tableName,
+                'source_id',
+                [
+                    'type' => \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER,
+                    'unsigned' => true,
+                    'nullable' => false,
+                    'default' => 0,
+                    'comment' => 'Original entity Id for attribute value',
+                ]
+            );
+            $connection->dropIndex($tableName, $connection->getPrimaryKeyName($tableName));
+            $primaryKeyFields = ['entity_id', 'attribute_id', 'store_id', 'value', 'source_id'];
+            $setup->getConnection()->addIndex(
+                $tableName,
+                $connection->getIndexName($tableName, $primaryKeyFields),
+                $primaryKeyFields,
+                \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_PRIMARY
+            );
+        }
+    }
+
     /**
      * @param SchemaSetupInterface $setup
      * @return void
diff --git a/app/code/Magento/Catalog/etc/module.xml b/app/code/Magento/Catalog/etc/module.xml
index c629bf6a180ccc0ec42e51a67352f6eaa673dd86..0c9e6bb356fe14101d467a04aa91b23fb6873f09 100644
--- a/app/code/Magento/Catalog/etc/module.xml
+++ b/app/code/Magento/Catalog/etc/module.xml
@@ -6,7 +6,7 @@
  */
 -->
 <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
-    <module name="Magento_Catalog" setup_version="2.1.1">
+    <module name="Magento_Catalog" setup_version="2.1.2">
         <sequence>
             <module name="Magento_Eav"/>
             <module name="Magento_Cms"/>
diff --git a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Aggregation/DataProvider.php b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Aggregation/DataProvider.php
index 9b75e6e6e0c325819d09f543cc445ae61eb7fb84..ddf86951068acc3c95017e54e489faf92e4b28cc 100644
--- a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Aggregation/DataProvider.php
+++ b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Aggregation/DataProvider.php
@@ -6,6 +6,7 @@
 namespace Magento\CatalogSearch\Model\Adapter\Mysql\Aggregation;
 
 use Magento\Catalog\Model\Product;
+use Magento\CatalogInventory\Model\Stock;
 use Magento\Customer\Model\Session;
 use Magento\Eav\Model\Config;
 use Magento\Framework\App\ResourceConnection;
@@ -79,7 +80,13 @@ class DataProvider implements DataProviderInterface
 
         $select = $this->getSelect();
 
-        if ($attribute->getAttributeCode() == 'price') {
+        $select->joinInner(
+            ['entities' => $entityIdsTable->getName()],
+            'main_table.entity_id  = entities.entity_id',
+            []
+        );
+
+        if ($attribute->getAttributeCode() === 'price') {
             /** @var \Magento\Store\Model\Store $store */
             $store = $this->scopeResolver->getScope($currentScope);
             if (!$store instanceof \Magento\Store\Model\Store) {
@@ -94,19 +101,24 @@ class DataProvider implements DataProviderInterface
             $currentScopeId = $this->scopeResolver->getScope($currentScope)
                 ->getId();
             $table = $this->resource->getTableName(
-                'catalog_product_index_eav' . ($attribute->getBackendType() == 'decimal' ? '_decimal' : '')
+                'catalog_product_index_eav' . ($attribute->getBackendType() === 'decimal' ? '_decimal' : '')
             );
-            $select->from(['main_table' => $table], ['value'])
+            $subSelect = $select;
+            $subSelect->from(['main_table' => $table], ['main_table.value'])
+                ->joinLeft(
+                    ['stock_index' => $this->resource->getTableName('cataloginventory_stock_status')],
+                    'main_table.source_id = stock_index.product_id',
+                    []
+                )
                 ->where('main_table.attribute_id = ?', $attribute->getAttributeId())
-                ->where('main_table.store_id = ? ', $currentScopeId);
+                ->where('main_table.store_id = ? ', $currentScopeId)
+                ->where('stock_index.stock_status = ?', Stock::STOCK_IN_STOCK)
+                ->group(['main_table.entity_id', 'main_table.value']);
+            $parentSelect = $this->getSelect();
+            $parentSelect->from(['main_table' => $subSelect], ['main_table.value']);
+            $select = $parentSelect;
         }
 
-        $select->joinInner(
-            ['entities' => $entityIdsTable->getName()],
-            'main_table.entity_id  = entities.entity_id',
-            []
-        );
-
         return $select;
     }
 
diff --git a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/AliasResolver.php b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/AliasResolver.php
new file mode 100644
index 0000000000000000000000000000000000000000..7099ce2502b19e21de48ae570e2dc063be6162d1
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/AliasResolver.php
@@ -0,0 +1,44 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\CatalogSearch\Model\Adapter\Mysql\Filter;
+
+
+use Magento\CatalogSearch\Model\Search\RequestGenerator;
+
+/**
+ * Purpose of class is to resolve table alias for Search Request filter
+ */
+class AliasResolver
+{
+    /**
+     * The suffix for stock status filter that may be added to the query beside the filter query
+     * Used when showing of Out of Stock products is disabled.
+     */
+    const STOCK_FILTER_SUFFIX = '_stock';
+
+    /**
+     * @param \Magento\Framework\Search\Request\FilterInterface $filter
+     * @return string alias of the filter in database
+     */
+    public function getAlias(\Magento\Framework\Search\Request\FilterInterface $filter)
+    {
+        $alias = null;
+        $field = $filter->getField();
+        switch ($field) {
+            case 'price':
+                $alias = 'price_index';
+                break;
+            case 'category_ids':
+                $alias = 'category_ids_index';
+                break;
+            default:
+                $alias = $field . RequestGenerator::FILTER_SUFFIX;
+                break;
+        }
+        return $alias;
+    }
+}
diff --git a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/Preprocessor.php b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/Preprocessor.php
index 05205f04f8b99cd903f345f8c1f7dc04384e7aee..fb579c1dce29c7da26b35288ff61040363f4f510 100644
--- a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/Preprocessor.php
+++ b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/Preprocessor.php
@@ -8,8 +8,11 @@ namespace Magento\CatalogSearch\Model\Adapter\Mysql\Filter;
 use Magento\Catalog\Api\Data\ProductInterface;
 use Magento\Catalog\Model\Product;
 use Magento\Catalog\Model\ResourceModel\Eav\Attribute;
+use Magento\CatalogInventory\Model\Stock;
 use Magento\CatalogSearch\Model\Search\TableMapper;
 use Magento\Eav\Model\Config;
+use Magento\Framework\App\Config\ScopeConfigInterface;
+use Magento\Framework\App\ObjectManager;
 use Magento\Framework\App\ResourceConnection;
 use Magento\Framework\App\ScopeResolverInterface;
 use Magento\Framework\DB\Adapter\AdapterInterface;
@@ -17,6 +20,7 @@ use Magento\Framework\EntityManager\MetadataPool;
 use Magento\Framework\Search\Adapter\Mysql\ConditionManager;
 use Magento\Framework\Search\Adapter\Mysql\Filter\PreprocessorInterface;
 use Magento\Framework\Search\Request\FilterInterface;
+use Magento\Store\Model\ScopeInterface;
 use Magento\Store\Model\Store;
 
 /**
@@ -60,9 +64,14 @@ class Preprocessor implements PreprocessorInterface
     private $metadataPool;
 
     /**
-     * @var TableMapper
+     * @var ScopeConfigInterface
      */
-    private $tableMapper;
+    private $scopeConfig;
+
+    /**
+     * @var AliasResolver
+     */
+    private $aliasResolver;
 
     /**
      * @param ConditionManager $conditionManager
@@ -71,6 +80,9 @@ class Preprocessor implements PreprocessorInterface
      * @param ResourceConnection $resource
      * @param TableMapper $tableMapper
      * @param string $attributePrefix
+     * @param ScopeConfigInterface $scopeConfig
+     * @param AliasResolver $aliasResolver
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
     public function __construct(
         ConditionManager $conditionManager,
@@ -78,7 +90,9 @@ class Preprocessor implements PreprocessorInterface
         Config $config,
         ResourceConnection $resource,
         TableMapper $tableMapper,
-        $attributePrefix
+        $attributePrefix,
+        ScopeConfigInterface $scopeConfig = null,
+        AliasResolver $aliasResolver = null
     ) {
         $this->conditionManager = $conditionManager;
         $this->scopeResolver = $scopeResolver;
@@ -86,7 +100,16 @@ class Preprocessor implements PreprocessorInterface
         $this->resource = $resource;
         $this->connection = $resource->getConnection();
         $this->attributePrefix = $attributePrefix;
-        $this->tableMapper = $tableMapper;
+
+        if (null === $scopeConfig) {
+            $scopeConfig = ObjectManager::getInstance()->get(ScopeConfigInterface::class);
+        }
+        if (null === $aliasResolver) {
+            $aliasResolver = ObjectManager::getInstance()->get(AliasResolver::class);
+        }
+
+        $this->scopeConfig = $scopeConfig;
+        $this->aliasResolver = $aliasResolver;
     }
 
     /**
@@ -117,7 +140,7 @@ class Preprocessor implements PreprocessorInterface
         } elseif ($filter->getField() === 'category_ids') {
             return 'category_ids_index.category_id = ' . (int) $filter->getValue();
         } elseif ($attribute->isStatic()) {
-            $alias = $this->tableMapper->getMappingAlias($filter);
+            $alias = $this->aliasResolver->getAlias($filter);
             $resultQuery = str_replace(
                 $this->connection->quoteIdentifier($attribute->getAttributeCode()),
                 $this->connection->quoteIdentifier($alias . '.' . $attribute->getAttributeCode()),
@@ -208,7 +231,7 @@ class Preprocessor implements PreprocessorInterface
      */
     private function processTermSelect(FilterInterface $filter, $isNegation)
     {
-        $alias = $this->tableMapper->getMappingAlias($filter);
+        $alias = $this->aliasResolver->getAlias($filter);
         if (is_array($filter->getValue())) {
             $value = sprintf(
                 '%s IN (%s)',
@@ -224,9 +247,31 @@ class Preprocessor implements PreprocessorInterface
             $value
         );
 
+        if ($this->isAddStockFilter()) {
+            $resultQuery = sprintf(
+                '%1$s AND %2$s%3$s.stock_status = %4$s',
+                $resultQuery,
+                $alias,
+                AliasResolver::STOCK_FILTER_SUFFIX,
+                Stock::STOCK_IN_STOCK
+            );
+        }
+
         return $resultQuery;
     }
 
+    /**
+     * @return bool
+     */
+    private function isAddStockFilter()
+    {
+        $isShowOutOfStock = $this->scopeConfig->isSetFlag(
+            'cataloginventory/options/show_out_of_stock',
+            ScopeInterface::SCOPE_STORE
+        );
+        return false === $isShowOutOfStock;
+    }
+
     /**
      * Get product metadata pool
      *
diff --git a/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/ExclusionStrategy.php b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/ExclusionStrategy.php
new file mode 100644
index 0000000000000000000000000000000000000000..8626b2ea15b077fc42380afaba82b245e7387937
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/ExclusionStrategy.php
@@ -0,0 +1,83 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\CatalogSearch\Model\Search\FilterMapper;
+
+use Magento\CatalogSearch\Model\Adapter\Mysql\Filter\AliasResolver;
+
+/**
+ * Strategy which processes exclusions from general rules
+ */
+class ExclusionStrategy implements FilterStrategyInterface
+{
+    /**
+     * @var \Magento\Framework\App\ResourceConnection
+     */
+    private $resourceConnection;
+
+    /**
+     * @var AliasResolver
+     */
+    private $aliasResolver;
+
+    /**
+     * @var \Magento\Store\Model\StoreManagerInterface
+     */
+    private $storeManager;
+
+    /**
+     * @param \Magento\Framework\App\ResourceConnection $resourceConnection
+     * @param \Magento\Store\Model\StoreManagerInterface $storeManager
+     * @param AliasResolver $aliasResolver
+     */
+    public function __construct(
+        \Magento\Framework\App\ResourceConnection $resourceConnection,
+        \Magento\Store\Model\StoreManagerInterface $storeManager,
+        AliasResolver $aliasResolver
+    ) {
+        $this->resourceConnection = $resourceConnection;
+        $this->storeManager = $storeManager;
+        $this->aliasResolver = $aliasResolver;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function apply(
+        \Magento\Framework\Search\Request\FilterInterface $filter,
+        \Magento\Framework\DB\Select $select
+    ) {
+        $isApplied = false;
+        $field = $filter->getField();
+        if ('price' === $field) {
+            $alias = $this->aliasResolver->getAlias($filter);
+            $tableName = $this->resourceConnection->getTableName('catalog_product_index_price');
+            $select->joinInner(
+                [
+                    $alias => $tableName
+                ],
+                $this->resourceConnection->getConnection()->quoteInto(
+                    'search_index.entity_id = price_index.entity_id AND price_index.website_id = ?',
+                    $this->storeManager->getWebsite()->getId()
+                ),
+                []
+            );
+            $isApplied = true;
+        } elseif ('category_ids' === $field) {
+            $alias = $this->aliasResolver->getAlias($filter);
+            $tableName = $this->resourceConnection->getTableName('catalog_category_product_index');
+            $select->joinInner(
+                [
+                    $alias => $tableName
+                ],
+                'search_index.entity_id = category_ids_index.product_id',
+                []
+            );
+            $isApplied = true;
+        }
+        return $isApplied;
+    }
+}
diff --git a/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/FilterContext.php b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/FilterContext.php
new file mode 100644
index 0000000000000000000000000000000000000000..d244e3d5f754893fb671a3b6c81565eb5c920ff7
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/FilterContext.php
@@ -0,0 +1,100 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\CatalogSearch\Model\Search\FilterMapper;
+
+
+use Magento\CatalogSearch\Model\Adapter\Mysql\Filter\AliasResolver;
+use Magento\Eav\Model\Config as EavConfig;
+use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
+
+/**
+ * FilterContext represents a Context of the Strategy pattern
+ * Its responsibility is to choose appropriate strategy to apply passed filter to the Select
+ */
+class FilterContext implements FilterStrategyInterface
+{
+    /**
+     * @var ExclusionStrategy
+     */
+    private $exclusionStrategy;
+
+    /**
+     * @var EavConfig
+     */
+    private $eavConfig;
+
+    /**
+     * @var TermDropdownStrategy
+     */
+    private $termDropdownStrategy;
+
+    /**
+     * @var StaticAttributeStrategy
+     */
+    private $staticAttributeStrategy;
+
+    /**
+     * @var AliasResolver
+     */
+    private $aliasResolver;
+
+    /**
+     * @param EavConfig $eavConfig
+     * @param AliasResolver $aliasResolver
+     * @param ExclusionStrategy $exclusionStrategy
+     * @param TermDropdownStrategy $termDropdownStrategy
+     * @param StaticAttributeStrategy $staticAttributeStrategy
+     */
+    public function __construct(
+        EavConfig $eavConfig,
+        AliasResolver $aliasResolver,
+        ExclusionStrategy $exclusionStrategy,
+        TermDropdownStrategy $termDropdownStrategy,
+        StaticAttributeStrategy $staticAttributeStrategy
+    ) {
+        $this->eavConfig = $eavConfig;
+        $this->aliasResolver = $aliasResolver;
+        $this->exclusionStrategy = $exclusionStrategy;
+        $this->termDropdownStrategy = $termDropdownStrategy;
+        $this->staticAttributeStrategy = $staticAttributeStrategy;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function apply(
+        \Magento\Framework\Search\Request\FilterInterface $filter,
+        \Magento\Framework\DB\Select $select
+    ) {
+        $isApplied = $this->exclusionStrategy->apply($filter, $select);
+
+        if (!$isApplied) {
+            $attribute = $this->getAttributeByCode($filter->getField());
+            if ($attribute) {
+                if ($filter->getType() === \Magento\Framework\Search\Request\FilterInterface::TYPE_TERM
+                    && in_array($attribute->getFrontendInput(), ['select', 'multiselect'], true)
+                ) {
+                    $isApplied = $this->termDropdownStrategy->apply($filter, $select);
+                } elseif ($attribute->getBackendType() === AbstractAttribute::TYPE_STATIC) {
+                    $isApplied = $this->staticAttributeStrategy->apply($filter, $select);
+                }
+            }
+        }
+
+        return $isApplied;
+    }
+
+    /**
+     * @param string $field
+     * @return \Magento\Catalog\Model\ResourceModel\Eav\Attribute
+     * @throws \Magento\Framework\Exception\LocalizedException
+     */
+    private function getAttributeByCode($field)
+    {
+        return $this->eavConfig->getAttribute(\Magento\Catalog\Model\Product::ENTITY, $field);
+    }
+}
diff --git a/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/FilterStrategyInterface.php b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/FilterStrategyInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..cf17f7d5132efbf8246af0de674d7c789551aa3e
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/FilterStrategyInterface.php
@@ -0,0 +1,23 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\CatalogSearch\Model\Search\FilterMapper;
+
+/**
+ * FilterStrategyInterface provides the interface to work with strategies
+ */
+interface FilterStrategyInterface
+{
+    /**
+     * @param \Magento\Framework\Search\Request\FilterInterface $filter
+     * @param \Magento\Framework\DB\Select $select
+     * @return bool is filter was applied
+     */
+    public function apply(
+        \Magento\Framework\Search\Request\FilterInterface $filter,
+        \Magento\Framework\DB\Select $select
+    );
+}
diff --git a/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/StaticAttributeStrategy.php b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/StaticAttributeStrategy.php
new file mode 100644
index 0000000000000000000000000000000000000000..eb9d61b5a7f4c352d23096050a5acd292dc80d54
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/StaticAttributeStrategy.php
@@ -0,0 +1,74 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\CatalogSearch\Model\Search\FilterMapper;
+
+use Magento\CatalogSearch\Model\Adapter\Mysql\Filter\AliasResolver;
+use Magento\Eav\Model\Config as EavConfig;
+
+/**
+ * This strategy handles static attributes
+ */
+class StaticAttributeStrategy implements FilterStrategyInterface
+{
+    /**
+     * @var \Magento\Framework\App\ResourceConnection
+     */
+    private $resourceConnection;
+
+    /**
+     * @var AliasResolver
+     */
+    private $aliasResolver;
+
+    /**
+     * @var EavConfig
+     */
+    private $eavConfig;
+
+    /**
+     * @param \Magento\Framework\App\ResourceConnection $resourceConnection
+     * @param EavConfig $eavConfig
+     * @param AliasResolver $aliasResolver
+     */
+    public function __construct(
+        \Magento\Framework\App\ResourceConnection $resourceConnection,
+        EavConfig $eavConfig,
+        AliasResolver $aliasResolver
+    ) {
+        $this->resourceConnection = $resourceConnection;
+        $this->eavConfig = $eavConfig;
+        $this->aliasResolver = $aliasResolver;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function apply(
+        \Magento\Framework\Search\Request\FilterInterface $filter,
+        \Magento\Framework\DB\Select $select
+    ) {
+        $attribute = $this->getAttributeByCode($filter->getField());
+        $alias = $this->aliasResolver->getAlias($filter);
+        $select->joinInner(
+            [$alias => $attribute->getBackendTable()],
+            'search_index.entity_id = '
+            . $this->resourceConnection->getConnection()->quoteIdentifier("$alias.entity_id"),
+            []
+        );
+        return true;
+    }
+
+    /**
+     * @param string $field
+     * @return \Magento\Catalog\Model\ResourceModel\Eav\Attribute
+     * @throws \Magento\Framework\Exception\LocalizedException
+     */
+    private function getAttributeByCode($field)
+    {
+        return $this->eavConfig->getAttribute(\Magento\Catalog\Model\Product::ENTITY, $field);
+    }
+}
diff --git a/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/TermDropdownStrategy.php b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/TermDropdownStrategy.php
new file mode 100644
index 0000000000000000000000000000000000000000..76828fe28f4348c8b90c9f1bf695906242c77222
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/TermDropdownStrategy.php
@@ -0,0 +1,127 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\CatalogSearch\Model\Search\FilterMapper;
+
+use Magento\CatalogSearch\Model\Adapter\Mysql\Filter\AliasResolver;
+use Magento\Eav\Model\Config as EavConfig;
+use Magento\Framework\App\Config\ScopeConfigInterface;
+use Magento\Framework\App\ResourceConnection;
+use Magento\Store\Model\ScopeInterface;
+use Magento\Store\Model\StoreManagerInterface;
+
+/**
+ * This strategy handles attributes which comply with two criteria:
+ *   - The filter for dropdown or multi-select attribute
+ *   - The filter is Term filter
+ *
+ */
+class TermDropdownStrategy implements FilterStrategyInterface
+{
+    /**
+     * @var AliasResolver
+     */
+    private $aliasResolver;
+
+    /**
+     * @var StoreManagerInterface
+     */
+    private $storeManager;
+
+    /**
+     * @var EavConfig
+     */
+    private $eavConfig;
+
+    /**
+     * @var ResourceConnection
+     */
+    private $resourceConnection;
+
+    /**
+     * @var ScopeConfigInterface
+     */
+    private $scopeConfig;
+
+    /**
+     * @param StoreManagerInterface $storeManager
+     * @param ResourceConnection $resourceConnection
+     * @param EavConfig $eavConfig
+     * @param ScopeConfigInterface $scopeConfig
+     * @param AliasResolver $aliasResolver
+     */
+    public function __construct(
+        StoreManagerInterface $storeManager,
+        ResourceConnection $resourceConnection,
+        EavConfig $eavConfig,
+        ScopeConfigInterface $scopeConfig,
+        AliasResolver $aliasResolver
+    ) {
+        $this->storeManager = $storeManager;
+        $this->resourceConnection = $resourceConnection;
+        $this->eavConfig = $eavConfig;
+        $this->scopeConfig = $scopeConfig;
+        $this->aliasResolver = $aliasResolver;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws \Magento\Framework\Exception\LocalizedException
+     */
+    public function apply(
+        \Magento\Framework\Search\Request\FilterInterface $filter,
+        \Magento\Framework\DB\Select $select
+    ) {
+        $alias = $this->aliasResolver->getAlias($filter);
+        $attribute = $this->getAttributeByCode($filter->getField());
+        $joinCondition = sprintf(
+            'search_index.entity_id = %1$s.entity_id AND %1$s.attribute_id = %2$d AND %1$s.store_id = %3$d',
+            $alias,
+            $attribute->getId(),
+            $this->storeManager->getWebsite()->getId()
+        );
+        $select->joinLeft(
+            [$alias => $this->resourceConnection->getTableName('catalog_product_index_eav')],
+            $joinCondition,
+            []
+        );
+        if ($this->isAddStockFilter()) {
+            $stockAlias = $alias . AliasResolver::STOCK_FILTER_SUFFIX;
+            $select->joinLeft(
+                [
+                    $stockAlias => $this->resourceConnection->getTableName('cataloginventory_stock_status'),
+                ],
+                sprintf('%2$s.product_id = %1$s.source_id', $alias, $stockAlias),
+                []
+            );
+        }
+
+        return true;
+    }
+
+    /**
+     * @param string $field
+     * @return \Magento\Catalog\Model\ResourceModel\Eav\Attribute
+     * @throws \Magento\Framework\Exception\LocalizedException
+     */
+    private function getAttributeByCode($field)
+    {
+        return $this->eavConfig->getAttribute(\Magento\Catalog\Model\Product::ENTITY, $field);
+    }
+
+    /**
+     * @return bool
+     */
+    private function isAddStockFilter()
+    {
+        $isShowOutOfStock = $this->scopeConfig->isSetFlag(
+            'cataloginventory/options/show_out_of_stock',
+            ScopeInterface::SCOPE_STORE
+        );
+
+        return false === $isShowOutOfStock;
+    }
+}
diff --git a/app/code/Magento/CatalogSearch/Model/Search/IndexBuilder.php b/app/code/Magento/CatalogSearch/Model/Search/IndexBuilder.php
index 1d30bb4a14d23a94d79a2faa5bc20823df220d8d..0e96e4de700259c9dc48c8f876983e2642316a86 100644
--- a/app/code/Magento/CatalogSearch/Model/Search/IndexBuilder.php
+++ b/app/code/Magento/CatalogSearch/Model/Search/IndexBuilder.php
@@ -99,6 +99,7 @@ class IndexBuilder implements IndexBuilderInterface
      *
      * @param RequestInterface $request
      * @return Select
+     * @throws \LogicException
      */
     public function build(RequestInterface $request)
     {
@@ -132,7 +133,7 @@ class IndexBuilder implements IndexBuilderInterface
                 ),
                 []
             );
-            $select->where('stock_index.stock_status = ?', Stock::DEFAULT_STOCK_ID);
+            $select->where('stock_index.stock_status = ?', Stock::STOCK_IN_STOCK);
         }
 
         return $select;
diff --git a/app/code/Magento/CatalogSearch/Model/Search/TableMapper.php b/app/code/Magento/CatalogSearch/Model/Search/TableMapper.php
index ca7298e1beaac560ee66427f2b9d57db7ce21508..ac726192856a5fa555eae6757cd83b16930565ee 100644
--- a/app/code/Magento/CatalogSearch/Model/Search/TableMapper.php
+++ b/app/code/Magento/CatalogSearch/Model/Search/TableMapper.php
@@ -6,10 +6,11 @@
 
 namespace Magento\CatalogSearch\Model\Search;
 
-use Magento\Catalog\Model\Product;
 use Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory;
+use Magento\CatalogSearch\Model\Adapter\Mysql\Filter\AliasResolver;
+use Magento\CatalogSearch\Model\Search\FilterMapper\FilterStrategyInterface;
 use Magento\Eav\Model\Config as EavConfig;
-use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
+use Magento\Framework\App\Config\ScopeConfigInterface;
 use Magento\Framework\App\ObjectManager;
 use Magento\Framework\App\ResourceConnection as AppResource;
 use Magento\Framework\DB\Select;
@@ -21,12 +22,15 @@ use Magento\Framework\Search\Request\QueryInterface as RequestQueryInterface;
 use Magento\Store\Model\StoreManagerInterface;
 
 /**
+ * Responsibility of the TableMapper is to collect all filters from the search query
+ * and pass them one by one for processing in the FilterContext,
+ * which will apply them to the Select
  * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  */
 class TableMapper
 {
     /**
-     * @var Resource
+     * @var AppResource
      */
     private $resource;
 
@@ -40,22 +44,59 @@ class TableMapper
      */
     private $eavConfig;
 
+    /**
+     * @var ScopeConfigInterface
+     */
+    private $scopeConfig;
+
+    /**
+     * @var FilterStrategyInterface
+     */
+    private $filterStrategy;
+
+    /**
+     * @var AliasResolver
+     */
+    private $aliasResolver;
+
     /**
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      * @param AppResource $resource
      * @param StoreManagerInterface $storeManager
      * @param CollectionFactory $attributeCollectionFactory
      * @param EavConfig $eavConfig
+     * @param ScopeConfigInterface $scopeConfig
+     * @param FilterStrategyInterface $filterStrategy
+     * @param AliasResolver $aliasResolver
      */
     public function __construct(
         AppResource $resource,
         StoreManagerInterface $storeManager,
         CollectionFactory $attributeCollectionFactory,
-        EavConfig $eavConfig = null
+        EavConfig $eavConfig = null,
+        ScopeConfigInterface $scopeConfig = null,
+        FilterStrategyInterface $filterStrategy = null,
+        AliasResolver $aliasResolver = null
     ) {
         $this->resource = $resource;
         $this->storeManager = $storeManager;
-        $this->eavConfig = $eavConfig !== null ? $eavConfig : ObjectManager::getInstance()->get(EavConfig::class);
+
+        if (null === $eavConfig) {
+            $eavConfig = ObjectManager::getInstance()->get(EavConfig::class);
+        }
+        if (null === $scopeConfig) {
+            $scopeConfig = ObjectManager::getInstance()->get(ScopeConfigInterface::class);
+        }
+        if (null === $filterStrategy) {
+            $filterStrategy = ObjectManager::getInstance()->get(FilterStrategyInterface::class);
+        }
+        if (null === $aliasResolver) {
+            $aliasResolver = ObjectManager::getInstance()->get(AliasResolver::class);
+        }
+        $this->eavConfig = $eavConfig;
+        $this->scopeConfig = $scopeConfig;
+        $this->filterStrategy = $filterStrategy;
+        $this->aliasResolver = $aliasResolver;
     }
 
     /**
@@ -66,111 +107,53 @@ class TableMapper
      */
     public function addTables(Select $select, RequestInterface $request)
     {
-        $mappedTables = [];
-        $filters = $this->getFilters($request->getQuery());
+        $appliedFilters = [];
+        $filters = $this->getFiltersFromQuery($request->getQuery());
         foreach ($filters as $filter) {
-            list($alias, $table, $mapOn, $mappedFields, $joinType) = $this->getMappingData($filter);
-            if (!array_key_exists($alias, $mappedTables)) {
-                switch ($joinType) {
-                    case \Magento\Framework\DB\Select::INNER_JOIN:
-                        $select->joinInner(
-                            [$alias => $table],
-                            $mapOn,
-                            $mappedFields
-                        );
-                        break;
-                    case \Magento\Framework\DB\Select::LEFT_JOIN:
-                        $select->joinLeft(
-                            [$alias => $table],
-                            $mapOn,
-                            $mappedFields
-                        );
-                        break;
-                    default:
-                        throw new \LogicException(__('Unsupported join type: %1', $joinType));
+            $alias = $this->aliasResolver->getAlias($filter);
+            if (!array_key_exists($alias, $appliedFilters)) {
+                $isApplied = $this->filterStrategy->apply($filter, $select);
+                if ($isApplied) {
+                    $appliedFilters[$alias] = true;
                 }
-                $mappedTables[$alias] = $table;
             }
         }
         return $select;
     }
 
     /**
+     * This method is deprecated.
+     * Please use \Magento\CatalogSearch\Model\Adapter\Mysql\Filter\AliasResolver::getAlias() instead.
+     *
+     * @deprecated
+     * @see AliasResolver::getAlias()
+     *
      * @param FilterInterface $filter
      * @return string
      */
     public function getMappingAlias(FilterInterface $filter)
     {
-        list($alias) = $this->getMappingData($filter);
-        return $alias;
-    }
-
-    /**
-     * Returns mapping data for field in format: [
-     *  'table_alias',
-     *  'table',
-     *  'join_condition',
-     *  ['fields'],
-     *  'joinType'
-     * ]
-     * @param FilterInterface $filter
-     * @return array
-     */
-    private function getMappingData(FilterInterface $filter)
-    {
-        $alias = null;
-        $table = null;
-        $mapOn = null;
-        $mappedFields = null;
-        $field = $filter->getField();
-        $joinType = \Magento\Framework\DB\Select::INNER_JOIN;
-        $fieldToTableMap = $this->getFieldToTableMap($field);
-        if ($fieldToTableMap) {
-            list($alias, $table, $mapOn, $mappedFields) = $fieldToTableMap;
-            $table = $this->resource->getTableName($table);
-        } elseif ($attribute = $this->getAttributeByCode($field)) {
-            if ($filter->getType() === FilterInterface::TYPE_TERM
-                && in_array($attribute->getFrontendInput(), ['select', 'multiselect'], true)
-            ) {
-                $joinType = \Magento\Framework\DB\Select::LEFT_JOIN;
-                $table = $this->resource->getTableName('catalog_product_index_eav');
-                $alias = $field . RequestGenerator::FILTER_SUFFIX;
-                $mapOn = sprintf(
-                    'search_index.entity_id = %1$s.entity_id AND %1$s.attribute_id = %2$d AND %1$s.store_id = %3$d',
-                    $alias,
-                    $attribute->getId(),
-                    $this->getStoreId()
-                );
-                $mappedFields = [];
-            } elseif ($attribute->getBackendType() === AbstractAttribute::TYPE_STATIC) {
-                $table = $attribute->getBackendTable();
-                $alias = $field . RequestGenerator::FILTER_SUFFIX;
-                $mapOn = 'search_index.entity_id = ' . $alias . '.entity_id';
-                $mappedFields = null;
-            }
-        }
-
-        return [$alias, $table, $mapOn, $mappedFields, $joinType];
+        return $this->aliasResolver->getAlias($filter);
     }
 
     /**
      * @param RequestQueryInterface $query
      * @return FilterInterface[]
      */
-    private function getFilters($query)
+    private function getFiltersFromQuery(RequestQueryInterface $query)
     {
         $filters = [];
         switch ($query->getType()) {
             case RequestQueryInterface::TYPE_BOOL:
                 /** @var \Magento\Framework\Search\Request\Query\BoolExpression $query */
                 foreach ($query->getMust() as $subQuery) {
-                    $filters = array_merge($filters, $this->getFilters($subQuery));
+                    $filters = array_merge($filters, $this->getFiltersFromQuery($subQuery));
                 }
                 foreach ($query->getShould() as $subQuery) {
-                    $filters = array_merge($filters, $this->getFilters($subQuery));
+                    $filters = array_merge($filters, $this->getFiltersFromQuery($subQuery));
                 }
                 foreach ($query->getMustNot() as $subQuery) {
-                    $filters = array_merge($filters, $this->getFilters($subQuery));
+                    $filters = array_merge($filters, $this->getFiltersFromQuery($subQuery));
                 }
                 break;
             case RequestQueryInterface::TYPE_FILTER:
@@ -219,57 +202,4 @@ class TableMapper
         }
         return $filters;
     }
-
-    /**
-     * @return int
-     */
-    private function getWebsiteId()
-    {
-        return $this->storeManager->getWebsite()->getId();
-    }
-
-    /**
-     * @return int
-     */
-    private function getStoreId()
-    {
-        return $this->storeManager->getStore()->getId();
-    }
-
-    /**
-     * @param string $field
-     * @return array|null
-     */
-    private function getFieldToTableMap($field)
-    {
-        $fieldToTableMap = [
-            'price' => [
-                'price_index',
-                'catalog_product_index_price',
-                $this->resource->getConnection()->quoteInto(
-                    'search_index.entity_id = price_index.entity_id AND price_index.website_id = ?',
-                    $this->getWebsiteId()
-                ),
-                []
-            ],
-            'category_ids' => [
-                'category_ids_index',
-                'catalog_category_product_index',
-                'search_index.entity_id = category_ids_index.product_id',
-                []
-            ]
-        ];
-        return array_key_exists($field, $fieldToTableMap) ? $fieldToTableMap[$field] : null;
-    }
-
-    /**
-     * @param string $field
-     * @return \Magento\Catalog\Model\ResourceModel\Eav\Attribute
-     * @throws \Magento\Framework\Exception\LocalizedException
-     */
-    private function getAttributeByCode($field)
-    {
-        $attribute = $this->eavConfig->getAttribute(Product::ENTITY, $field);
-        return $attribute;
-    }
 }
diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/AliasResolverTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/AliasResolverTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..ab01d2553a3edcebde8a564a8ca8b8bb8cf2c7dd
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/AliasResolverTest.php
@@ -0,0 +1,68 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\CatalogSearch\Test\Unit\Model\Adapter\Mysql\Filter;
+
+use Magento\CatalogSearch\Model\Search\RequestGenerator;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
+
+class AliasResolverTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var \Magento\CatalogSearch\Model\Adapter\Mysql\Filter\AliasResolver
+     */
+    private $aliasResolver;
+
+    /**
+     * @inheritDoc
+     */
+    protected function setUp()
+    {
+        $objectManagerHelper = new ObjectManagerHelper($this);
+        $this->aliasResolver = $objectManagerHelper->getObject(
+            \Magento\CatalogSearch\Model\Adapter\Mysql\Filter\AliasResolver::class,
+            []
+        );
+    }
+
+    /**
+     * @param string $field
+     * @param string $expectedAlias
+     * @dataProvider aliasDataProvider
+     */
+    public function testGetFilterAlias($field, $expectedAlias)
+    {
+        $filter = $this->getMockBuilder(\Magento\Framework\Search\Request\Filter\Term::class)
+            ->setMethods(['getField'])
+            ->disableOriginalConstructor()
+            ->getMock();
+        $filter->expects($this->once())
+            ->method('getField')
+            ->willReturn($field);
+        $this->assertSame($expectedAlias, $this->aliasResolver->getAlias($filter));
+    }
+
+    /**
+     * @return array
+     */
+    public function aliasDataProvider()
+    {
+        return [
+            'general' => [
+                'field' => 'general',
+                'alias' => 'general' . RequestGenerator::FILTER_SUFFIX,
+            ],
+            'price' => [
+                'field' => 'price',
+                'alias' => 'price_index',
+            ],
+            'category_ids' => [
+                'field' => 'category_ids',
+                'alias' => 'category_ids_index',
+            ],
+        ];
+    }
+}
diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/PreprocessorTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/PreprocessorTest.php
index 0949bf6469be92611fbefa4c45962d8255570892..2cd6935586bd816bcc8c1e444b242c3b3f7a1472 100644
--- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/PreprocessorTest.php
+++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/PreprocessorTest.php
@@ -6,6 +6,7 @@
 
 namespace Magento\CatalogSearch\Test\Unit\Model\Adapter\Mysql\Filter;
 
+use Magento\CatalogSearch\Model\Adapter\Mysql\Filter\AliasResolver;
 use Magento\Framework\DB\Select;
 use Magento\Framework\EntityManager\EntityMetadata;
 use Magento\Framework\Search\Request\FilterInterface;
@@ -18,9 +19,9 @@ use PHPUnit_Framework_MockObject_MockObject as MockObject;
 class PreprocessorTest extends \PHPUnit_Framework_TestCase
 {
     /**
-     * @var \Magento\CatalogSearch\Model\Search\TableMapper|\PHPUnit_Framework_MockObject_MockObject
+     * @var AliasResolver|\PHPUnit_Framework_MockObject_MockObject
      */
-    private $tableMapper;
+    private $aliasResolver;
 
     /**
      * @var \Magento\Framework\DB\Adapter\AdapterInterface|MockObject
@@ -141,7 +142,7 @@ class PreprocessorTest extends \PHPUnit_Framework_TestCase
                 )
             );
 
-        $this->tableMapper = $this->getMockBuilder(\Magento\CatalogSearch\Model\Search\TableMapper::class)
+        $this->aliasResolver = $this->getMockBuilder(AliasResolver::class)
             ->disableOriginalConstructor()
             ->getMock();
         $this->metadataPoolMock = $this->getMockBuilder(\Magento\Framework\EntityManager\MetadataPool::class)
@@ -164,7 +165,7 @@ class PreprocessorTest extends \PHPUnit_Framework_TestCase
                 'resource' => $resource,
                 'attributePrefix' => 'attr_',
                 'metadataPool' => $this->metadataPoolMock,
-                'tableMapper' => $this->tableMapper,
+                'aliasResolver' => $this->aliasResolver,
             ]
         );
     }
@@ -234,7 +235,7 @@ class PreprocessorTest extends \PHPUnit_Framework_TestCase
 
         $this->attribute->method('getAttributeCode')
             ->willReturn('static_attribute');
-        $this->tableMapper->expects($this->once())->method('getMappingAlias')
+        $this->aliasResolver->expects($this->once())->method('getAlias')
             ->willReturn('attr_table_alias');
         $this->filter->expects($this->exactly(3))
             ->method('getField')
@@ -272,7 +273,7 @@ class PreprocessorTest extends \PHPUnit_Framework_TestCase
             ->method('getFrontendInput')
             ->willReturn($frontendInput);
 
-        $this->tableMapper->expects($this->once())->method('getMappingAlias')
+        $this->aliasResolver->expects($this->once())->method('getAlias')
             ->willReturn('termAttrAlias');
 
         $this->filter->expects($this->exactly(3))
diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/FilterMapper/FilterContextTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/FilterMapper/FilterContextTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..9f133b763889bafb4843f7f9f868a3995a842eed
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/FilterMapper/FilterContextTest.php
@@ -0,0 +1,236 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\CatalogSearch\Test\Unit\Model\Search\FilterMapper;
+
+use Magento\Catalog\Model\ResourceModel\Eav\Attribute;
+use Magento\CatalogSearch\Model\Adapter\Mysql\Filter\AliasResolver;
+use Magento\CatalogSearch\Model\Search\FilterMapper\ExclusionStrategy;
+use Magento\CatalogSearch\Model\Search\FilterMapper\FilterContext;
+use Magento\CatalogSearch\Model\Search\FilterMapper\StaticAttributeStrategy;
+use Magento\CatalogSearch\Model\Search\FilterMapper\TermDropdownStrategy;
+use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
+use Magento\Framework\Search\Request\FilterInterface;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+
+class FilterContextTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var FilterContext|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $filterContext;
+
+    /**
+     * @var AliasResolver|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $aliasResolver;
+
+    /**
+     * @var \Magento\Eav\Model\Config|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $eavConfig;
+
+    /**
+     * @var ExclusionStrategy|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $exclusionStrategy;
+
+    /**
+     * @var TermDropdownStrategy|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $termDropdownStrategy;
+
+    /**
+     * @var StaticAttributeStrategy|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $staticAttributeStrategy;
+
+    /**
+     * @var \Magento\Framework\DB\Select
+     */
+    private $select;
+
+    /**
+     * @inheritDoc
+     */
+    protected function setUp()
+    {
+        $this->eavConfig = $this->getMockBuilder(\Magento\Eav\Model\Config::class)
+            ->disableOriginalConstructor()
+            ->setMethods(['getAttribute'])
+            ->getMock();
+        $this->aliasResolver = $this->getMockBuilder(
+            AliasResolver::class
+        )
+            ->disableOriginalConstructor()
+            ->setMethods(['getAlias'])
+            ->getMock();
+        $this->exclusionStrategy = $this->getMockBuilder(ExclusionStrategy::class)
+            ->disableOriginalConstructor()
+            ->setMethods(['apply'])
+            ->getMock();
+        $this->termDropdownStrategy = $this->getMockBuilder(TermDropdownStrategy::class)
+            ->disableOriginalConstructor()
+            ->setMethods(['apply'])
+            ->getMock();
+        $this->staticAttributeStrategy = $this->getMockBuilder(StaticAttributeStrategy::class)
+            ->disableOriginalConstructor()
+            ->setMethods(['apply'])
+            ->getMock();
+        $this->select = $this->getMockBuilder(\Magento\Framework\DB\Select::class)
+            ->disableOriginalConstructor()
+            ->setMethods([])
+            ->getMock();
+        $objectManager = new ObjectManager($this);
+        $this->filterContext = $objectManager->getObject(
+            FilterContext::class,
+            [
+                'eavConfig' => $this->eavConfig,
+                'aliasResolver' => $this->aliasResolver,
+                'exclusionStrategy' => $this->exclusionStrategy,
+                'termDropdownStrategy' => $this->termDropdownStrategy,
+                'staticAttributeStrategy' => $this->staticAttributeStrategy,
+            ]
+        );
+    }
+
+    public function testApplyOnExclusionFilter()
+    {
+        $filter = $this->createFilterMock();
+        $this->exclusionStrategy->expects($this->once())
+            ->method('apply')
+            ->with($filter, $this->select)
+            ->willReturn(true);
+        $this->eavConfig->expects($this->never())->method('getAttribute');
+        $this->assertTrue($this->filterContext->apply($filter, $this->select));
+    }
+
+    public function testApplyFilterWithoutAttribute()
+    {
+        $filter = $this->createFilterMock('some_field');
+        $this->exclusionStrategy->expects($this->once())
+            ->method('apply')
+            ->with($filter, $this->select)
+            ->willReturn(false);
+        $this->eavConfig->expects($this->once())
+            ->method('getAttribute')
+            ->with(\Magento\Catalog\Model\Product::ENTITY, 'some_field')
+            ->willReturn(null);
+        $this->assertFalse($this->filterContext->apply($filter, $this->select));
+    }
+
+    public function testApplyOnTermFilterBySelect()
+    {
+        $filter = $this->createFilterMock('select_field', FilterInterface::TYPE_TERM);
+        $attribute = $this->createAttributeMock('select');
+        $this->eavConfig->expects($this->once())
+            ->method('getAttribute')
+            ->with(\Magento\Catalog\Model\Product::ENTITY, 'select_field')
+            ->willReturn($attribute);
+        $this->exclusionStrategy->expects($this->once())
+            ->method('apply')
+            ->with($filter, $this->select)
+            ->willReturn(false);
+        $this->termDropdownStrategy->expects($this->once())
+            ->method('apply')
+            ->with($filter, $this->select)
+            ->willReturn(true);
+        $this->assertTrue($this->filterContext->apply($filter, $this->select));
+    }
+
+    public function testApplyOnTermFilterByMultiSelect()
+    {
+        $filter = $this->createFilterMock('multiselect_field', FilterInterface::TYPE_TERM);
+        $attribute = $this->createAttributeMock('multiselect');
+        $this->eavConfig->expects($this->once())
+            ->method('getAttribute')
+            ->with(\Magento\Catalog\Model\Product::ENTITY, 'multiselect_field')
+            ->willReturn($attribute);
+        $this->exclusionStrategy->expects($this->once())
+            ->method('apply')
+            ->with($filter, $this->select)
+            ->willReturn(false);
+        $this->termDropdownStrategy->expects($this->once())
+            ->method('apply')
+            ->with($filter, $this->select)
+            ->willReturn(true);
+        $this->assertTrue($this->filterContext->apply($filter, $this->select));
+    }
+
+    public function testApplyOnTermFilterByStaticAttribute()
+    {
+        $filter = $this->createFilterMock('multiselect_field', FilterInterface::TYPE_TERM);
+        $attribute = $this->createAttributeMock('text', AbstractAttribute::TYPE_STATIC);
+        $this->eavConfig->expects($this->once())
+            ->method('getAttribute')
+            ->with(\Magento\Catalog\Model\Product::ENTITY, 'multiselect_field')
+            ->willReturn($attribute);
+        $this->exclusionStrategy->expects($this->once())
+            ->method('apply')
+            ->with($filter, $this->select)
+            ->willReturn(false);
+        $this->staticAttributeStrategy->expects($this->once())
+            ->method('apply')
+            ->with($filter, $this->select)
+            ->willReturn(true);
+        $this->assertTrue($this->filterContext->apply($filter, $this->select));
+    }
+
+    public function testApplyOnTermFilterByUnknownAttributeType()
+    {
+        $filter = $this->createFilterMock('multiselect_field', FilterInterface::TYPE_TERM);
+        $attribute = $this->createAttributeMock('text', 'text');
+        $this->eavConfig->expects($this->once())
+            ->method('getAttribute')
+            ->with(\Magento\Catalog\Model\Product::ENTITY, 'multiselect_field')
+            ->willReturn($attribute);
+        $this->exclusionStrategy->expects($this->once())
+            ->method('apply')
+            ->with($filter, $this->select)
+            ->willReturn(false);
+        $this->assertFalse($this->filterContext->apply($filter, $this->select));
+    }
+
+    /**
+     * @param string $field
+     * @param string $type
+     * @return FilterInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private function createFilterMock($field = null, $type = null)
+    {
+        $filter = $this->getMockBuilder(FilterInterface::class)
+            ->setMethods(['getField', 'getType'])
+            ->getMockForAbstractClass();
+        $filter->expects($this->any())
+            ->method('getField')
+            ->willReturn($field);
+        $filter->expects($this->any())
+            ->method('getType')
+            ->willReturn($type);
+
+        return $filter;
+    }
+
+    /**
+     * @param string|null $frontendInput
+     * @param string|null $backendType
+     * @return Attribute|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private function createAttributeMock($frontendInput = null, $backendType = null)
+    {
+        $attribute = $this->getMockBuilder(Attribute::class)
+            ->disableOriginalConstructor()
+            ->setMethods(['getFrontendInput', 'getBackendType'])
+            ->getMock();
+        $attribute->expects($this->any())
+            ->method('getFrontendInput')
+            ->willReturn($frontendInput);
+        $attribute->expects($this->any())
+            ->method('getBackendType')
+            ->willReturn($backendType);
+        return $attribute;
+    }
+}
diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/TableMapperTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/TableMapperTest.php
index dd9a556146ab3e40e4e1182f44c959c8260d9c8d..dad4ad8095f5af27fa3ce7cd48b1803c706d80a6 100644
--- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/TableMapperTest.php
+++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/TableMapperTest.php
@@ -6,6 +6,9 @@
 
 namespace Magento\CatalogSearch\Test\Unit\Model\Search;
 
+use Magento\Catalog\Model\ResourceModel\Product\Attribute\Collection;
+use Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory;
+use Magento\CatalogSearch\Model\Adapter\Mysql\Filter\AliasResolver;
 use Magento\Framework\Search\Request\FilterInterface;
 use Magento\Framework\Search\Request\QueryInterface;
 use \Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
@@ -16,8 +19,10 @@ use \Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
  */
 class TableMapperTest extends \PHPUnit_Framework_TestCase
 {
-    const WEBSITE_ID = 4512;
-    const STORE_ID = 2514;
+    /**
+     * @var AliasResolver|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $aliasResolver;
 
     /**
      * @var \Magento\Eav\Model\Config|\PHPUnit_Framework_MockObject_MockObject
@@ -25,7 +30,7 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase
     private $eavConfig;
 
     /**
-     * @var \Magento\Catalog\Model\ResourceModel\Product\Attribute\Collection|\PHPUnit_Framework_MockObject_MockObject
+     * @var Collection|\PHPUnit_Framework_MockObject_MockObject
      */
     private $attributeCollection;
 
@@ -59,11 +64,6 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase
      */
     private $resource;
 
-    /**
-     * @var \Magento\Store\Api\Data\StoreInterface|\PHPUnit_Framework_MockObject_MockObject
-     */
-    private $store;
-
     /**
      * @var \Magento\CatalogSearch\Model\Search\TableMapper
      */
@@ -76,65 +76,49 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase
         $this->connection = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class)
             ->disableOriginalConstructor()
             ->getMock();
-        $this->connection->expects($this->any())
-            ->method('quoteInto')
-            ->willReturnCallback(
-                function ($query, $expression) {
-                    return str_replace('?', $expression, $query);
-                }
-            );
+        $this->connection->expects($this->never())->method('quoteInto');
 
         $this->resource = $this->getMockBuilder(\Magento\Framework\App\ResourceConnection::class)
             ->disableOriginalConstructor()
             ->getMock();
-        $this->resource->method('getTableName')
-            ->willReturnCallback(
-                function ($table) {
-                    return 'prefix_' . $table;
-                }
-            );
-        $this->resource->expects($this->any())
-            ->method('getConnection')
-            ->willReturn($this->connection);
+        $this->resource->expects($this->never())->method('getTableName');
+        $this->resource->expects($this->never())->method('getConnection');
 
         $this->website = $this->getMockBuilder(\Magento\Store\Api\Data\WebsiteInterface::class)
             ->disableOriginalConstructor()
             ->getMockForAbstractClass();
-        $this->website->expects($this->any())
-            ->method('getId')
-            ->willReturn(self::WEBSITE_ID);
-        $this->store = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class)
-            ->disableOriginalConstructor()
-            ->getMockForAbstractClass();
-        $this->store->expects($this->any())
-            ->method('getId')
-            ->willReturn(self::STORE_ID);
+        $this->website->expects($this->never())->method('getId');
+
         $this->storeManager = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class)
             ->disableOriginalConstructor()
             ->getMock();
-        $this->storeManager->expects($this->any())
-            ->method('getWebsite')
-            ->willReturn($this->website);
-        $this->storeManager->expects($this->any())
-            ->method('getStore')
-            ->willReturn($this->store);
-        $this->attributeCollection = $this->getMockBuilder(
-            \Magento\Catalog\Model\ResourceModel\Product\Attribute\Collection::class
-        )
+        $this->storeManager->expects($this->never())->method('getWebsite');
+        $this->storeManager->expects($this->never())->method('getStore');
+
+        $this->attributeCollection = $this->getMockBuilder(Collection::class)
             ->disableOriginalConstructor()
             ->getMock();
-        $attributeCollectionFactory = $this->getMockBuilder(
-            \Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory::class
-        )
+        $attributeCollectionFactory = $this->getMockBuilder(CollectionFactory::class)
             ->setMethods(['create'])
             ->disableOriginalConstructor()
             ->getMock();
         $attributeCollectionFactory->expects($this->never())
             ->method('create');
+
         $this->eavConfig = $this->getMockBuilder(\Magento\Eav\Model\Config::class)
             ->setMethods(['getAttribute'])
             ->disableOriginalConstructor()
             ->getMock();
+
+        $this->aliasResolver = $this->getMockBuilder(AliasResolver::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $this->aliasResolver->expects($this->any())
+            ->method('getAlias')
+            ->willReturnCallback(function (FilterInterface $filter) {
+                return $filter->getField() . '_alias';
+            });
+
         $this->target = $objectManager->getObject(
             \Magento\CatalogSearch\Model\Search\TableMapper::class,
             [
@@ -142,6 +126,7 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase
                 'storeManager' => $this->storeManager,
                 'attributeCollectionFactory' => $attributeCollectionFactory,
                 'eavConfig' => $this->eavConfig,
+                'aliasResolver' => $this->aliasResolver,
             ]
         );
 
@@ -160,14 +145,7 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase
         $this->request->expects($this->once())
             ->method('getQuery')
             ->willReturn($query);
-        $this->select->expects($this->once())
-            ->method('joinInner')
-            ->with(
-                ['price_index' => 'prefix_catalog_product_index_price'],
-                'search_index.entity_id = price_index.entity_id AND price_index.website_id = ' . self::WEBSITE_ID,
-                []
-            )
-            ->willReturnSelf();
+
         $select = $this->target->addTables($this->select, $this->request);
         $this->assertEquals($this->select, $select, 'Returned results isn\'t equal to passed select');
     }
@@ -176,18 +154,10 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase
     {
         $priceFilter = $this->createRangeFilter('static');
         $query = $this->createFilterQuery($priceFilter);
-        $this->createAttributeMock('static', 'static', 'backend_table', 0, 'select');
         $this->request->expects($this->once())
             ->method('getQuery')
             ->willReturn($query);
-        $this->select->expects($this->once())
-            ->method('joinInner')
-            ->with(
-                ['static_filter' => 'backend_table'],
-                'search_index.entity_id = static_filter.entity_id',
-                null
-            )
-            ->willReturnSelf();
+
         $select = $this->target->addTables($this->select, $this->request);
         $this->assertEquals($this->select, $select, 'Returned results isn\'t equal to passed select');
     }
@@ -199,46 +169,25 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase
         $this->request->expects($this->once())
             ->method('getQuery')
             ->willReturn($query);
-        $this->select->expects($this->once())
-            ->method('joinInner')
-            ->with(
-                ['category_ids_index' => 'prefix_catalog_category_product_index'],
-                'search_index.entity_id = category_ids_index.product_id',
-                []
-            )
-            ->willReturnSelf();
+
         $select = $this->target->addTables($this->select, $this->request);
         $this->assertEquals($this->select, $select, 'Returned results isn\'t equal to passed select');
     }
 
     public function testAddTermFilter()
     {
-        $this->createAttributeMock('color', null, null, 132, 'select', 0);
         $categoryIdsFilter = $this->createTermFilter('color');
         $query = $this->createFilterQuery($categoryIdsFilter);
         $this->request->expects($this->once())
             ->method('getQuery')
             ->willReturn($query);
-        $this->select->expects($this->once())
-            ->method('joinLeft')
-            ->with(
-                ['color_filter' => 'prefix_catalog_product_index_eav'],
-                'search_index.entity_id = color_filter.entity_id'
-                . ' AND color_filter.attribute_id = 132'
-                . ' AND color_filter.store_id = 2514',
-                []
-            )
-            ->willReturnSelf();
+
         $select = $this->target->addTables($this->select, $this->request);
         $this->assertEquals($this->select, $select, 'Returned results isn\'t equal to passed select');
     }
 
     public function testAddBoolQueryWithTermFiltersInside()
     {
-        $this->createAttributeMock('must1', null, null, 101, 'select', 0);
-        $this->createAttributeMock('should1', null, null, 102, 'select', 1);
-        $this->createAttributeMock('mustNot1', null, null, 103, 'select', 2);
-
         $query = $this->createBoolQuery(
             [
                 $this->createFilterQuery($this->createTermFilter('must1')),
@@ -253,45 +202,13 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase
         $this->request->expects($this->once())
             ->method('getQuery')
             ->willReturn($query);
-        $this->select->expects($this->at(0))
-            ->method('joinLeft')
-            ->with(
-                ['must1_filter' => 'prefix_catalog_product_index_eav'],
-                'search_index.entity_id = must1_filter.entity_id'
-                . ' AND must1_filter.attribute_id = 101'
-                . ' AND must1_filter.store_id = 2514',
-                []
-            )
-            ->willReturnSelf();
-        $this->select->expects($this->at(1))
-            ->method('joinLeft')
-            ->with(
-                ['should1_filter' => 'prefix_catalog_product_index_eav'],
-                'search_index.entity_id = should1_filter.entity_id'
-                . ' AND should1_filter.attribute_id = 102'
-                . ' AND should1_filter.store_id = 2514',
-                []
-            )
-            ->willReturnSelf();
-        $this->select->expects($this->at(2))
-            ->method('joinLeft')
-            ->with(
-                ['mustNot1_filter' => 'prefix_catalog_product_index_eav'],
-                'search_index.entity_id = mustNot1_filter.entity_id'
-                . ' AND mustNot1_filter.attribute_id = 103'
-                . ' AND mustNot1_filter.store_id = 2514',
-                []
-            )
-            ->willReturnSelf();
+
         $select = $this->target->addTables($this->select, $this->request);
         $this->assertEquals($this->select, $select, 'Returned results isn\'t equal to passed select');
     }
 
     public function testAddBoolQueryWithTermAndPriceFiltersInside()
     {
-        $this->createAttributeMock('must1', null, null, 101, 'select', 0);
-        $this->createAttributeMock('should1', null, null, 102, 'select', 1);
-        $this->createAttributeMock('mustNot1', null, null, 103, 'select', 2);
         $query = $this->createBoolQuery(
             [
                 $this->createFilterQuery($this->createTermFilter('must1')),
@@ -307,53 +224,13 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase
         $this->request->expects($this->once())
             ->method('getQuery')
             ->willReturn($query);
-        $this->select->expects($this->at(0))
-            ->method('joinLeft')
-            ->with(
-                ['must1_filter' => 'prefix_catalog_product_index_eav'],
-                'search_index.entity_id = must1_filter.entity_id'
-                . ' AND must1_filter.attribute_id = 101'
-                . ' AND must1_filter.store_id = 2514',
-                []
-            )
-            ->willReturnSelf();
-        $this->select->expects($this->at(1))
-            ->method('joinInner')
-            ->with(
-                ['price_index' => 'prefix_catalog_product_index_price'],
-                'search_index.entity_id = price_index.entity_id AND price_index.website_id = ' . self::WEBSITE_ID,
-                []
-            )
-            ->willReturnSelf();
-        $this->select->expects($this->at(2))
-            ->method('joinLeft')
-            ->with(
-                ['should1_filter' => 'prefix_catalog_product_index_eav'],
-                'search_index.entity_id = should1_filter.entity_id'
-                . ' AND should1_filter.attribute_id = 102'
-                . ' AND should1_filter.store_id = 2514',
-                []
-            )
-            ->willReturnSelf();
-        $this->select->expects($this->at(3))
-            ->method('joinLeft')
-            ->with(
-                ['mustNot1_filter' => 'prefix_catalog_product_index_eav'],
-                'search_index.entity_id = mustNot1_filter.entity_id'
-                . ' AND mustNot1_filter.attribute_id = 103'
-                . ' AND mustNot1_filter.store_id = 2514',
-                []
-            )
-            ->willReturnSelf();
+
         $select = $this->target->addTables($this->select, $this->request);
         $this->assertEquals($this->select, $select, 'Returned results isn\'t equal to passed select');
     }
 
     public function testAddBoolFilterWithTermFiltersInside()
     {
-        $this->createAttributeMock('must1', null, null, 101, 'select', 0);
-        $this->createAttributeMock('should1', null, null, 102, 'select', 1);
-        $this->createAttributeMock('mustNot1', null, null, 103, 'select', 2);
         $query = $this->createFilterQuery(
             $this->createBoolFilter(
                 [
@@ -370,45 +247,13 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase
         $this->request->expects($this->once())
             ->method('getQuery')
             ->willReturn($query);
-        $this->select->expects($this->at(0))
-            ->method('joinLeft')
-            ->with(
-                ['must1_filter' => 'prefix_catalog_product_index_eav'],
-                'search_index.entity_id = must1_filter.entity_id'
-                . ' AND must1_filter.attribute_id = 101'
-                . ' AND must1_filter.store_id = 2514',
-                []
-            )
-            ->willReturnSelf();
-        $this->select->expects($this->at(1))
-            ->method('joinLeft')
-            ->with(
-                ['should1_filter' => 'prefix_catalog_product_index_eav'],
-                'search_index.entity_id = should1_filter.entity_id'
-                . ' AND should1_filter.attribute_id = 102'
-                . ' AND should1_filter.store_id = 2514',
-                []
-            )
-            ->willReturnSelf();
-        $this->select->expects($this->at(2))
-            ->method('joinLeft')
-            ->with(
-                ['mustNot1_filter' => 'prefix_catalog_product_index_eav'],
-                'search_index.entity_id = mustNot1_filter.entity_id'
-                . ' AND mustNot1_filter.attribute_id = 103'
-                . ' AND mustNot1_filter.store_id = 2514',
-                []
-            )
-            ->willReturnSelf();
+
         $select = $this->target->addTables($this->select, $this->request);
         $this->assertEquals($this->select, $select, 'Returned results isn\'t equal to passed select');
     }
 
     public function testAddBoolFilterWithBoolFiltersInside()
     {
-        $this->createAttributeMock('must1', null, null, 101, 'select', 0);
-        $this->createAttributeMock('should1', null, null, 102, 'select', 1);
-        $this->createAttributeMock('mustNot1', null, null, 103, 'select', 2);
         $query = $this->createFilterQuery(
             $this->createBoolFilter(
                 [
@@ -425,36 +270,7 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase
         $this->request->expects($this->once())
             ->method('getQuery')
             ->willReturn($query);
-        $this->select->expects($this->at(0))
-            ->method('joinLeft')
-            ->with(
-                ['must1_filter' => 'prefix_catalog_product_index_eav'],
-                'search_index.entity_id = must1_filter.entity_id'
-                . ' AND must1_filter.attribute_id = 101'
-                . ' AND must1_filter.store_id = 2514',
-                []
-            )
-            ->willReturnSelf();
-        $this->select->expects($this->at(1))
-            ->method('joinLeft')
-            ->with(
-                ['should1_filter' => 'prefix_catalog_product_index_eav'],
-                'search_index.entity_id = should1_filter.entity_id'
-                . ' AND should1_filter.attribute_id = 102'
-                . ' AND should1_filter.store_id = 2514',
-                []
-            )
-            ->willReturnSelf();
-        $this->select->expects($this->at(2))
-            ->method('joinLeft')
-            ->with(
-                ['mustNot1_filter' => 'prefix_catalog_product_index_eav'],
-                'search_index.entity_id = mustNot1_filter.entity_id'
-                . ' AND mustNot1_filter.attribute_id = 103'
-                . ' AND mustNot1_filter.store_id = 2514',
-                []
-            )
-            ->willReturnSelf();
+
         $select = $this->target->addTables($this->select, $this->request);
         $this->assertEquals($this->select, $select, 'Returned results isn\'t equal to passed select');
     }
@@ -472,6 +288,7 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase
             ->willReturn(QueryInterface::TYPE_FILTER);
         $query->method('getReference')
             ->willReturn($filter);
+
         return $query;
     }
 
@@ -495,6 +312,7 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase
             ->willReturn($should);
         $query->method('getMustNot')
             ->willReturn($mustNot);
+
         return $query;
     }
 
@@ -518,6 +336,7 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase
             ->willReturn($should);
         $query->method('getMustNot')
             ->willReturn($mustNot);
+
         return $query;
     }
 
@@ -532,6 +351,7 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase
             FilterInterface::TYPE_RANGE,
             $field
         );
+
         return $filter;
     }
 
@@ -546,6 +366,7 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase
             FilterInterface::TYPE_TERM,
             $field
         );
+
         return $filter;
     }
 
@@ -564,40 +385,7 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase
             ->willReturn($type);
         $filter->method('getField')
             ->willReturn($field);
-        return $filter;
-    }
 
-    /**
-     * @param string $code
-     * @param string $backendType
-     * @param string $backendTable
-     * @param int $attributeId
-     * @param string $frontendInput
-     * @param int $positionInCollection
-     */
-    private function createAttributeMock(
-        $code,
-        $backendType = null,
-        $backendTable = null,
-        $attributeId = 120,
-        $frontendInput = 'select',
-        $positionInCollection = 0
-    ) {
-        $attribute = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class)
-            ->setMethods(['getBackendType', 'getBackendTable', 'getId', 'getFrontendInput'])
-            ->disableOriginalConstructor()
-            ->getMock();
-        $attribute->method('getId')
-            ->willReturn($attributeId);
-        $attribute->method('getBackendType')
-            ->willReturn($backendType);
-        $attribute->method('getBackendTable')
-            ->willReturn($backendTable);
-        $attribute->method('getFrontendInput')
-            ->willReturn($frontendInput);
-        $this->eavConfig->expects($this->at($positionInCollection))
-            ->method('getAttribute')
-            ->with(\Magento\Catalog\Model\Product::ENTITY, $code)
-            ->willReturn($attribute);
+        return $filter;
     }
 }
diff --git a/app/code/Magento/CatalogSearch/etc/di.xml b/app/code/Magento/CatalogSearch/etc/di.xml
index f62b4e47767a25230c5c2ad8372f99acc6bda2eb..7e9451f9b83e7ada30455509b8fd686a0c251705 100644
--- a/app/code/Magento/CatalogSearch/etc/di.xml
+++ b/app/code/Magento/CatalogSearch/etc/di.xml
@@ -11,6 +11,7 @@
     <preference for="Magento\Framework\Search\Adapter\Mysql\Filter\PreprocessorInterface" type="Magento\CatalogSearch\Model\Adapter\Mysql\Filter\Preprocessor" />
     <preference for="Magento\Framework\Search\Dynamic\DataProviderInterface" type="Magento\CatalogSearch\Model\Adapter\Mysql\Dynamic\DataProvider" />
     <preference for="Magento\Framework\Search\Adapter\OptionsInterface" type="Magento\CatalogSearch\Model\Adapter\Options" />
+    <preference for="Magento\CatalogSearch\Model\Search\FilterMapper\FilterStrategyInterface" type="Magento\CatalogSearch\Model\Search\FilterMapper\FilterContext"/>
     <type name="Magento\CatalogSearch\Model\Indexer\IndexerHandlerFactory">
         <arguments>
             <argument name="configPath" xsi:type="const">Magento\CatalogSearch\Model\ResourceModel\EngineInterface::CONFIG_ENGINE_PATH</argument>
diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php
index 4e6fa8ae5210d77726b393047a7433ca03d69c7d..fbb69798ff94fbdf8caafc7486e564da662cf655 100644
--- a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php
+++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php
@@ -8,6 +8,7 @@
 namespace Magento\ConfigurableProduct\Model\ResourceModel\Product\Indexer\Price;
 
 use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Catalog\Model\Product\Attribute\Source\Status as ProductStatus;
 
 class Configurable extends \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice
 {
@@ -166,6 +167,9 @@ class Configurable extends \Magento\Catalog\Model\ResourceModel\Product\Indexer\
         $this->_prepareConfigurableOptionAggregateTable();
         $this->_prepareConfigurableOptionPriceTable();
 
+        $statusAttribute = $this->_getAttribute(ProductInterface::STATUS);
+        $linkField = $metadata->getLinkField();
+
         $select = $connection->select()->from(
             ['i' => $this->_getDefaultFinalPriceTable()],
             []
@@ -175,7 +179,7 @@ class Configurable extends \Magento\Catalog\Model\ResourceModel\Product\Indexer\
             ['parent_id' => 'e.entity_id']
         )->join(
             ['l' => $this->getTable('catalog_product_super_link')],
-            'l.parent_id = e.' . $metadata->getLinkField(),
+            'l.parent_id = e.' . $linkField,
             ['product_id']
         )->columns(
             ['customer_group_id', 'website_id'],
@@ -186,11 +190,21 @@ class Configurable extends \Magento\Catalog\Model\ResourceModel\Product\Indexer\
             []
         )->where(
             'le.required_options=0'
+        )->join(
+            ['product_status' => $this->getTable($statusAttribute->getBackend()->getTable())],
+            sprintf(
+                'le.%1$s = product_status.%1$s AND product_status.attribute_id = %2$s',
+                $linkField,
+                $statusAttribute->getAttributeId()
+            ),
+            []
+        )->where(
+            'product_status.value=' . ProductStatus::STATUS_ENABLED
         )->group(
-            ['parent_id', 'i.customer_group_id', 'i.website_id', 'l.product_id']
+            ['e.entity_id', 'i.customer_group_id', 'i.website_id', 'l.product_id']
         );
-        $priceColumn = $this->_addAttributeToSelect($select, 'price', 'l.product_id', 0, null, true);
-        $tierPriceColumn = $connection->getCheckSql("MIN(i.tier_price) IS NOT NULL", "i.tier_price", 'NULL');
+        $priceColumn = $this->_addAttributeToSelect($select, 'price', 'le.' . $linkField, 0, null, true);
+        $tierPriceColumn = $connection->getIfNullSql('MIN(i.tier_price)', 'NULL');
 
         $select->columns(
             ['price' => $priceColumn, 'tier_price' => $tierPriceColumn]
diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php
index 77bf7af2a4031ac71488b7644225a6c13dc520f2..59e12d72ded6c6cd4de5aaf323acd5f1ffbbd122 100644
--- a/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php
+++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php
@@ -84,7 +84,7 @@ class Save extends \Magento\Customer\Controller\Adminhtml\Index
         $result = $metadataForm->compactData($formData);
 
         // Re-initialize additional attributes
-        $formData = array_replace($formData, $result);
+        $formData = array_replace($result, $formData);
 
         // Unset unused attributes
         $formAttributes = $metadataForm->getAttributes();
diff --git a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Save.php b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Save.php
index 57477b5ce0623f57bfc048839cc8bdaee8d40593..efa45512acc8320cd878c97b92c5912c7713f882 100644
--- a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Save.php
+++ b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Save.php
@@ -26,8 +26,13 @@ class Save extends \Magento\SalesRule\Controller\Adminhtml\Promo\Quote
                     ['request' => $this->getRequest()]
                 );
                 $data = $this->getRequest()->getPostValue();
+
+                $filterValues = ['from_date' => $this->_dateFilter];
+                if ($this->getRequest()->getParam('to_date')) {
+                    $filterValues['to_date'] = $this->_dateFilter;
+                }
                 $inputFilter = new \Zend_Filter_Input(
-                    ['from_date' => $this->_dateFilter, 'to_date' => $this->_dateFilter],
+                    $filterValues,
                     [],
                     $data
                 );
diff --git a/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows-grid.js b/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows-grid.js
index de10c065d123bf773f9fba66135b2308a2abe3e9..518f09fa73ba6c6771654c7c9fc3f3ce07be79be 100644
--- a/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows-grid.js
+++ b/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows-grid.js
@@ -52,13 +52,15 @@ define([
                 obj;
 
             if (this.recordData().length && !this.update) {
-                this.recordData.each(function (recordData) {
+                _.each(this.recordData(), function (recordData) {
                     obj = {};
                     obj[this.map[this.identificationProperty]] = recordData[this.identificationProperty];
                     insertData.push(obj);
                 }, this);
 
-                this.source.set(this.dataProvider, insertData);
+                if (insertData.length) {
+                    this.source.set(this.dataProvider, insertData);
+                }
             }
         },
 
@@ -178,7 +180,7 @@ define([
                 tmpObj = {};
 
             if (data.length !== this.relatedData.length) {
-                data.forEach(function (obj) {
+                _.each(data, function (obj) {
                     tmpObj[this.identificationDRProperty] = obj[this.identificationDRProperty];
 
                     if (!_.findWhere(this.relatedData, tmpObj)) {
@@ -193,7 +195,7 @@ define([
         /**
          * Processing insert data
          *
-         * @param {Array} data
+         * @param {Object} data
          */
         processingInsertData: function (data) {
             var changes,
diff --git a/app/code/Magento/Ui/view/base/web/templates/dynamic-rows/templates/collapsible.html b/app/code/Magento/Ui/view/base/web/templates/dynamic-rows/templates/collapsible.html
index f3319a05525f2f55bc4ba43ec5ce9bc1e4e2c300..d1ec1d26df6c56dff099b09aa0eb83232687573e 100644
--- a/app/code/Magento/Ui/view/base/web/templates/dynamic-rows/templates/collapsible.html
+++ b/app/code/Magento/Ui/view/base/web/templates/dynamic-rows/templates/collapsible.html
@@ -43,6 +43,7 @@
                             </div>
 
                             <button class="action-delete"
+                                    data-index="delete_button"
                                     type="button"
                                     title="'Delete'"
                                     click="function(){
diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/Block/Adminhtml/Catalog/Product/Edit/Section/Bundle.php b/dev/tests/functional/tests/app/Magento/Bundle/Test/Block/Adminhtml/Catalog/Product/Edit/Section/Bundle.php
index e94ec38d010f3d519e8ab2e06c5fdd21dccd544c..5cf84cf6764270708bd30327f2596253f510370d 100644
--- a/dev/tests/functional/tests/app/Magento/Bundle/Test/Block/Adminhtml/Catalog/Product/Edit/Section/Bundle.php
+++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/Block/Adminhtml/Catalog/Product/Edit/Section/Bundle.php
@@ -8,7 +8,6 @@ namespace Magento\Bundle\Test\Block\Adminhtml\Catalog\Product\Edit\Section;
 
 use Magento\Mtf\Client\Element\SimpleElement;
 use Magento\Bundle\Test\Block\Adminhtml\Catalog\Product\Edit\Section\Bundle\Option;
-use Magento\Mtf\Client\Element;
 use Magento\Mtf\Client\ElementInterface;
 use Magento\Mtf\Client\Locator;
 use Magento\Ui\Test\Block\Adminhtml\Section;
@@ -53,6 +52,13 @@ class Bundle extends Section
      */
     protected $bundleOptionRow = './tr[%d]';
 
+    /**
+     * Selector for trash can button in bundle option row.
+     *
+     * @var string
+     */
+    protected $deleteOption = './tr[%d]//*[@data-index="delete_button"]';
+
     /**
      * Get bundle options block.
      *
@@ -83,17 +89,49 @@ class Bundle extends Section
         if (!isset($fields['bundle_selections'])) {
             return $this;
         }
+
+        $context = $this->_rootElement->find($this->bundleOptions, Locator::SELECTOR_XPATH);
+
+        if (isset($fields['bundle_selections']['value']['bundle_options'])) {
+            foreach ($fields['bundle_selections']['value']['bundle_options'] as $key => $bundleOption) {
+                $count = $key + 1;
+                $itemOption = $context->find(sprintf($this->openOption, $count), Locator::SELECTOR_XPATH);
+                $isContent = $context->find(sprintf($this->optionContent, $count), Locator::SELECTOR_XPATH)
+                    ->isVisible();
+                if ($itemOption->isVisible() && !$isContent) {
+                    $itemOption->click();
+                } elseif (!$itemOption->isVisible()) {
+                    $this->_rootElement->find($this->addNewOption)->click();
+                }
+                $this->getBundleOptionBlock($count, $context)->fillOption($bundleOption);
+            }
+        }
+
+        if (isset($fields['bundle_selections']['value']['bundle_options_delete'])) {
+            $this->deleteFieldsData($fields['bundle_selections']['value']['bundle_options_delete']);
+        }
+
+        return $this;
+    }
+
+    /**
+     * Delete some bundle options.
+     *
+     * @param array $fields
+     * @return $this
+     */
+    public function deleteFieldsData(array $fields)
+    {
         $context = $this->_rootElement->find($this->bundleOptions, Locator::SELECTOR_XPATH);
-        foreach ($fields['bundle_selections']['value']['bundle_options'] as $key => $bundleOption) {
-            $count = $key + 1;
-            $itemOption = $context->find(sprintf($this->openOption, $count), Locator::SELECTOR_XPATH);
-            $isContent = $context->find(sprintf($this->optionContent, $count), Locator::SELECTOR_XPATH)->isVisible();
-            if ($itemOption->isVisible() && !$isContent) {
-                $itemOption->click();
-            } elseif (!$itemOption->isVisible()) {
-                $this->_rootElement->find($this->addNewOption)->click();
+        foreach (array_keys($fields) as $key) {
+            $bundleOptionIndex = $key + 1;
+            $deleteOption = $context->find(
+                sprintf($this->deleteOption, $bundleOptionIndex),
+                Locator::SELECTOR_XPATH
+            );
+            if ($deleteOption->isVisible()) {
+                $deleteOption->click();
             }
-            $this->getBundleOptionBlock($count, $context)->fillOption($bundleOption);
         }
         return $this;
     }
diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertBundleOptionsDeleted.php b/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertBundleOptionsDeleted.php
new file mode 100644
index 0000000000000000000000000000000000000000..69e694abd261ab4ca4e4102f542b72f612552d5b
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertBundleOptionsDeleted.php
@@ -0,0 +1,78 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Bundle\Test\Constraint;
+
+use Magento\Bundle\Test\Fixture\BundleProduct;
+use Magento\Mtf\Constraint\AbstractConstraint;
+use Magento\Catalog\Test\Page\Adminhtml\CatalogProductEdit;
+use Magento\Catalog\Test\Page\Adminhtml\CatalogProductIndex;
+use Magento\Mtf\Fixture\FixtureInterface;
+
+/**
+ * Assert bundle product form.
+ */
+class AssertBundleOptionsDeleted extends AbstractConstraint
+{
+    /**
+     * Assert that displayed price view for bundle product on product page equals passed from fixture.
+     * @param BundleProduct $product
+     * @param BundleProduct $originalProduct
+     * @param CatalogProductIndex $productGrid
+     * @param CatalogProductEdit $productPage
+     * @return void
+     */
+    public function processAssert(
+        BundleProduct $product,
+        BundleProduct $originalProduct,
+        CatalogProductIndex $productGrid,
+        CatalogProductEdit $productPage
+    ) {
+        $filter = ['sku' => $product->getSku()];
+        $productGrid->open();
+        $productGrid->getProductGrid()->searchAndOpen($filter);
+
+        $productData = $product->getData()['bundle_selections']['bundle_options'];
+        $originalProductData = $originalProduct->getData()['bundle_selections']['bundle_options'];
+        $formData = $productPage->getProductForm()->getData($product)['bundle_selections'];
+
+        $productDataLength = count($productData);
+        $formDataLength = count($productData);
+        \PHPUnit_Framework_Assert::assertEquals($productDataLength, $formDataLength);
+
+        foreach ($productData as $index => $option) {
+            $productAssociatedDataLength = count($option['assigned_products']);
+            $formAssociatedDataLength = count($formData[$index]['assigned_products']);
+            \PHPUnit_Framework_Assert::assertEquals($productAssociatedDataLength, $formAssociatedDataLength);
+
+            foreach ($option['assigned_products'] as $productIndex => $associatedProduct) {
+                $associatedProduct['data']['getProductName'] =
+                    $originalProductData[$index+1]['assigned_products'][$productIndex]['search_data']['name'];
+                $associatedProduct = $associatedProduct['data'];
+                $errorAssociatedProducts = array_diff(
+                    $associatedProduct,
+                    $formData[$index]['assigned_products'][$productIndex]
+                );
+                \PHPUnit_Framework_Assert::assertCount(0, $errorAssociatedProducts);
+            }
+
+            unset($option['assigned_products']);
+            unset($formData[$index]['assigned_products']);
+            $errorFields = array_diff($option, $formData[$index]);
+            \PHPUnit_Framework_Assert::assertCount(0, $errorFields);
+        }
+    }
+
+    /**
+     * Returns a string representation of the object
+     *
+     * @return string
+     */
+    public function toString()
+    {
+        return 'Bundle options were not deleted correctly. There is difference with expected options';
+    }
+}
diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/Repository/BundleProduct.xml b/dev/tests/functional/tests/app/Magento/Bundle/Test/Repository/BundleProduct.xml
index 2b5e9a437b32bdec615fd9e2a928b3784ba68fb4..6a507a7a99b437a793b4096f8f10d9e33ccb825a 100644
--- a/dev/tests/functional/tests/app/Magento/Bundle/Test/Repository/BundleProduct.xml
+++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/Repository/BundleProduct.xml
@@ -225,5 +225,27 @@
                 <item name="dataset" xsi:type="string">bundle_fixed_100_dollar</item>
             </field>
         </dataset>
+
+        <dataset name="with_3_bundle_options">
+            <field name="name" xsi:type="string">Bundle with 3 options %isolation%</field>
+            <field name="url_key" xsi:type="string">with_3_bundle_options-%isolation%</field>
+            <field name="sku" xsi:type="string">sku_with_3_bundle_options_%isolation%</field>
+            <field name="sku_type" xsi:type="string">No</field>
+            <field name="price_type" xsi:type="string">No</field>
+            <field name="price" xsi:type="array">
+                <item name="value" xsi:type="string">100</item>
+            </field>
+            <field name="tax_class_id" xsi:type="array">
+                <item name="dataset" xsi:type="string">taxable_goods</item>
+            </field>
+            <field name="website_ids" xsi:type="array">
+                <item name="0" xsi:type="array">
+                    <item name="dataset" xsi:type="string">default</item>
+                </item>
+            </field>
+            <field name="bundle_selections" xsi:type="array">
+                <item name="dataset" xsi:type="string">with_3_options</item>
+            </field>
+        </dataset>
     </repository>
 </config>
diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/Repository/BundleProduct/BundleSelections.xml b/dev/tests/functional/tests/app/Magento/Bundle/Test/Repository/BundleProduct/BundleSelections.xml
index 3e9b055c48ca5a1bfa5df3410ec48ef708a71fa7..7131aab3cffcbb69ca84f1078540a2dbada2ae28 100644
--- a/dev/tests/functional/tests/app/Magento/Bundle/Test/Repository/BundleProduct/BundleSelections.xml
+++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/Repository/BundleProduct/BundleSelections.xml
@@ -665,5 +665,170 @@
                 </item>
             </field>
         </dataset>
+
+        <dataset name="with_3_options">
+            <field name="bundle_options" xsi:type="array">
+                <item name="0" xsi:type="array">
+                    <item name="title" xsi:type="string">Option 1</item>
+                    <item name="type" xsi:type="string">Drop-down</item>
+                    <item name="required" xsi:type="string">No</item>
+                    <item name="assigned_products" xsi:type="array">
+                        <item name="0" xsi:type="array">
+                            <item name="search_data" xsi:type="array">
+                                <item name="name" xsi:type="string">%product_name%</item>
+                            </item>
+                            <item name="data" xsi:type="array">
+                                <item name="selection_qty" xsi:type="string">1</item>
+                                <item name="selection_price_value" xsi:type="string">1</item>
+                                <item name="selection_price_type" xsi:type="string">Fixed</item>
+                            </item>
+                        </item>
+                    </item>
+                </item>
+                <item name="1" xsi:type="array">
+                    <item name="title" xsi:type="string">Option 2</item>
+                    <item name="type" xsi:type="string">Radio Buttons</item>
+                    <item name="required" xsi:type="string">No</item>
+                    <item name="assigned_products" xsi:type="array">
+                        <item name="0" xsi:type="array">
+                            <item name="search_data" xsi:type="array">
+                                <item name="name" xsi:type="string">%product_name%</item>
+                            </item>
+                            <item name="data" xsi:type="array">
+                                <item name="selection_qty" xsi:type="string">20</item>
+                                <item name="selection_price_value" xsi:type="string">20</item>
+                                <item name="selection_price_type" xsi:type="string">Fixed</item>
+                            </item>
+                        </item>
+                        <item name="1" xsi:type="array">
+                            <item name="search_data" xsi:type="array">
+                                <item name="name" xsi:type="string">%product_name%</item>
+                            </item>
+                            <item name="data" xsi:type="array">
+                                <item name="selection_qty" xsi:type="string">21</item>
+                                <item name="selection_price_value" xsi:type="string">21</item>
+                                <item name="selection_price_type" xsi:type="string">Fixed</item>
+                            </item>
+                        </item>
+                        <item name="2" xsi:type="array">
+                            <item name="search_data" xsi:type="array">
+                                <item name="name" xsi:type="string">%product_name%</item>
+                            </item>
+                            <item name="data" xsi:type="array">
+                                <item name="selection_qty" xsi:type="string">22</item>
+                                <item name="selection_price_value" xsi:type="string">22</item>
+                                <item name="selection_price_type" xsi:type="string">Fixed</item>
+                            </item>
+                        </item>
+                    </item>
+                </item>
+                <item name="2" xsi:type="array">
+                    <item name="title" xsi:type="string">Option 3</item>
+                    <item name="type" xsi:type="string">Drop-down</item>
+                    <item name="required" xsi:type="string">No</item>
+                    <item name="assigned_products" xsi:type="array">
+                        <item name="0" xsi:type="array">
+                            <item name="search_data" xsi:type="array">
+                                <item name="name" xsi:type="string">%product_name%</item>
+                            </item>
+                            <item name="data" xsi:type="array">
+                                <item name="selection_qty" xsi:type="string">3</item>
+                                <item name="selection_price_value" xsi:type="string">3</item>
+                                <item name="selection_price_type" xsi:type="string">Fixed</item>
+                            </item>
+                        </item>
+                    </item>
+                </item>
+            </field>
+            <field name="products" xsi:type="array">
+                <item name="0" xsi:type="array">
+                    <item name="0" xsi:type="string">catalogProductSimple::default</item>
+                </item>
+                <item name="1" xsi:type="array">
+                    <item name="0" xsi:type="string">catalogProductSimple::default</item>
+                    <item name="1" xsi:type="string">catalogProductSimple::product_100_dollar</item>
+                    <item name="2" xsi:type="string">catalogProductSimple::product_without_category</item>
+                </item>
+                <item name="2" xsi:type="array">
+                    <item name="0" xsi:type="string">catalogProductSimple::default</item>
+                </item>
+            </field>
+        </dataset>
+
+        <dataset name="with_3_options_delete">
+            <field name="bundle_options_delete" xsi:type="array">
+                <item name="0" xsi:type="array">
+                    <item name="title" xsi:type="string">Option 1</item>
+                    <item name="type" xsi:type="string">Drop-down</item>
+                    <item name="required" xsi:type="string">No</item>
+                </item>
+            </field>
+            <field name="bundle_options" xsi:type="array">
+                <item name="0" xsi:type="array">
+                    <item name="title" xsi:type="string">Option 2</item>
+                    <item name="type" xsi:type="string">Radio Buttons</item>
+                    <item name="required" xsi:type="string">No</item>
+                    <item name="assigned_products" xsi:type="array">
+                        <item name="0" xsi:type="array">
+                            <item name="search_data" xsi:type="array">
+                                <item name="name" xsi:type="string">%product_name%</item>
+                            </item>
+                            <item name="data" xsi:type="array">
+                                <item name="selection_qty" xsi:type="string">20</item>
+                                <item name="selection_price_value" xsi:type="string">20</item>
+                                <item name="selection_price_type" xsi:type="string">Fixed</item>
+                            </item>
+                        </item>
+                        <item name="1" xsi:type="array">
+                            <item name="search_data" xsi:type="array">
+                                <item name="name" xsi:type="string">%product_name%</item>
+                            </item>
+                            <item name="data" xsi:type="array">
+                                <item name="selection_qty" xsi:type="string">21</item>
+                                <item name="selection_price_value" xsi:type="string">21</item>
+                                <item name="selection_price_type" xsi:type="string">Fixed</item>
+                            </item>
+                        </item>
+                        <item name="2" xsi:type="array">
+                            <item name="search_data" xsi:type="array">
+                                <item name="name" xsi:type="string">%product_name%</item>
+                            </item>
+                            <item name="data" xsi:type="array">
+                                <item name="selection_qty" xsi:type="string">22</item>
+                                <item name="selection_price_value" xsi:type="string">22</item>
+                                <item name="selection_price_type" xsi:type="string">Fixed</item>
+                            </item>
+                        </item>
+                    </item>
+                </item>
+                <item name="1" xsi:type="array">
+                    <item name="title" xsi:type="string">Option 3</item>
+                    <item name="type" xsi:type="string">Drop-down</item>
+                    <item name="required" xsi:type="string">No</item>
+                    <item name="assigned_products" xsi:type="array">
+                        <item name="0" xsi:type="array">
+                            <item name="search_data" xsi:type="array">
+                                <item name="name" xsi:type="string">%product_name%</item>
+                            </item>
+                            <item name="data" xsi:type="array">
+                                <item name="selection_qty" xsi:type="string">3</item>
+                                <item name="selection_price_value" xsi:type="string">3</item>
+                                <item name="selection_price_type" xsi:type="string">Fixed</item>
+                            </item>
+                        </item>
+                    </item>
+                </item>
+            </field>
+            <field name="products" xsi:type="array">
+                <item name="0" xsi:type="array">
+                    <item name="0" xsi:type="string">catalogProductSimple::default</item>
+                    <item name="1" xsi:type="string">catalogProductSimple::product_100_dollar</item>
+                    <item name="2" xsi:type="string">catalogProductSimple::product_without_category</item>
+                </item>
+                <item name="1" xsi:type="array">
+                    <item name="0" xsi:type="string">catalogProductSimple::default</item>
+                </item>
+            </field>
+        </dataset>
     </repository>
 </config>
diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/UpdateBundleOptionsTest.php b/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/UpdateBundleOptionsTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..3c17360200f44f2fe8875590578c6960ea7757d0
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/UpdateBundleOptionsTest.php
@@ -0,0 +1,94 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Bundle\Test\TestCase;
+
+use Magento\Bundle\Test\Fixture\BundleProduct;
+use Magento\Catalog\Test\Page\Adminhtml\CatalogProductEdit;
+use Magento\Catalog\Test\Page\Adminhtml\CatalogProductIndex;
+use Magento\Mtf\TestCase\Injectable;
+
+/**
+ * Test Flow:
+ * 1. Login as admin
+ * 2. Navigate to the Products>Inventory>Catalog
+ * 3. Click on Bundle product in the grid to edit it
+ * 4. Fill in some Bundle Options data according to data set
+ * 5. Delete some Bundle Options data according to data set
+ * 6. Save product
+ * 7. Verify Bundle Options in the updated product
+ *
+ * @group Bundle_Product
+ * @ZephyrId MAGETWO-26195
+ */
+class UpdateBundleOptionsTest extends Injectable
+{
+    /* tags */
+    const MVP = 'yes';
+    /* end tags */
+
+    /**
+     * Page product on backend
+     *
+     * @var CatalogProductIndex
+     */
+    protected $catalogProductIndex;
+
+    /**
+     * Edit page on backend
+     *
+     * @var CatalogProductEdit
+     */
+    protected $catalogProductEdit;
+
+    /**
+     * Injection data
+     *
+     * @param CatalogProductIndex $catalogProductIndexNewPage
+     * @param CatalogProductEdit $catalogProductEditPage
+     * @return void
+     */
+    public function __inject(
+        CatalogProductIndex $catalogProductIndexNewPage,
+        CatalogProductEdit $catalogProductEditPage
+    ) {
+        $this->catalogProductIndex = $catalogProductIndexNewPage;
+        $this->catalogProductEdit = $catalogProductEditPage;
+    }
+
+    /**
+     * Test update bundle product
+     *
+     * @param BundleProduct $product
+     * @param BundleProduct $originalProduct
+     * @return void
+     */
+    public function test(BundleProduct $product, BundleProduct $originalProduct)
+    {
+        // Preconditions
+        $originalProduct->persist();
+
+        // Steps
+        $filter = ['sku' => $originalProduct->getSku()];
+
+        $this->catalogProductIndex->open();
+        $this->catalogProductIndex->getProductGrid()->searchAndOpen($filter);
+
+        $form = $this->catalogProductEdit->getProductForm();
+        $form->openSection('bundle');
+        $container = $form->getSection('bundle');
+        $containerFields = $product->getData()['bundle_selections']['bundle_options_delete'];
+        $container->deleteFieldsData($containerFields);
+
+        $form->openSection('product-details');
+        $container = $form->getSection('product-details');
+        $containerFields = $product->getData();
+        unset($containerFields['bundle_selections']);
+        $container->setFieldsData($containerFields);
+
+        $this->catalogProductEdit->getFormPageActions()->save();
+    }
+}
diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/UpdateBundleOptionsTest.xml b/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/UpdateBundleOptionsTest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..795665d66d2bf7d34438381d3390e94c8458d3b7
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/UpdateBundleOptionsTest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+ -->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd">
+    <testCase name="Magento\Bundle\Test\TestCase\UpdateBundleOptionsTest" summary="Update Bundle Product Options" ticketId="MAGETWO-26195">
+        <variation name="UpdateBundleOptionsTestVariation1">
+            <data name="description" xsi:type="string">Update bundle product options</data>
+            <data name="originalProduct/dataset" xsi:type="string">with_3_bundle_options</data>
+            <data name="product/data/name" xsi:type="string">bundle_3_options_%isolation%</data>
+            <data name="product/data/sku" xsi:type="string">bundle_3_options_%isolation%</data>
+            <data name="product/data/bundle_selections/dataset" xsi:type="string">with_3_options_delete</data>
+            <constraint name="Magento\Bundle\Test\Constraint\AssertBundleOptionsDeleted" />
+        </variation>
+    </testCase>
+</config>
\ No newline at end of file
diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Constraint/AssertConfigurableProductPage.php b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Constraint/AssertConfigurableProductPage.php
index 62d6c72efe8c6aa8d0a8ed4aff4594316318c9e3..ad9807b76d6e532fd00e9f22fac7bf4007a80502 100644
--- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Constraint/AssertConfigurableProductPage.php
+++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Constraint/AssertConfigurableProductPage.php
@@ -125,15 +125,23 @@ class AssertConfigurableProductPage extends AssertProductPage
     protected function getLowestConfigurablePrice()
     {
         $price = null;
-        $configurableOptions = $this->product->getConfigurableAttributesData();
-
-        foreach ($configurableOptions['matrix'] as $option) {
-            $price = $price === null ? $option['price'] : $price;
-            if ($price > $option['price']) {
-                $price = $option['price'];
+        $priceDataConfig = $this->product->getDataFieldConfig('price');
+        if (isset($priceDataConfig['source'])) {
+            $priceData = $priceDataConfig['source']->getPriceData();
+            if (isset($priceData['price_from'])) {
+                $price = $priceData['price_from'];
             }
         }
 
+        if (null === $price) {
+            $configurableOptions = $this->product->getConfigurableAttributesData();
+            foreach ($configurableOptions['matrix'] as $option) {
+                $price = $price === null ? $option['price'] : $price;
+                if ($price > $option['price']) {
+                    $price = $option['price'];
+                }
+            }
+        }
         return $price;
     }
 }
diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Repository/ConfigurableProduct/Price.xml b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Repository/ConfigurableProduct/Price.xml
index 2e9d708dd42d82ca79d0507b6a698c0c5c897d3b..1b6282b9432c39fd444ca2089f1b8c8aa6bfb876 100644
--- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Repository/ConfigurableProduct/Price.xml
+++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Repository/ConfigurableProduct/Price.xml
@@ -17,5 +17,8 @@
         <dataset name="MAGETWO-12620">
             <field name="category_price" xsi:type="string">11</field>
         </dataset>
+        <dataset name="from-9">
+            <field name="price_from" xsi:type="string">9</field>
+        </dataset>
     </repository>
 </config>
diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/CreateConfigurableProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/CreateConfigurableProductEntityTest.xml
index 58435b066261ed28cc7f33ff01fc250940b6eb17..187283da4602fd56c6fb6f2441d11dc5c1ad05bd 100644
--- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/CreateConfigurableProductEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/CreateConfigurableProductEntityTest.xml
@@ -58,6 +58,7 @@
             <data name="product/data/checkout_data/dataset" xsi:type="string">configurable_two_new_options_with_special_price</data>
             <data name="product/data/name" xsi:type="string">Configurable Product %isolation%</data>
             <data name="product/data/sku" xsi:type="string">configurable_sku_%isolation%</data>
+            <data name="product/data/price/dataset" xsi:type="string">from-9</data>
             <data name="product/data/price/value" xsi:type="string">100</data>
             <data name="product/data/special_price" xsi:type="string">9</data>
             <data name="product/data/short_description" xsi:type="string">Configurable short description</data>
diff --git a/dev/tests/functional/tests/app/Magento/SalesRule/Test/Constraint/AssertCartPriceRuleForm.php b/dev/tests/functional/tests/app/Magento/SalesRule/Test/Constraint/AssertCartPriceRuleForm.php
index bfe0bc9eabddfb0cc0f746f79efdad5b6ad0a43b..1f3f0e5a300b46ac9a193666796e52c584b8c944 100644
--- a/dev/tests/functional/tests/app/Magento/SalesRule/Test/Constraint/AssertCartPriceRuleForm.php
+++ b/dev/tests/functional/tests/app/Magento/SalesRule/Test/Constraint/AssertCartPriceRuleForm.php
@@ -25,8 +25,6 @@ class AssertCartPriceRuleForm extends AbstractConstraint
     protected $skippedFields = [
         'conditions_serialized',
         'actions_serialized',
-        'from_date',
-        'to_date',
         'rule_id'
     ];
 
diff --git a/dev/tests/functional/tests/app/Magento/SalesRule/Test/Repository/SalesRule.xml b/dev/tests/functional/tests/app/Magento/SalesRule/Test/Repository/SalesRule.xml
index 13b68aca6c6eb7867286019b3a9609b3b93db2a8..3a35215d841d50780d212084d76d014af8708aa4 100644
--- a/dev/tests/functional/tests/app/Magento/SalesRule/Test/Repository/SalesRule.xml
+++ b/dev/tests/functional/tests/app/Magento/SalesRule/Test/Repository/SalesRule.xml
@@ -114,10 +114,10 @@
             <field name="uses_per_coupon" xsi:type="string">13</field>
             <field name="uses_per_customer" xsi:type="string">63</field>
             <field name="from_date" xsi:type="array">
-                <item name="pattern" xsi:type="string">3/25/2014</item>
+                <item name="pattern" xsi:type="string">03/25/2014</item>
             </field>
             <field name="to_date" xsi:type="array">
-                <item name="pattern" xsi:type="string">6/29/2024</item>
+                <item name="pattern" xsi:type="string">-</item>
             </field>
             <field name="sort_order" xsi:type="string">1</field>
             <field name="is_rss" xsi:type="string">Yes</field>
diff --git a/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenCreatingNewUserTest.php b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenCreatingNewUserTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..4b42430c0b8a81987b78d907f8b4a4c56d6db834
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenCreatingNewUserTest.php
@@ -0,0 +1,134 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Security\Test\TestCase;
+
+
+use Magento\User\Test\Page\Adminhtml\UserEdit;
+use Magento\User\Test\Page\Adminhtml\UserIndex;
+use Magento\Mtf\TestCase\Injectable;
+use Magento\User\Test\Fixture\User;
+use Magento\Backend\Test\Page\AdminAuthLogin;
+
+/**
+ * Preconditions:
+ * 1. Create admin user.
+ * 2. Configure 'Maximum Login Failures to Lockout Account'.
+ *
+ * Steps:
+ * 1. Log in to backend as admin user.
+ * 2. Navigate to System > All Users.
+ * 3. Click on Add New User.
+ * 4. Fill in all data according to data set (password is incorrect).
+ * 5. Perform action 4 specified number of times.
+ * 6. "You have entered an invalid password for current user." appears after each attempt.
+ * 7. Perform all assertions.
+ *
+ * @ZephyrId MAGETWO-49034
+ */
+class LockAdminUserWhenCreatingNewUserTest extends Injectable
+{
+    /* tags */
+    const MVP = 'yes';
+    const SEVERITY = 'S2';
+    /* end tags */
+
+    /**
+     * User grid page
+     *
+     * @var UserIndex
+     */
+    protected $userIndexPage;
+
+    /**
+     * User new/edit page
+     *
+     * @var UserEdit
+     */
+    protected $userEditPage;
+
+    /**
+     * Configuration setting.
+     *
+     * @var string
+     */
+    protected $configData;
+
+    /**
+     * @var AdminAuthLogin page
+     */
+    protected $adminAuthLogin;
+
+    /**
+     * Setup data for test.
+     * @param UserIndex $userIndex
+     * @param UserEdit $userEdit
+     * @param AdminAuthLogin $adminAuthLogin
+     */
+    public function __inject(
+        UserIndex $userIndex,
+        UserEdit $userEdit,
+        AdminAuthLogin $adminAuthLogin
+    ) {
+        $this->userIndexPage = $userIndex;
+        $this->userEditPage = $userEdit;
+        $this->adminAuthLogin = $adminAuthLogin;
+    }
+
+    /**
+     * Runs Lock admin user when creating new user test.
+     *
+     * @param int $attempts
+     * @param User $customAdmin,
+     * @param User $user,
+     * @param string $configData
+     * @return void
+     */
+    public function test(
+        $attempts,
+        User $customAdmin,
+        User $user,
+        $configData
+    ) {
+        $this->configData = $configData;
+
+        // Preconditions
+        $this->objectManager->create(
+            \Magento\Config\Test\TestStep\SetupConfigurationStep::class,
+            ['configData' => $this->configData]
+        )->run();
+        $customAdmin->persist();
+
+        // Steps
+        $this->adminAuthLogin->open();
+        $this->adminAuthLogin->getLoginBlock()->fill($customAdmin);
+        $this->adminAuthLogin->getLoginBlock()->submit();
+        $this->userIndexPage->open();
+        $this->userIndexPage->getPageActions()->addNew();
+        for ($i = 0; $i < $attempts; $i++) {
+            $this->userEditPage->getUserForm()->fill($user);
+            $this->userEditPage->getPageActions()->save();
+        }
+
+        // Reload
+        $this->adminAuthLogin->open();
+        $this->adminAuthLogin->getLoginBlock()->fill($customAdmin);
+        $this->adminAuthLogin->getLoginBlock()->submit();
+    }
+
+    /**
+     * Clean data after running test.
+     *
+     * @return void
+     */
+    public function tearDown()
+    {
+        $this->objectManager->create(
+            \Magento\Config\Test\TestStep\SetupConfigurationStep::class,
+            ['configData' => $this->configData, 'rollback' => true]
+        )->run();
+    }
+}
diff --git a/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenCreatingNewUserTest.xml b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenCreatingNewUserTest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..e36f8b4625dd66eeb5a41f15c74c4bdf6e7eeeea
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenCreatingNewUserTest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+ -->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd">
+    <testCase name="Magento\Security\Test\TestCase\LockAdminUserWhenCreatingNewUserTest" summary="Lock admin user after entering incorrect password while creating new User">
+        <variation name="LockAdminUserWhenCreatingNewUserTestVariation1">
+            <data name="configData" xsi:type="string">user_lockout_failures</data>
+            <data name="tag" xsi:type="string">severity:S2</data>
+            <data name="customAdmin/dataset" xsi:type="string">custom_admin_with_default_role</data>
+            <data name="user/data/username" xsi:type="string">AdminUser%isolation%</data>
+            <data name="user/data/firstname" xsi:type="string">FirstName%isolation%</data>
+            <data name="user/data/lastname" xsi:type="string">LastName%isolation%</data>
+            <data name="user/data/email" xsi:type="string">email%isolation%@example.com</data>
+            <data name="user/data/password" xsi:type="string">123123q</data>
+            <data name="user/data/password_confirmation" xsi:type="string">123123q</data>
+            <data name="user/data/current_password" xsi:type="string">incorrect password</data>
+            <data name="attempts" xsi:type="string">4</data>
+            <constraint name="Magento\Security\Test\Constraint\AssertUserIsLocked" />
+        </variation>
+    </testCase>
+</config>
diff --git a/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingIntegrationTest.php b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingIntegrationTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..4a1c1d0507c91ea3b9a86d254f6f2c01d58a7d2d
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingIntegrationTest.php
@@ -0,0 +1,143 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Security\Test\TestCase;
+
+use Magento\Integration\Test\Fixture\Integration;
+use Magento\User\Test\Fixture\User;
+use Magento\Integration\Test\Page\Adminhtml\IntegrationIndex;
+use Magento\Integration\Test\Page\Adminhtml\IntegrationNew;
+use Magento\Mtf\TestCase\Injectable;
+use Magento\Backend\Test\Page\AdminAuthLogin;
+
+/**
+ * Preconditions:
+ * 1. Create admin user.
+ * 2. Create integration.
+ * 3. Configure 'Maximum Login Failures to Lockout Account'.
+ *
+ * Steps:
+ * 1. Log in to backend as admin user.
+ * 2. Navigate to System > Extensions > Integrations.
+ * 3. Start to edit existing Integration.
+ * 4. Fill in all data according to data set (password is incorrect).
+ * 5. Perform action 4 specified number of times.
+ * 6. "You have entered an invalid password for current user." appears after each attempt.
+ * 7. Perform all assertions.
+ *
+ * @ZephyrId MAGETWO-49039
+ */
+class LockAdminUserWhenEditingIntegrationTest extends Injectable
+{
+    /* tags */
+    const MVP = 'yes';
+    const SEVERITY = 'S2';
+    /* end tags */
+
+    /**
+     * Integration grid page.
+     *
+     * @var IntegrationIndex
+     */
+    protected $integrationIndexPage;
+
+    /**
+     * Integration new page.
+     *
+     * @var IntegrationNew
+     */
+    protected $integrationNewPage;
+
+    /**
+     * Configuration setting.
+     *
+     * @var string
+     */
+    protected $configData;
+
+    /**
+     * @var AdminAuthLogin
+     */
+    protected $adminAuthLogin;
+
+    /**
+     * Preparing pages for test.
+     *
+     * @param IntegrationIndex $integrationIndex
+     * @param IntegrationNew $integrationNew
+     * @param AdminAuthLogin $adminAuthLogin
+     * @return void
+     */
+    public function __inject(
+        IntegrationIndex $integrationIndex,
+        IntegrationNew $integrationNew,
+        AdminAuthLogin $adminAuthLogin
+    ) {
+        $this->integrationIndexPage = $integrationIndex;
+        $this->integrationNewPage = $integrationNew;
+        $this->adminAuthLogin = $adminAuthLogin;
+    }
+
+    /**
+     * Run Lock user when creating new integration test.
+     *
+     * @param Integration $initintegration
+     * @param Integration $integration
+     * @param int $attempts
+     * @param User $customAdmin
+     * @param string $configData
+     * @return void
+     */
+    public function test(
+        Integration $initintegration,
+        Integration $integration,
+        $attempts,
+        User $customAdmin,
+        $configData
+    ) {
+        $this->configData = $configData;
+
+        // Preconditions
+        $this->objectManager->create(
+            \Magento\Config\Test\TestStep\SetupConfigurationStep::class,
+            ['configData' => $this->configData]
+        )->run();
+        $customAdmin->persist();
+        $initintegration->persist();
+
+        // login to backend with new user
+        $this->adminAuthLogin->open();
+        $this->adminAuthLogin->getLoginBlock()->fill($customAdmin);
+        $this->adminAuthLogin->getLoginBlock()->submit();
+
+        // Steps
+        $filter = ['name' => $initintegration->getName()];
+        $this->integrationIndexPage->open();
+        $this->integrationIndexPage->getIntegrationGrid()->searchAndOpen($filter);
+        for ($i = 0; $i < $attempts; $i++) {
+            $this->integrationNewPage->getIntegrationForm()->fill($integration);
+            $this->integrationNewPage->getFormPageActions()->save();
+        }
+
+        // Reload page
+        $this->adminAuthLogin->open();
+        $this->adminAuthLogin->getLoginBlock()->fill($customAdmin);
+        $this->adminAuthLogin->getLoginBlock()->submit();
+    }
+
+    /**
+     * Clean data after running test.
+     *
+     * @return void
+     */
+    public function tearDown()
+    {
+        $this->objectManager->create(
+            \Magento\Config\Test\TestStep\SetupConfigurationStep::class,
+            ['configData' => $this->configData, 'rollback' => true]
+        )->run();
+    }
+}
diff --git a/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingIntegrationTest.xml b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingIntegrationTest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..6efc88d78cd3836f00db79d2c567d3e6e38d1bb1
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingIntegrationTest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+ -->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd">
+    <testCase name="Magento\Security\Test\TestCase\LockAdminUserWhenEditingIntegrationTest" summary="Lock admin user after entering incorrect password while editing integration">
+        <variation name="LockAdminUserWhenCreatingNewIntegrationTestVariation1">
+            <data name="configData" xsi:type="string">user_lockout_failures</data>
+            <data name="tag" xsi:type="string">severity:S2</data>
+            <data name="customAdmin/dataset" xsi:type="string">custom_admin_with_default_role</data>
+            <data name="initintegration/dataset" xsi:type="string">default_active</data>
+            <data name="integration/data/name" xsi:type="string">Integration%isolation%</data>
+            <data name="integration/data/current_password" xsi:type="string">incorrect password</data>
+            <data name="attempts" xsi:type="string">4</data>
+            <constraint name="Magento\Security\Test\Constraint\AssertUserIsLocked" />
+        </variation>
+    </testCase>
+</config>
\ No newline at end of file
diff --git a/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingRoleTest.php b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingRoleTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..bd7c8cef92d8a320b43615983a95ea724cce0d78
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingRoleTest.php
@@ -0,0 +1,140 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Security\Test\TestCase;
+
+use Magento\User\Test\Page\Adminhtml\UserRoleEditRole;
+use Magento\User\Test\Page\Adminhtml\UserRoleIndex;
+use Magento\Mtf\TestCase\Injectable;
+use Magento\User\Test\Fixture\User;
+use Magento\User\Test\Fixture\Role;
+use Magento\Backend\Test\Page\AdminAuthLogin;
+
+/**
+ * Preconditions:
+ * 1. Create new admin user and assign it to new role.
+ * 2. Configure 'Maximum Login Failures to Lockout Account'.
+ *
+ * Steps:
+ * 1. Log in to backend as new created admin user.
+ * 2. Navigate to System > User Roles.
+ * 3. Start editing existing User Role.
+ * 4. Fill in all data according to data set (password is incorrect).
+ * 5. Perform action 4 specified number of times.
+ * 6. Admin account is locked.
+ * 7. Perform all assertions.
+ *
+ * @ZephyrId MAGETWO-49037
+ * @Group Security
+ *
+ */
+class LockAdminUserWhenEditingRoleTest extends Injectable
+{
+    /* tags */
+    const MVP = 'yes';
+    const SEVERITY = 'S2';
+    /* end tags */
+
+    /**
+     * UserRoleIndex page.
+     *
+     * @var UserRoleIndex
+     */
+    protected $userRoleIndex;
+
+    /**
+     * UserRoleEditRole page.
+     *
+     * @var UserRoleEditRole
+     */
+    protected $userRoleEditRole;
+
+    /**
+     * Configuration setting.
+     *
+     * @var string
+     */
+    protected $configData;
+
+    /**
+     * Admin login Page.
+     *
+     * @var AdminAuthLogin
+     */
+    protected $adminAuthLogin;
+
+    /**
+     * Setup data for test.
+     *
+     * @param UserRoleIndex $userRoleIndex
+     * @param UserRoleEditRole $userRoleEditRole
+     * @param AdminAuthLogin $adminAuthLogin
+     * @return void
+     */
+    public function __inject(
+        UserRoleIndex $userRoleIndex,
+        UserRoleEditRole $userRoleEditRole,
+        AdminAuthLogin $adminAuthLogin
+    ) {
+        $this->userRoleIndex = $userRoleIndex;
+        $this->userRoleEditRole = $userRoleEditRole;
+        $this->adminAuthLogin = $adminAuthLogin;
+    }
+
+    /**
+     * Runs Lock admin user when editing existing role test.
+     *
+     * @param Role $role
+     * @param Role $initrole
+     * @param int $attempts
+     * @param User $customAdmin
+     * @param string $configData
+     * @return void
+     */
+    public function test(
+        Role $role,
+        Role $initrole,
+        $attempts,
+        User $customAdmin,
+        $configData
+    ) {
+        $this->configData = $configData;
+        // Preconditions
+        $this->objectManager->create(
+            \Magento\Config\Test\TestStep\SetupConfigurationStep::class,
+            ['configData' => $this->configData]
+        )->run();
+        $customAdmin->persist();
+        $initrole->persist();
+        // Steps login to backend with new user
+        $this->adminAuthLogin->open();
+        $this->adminAuthLogin->getLoginBlock()->fill($customAdmin);
+        $this->adminAuthLogin->getLoginBlock()->submit();
+        $filter = ['rolename' => $initrole->getRolename()];
+        $this->userRoleIndex->open();
+        $this->userRoleIndex->getRoleGrid()->searchAndOpen($filter);
+        for ($i = 0; $i < $attempts; $i++) {
+            $this->userRoleEditRole->getRoleFormTabs()->fill($role);
+            $this->userRoleEditRole->getPageActions()->save();
+        }
+        // Reload
+        $this->adminAuthLogin->open();
+        $this->adminAuthLogin->getLoginBlock()->fill($customAdmin);
+        $this->adminAuthLogin->getLoginBlock()->submit();
+    }
+
+    /**
+     * Clean data after running test.
+     *
+     * @return void
+     */
+    public function tearDown()
+    {
+        $this->objectManager->create(
+            \Magento\Config\Test\TestStep\SetupConfigurationStep::class,
+            ['configData' => $this->configData, 'rollback' => true]
+        )->run();
+    }
+}
diff --git a/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingRoleTest.xml b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingRoleTest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..1d081e5d2dd9af69f90aeed8b787c966238e62de
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingRoleTest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+ -->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd">
+    <testCase name="Magento\Security\Test\TestCase\LockAdminUserWhenEditingRoleTest" summary="Lock admin user after entering incorrect password while editing existing role">
+        <variation name="LockAdminUserWhenEditingUserRoleTestVariation1">
+            <data name="configData" xsi:type="string">user_lockout_failures</data>
+            <data name="tag" xsi:type="string">severity:S2</data>
+            <data name="initrole/dataset" xsi:type="string">default</data>
+            <data name="customAdmin/dataset" xsi:type="string">custom_admin_with_default_role</data>
+            <data name="role/data/rolename" xsi:type="string">NewAdminRole%isolation%</data>
+            <data name="role/data/current_password" xsi:type="string">incorrect password</data>
+            <data name="role/data/resource_access" xsi:type="string">All</data>
+            <data name="attempts" xsi:type="string">4</data>
+            <constraint name="Magento\Security\Test\Constraint\AssertUserIsLocked" />
+        </variation>
+    </testCase>
+</config>
diff --git a/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingUserTest.php b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingUserTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..912ebef9e91054236cc6b85b91c3cd8d973b54e9
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingUserTest.php
@@ -0,0 +1,133 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+namespace Magento\Security\Test\TestCase;
+
+use Magento\User\Test\Page\Adminhtml\UserEdit;
+use Magento\User\Test\Page\Adminhtml\UserIndex;
+use Magento\Backend\Test\Page\AdminAuthLogin;
+use Magento\User\Test\Fixture\User;
+use Magento\Mtf\TestCase\Injectable;
+
+/**
+ * Preconditions:
+ * 1. Create new admin user.
+ * 2. Configure 'Maximum Login Failures to Lockout Account'.
+ *
+ * Steps:
+ * 1. Log in to backend as new created admin user.
+ * 2. Navigate to System > All Users.
+ * 3. Start editing existing User.
+ * 4. Fill in all data according to data set (password is incorrect).
+ * 5. Perform action 4 specified number of times.
+ * 6. Admin account is locked.
+ * 7. Perform all assertions.
+ *
+ * @ZephyrId MAGETWO-49035
+ */
+class LockAdminUserWhenEditingUserTest extends Injectable
+{
+    /* tags */
+    const MVP = 'yes';
+    const SEVERITY = 'S2';
+    /* end tags */
+
+    /**
+     * User grid page
+     *
+     * @var UserIndex
+     */
+    protected $userIndexPage;
+
+    /**
+     * User edit page
+     *
+     * @var UserEdit
+     */
+    protected $userEditPage;
+
+    /**
+     * @var $configData
+     */
+    protected $configData;
+
+    /**
+     * @var AdminAuthLogin page
+     */
+    protected $adminAuthLogin;
+
+    /**
+     * Setup data for test.
+     * @param UserIndex $userIndex
+     * @param UserEdit $userEdit
+     * @param AdminAuthLogin $adminAuthLogin
+     */
+    public function __inject(
+        UserIndex $userIndex,
+        UserEdit $userEdit,
+        AdminAuthLogin $adminAuthLogin
+    ) {
+        $this->userIndexPage = $userIndex;
+        $this->userEditPage = $userEdit;
+        $this->adminAuthLogin = $adminAuthLogin;
+    }
+
+    /**
+     * Runs Lock admin user when editing existing role test.
+     *
+     * @param User $user
+     * @param int $attempts
+     * @param User $customAdmin
+     * @param string $configData
+     * @return void
+     */
+    public function test(
+        $attempts,
+        User $customAdmin,
+        User $user,
+        $configData
+    ) {
+        $this->configData = $configData;
+
+        // Preconditions
+        $this->objectManager->create(
+            \Magento\Config\Test\TestStep\SetupConfigurationStep::class,
+            ['configData' => $this->configData]
+        )->run();
+        $customAdmin->persist();
+
+        // Steps login to backend with new user
+        $this->adminAuthLogin->open();
+        $this->adminAuthLogin->getLoginBlock()->fill($customAdmin);
+        $this->adminAuthLogin->getLoginBlock()->submit();
+        // Select user to edit.
+        $filter = ['username' => $customAdmin->getUsername()];
+        $this->userIndexPage->open();
+        $this->userIndexPage->getUserGrid()->searchAndOpen($filter);
+        // Edit user with wrong password
+        for ($i = 0; $i < $attempts; $i++) {
+            $this->userEditPage->getUserForm()->fill($user);
+            $this->userEditPage->getPageActions()->save();
+        }
+        // Reload
+        $this->adminAuthLogin->open();
+        $this->adminAuthLogin->getLoginBlock()->fill($customAdmin);
+        $this->adminAuthLogin->getLoginBlock()->submit();
+    }
+
+    /**
+     * Clean data after running test.
+     *
+     * @return void
+     */
+    public function tearDown()
+    {
+        $this->objectManager->create(
+            \Magento\Config\Test\TestStep\SetupConfigurationStep::class,
+            ['configData' => $this->configData, 'rollback' => true]
+        )->run();
+    }
+}
diff --git a/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingUserTest.xml b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingUserTest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..e1ec2f79ce6b5d306e694c9d2ae8e8adea0d3387
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingUserTest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+ -->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd">
+    <testCase name="Magento\Security\Test\TestCase\LockAdminUserWhenEditingUserTest" summary="Lock admin user after entering incorrect password while editing existing user">
+        <variation name="LockAdminUserWhenEditingUseruserTestVariation1">
+            <data name="configData" xsi:type="string">user_lockout_failures</data>
+            <data name="tag" xsi:type="string">severity:S2</data>
+            <data name="customAdmin/dataset" xsi:type="string">custom_admin_with_default_role</data>
+            <data name="user/data/username" xsi:type="string">AdminUser%isolation%</data>
+            <data name="user/data/firstname" xsi:type="string">FirstName%isolation%</data>
+            <data name="user/data/lastname" xsi:type="string">LastName%isolation%</data>
+            <data name="user/data/email" xsi:type="string">email%isolation%@example.com</data>
+            <data name="user/data/password" xsi:type="string">123123qq</data>
+            <data name="user/data/password_confirmation" xsi:type="string">123123qq</data>
+            <data name="user/data/current_password" xsi:type="string">incorrect password</data>
+            <data name="attempts" xsi:type="string">4</data>
+            <constraint name="Magento\Security\Test\Constraint\AssertUserIsLocked" />
+        </variation>
+    </testCase>
+</config>
\ No newline at end of file
diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/ConfigurableTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/ConfigurableTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..5db97cc36614924ae915b7b924b2c7a1f575e344
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/ConfigurableTest.php
@@ -0,0 +1,70 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\ConfigurableProduct\Model\ResourceModel\Product\Indexer\Price;
+
+use Magento\Catalog\Api\ProductRepositoryInterface;
+use Magento\Catalog\Model\Product\Attribute\Source\Status;
+use Magento\Catalog\Model\ResourceModel\Product\Collection;
+use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
+use Magento\Store\Model\Store;
+use Magento\Store\Model\StoreManagerInterface;
+use Magento\TestFramework\Helper\Bootstrap;
+
+/**
+ * @magentoAppArea adminhtml
+ */
+class ConfigurableTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var StoreManagerInterface
+     */
+    private $storeManager;
+
+    /**
+     * @var ProductRepositoryInterface
+     */
+    private $productRepository;
+
+    protected function setUp()
+    {
+        $this->storeManager = Bootstrap::getObjectManager()->get(StoreManagerInterface::class);
+        $this->productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class);
+    }
+
+    /**
+     * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php
+     */
+    public function testGetProductFinalPriceIfOneOfChildIsDisabled()
+    {
+        /** @var Collection $collection */
+        $collection = Bootstrap::getObjectManager()->get(CollectionFactory::class)
+            ->create();
+        $configurableProduct = $collection
+            ->addIdFilter([1])
+            ->addMinimalPrice()
+            ->load()
+            ->getFirstItem();
+        $this->assertEquals(10, $configurableProduct->getMinimalPrice());
+
+        $childProduct = $this->productRepository->getById(10, false, null, true);
+        $childProduct->setStatus(Status::STATUS_DISABLED);
+        // update in global scope
+        $currentStoreId = $this->storeManager->getStore()->getId();
+        $this->storeManager->setCurrentStore(Store::ADMIN_CODE);
+        $this->productRepository->save($childProduct);
+        $this->storeManager->setCurrentStore($currentStoreId);
+
+        /** @var Collection $collection */
+        $collection = Bootstrap::getObjectManager()->get(CollectionFactory::class)
+            ->create();
+        $configurableProduct = $collection
+            ->addIdFilter([1])
+            ->addMinimalPrice()
+            ->load()
+            ->getFirstItem();
+        $this->assertEquals(20, $configurableProduct->getMinimalPrice());
+    }
+}
diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php
index 1576ff90cb38ee4cd89b68766395fa0746d071d6..5f9b9e928b3178f3ae06a8d5a885022cc8e954cc 100644
--- a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php
+++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php
@@ -313,6 +313,8 @@ class IndexTest extends \Magento\TestFramework\TestCase\AbstractBackendControlle
         $this->assertEquals(2, count($addresses));
         $updatedAddress = $this->addressRepository->getById(1);
         $this->assertEquals('update firstname', $updatedAddress->getFirstname());
+        $this->assertTrue($updatedAddress->isDefaultBilling());
+        $this->assertEquals($updatedAddress->getId(), $customer->getDefaultBilling());
         $newAddress = $this->accountManagement->getDefaultShippingAddress($customerId);
         $this->assertEquals('new firstname', $newAddress->getFirstname());
 
diff --git a/dev/tests/integration/testsuite/Magento/Framework/Search/Adapter/Mysql/AdapterTest.php b/dev/tests/integration/testsuite/Magento/Framework/Search/Adapter/Mysql/AdapterTest.php
index 9acfa6f1caab150b0a0db2d54e87358adf88cf5b..67a6e416973493887912e2e62674108b1694c315 100644
--- a/dev/tests/integration/testsuite/Magento/Framework/Search/Adapter/Mysql/AdapterTest.php
+++ b/dev/tests/integration/testsuite/Magento/Framework/Search/Adapter/Mysql/AdapterTest.php
@@ -414,6 +414,37 @@ class AdapterTest extends \PHPUnit_Framework_TestCase
         $this->assertEquals($expectedRecordsCount, $queryResponse->count());
     }
 
+    /**
+     * @magentoDataFixture Magento/Framework/Search/_files/product_configurable.php
+     * @magentoConfigFixture current_store catalog/search/engine mysql
+     */
+    public function testAdvancedSearchCompositeProductWithOutOfStockOption()
+    {
+        /** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute */
+        $attribute = $this->objectManager->get(\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class)
+            ->loadByCode(\Magento\Catalog\Model\Product::ENTITY, 'test_configurable');
+        /** @var \Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection $selectOptions */
+        $selectOptions = $this->objectManager
+            ->create(\Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection::class)
+            ->setAttributeFilter($attribute->getId());
+
+        $firstOption = $selectOptions->getFirstItem();
+        $firstOptionId = $firstOption->getId();
+        $this->requestBuilder->bind('test_configurable', $firstOptionId);
+        $this->requestBuilder->setRequestName('filter_out_of_stock_child');
+
+        $queryResponse = $this->executeQuery();
+        $this->assertEquals(0, $queryResponse->count());
+
+        $secondOption = $selectOptions->getLastItem();
+        $secondOptionId = $secondOption->getId();
+        $this->requestBuilder->bind('test_configurable', $secondOptionId);
+        $this->requestBuilder->setRequestName('filter_out_of_stock_child');
+
+        $queryResponse = $this->executeQuery();
+        $this->assertEquals(1, $queryResponse->count());
+    }
+
     public function dateDataProvider()
     {
         return [
diff --git a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/configurable_attribute.php b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/configurable_attribute.php
new file mode 100644
index 0000000000000000000000000000000000000000..030e0250c3ec7fed4747aed4e168413207c7daf7
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/configurable_attribute.php
@@ -0,0 +1,61 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\Eav\Api\AttributeRepositoryInterface;
+
+$eavConfig = Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class);
+$attribute = $eavConfig->getAttribute('catalog_product', 'test_configurable');
+
+$eavConfig->clear();
+
+/** @var $installer \Magento\Catalog\Setup\CategorySetup */
+$installer = Bootstrap::getObjectManager()->create(\Magento\Catalog\Setup\CategorySetup::class);
+
+if (!$attribute->getId()) {
+
+    /** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */
+    $attribute = Bootstrap::getObjectManager()->create(
+        \Magento\Catalog\Model\ResourceModel\Eav\Attribute::class
+    );
+
+    /** @var AttributeRepositoryInterface $attributeRepository */
+    $attributeRepository = Bootstrap::getObjectManager()->create(AttributeRepositoryInterface::class);
+
+    $attribute->setData(
+        [
+            'attribute_code' => 'test_configurable',
+            'entity_type_id' => $installer->getEntityTypeId('catalog_product'),
+            'is_global' => 1,
+            'is_user_defined' => 1,
+            'frontend_input' => 'select',
+            'is_unique' => 0,
+            'is_required' => 0,
+            'is_searchable' => 1,
+            'is_visible_in_advanced_search' => 1,
+            'is_comparable' => 0,
+            'is_filterable' => 1,
+            'is_filterable_in_search' => 1,
+            'is_used_for_promo_rules' => 0,
+            'is_html_allowed_on_front' => 1,
+            'is_visible_on_front' => 0,
+            'used_in_product_listing' => 0,
+            'used_for_sort_by' => 0,
+            'frontend_label' => ['Test Configurable'],
+            'backend_type' => 'int',
+            'option' => [
+                'value' => ['option_0' => ['Option 1'], 'option_1' => ['Option 2']],
+                'order' => ['option_0' => 1, 'option_1' => 2],
+            ],
+        ]
+    );
+
+    $attributeRepository->save($attribute);
+}
+
+/* Assign attribute to attribute set */
+$installer->addAttributeToGroup('catalog_product', 'Default', 'General', $attribute->getId());
+$eavConfig->clear();
diff --git a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/configurable_attribute_rollback.php b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/configurable_attribute_rollback.php
new file mode 100644
index 0000000000000000000000000000000000000000..bd18100f6d97b4b9b56c9743e8064e0723fdc6c8
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/configurable_attribute_rollback.php
@@ -0,0 +1,28 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+/** @var \Magento\Framework\Registry $registry */
+$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class);
+
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', true);
+$productCollection = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
+    ->get(\Magento\Catalog\Model\ResourceModel\Product\Collection::class);
+foreach ($productCollection as $product) {
+    $product->delete();
+}
+
+$eavConfig = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class);
+$attribute = $eavConfig->getAttribute('catalog_product', 'test_configurable');
+if ($attribute instanceof \Magento\Eav\Model\Entity\Attribute\AbstractAttribute
+    && $attribute->getId()
+) {
+    $attribute->delete();
+}
+$eavConfig->clear();
+
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', false);
diff --git a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/product_configurable.php b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/product_configurable.php
new file mode 100644
index 0000000000000000000000000000000000000000..590f3c8041c6d819fafa14ef7ddc4d1f2ebccbeb
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/product_configurable.php
@@ -0,0 +1,147 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+use Magento\Catalog\Api\ProductRepositoryInterface;
+use Magento\Catalog\Model\Product;
+use Magento\Catalog\Model\Product\Attribute\Source\Status;
+use Magento\Catalog\Model\Product\Type;
+use Magento\Catalog\Model\Product\Visibility;
+use Magento\Catalog\Setup\CategorySetup;
+use Magento\ConfigurableProduct\Helper\Product\Options\Factory;
+use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
+use Magento\Eav\Api\Data\AttributeOptionInterface;
+use Magento\TestFramework\Helper\Bootstrap;
+
+\Magento\TestFramework\Helper\Bootstrap::getInstance()->reinitialize();
+
+require __DIR__ . '/configurable_attribute.php';
+
+/** @var ProductRepositoryInterface $productRepository */
+$productRepository = Bootstrap::getObjectManager()
+    ->create(ProductRepositoryInterface::class);
+
+/** @var $installer CategorySetup */
+$installer = Bootstrap::getObjectManager()->create(CategorySetup::class);
+
+/* Create simple products per each option value*/
+/** @var AttributeOptionInterface[] $options */
+$options = $attribute->getOptions();
+
+$attributeValues = [];
+$attributeSetId = $installer->getAttributeSetId('catalog_product', 'Default');
+$associatedProductIds = [];
+$productIds = [10, 20];
+array_shift($options); //remove the first option which is empty
+
+$isFirstOption = true;
+foreach ($options as $option) {
+    /** @var $product Product */
+    $product = Bootstrap::getObjectManager()->create(Product::class);
+    $productId = array_shift($productIds);
+    $product->setTypeId(Type::TYPE_SIMPLE)
+        ->setId($productId)
+        ->setAttributeSetId($attributeSetId)
+        ->setWebsiteIds([1])
+        ->setName('Configurable Option' . $option->getLabel())
+        ->setSku('simple_' . $productId)
+        ->setPrice($productId)
+        ->setTestConfigurable($option->getValue())
+        ->setVisibility(Visibility::VISIBILITY_NOT_VISIBLE)
+        ->setStatus(Status::STATUS_ENABLED)
+        ->setStockData(
+            [
+                'use_config_manage_stock' => 1,
+                'qty' => 100,
+                'is_qty_decimal' => 0,
+                'is_in_stock' => (int)!$isFirstOption,
+            ]
+        );
+
+    $product = $productRepository->save($product);
+
+    /** @var \Magento\CatalogInventory\Model\Stock\Item $stockItem */
+    $stockItem = Bootstrap::getObjectManager()->create(\Magento\CatalogInventory\Model\Stock\Item::class);
+    $stockItem->load($productId, 'product_id');
+
+    if (!$stockItem->getProductId()) {
+        $stockItem->setProductId($productId);
+    }
+    $stockItem->setUseConfigManageStock(1);
+    $stockItem->setQty(1000);
+    $stockItem->setIsQtyDecimal(0);
+    $stockItem->setIsInStock((int)!$isFirstOption);
+    $stockItem->save();
+
+    $attributeValues[] = [
+        'label' => 'test',
+        'attribute_id' => $attribute->getId(),
+        'value_index' => $option->getValue(),
+    ];
+    $associatedProductIds[] = $product->getId();
+    $isFirstOption = false;
+}
+
+/** @var $product Product */
+$product = Bootstrap::getObjectManager()->create(Product::class);
+
+/** @var Factory $optionsFactory */
+$optionsFactory = Bootstrap::getObjectManager()->create(Factory::class);
+
+$configurableAttributesData = [
+    [
+        'attribute_id' => $attribute->getId(),
+        'code' => $attribute->getAttributeCode(),
+        'label' => $attribute->getStoreLabel(),
+        'position' => '0',
+        'values' => $attributeValues,
+    ],
+];
+
+$configurableOptions = $optionsFactory->create($configurableAttributesData);
+
+$extensionConfigurableAttributes = $product->getExtensionAttributes();
+$extensionConfigurableAttributes->setConfigurableProductOptions($configurableOptions);
+$extensionConfigurableAttributes->setConfigurableProductLinks($associatedProductIds);
+
+$product->setExtensionAttributes($extensionConfigurableAttributes);
+
+// Remove any previously created product with the same id.
+/** @var \Magento\Framework\Registry $registry */
+$registry = Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class);
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', true);
+try {
+    $productToDelete = $productRepository->getById(1);
+    $productRepository->delete($productToDelete);
+
+    /** @var \Magento\Quote\Model\ResourceModel\Quote\Item $itemResource */
+    $itemResource = Bootstrap::getObjectManager()->get(\Magento\Quote\Model\ResourceModel\Quote\Item::class);
+    $itemResource->getConnection()->delete(
+        $itemResource->getMainTable(),
+        'product_id = ' . $productToDelete->getId()
+    );
+} catch (\Exception $e) {
+    // Nothing to remove
+}
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', false);
+
+$product->setTypeId(Configurable::TYPE_CODE)
+    ->setId(1)
+    ->setAttributeSetId($attributeSetId)
+    ->setWebsiteIds([1])
+    ->setName('Configurable Product')
+    ->setSku('configurable')
+    ->setVisibility(Visibility::VISIBILITY_BOTH)
+    ->setStatus(Status::STATUS_ENABLED)
+    ->setStockData(['use_config_manage_stock' => 1, 'is_in_stock' => 1]);
+
+$productRepository->save($product);
+//
+///** @var \Magento\Catalog\Model\Indexer\Product\Eav\Processor $eavIndexer */
+//$eavIndexer = Bootstrap::getObjectManager()
+//    ->get(\Magento\Catalog\Model\Indexer\Product\Eav\Processor::class);
+//$eavIndexer->reindexAll();
diff --git a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/product_configurable_rollback.php b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/product_configurable_rollback.php
new file mode 100644
index 0000000000000000000000000000000000000000..8ba4e3abe21cc14a75a66270d22ea262dffeb788
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/product_configurable_rollback.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Copyright © 2016 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+
+/** @var \Magento\Framework\Registry $registry */
+$registry = $objectManager->get(\Magento\Framework\Registry::class);
+
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', true);
+
+/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */
+$productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
+    ->get(\Magento\Catalog\Api\ProductRepositoryInterface::class);
+
+foreach (['simple_10', 'simple_20', 'configurable'] as $sku) {
+    try {
+        $product = $productRepository->get($sku, false, null, true);
+
+        $stockStatus = $objectManager->create(\Magento\CatalogInventory\Model\Stock\Status::class);
+        $stockStatus->load($product->getEntityId(), 'product_id');
+        $stockStatus->delete();
+
+        $productRepository->delete($product);
+    } catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
+        //Product already removed
+    }
+}
+
+require __DIR__ . '/configurable_attribute_rollback.php';
+
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', false);
diff --git a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/requests.xml b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/requests.xml
index 0cdfa11c3b6b5744abbd911e063553cd23039cc5..4cdc9a93e1f1f0ee093b8e0fd8e555a157fce851 100644
--- a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/requests.xml
+++ b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/requests.xml
@@ -387,4 +387,24 @@
         <from>0</from>
         <size>10</size>
     </request>
+    <request query="filter_out_of_stock_child" index="catalogsearch_fulltext">
+        <dimensions>
+            <dimension name="scope" value="default"/>
+        </dimensions>
+        <queries>
+            <query xsi:type="boolQuery" name="filter_out_of_stock_child" boost="1">
+                <queryReference clause="must" ref="test_configurable"/>
+            </query>
+            <query xsi:type="filteredQuery" name="test_configurable">
+                <filterReference clause="must" ref="test_configurable_filter"/>
+            </query>
+        </queries>
+        <filters>
+            <filter xsi:type="termFilter" name="test_configurable_filter" field="test_configurable" value="$test_configurable$"/>
+        </filters>
+        <aggregations/>
+        <from>0</from>
+        <size>10</size>
+    </request>
+
 </requests>
diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Search/RequestConfigTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Search/RequestConfigTest.php
index 52244463a535693746eddf8f4e0d07b05cf9de08..ff12618b399639fb8c4f56a422828cbb7c0c1e67 100644
--- a/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Search/RequestConfigTest.php
+++ b/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Search/RequestConfigTest.php
@@ -100,7 +100,7 @@ Element 'filterReference': The attribute 'clause' is required but missing.
 Element 'filterReference': The attribute 'ref' is required but missing.
 Element 'filter': The attribute 'field' is required but missing.
 Element 'metric', attribute 'type': [facet 'enumeration'] " .
-                "The value 'sumasdasd' is not an element of the set {'sum', 'count', 'min', 'max'}.
+                "The value 'sumasdasd' is not an element of the set {'sum', 'count', 'min', 'max', 'avg'}.
 Element 'metric', attribute 'type': 'sumasdasd' is not a valid value of the local atomic type.
 Element 'bucket': Missing child element(s). Expected is one of ( metrics, ranges ).
 Element 'request': Missing child element(s). Expected is ( from )."
diff --git a/lib/internal/Magento/Framework/Search/Adapter/Mysql/Adapter.php b/lib/internal/Magento/Framework/Search/Adapter/Mysql/Adapter.php
index 8ddb6255b9b862a37dea1aaefd742a4c03923d24..3d280f1224b8f3bb2e9082436fa7524308730387 100644
--- a/lib/internal/Magento/Framework/Search/Adapter/Mysql/Adapter.php
+++ b/lib/internal/Magento/Framework/Search/Adapter/Mysql/Adapter.php
@@ -71,6 +71,7 @@ class Adapter implements AdapterInterface
 
     /**
      * {@inheritdoc}
+     * @throws \LogicException
      */
     public function query(RequestInterface $request)
     {
diff --git a/lib/internal/Magento/Framework/Search/Adapter/Mysql/Aggregation/Builder/Metrics.php b/lib/internal/Magento/Framework/Search/Adapter/Mysql/Aggregation/Builder/Metrics.php
index 5860591eaf702494614ec89a90e816e159ffeb12..cf544eb126e1195096603979bd7378e08f44730d 100644
--- a/lib/internal/Magento/Framework/Search/Adapter/Mysql/Aggregation/Builder/Metrics.php
+++ b/lib/internal/Magento/Framework/Search/Adapter/Mysql/Aggregation/Builder/Metrics.php
@@ -14,7 +14,7 @@ class Metrics
      *
      * @var string[]
      */
-    private $mapMetrics = ['count', 'sum', 'min', 'max', 'avg'];
+    private $allowedMetrics = ['count', 'sum', 'min', 'max', 'avg'];
 
     /**
      * Build metrics for Select->columns
@@ -30,7 +30,7 @@ class Metrics
 
         foreach ($metrics as $metric) {
             $metricType = $metric->getType();
-            if (in_array($metricType, $this->mapMetrics)) {
+            if (in_array($metricType, $this->allowedMetrics, true)) {
                 $selectAggregations[$metricType] = "$metricType(main_table.value)";
             }
         }
diff --git a/lib/internal/Magento/Framework/Search/etc/requests.xsd b/lib/internal/Magento/Framework/Search/etc/requests.xsd
index f185699c5a5e890757850fa8985b2e0400c56c35..294232513b7d2ec1938e243423a3113e151fd0c0 100644
--- a/lib/internal/Magento/Framework/Search/etc/requests.xsd
+++ b/lib/internal/Magento/Framework/Search/etc/requests.xsd
@@ -263,6 +263,7 @@
               <xs:enumeration value="count" />
               <xs:enumeration value="min" />
               <xs:enumeration value="max" />
+              <xs:enumeration value="avg" />
             </xs:restriction>
           </xs:simpleType>
         </xs:attribute>