@@ -55,9 +55,9 @@ class DbStorage extends AbstractStorage
55
55
private $ maxRetryCount ;
56
56
57
57
/**
58
- * @param UrlRewriteFactory $urlRewriteFactory
59
- * @param DataObjectHelper $dataObjectHelper
60
- * @param ResourceConnection $resource
58
+ * @param UrlRewriteFactory $urlRewriteFactory
59
+ * @param DataObjectHelper $dataObjectHelper
60
+ * @param ResourceConnection $resource
61
61
* @param LoggerInterface|null $logger
62
62
* @param int $maxRetryCount
63
63
*/
@@ -66,7 +66,7 @@ public function __construct(
66
66
DataObjectHelper $ dataObjectHelper ,
67
67
ResourceConnection $ resource ,
68
68
LoggerInterface $ logger = null ,
69
- int $ maxRetryCount = 3
69
+ int $ maxRetryCount = 5
70
70
) {
71
71
$ this ->connection = $ resource ->getConnection ();
72
72
$ this ->resource = $ resource ;
@@ -111,7 +111,6 @@ protected function doFindOneByData(array $data)
111
111
&& is_string ($ data [UrlRewrite::REQUEST_PATH ])
112
112
) {
113
113
$ result = null ;
114
-
115
114
$ requestPath = $ data [UrlRewrite::REQUEST_PATH ];
116
115
$ decodedRequestPath = urldecode ($ requestPath );
117
116
$ data [UrlRewrite::REQUEST_PATH ] = array_unique (
@@ -122,16 +121,13 @@ protected function doFindOneByData(array $data)
122
121
rtrim ($ decodedRequestPath , '/ ' ) . '/ ' ,
123
122
]
124
123
);
125
-
126
124
$ resultsFromDb = $ this ->connection ->fetchAll ($ this ->prepareSelect ($ data ));
127
125
if ($ resultsFromDb ) {
128
126
$ urlRewrite = $ this ->extractMostRelevantUrlRewrite ($ requestPath , $ resultsFromDb );
129
127
$ result = $ this ->prepareUrlRewrite ($ requestPath , $ urlRewrite );
130
128
}
131
-
132
129
return $ result ;
133
130
}
134
-
135
131
return $ this ->connection ->fetchRow ($ this ->prepareSelect ($ data ));
136
132
}
137
133
@@ -224,23 +220,30 @@ private function deleteOldUrls(array $uniqueEntities): void
224
220
);
225
221
foreach ($ uniqueEntities as $ storeId => $ entityTypes ) {
226
222
foreach ($ entityTypes as $ entityType => $ entities ) {
223
+ // phpcs:ignore Magento2.Performance.ForeachArrayMerge
224
+ $ requestPaths = array_merge (...$ entities );
225
+ $ requestPathFilter = '' ;
226
+ if (!empty ($ requestPaths )) {
227
+ $ requestPathFilter = ' AND ' . $ this ->connection ->quoteIdentifier (UrlRewrite::REQUEST_PATH )
228
+ . ' NOT IN ( ' . $ this ->connection ->quote ($ requestPaths ) . ') ' ;
229
+ }
227
230
$ oldUrlsSelect ->orWhere (
228
231
$ this ->connection ->quoteIdentifier (UrlRewrite::STORE_ID )
229
- . ' = ' . $ this ->connection ->quote ($ storeId , 'INTEGER ' ) .
230
- ' AND ' . $ this ->connection ->quoteIdentifier (UrlRewrite::ENTITY_ID )
231
- . ' IN ( ' . $ this ->connection ->quote (array_keys ($ entities ), 'INTEGER ' ) . ') ' .
232
- ' AND ' . $ this ->connection ->quoteIdentifier (UrlRewrite::ENTITY_TYPE )
233
- . ' = ' . $ this ->connection ->quote ($ entityType ) .
234
- ' AND ' . $ this ->connection ->quoteIdentifier (UrlRewrite::REQUEST_PATH )
235
- . ' NOT IN ( ' . $ this ->connection ->quote (array_merge (...$ entities )) . ') '
232
+ . ' = ' . $ this ->connection ->quote ($ storeId , 'INTEGER ' )
233
+ . ' AND ' . $ this ->connection ->quoteIdentifier (UrlRewrite::ENTITY_ID )
234
+ . ' IN ( ' . $ this ->connection ->quote (array_keys ($ entities ), 'INTEGER ' ) . ') '
235
+ . ' AND ' . $ this ->connection ->quoteIdentifier (UrlRewrite::ENTITY_TYPE )
236
+ . ' = ' . $ this ->connection ->quote ($ entityType )
237
+ . $ requestPathFilter
236
238
);
237
239
}
238
240
}
239
241
// prevent query locking in a case when nothing to delete
240
242
$ checkOldUrlsSelect = clone $ oldUrlsSelect ;
241
243
$ checkOldUrlsSelect ->reset (Select::COLUMNS );
242
- $ checkOldUrlsSelect ->columns ('count(*) ' );
243
- $ hasOldUrls = (bool )$ this ->connection ->fetchOne ($ checkOldUrlsSelect );
244
+ $ checkOldUrlsSelect ->columns ([new \Zend_Db_Expr ('1 ' )]);
245
+ $ checkOldUrlsSelect ->limit (1 );
246
+ $ hasOldUrls = false !== $ this ->connection ->fetchOne ($ checkOldUrlsSelect );
244
247
if ($ hasOldUrls ) {
245
248
$ this ->connection ->query (
246
249
$ oldUrlsSelect ->deleteFromSelect (
@@ -250,6 +253,59 @@ private function deleteOldUrls(array $uniqueEntities): void
250
253
}
251
254
}
252
255
256
+ /**
257
+ * Checks for duplicates both inside the new urls, and outside.
258
+ * Because we are using INSERT ON DUPLICATE UPDATE, the insert won't give us an error.
259
+ * So, we have to check for existing requestPaths in database with different entity_id.
260
+ * And also, we need to check to make sure we don't have same requestPath more than once in our new rewrites.
261
+ *
262
+ * @param array $uniqueEntities
263
+ * @return void
264
+ */
265
+ private function checkDuplicates (array $ uniqueEntities ): void
266
+ {
267
+ $ oldUrlsSelect = $ this ->connection ->select ();
268
+ $ oldUrlsSelect ->from (
269
+ $ this ->resource ->getTableName (self ::TABLE_NAME ),
270
+ [new \Zend_Db_Expr ('1 ' )]
271
+ );
272
+ $ allEmpty = true ;
273
+ foreach ($ uniqueEntities as $ storeId => $ entityTypes ) {
274
+ $ newRequestPaths = [];
275
+ foreach ($ entityTypes as $ entityType => $ entities ) {
276
+ $ requestPaths = array_merge (...$ entities );
277
+ $ requestPathFilter = '' ;
278
+ if (empty ($ requestPaths )) {
279
+ continue ;
280
+ }
281
+ $ allEmpty = false ;
282
+ $ oldUrlsSelect ->orWhere (
283
+ $ this ->connection ->quoteIdentifier (UrlRewrite::STORE_ID )
284
+ . ' = ' . $ this ->connection ->quote ($ storeId , 'INTEGER ' )
285
+ . ' AND ( ' . $ this ->connection ->quoteIdentifier (UrlRewrite::ENTITY_ID )
286
+ . ' NOT IN ( ' . $ this ->connection ->quote (array_keys ($ entities ), 'INTEGER ' ) . ') '
287
+ . ' OR ' . $ this ->connection ->quoteIdentifier (UrlRewrite::ENTITY_TYPE )
288
+ . ' != ' . $ this ->connection ->quote ($ entityType )
289
+ . ') AND ' . $ this ->connection ->quoteIdentifier (UrlRewrite::REQUEST_PATH )
290
+ . ' IN ( ' . $ this ->connection ->quote ($ requestPaths ) . ') '
291
+ );
292
+ foreach ($ requestPaths as $ requestPath ) {
293
+ if (isset ($ newRequestPaths [$ requestPath ])) {
294
+ throw new \Magento \Framework \Exception \AlreadyExistsException ();
295
+ }
296
+ $ newRequestPaths [$ requestPath ] = true ;
297
+ }
298
+ }
299
+ }
300
+ if ($ allEmpty ) {
301
+ return ;
302
+ }
303
+ $ oldUrlsSelect ->limit (1 );
304
+ if (false !== $ this ->connection ->fetchOne ($ oldUrlsSelect )) {
305
+ throw new \Magento \Framework \Exception \AlreadyExistsException ();
306
+ }
307
+ }
308
+
253
309
/**
254
310
* Prepare array with unique entities
255
311
*
@@ -260,7 +316,16 @@ private function prepareUniqueEntities(array $urls): array
260
316
{
261
317
$ uniqueEntities = [];
262
318
foreach ($ urls as $ url ) {
263
- $ uniqueEntities [$ url ->getStoreId ()][$ url ->getEntityType ()][$ url ->getEntityId ()][] = $ url ->getRequestPath ();
319
+ $ storeId = $ url ->getStoreId ();
320
+ $ entityType = $ url ->getEntityType ();
321
+ $ entityId = $ url ->getEntityId ();
322
+ $ requestPath = $ url ->getRequestPath ();
323
+ if (null === $ requestPath ) { // Note: because SQL unique keys allow multiple nulls, we skip it.
324
+ if (!isset ($ uniqueEntities [$ storeId ][$ entityType ][$ entityId ])) {
325
+ $ uniqueEntities [$ storeId ][$ entityType ][$ entityId ] = [];
326
+ }
327
+ }
328
+ $ uniqueEntities [$ storeId ][$ entityType ][$ entityId ][] = $ requestPath ;
264
329
}
265
330
return $ uniqueEntities ;
266
331
}
@@ -275,13 +340,18 @@ protected function doReplace(array $urls): array
275
340
foreach ($ urls as $ url ) {
276
341
$ data [] = $ url ->toArray ();
277
342
}
278
- for ($ tries = 0 ; $ tries < $ this -> maxRetryCount ; $ tries ++) {
343
+ for ($ tries = 0 ; ; $ tries ++) {
279
344
$ this ->connection ->beginTransaction ();
280
345
try {
281
346
$ this ->deleteOldUrls ($ uniqueEntities );
347
+ $ this ->checkDuplicates ($ uniqueEntities );
282
348
$ this ->upsertMultiple ($ data );
283
349
$ this ->connection ->commit ();
284
350
} catch (\Magento \Framework \DB \Adapter \DeadlockException $ deadlockException ) {
351
+ $ this ->connection ->rollBack ();
352
+ if ($ tries >= $ this ->maxRetryCount ) {
353
+ throw $ deadlockException ;
354
+ }
285
355
continue ;
286
356
} catch (\Magento \Framework \Exception \AlreadyExistsException $ e ) {
287
357
$ this ->connection ->rollBack ();
@@ -295,6 +365,13 @@ protected function doReplace(array $urls): array
295
365
]
296
366
);
297
367
if (isset ($ urlFound [UrlRewrite::URL_REWRITE_ID ])) {
368
+ if (isset ($ uniqueEntities
369
+ [$ urlFound [UrlRewrite::STORE_ID ]]
370
+ [$ urlFound [UrlRewrite::ENTITY_TYPE ]]
371
+ [$ urlFound [UrlRewrite::ENTITY_ID ]
372
+ ])) {
373
+ continue ; // Note: If it's one of the entities we are updating, then it is okay.
374
+ }
298
375
$ urlConflicted [$ urlFound [UrlRewrite::URL_REWRITE_ID ]] = $ url ->toArray ();
299
376
}
300
377
}
@@ -312,15 +389,14 @@ protected function doReplace(array $urls): array
312
389
$ this ->connection ->rollBack ();
313
390
throw $ e ;
314
391
}
315
- break ;
392
+ return $ urls ;
316
393
}
317
- return $ urls ;
318
394
}
319
395
320
396
/**
321
397
* Insert multiple
322
398
*
323
- * @param array $data
399
+ * @param array $data
324
400
* @return void
325
401
* @throws \Magento\Framework\Exception\AlreadyExistsException|\Exception
326
402
* @throws \Exception
@@ -352,15 +428,17 @@ protected function insertMultiple($data): void
352
428
*/
353
429
private function upsertMultiple (array $ data ): void
354
430
{
431
+
355
432
$ this ->connection ->insertOnDuplicate ($ this ->resource ->getTableName (self ::TABLE_NAME ), $ data );
356
433
}
357
434
358
435
/**
359
436
* Get filter for url rows deletion due to provided urls
360
437
*
361
- * @param UrlRewrite[] $urls
362
- * @return array
438
+ * @param UrlRewrite[] $urls
439
+ * @return array
363
440
* @deprecated 101.0.3 Not used anymore.
441
+ * @see nothing
364
442
*/
365
443
protected function createFilterDataBasedOnUrls ($ urls ): array
366
444
{
0 commit comments