Skip to content

Commit 7051798

Browse files
committed
Merge remote-tracking branch 'l3/ACP2E-531' into PR_L3_05_04_2022
2 parents f3fb295 + bf9d8d7 commit 7051798

File tree

7 files changed

+542
-39
lines changed

7 files changed

+542
-39
lines changed

app/code/Magento/Catalog/Model/Indexer/Category/Product/AbstractAction.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,23 +29,23 @@ abstract class AbstractAction
2929
/**
3030
* Chunk size
3131
*/
32-
const RANGE_CATEGORY_STEP = 500;
32+
public const RANGE_CATEGORY_STEP = 500;
3333

3434
/**
3535
* Chunk size for product
3636
*/
37-
const RANGE_PRODUCT_STEP = 1000000;
37+
public const RANGE_PRODUCT_STEP = 1000000;
3838

3939
/**
4040
* Catalog category index table name
4141
*/
42-
const MAIN_INDEX_TABLE = 'catalog_category_product_index';
42+
public const MAIN_INDEX_TABLE = 'catalog_category_product_index';
4343

4444
/**
4545
* Suffix for table to show it is temporary
4646
* @deprecated see getIndexTable
4747
*/
48-
const TEMPORARY_TABLE_SUFFIX = '_tmp';
48+
public const TEMPORARY_TABLE_SUFFIX = '_tmp';
4949

5050
/**
5151
* Cached non anchor categories select by store id
@@ -582,7 +582,7 @@ protected function createAnchorSelect(Store $store)
582582
'category_id' => 'cc.entity_id',
583583
'product_id' => 'ccp.product_id',
584584
'position' => new \Zend_Db_Expr(
585-
$this->connection->getIfNullSql('ccp2.position', 'ccp.position + 10000')
585+
$this->connection->getIfNullSql('ccp2.position', 'MIN(ccp.position) + 10000')
586586
),
587587
'is_parent' => new \Zend_Db_Expr('0'),
588588
'store_id' => new \Zend_Db_Expr($store->getId()),
@@ -823,7 +823,7 @@ protected function getAllProducts(Store $store)
823823
'category_id' => new \Zend_Db_Expr($store->getRootCategoryId()),
824824
'product_id' => 'cp.entity_id',
825825
'position' => new \Zend_Db_Expr(
826-
$this->connection->getCheckSql('ccp.product_id IS NOT NULL', 'ccp.position', '0')
826+
$this->connection->getCheckSql('ccp.product_id IS NOT NULL', 'MIN(ccp.position)', '10000')
827827
),
828828
'is_parent' => new \Zend_Db_Expr(
829829
$this->connection->getCheckSql('ccp.product_id IS NOT NULL', '1', '0')
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Catalog\Model\ResourceModel\Product\Collection;
9+
10+
use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer;
11+
use Magento\Catalog\Model\ResourceModel\Product\Collection;
12+
use Magento\Framework\DB\Select;
13+
14+
class JoinMinimalPosition
15+
{
16+
/**
17+
* @var TableMaintainer
18+
*/
19+
private $tableMaintainer;
20+
21+
/**
22+
* @param TableMaintainer $tableMaintainer
23+
*/
24+
public function __construct(
25+
TableMaintainer $tableMaintainer
26+
) {
27+
$this->tableMaintainer = $tableMaintainer;
28+
}
29+
30+
/**
31+
* Add minimal position to the collection select
32+
*
33+
* @param Collection $collection
34+
* @param array $categoryIds
35+
* @return void
36+
* @throws \Magento\Framework\Exception\LocalizedException
37+
* @throws \Zend_Db_Select_Exception
38+
*/
39+
public function execute(Collection $collection, array $categoryIds): void
40+
{
41+
$positions = [];
42+
$connection = $collection->getConnection();
43+
$select = $collection->getSelect();
44+
45+
foreach ($categoryIds as $categoryId) {
46+
$table = 'cat_index_' . $categoryId;
47+
$conditions = [
48+
$table . '.product_id=e.entity_id',
49+
$connection->quoteInto(
50+
$table . '.store_id=?',
51+
$collection->getStoreId(),
52+
'int'
53+
),
54+
$connection->quoteInto(
55+
$table . '.category_id=?',
56+
$categoryId,
57+
'int'
58+
)
59+
];
60+
61+
$joinCond = implode(' AND ', $conditions);
62+
$fromPart = $select->getPart(Select::FROM);
63+
if (isset($fromPart[$table])) {
64+
$fromPart[$table]['joinCondition'] = $joinCond;
65+
$select->setPart(Select::FROM, $fromPart);
66+
} else {
67+
$select->joinLeft(
68+
[$table => $this->tableMaintainer->getMainTable($collection->getStoreId())],
69+
$joinCond,
70+
[]
71+
);
72+
}
73+
$positions[] = $connection->getIfNullSql($table . '.position', '~0');
74+
}
75+
76+
// Ensures that position attribute is registered in _joinFields
77+
// in order for sort by position to use cat_index_position field
78+
$collection->addExpressionAttributeToSelect('position', 'cat_index_position', 'entity_id');
79+
80+
$columns = $select->getPart(Select::COLUMNS);
81+
$preparedColumns = [];
82+
$columnFound = false;
83+
$minPos = $connection->getLeastSql($positions);
84+
85+
// Remove columns with alias cat_index_position
86+
// Find column entry that was added in addExpressionAttributeToSelect. Expected [, cat_index_position, position]
87+
// and replace it with [, LEAST(...), cat_index_position]
88+
foreach ($columns as $columnEntry) {
89+
if ($columnEntry[2] !== 'cat_index_position') {
90+
if ($columnEntry[2] === 'position' && $columnEntry[1] === 'cat_index_position') {
91+
if (!$columnFound) {
92+
$columnEntry[1] = $minPos;
93+
$columnEntry[2] = 'cat_index_position';
94+
$columnFound = true;
95+
} else {
96+
continue;
97+
}
98+
}
99+
$preparedColumns[] = $columnEntry;
100+
}
101+
}
102+
103+
$select->setPart(Select::COLUMNS, $preparedColumns);
104+
105+
if (!$columnFound) {
106+
$select->columns(['cat_index_position' => $minPos]);
107+
}
108+
}
109+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Catalog\Test\Unit\Model\ResourceModel\Product\Collection;
9+
10+
use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer;
11+
use Magento\Catalog\Model\ResourceModel\Product\Collection;
12+
use Magento\Catalog\Model\ResourceModel\Product\Collection\JoinMinimalPosition;
13+
use Magento\Framework\DB\Adapter\Pdo\Mysql;
14+
use Magento\Framework\DB\Select;
15+
use PHPUnit\Framework\MockObject\MockObject;
16+
use PHPUnit\Framework\TestCase;
17+
18+
/**
19+
* Test for JoinMinimalPosition
20+
*/
21+
class JoinMinimalPositionTest extends TestCase
22+
{
23+
/**
24+
* @var TableMaintainer|MockObject
25+
*/
26+
private $tableMaintainer;
27+
28+
/**
29+
* @var JoinMinimalPosition
30+
*/
31+
private $model;
32+
33+
/**
34+
* @inheritdoc
35+
*/
36+
protected function setUp(): void
37+
{
38+
parent::setUp();
39+
$this->tableMaintainer = $this->createMock(TableMaintainer::class);
40+
$this->model = new JoinMinimalPosition(
41+
$this->tableMaintainer
42+
);
43+
}
44+
45+
/**
46+
* Test that correct SQL is generated
47+
*
48+
* @return void
49+
* @throws \Magento\Framework\Exception\LocalizedException
50+
* @throws \Zend_Db_Select_Exception
51+
*/
52+
public function testExecute(): void
53+
{
54+
$expectedColumns = [
55+
[
56+
'e',
57+
'*',
58+
null
59+
],
60+
[
61+
'at_status',
62+
'value_id',
63+
'status'
64+
],
65+
[
66+
'e',
67+
'visibility',
68+
null
69+
],
70+
[
71+
'e',
72+
new \Zend_Db_Expr('LEAST(IFNULL(cat_index_3.position, ~0), IFNULL(cat_index_5.position, ~0))'),
73+
'cat_index_position'
74+
],
75+
];
76+
$expectedFromParts = [
77+
'e' => [
78+
'joinType' => 'from',
79+
'schema' => null,
80+
'tableName' => 'catalog_product_entity',
81+
'joinCondition' => null,
82+
],
83+
'cat_index_3' => [
84+
'joinType' => 'left join',
85+
'schema' => null,
86+
'tableName' => null,
87+
'joinCondition' => 'cat_index_3.product_id=e.entity_id' .
88+
' AND cat_index_3.store_id=1' .
89+
' AND cat_index_3.category_id=3',
90+
],
91+
'cat_index_5' => [
92+
'joinType' => 'left join',
93+
'schema' => null,
94+
'tableName' => null,
95+
'joinCondition' => 'cat_index_5.product_id=e.entity_id' .
96+
' AND cat_index_5.store_id=1' .
97+
' AND cat_index_5.category_id=5',
98+
]
99+
];
100+
$categoryIds = [3, 5];
101+
$collection = $this->getMockBuilder(Collection::class)
102+
->disableOriginalConstructor()
103+
->onlyMethods(['getConnection', 'getSelect', 'getStoreId'])
104+
->getMockForAbstractClass();
105+
$connection = $this->getMockBuilder(Mysql::class)
106+
->disableOriginalConstructor()
107+
->onlyMethods(['_connect'])
108+
->getMockForAbstractClass();
109+
$select = $this->getMockBuilder(Select::class)
110+
->disableOriginalConstructor()
111+
->getMockForAbstractClass();
112+
113+
$select->reset();
114+
$select->from(['e' => 'catalog_product_entity']);
115+
$select->columns(['cat_index_position' => 'position']);
116+
$select->columns(['status' => 'at_status.value_id']);
117+
$select->columns(['visibility']);
118+
119+
$collection->addStaticField('entity_id');
120+
$collection->method('getConnection')
121+
->willReturn($connection);
122+
$collection->method('getSelect')
123+
->willReturn($select);
124+
$collection->method('getStoreId')
125+
->willReturn(1);
126+
$this->model->execute($collection, $categoryIds);
127+
$this->assertEquals(
128+
$expectedFromParts,
129+
$select->getPart(Select::FROM)
130+
);
131+
$this->assertEquals(
132+
$expectedColumns,
133+
$select->getPart(Select::COLUMNS)
134+
);
135+
}
136+
}

app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ public function build(array $args, bool $includeAggregation): SearchCriteriaInte
104104
$this->addDefaultSortOrder($searchCriteria, $args, $isSearch);
105105
}
106106

107-
$this->addEntityIdSort($searchCriteria, $args);
107+
$this->addEntityIdSort($searchCriteria);
108108
$this->addVisibilityFilter($searchCriteria, $isSearch, !empty($args['filter']));
109109

110110
$searchCriteria->setCurrentPage($args['currentPage']);
@@ -137,15 +137,22 @@ private function addVisibilityFilter(SearchCriteriaInterface $searchCriteria, bo
137137
* Add sort by Entity ID
138138
*
139139
* @param SearchCriteriaInterface $searchCriteria
140-
* @param array $args
141140
*/
142-
private function addEntityIdSort(SearchCriteriaInterface $searchCriteria, array $args): void
141+
private function addEntityIdSort(SearchCriteriaInterface $searchCriteria): void
143142
{
144-
$sortOrder = !empty($args['sort']) ? reset($args['sort']) : SortOrder::SORT_DESC;
145143
$sortOrderArray = $searchCriteria->getSortOrders();
144+
$sortDir = SortOrder::SORT_DESC;
145+
if (count($sortOrderArray) > 0) {
146+
$sortOrder = end($sortOrderArray);
147+
// in the case the last sort order is by position, sort IDs in descendent order
148+
$sortDir = $sortOrder->getField() === EavAttributeInterface::POSITION
149+
? SortOrder::SORT_DESC
150+
: $sortOrder->getDirection();
151+
}
152+
146153
$sortOrderArray[] = $this->sortOrderBuilder
147154
->setField('_id')
148-
->setDirection($sortOrder)
155+
->setDirection($sortDir)
149156
->create();
150157
$searchCriteria->setSortOrders($sortOrderArray);
151158
}

0 commit comments

Comments
 (0)