From 51208fcb99c724a4c657328dce8ffc3d9b4c2bc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Somogyi?= Date: Mon, 27 Feb 2023 12:45:33 +0100 Subject: [PATCH] Fix #19770: Fix `yii\mutex\MysqlMutex` `keyPrefix` expression param binding --- framework/CHANGELOG.md | 1 + framework/mutex/MysqlMutex.php | 39 +++++++++++++++++++----- tests/framework/mutex/MysqlMutexTest.php | 23 ++++++++++++++ 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index fcf082ed10..d2d60eb346 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -20,6 +20,7 @@ Yii Framework 2 Change Log - Bug #19734: PHP 8.1 compatibility fix for `$query->orderBy(null)` (uaoleg) - Bug #19731: Fix `yii\data\Sort` to generate proper link when multisort is on and attribute has a default sort order set (bizley) - Bug #19735: Fix `yii\validators\NumberValidator` to use programmable message for the value validation (bizley) +- Bug #19770: Fix `yii\mutex\MysqlMutex` `keyPrefix` expression param binding (kamarton) 2.0.47 November 18, 2022 ------------------------ diff --git a/framework/mutex/MysqlMutex.php b/framework/mutex/MysqlMutex.php index cbbd49dad3..d7716e8c64 100644 --- a/framework/mutex/MysqlMutex.php +++ b/framework/mutex/MysqlMutex.php @@ -69,9 +69,13 @@ class MysqlMutex extends DbMutex { return $this->db->useMaster(function ($db) use ($name, $timeout) { /** @var \yii\db\Connection $db */ - return (bool) $db->createCommand( - 'SELECT GET_LOCK(SUBSTRING(CONCAT(:prefix, :name), 1, 64), :timeout)', - [':name' => $this->hashLockName($name), ':timeout' => $timeout, ':prefix' => $this->keyPrefix] + $nameData = $this->prepareName(); + return (bool)$db->createCommand( + 'SELECT GET_LOCK(' . $nameData[0] . ', :timeout), :prefix', + array_merge( + [':name' => $this->hashLockName($name), ':timeout' => $timeout, ':prefix' => $this->keyPrefix], + $nameData[1] + ) )->queryScalar(); }); } @@ -86,13 +90,33 @@ class MysqlMutex extends DbMutex { return $this->db->useMaster(function ($db) use ($name) { /** @var \yii\db\Connection $db */ - return (bool) $db->createCommand( - 'SELECT RELEASE_LOCK(SUBSTRING(CONCAT(:prefix, :name), 1, 64))', - [':name' => $this->hashLockName($name), ':prefix' => $this->keyPrefix] + $nameData = $this->prepareName(); + return (bool)$db->createCommand( + 'SELECT RELEASE_LOCK(' . $nameData[0] . '), :prefix', + array_merge( + [':name' => $this->hashLockName($name), ':prefix' => $this->keyPrefix], + $nameData[1] + ) )->queryScalar(); }); } + /** + * Prepare lock name + * @return array expression and params + * @since 2.0.48 + */ + protected function prepareName() + { + $params = []; + $expression = "SUBSTRING(CONCAT(:prefix, :name), 1, 64)"; + if ($this->keyPrefix instanceof Expression) { + $expression = strtr($expression, [':prefix' => $this->keyPrefix->expression]); + $params = $this->keyPrefix->params; + } + return [$expression, $params]; + } + /** * Generate hash for lock name to avoid exceeding lock name length limit. * @@ -101,7 +125,8 @@ class MysqlMutex extends DbMutex * @since 2.0.16 * @see https://github.com/yiisoft/yii2/pull/16836 */ - protected function hashLockName($name) { + protected function hashLockName($name) + { return sha1($name); } } diff --git a/tests/framework/mutex/MysqlMutexTest.php b/tests/framework/mutex/MysqlMutexTest.php index e3dfbfefbd..b90e92e51f 100644 --- a/tests/framework/mutex/MysqlMutexTest.php +++ b/tests/framework/mutex/MysqlMutexTest.php @@ -84,4 +84,27 @@ class MysqlMutexTest extends DatabaseTestCase $this->assertTrue($mutexOne->release($mutexName)); $this->assertTrue($mutexTwo->release($mutexName)); } + + /** + * @dataProvider mutexDataProvider() + * + * @param string $mutexName + */ + public function testThatMutexLocksWithKeyPrefixesExpressionCalculatedValue($mutexName) + { + $mutexOne = $this->createMutex(['keyPrefix' => new Expression('1+1')]); + $mutexTwo = $this->createMutex(['keyPrefix' => new Expression('1*2')]); + + $this->assertTrue($mutexOne->acquire($mutexName)); + $this->assertFalse($mutexTwo->acquire($mutexName)); + $this->assertTrue($mutexOne->release($mutexName)); + } + + public function testCreateMutex() + { + $mutex = $this->createMutex(['keyPrefix' => new Expression('1+1')]); + $this->assertInstanceOf(MysqlMutex::classname(), $mutex); + $this->assertInstanceOf(Expression::classname(), $mutex->keyPrefix); + $this->assertSame("1+1", $mutex->keyPrefix->expression); + } }