Skip to content

Commit bd6eebb

Browse files
authored
Merge pull request #1784 from magento-panda/MAGETWO-84444
Fixed issue: MAGETWO-84444: MySQL triggers for Indexers not removed after upgrade
2 parents 35d776d + 4afb99e commit bd6eebb

File tree

6 files changed

+170
-35
lines changed

6 files changed

+170
-35
lines changed

app/code/Magento/CatalogInventory/etc/mview.xml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@
55
* See COPYING.txt for license details.
66
*/
77
-->
8-
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Mview/etc/mview.xsd">
8+
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
9+
xsi:noNamespaceSchemaLocation="urn:magento:framework:Mview/etc/mview.xsd">
910
<view id="cataloginventory_stock" class="Magento\CatalogInventory\Model\Indexer\Stock" group="indexer">
1011
<subscriptions>
1112
<table name="cataloginventory_stock_item" entity_column="product_id" />
13+
<table name="catalog_product_entity" entity_column="entity_id" />
14+
<table name="catalog_product_entity_int" entity_column="entity_id" />
1215
</subscriptions>
1316
</view>
1417
</config>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
/**
3+
* Copyright © 2013-2017 Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
namespace Magento\Indexer\Setup;
8+
9+
use Magento\Framework\Setup\InstallDataInterface;
10+
use Magento\Framework\Setup\ModuleContextInterface;
11+
use Magento\Framework\Setup\ModuleDataSetupInterface;
12+
use Magento\Indexer\Model\IndexerFactory;
13+
use Magento\Framework\Indexer\ConfigInterface;
14+
15+
/**
16+
* Recurring data upgrade for indexer module
17+
*/
18+
class RecurringData implements InstallDataInterface
19+
{
20+
/**
21+
* @var IndexerFactory
22+
*/
23+
private $indexerFactory;
24+
25+
/**
26+
* @var ConfigInterface
27+
*/
28+
private $configInterface;
29+
30+
/**
31+
* @param IndexerFactory $indexerFactory
32+
* @param ConfigInterface $configInterface
33+
*/
34+
public function __construct(
35+
IndexerFactory $indexerFactory,
36+
ConfigInterface $configInterface
37+
) {
38+
$this->indexerFactory = $indexerFactory;
39+
$this->configInterface = $configInterface;
40+
}
41+
42+
/**
43+
* {@inheritdoc}
44+
* @throws \Exception
45+
*/
46+
public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
47+
{
48+
foreach (array_keys($this->configInterface->getIndexers()) as $indexerId) {
49+
$indexer = $this->indexerFactory->create()->load($indexerId);
50+
if ($indexer->isScheduled()) {
51+
$indexer->getView()->unsubscribe()->subscribe();
52+
}
53+
}
54+
}
55+
}

app/etc/di.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1221,4 +1221,11 @@
12211221
</argument>
12221222
</arguments>
12231223
</type>
1224+
<type name="Magento\Framework\Mview\View\Subscription">
1225+
<arguments>
1226+
<argument name="ignoredUpdateColumns" xsi:type="array">
1227+
<item name="updated_at" xsi:type="string">updated_at</item>
1228+
</argument>
1229+
</arguments>
1230+
</type>
12241231
</config>

lib/internal/Magento/Framework/Mview/Test/Unit/View/SubscriptionTest.php

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ protected function setUp()
6262

6363
$this->resourceMock->expects($this->any())
6464
->method('getTableName')
65-
->willReturn($this->tableName);
65+
->will($this->returnArgument(0));
6666

6767
$this->model = new Subscription(
6868
$this->resourceMock,
@@ -89,11 +89,15 @@ public function testGetColumnName()
8989
$this->assertEquals('columnName', $this->model->getColumnName());
9090
}
9191

92+
/**
93+
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
94+
*/
9295
public function testCreate()
9396
{
9497
$triggerName = 'trigger_name';
9598
$this->resourceMock->expects($this->atLeastOnce())->method('getTriggerName')->willReturn($triggerName);
9699
$triggerMock = $this->getMockBuilder('Magento\Framework\DB\Ddl\Trigger')
100+
->setMethods(['setName', 'getName', 'setTime', 'setEvent', 'setTable', 'addStatement'])
97101
->disableOriginalConstructor()
98102
->getMock();
99103
$triggerMock->expects($this->exactly(3))
@@ -114,8 +118,35 @@ public function testCreate()
114118
->method('setTable')
115119
->with($this->tableName)
116120
->will($this->returnSelf());
117-
$triggerMock->expects($this->exactly(6))
121+
122+
$triggerMock->expects($this->at(4))
123+
->method('addStatement')
124+
->with("INSERT IGNORE INTO test_view_cl (entity_id) VALUES (NEW.columnName);")
125+
->will($this->returnSelf());
126+
127+
$triggerMock->expects($this->at(5))
128+
->method('addStatement')
129+
->with("INSERT IGNORE INTO other_test_view_cl (entity_id) VALUES (NEW.columnName);")
130+
->will($this->returnSelf());
131+
132+
$triggerMock->expects($this->at(11))
133+
->method('addStatement')
134+
->with("INSERT IGNORE INTO test_view_cl (entity_id) VALUES (NEW.columnName);")
135+
->will($this->returnSelf());
136+
137+
$triggerMock->expects($this->at(12))
138+
->method('addStatement')
139+
->with("INSERT IGNORE INTO other_test_view_cl (entity_id) VALUES (NEW.columnName);")
140+
->will($this->returnSelf());
141+
142+
$triggerMock->expects($this->at(18))
143+
->method('addStatement')
144+
->with("INSERT IGNORE INTO test_view_cl (entity_id) VALUES (OLD.columnName);")
145+
->will($this->returnSelf());
146+
147+
$triggerMock->expects($this->at(19))
118148
->method('addStatement')
149+
->with("INSERT IGNORE INTO other_test_view_cl (entity_id) VALUES (OLD.columnName);")
119150
->will($this->returnSelf());
120151

121152
$changelogMock = $this->getMockForAbstractClass(

lib/internal/Magento/Framework/Mview/View/Subscription.php

Lines changed: 70 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
use Magento\Framework\App\ResourceConnection;
1212
use Magento\Framework\DB\Ddl\Trigger;
13+
use Magento\Framework\DB\Ddl\TriggerFactory;
14+
use Magento\Framework\Mview\ViewInterface;
1315

1416
class Subscription implements SubscriptionInterface
1517
{
@@ -21,12 +23,12 @@ class Subscription implements SubscriptionInterface
2123
protected $connection;
2224

2325
/**
24-
* @var \Magento\Framework\DB\Ddl\TriggerFactory
26+
* @var TriggerFactory
2527
*/
2628
protected $triggerFactory;
2729

2830
/**
29-
* @var \Magento\Framework\Mview\View\CollectionInterface
31+
* @var CollectionInterface
3032
*/
3133
protected $viewCollection;
3234

@@ -57,21 +59,32 @@ class Subscription implements SubscriptionInterface
5759
*/
5860
protected $resource;
5961

62+
/**
63+
* List of columns that can be updated in a subscribed table
64+
* without creating a new change log entry
65+
*
66+
* @var array
67+
*/
68+
private $ignoredUpdateColumns = [];
69+
6070
/**
6171
* @param ResourceConnection $resource
62-
* @param \Magento\Framework\DB\Ddl\TriggerFactory $triggerFactory
63-
* @param \Magento\Framework\Mview\View\CollectionInterface $viewCollection
64-
* @param \Magento\Framework\Mview\ViewInterface $view
72+
* @param TriggerFactory $triggerFactory
73+
* @param CollectionInterface $viewCollection
74+
* @param ViewInterface $view
6575
* @param string $tableName
6676
* @param string $columnName
77+
* @param array $ignoredUpdateColumns
78+
* @throws \DomainException
6779
*/
6880
public function __construct(
6981
ResourceConnection $resource,
70-
\Magento\Framework\DB\Ddl\TriggerFactory $triggerFactory,
71-
\Magento\Framework\Mview\View\CollectionInterface $viewCollection,
72-
\Magento\Framework\Mview\ViewInterface $view,
82+
TriggerFactory $triggerFactory,
83+
CollectionInterface $viewCollection,
84+
ViewInterface $view,
7385
$tableName,
74-
$columnName
86+
$columnName,
87+
array $ignoredUpdateColumns = []
7588
) {
7689
$this->connection = $resource->getConnection();
7790
$this->triggerFactory = $triggerFactory;
@@ -80,12 +93,14 @@ public function __construct(
8093
$this->tableName = $tableName;
8194
$this->columnName = $columnName;
8295
$this->resource = $resource;
96+
$this->ignoredUpdateColumns = $ignoredUpdateColumns;
8397
}
8498

8599
/**
86-
* Create subsciption
100+
* Create subscription
87101
*
88-
* @return \Magento\Framework\Mview\View\SubscriptionInterface
102+
* @return SubscriptionInterface
103+
* @throws \InvalidArgumentException
89104
*/
90105
public function create()
91106
{
@@ -102,7 +117,7 @@ public function create()
102117

103118
// Add statements for linked views
104119
foreach ($this->getLinkedViews() as $view) {
105-
/** @var \Magento\Framework\Mview\ViewInterface $view */
120+
/** @var ViewInterface $view */
106121
$trigger->addStatement($this->buildStatement($event, $view->getChangelog()));
107122
}
108123

@@ -116,7 +131,8 @@ public function create()
116131
/**
117132
* Remove subscription
118133
*
119-
* @return \Magento\Framework\Mview\View\SubscriptionInterface
134+
* @return SubscriptionInterface
135+
* @throws \InvalidArgumentException
120136
*/
121137
public function remove()
122138
{
@@ -131,7 +147,7 @@ public function remove()
131147

132148
// Add statements for linked views
133149
foreach ($this->getLinkedViews() as $view) {
134-
/** @var \Magento\Framework\Mview\ViewInterface $view */
150+
/** @var ViewInterface $view */
135151
$trigger->addStatement($this->buildStatement($event, $view->getChangelog()));
136152
}
137153

@@ -154,10 +170,10 @@ public function remove()
154170
protected function getLinkedViews()
155171
{
156172
if (!$this->linkedViews) {
157-
$viewList = $this->viewCollection->getViewsByStateMode(\Magento\Framework\Mview\View\StateInterface::MODE_ENABLED);
173+
$viewList = $this->viewCollection->getViewsByStateMode(StateInterface::MODE_ENABLED);
158174

159175
foreach ($viewList as $view) {
160-
/** @var \Magento\Framework\Mview\ViewInterface $view */
176+
/** @var ViewInterface $view */
161177
// Skip the current view
162178
if ($view->getId() == $this->getView()->getId()) {
163179
continue;
@@ -175,35 +191,58 @@ protected function getLinkedViews()
175191
}
176192

177193
/**
178-
* Build trigger statement for INSER, UPDATE, DELETE events
194+
* Build trigger statement for INSERT, UPDATE, DELETE events
179195
*
180196
* @param string $event
181-
* @param \Magento\Framework\Mview\View\ChangelogInterface $changelog
197+
* @param ChangelogInterface $changelog
182198
* @return string
183199
*/
184200
protected function buildStatement($event, $changelog)
185201
{
186202
switch ($event) {
187203
case Trigger::EVENT_INSERT:
204+
$trigger = 'INSERT IGNORE INTO %s (%s) VALUES (NEW.%s);';
205+
break;
206+
188207
case Trigger::EVENT_UPDATE:
189-
return sprintf(
190-
"INSERT IGNORE INTO %s (%s) VALUES (NEW.%s);",
191-
$this->connection->quoteIdentifier($this->resource->getTableName($changelog->getName())),
192-
$this->connection->quoteIdentifier($changelog->getColumnName()),
193-
$this->connection->quoteIdentifier($this->getColumnName())
194-
);
208+
$trigger = 'INSERT IGNORE INTO %s (%s) VALUES (NEW.%s);';
209+
210+
if ($this->connection->isTableExists($this->getTableName())
211+
&& $describe = $this->connection->describeTable($this->getTableName())
212+
) {
213+
$columnNames = array_column($describe, 'COLUMN_NAME');
214+
$columnNames = array_diff($columnNames, $this->ignoredUpdateColumns);
215+
if ($columnNames) {
216+
$columns = [];
217+
foreach ($columnNames as $columnName) {
218+
$columns[] = sprintf(
219+
'NEW.%1$s != OLD.%1$s',
220+
$this->connection->quoteIdentifier($columnName)
221+
);
222+
}
223+
$trigger = sprintf(
224+
"IF (%s) THEN %s END IF;",
225+
implode(' OR ', $columns),
226+
$trigger
227+
);
228+
}
229+
}
230+
break;
195231

196232
case Trigger::EVENT_DELETE:
197-
return sprintf(
198-
"INSERT IGNORE INTO %s (%s) VALUES (OLD.%s);",
199-
$this->connection->quoteIdentifier($this->resource->getTableName($changelog->getName())),
200-
$this->connection->quoteIdentifier($changelog->getColumnName()),
201-
$this->connection->quoteIdentifier($this->getColumnName())
202-
);
233+
$trigger = 'INSERT IGNORE INTO %s (%s) VALUES (OLD.%s);';
234+
break;
203235

204236
default:
205237
return '';
206238
}
239+
240+
return sprintf(
241+
$trigger,
242+
$this->connection->quoteIdentifier($this->resource->getTableName($changelog->getName())),
243+
$this->connection->quoteIdentifier($changelog->getColumnName()),
244+
$this->connection->quoteIdentifier($this->getColumnName())
245+
);
207246
}
208247

209248
/**
@@ -225,7 +264,7 @@ private function getAfterEventTriggerName($event)
225264
/**
226265
* Retrieve View related to subscription
227266
*
228-
* @return \Magento\Framework\Mview\ViewInterface
267+
* @return ViewInterface
229268
* @codeCoverageIgnore
230269
*/
231270
public function getView()

lib/internal/Magento/Framework/Mview/etc/mview.xsd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@
106106
<xs:simpleType name="subscriptionModelType">
107107
<xs:annotation>
108108
<xs:documentation>
109-
Subscription model must be a valid PHP class or interface name.
109+
DEPRECATED. Subscription model must be a valid PHP class or interface name.
110110
</xs:documentation>
111111
</xs:annotation>
112112
<xs:restriction base="xs:string">

0 commit comments

Comments
 (0)