From 796ecec247a70e371ce02733e755f4f6ec788c44 Mon Sep 17 00:00:00 2001 From: Kijin Sung Date: Mon, 21 Jul 2025 00:57:47 +0900 Subject: [PATCH] Fix incorrect prefixing of CTE names and ON DUPLICATE KEY UPDATE ... #2587 --- common/framework/DB.php | 47 ++++++++++++++++++++++----------- tests/unit/framework/DBTest.php | 16 +++++++++++ 2 files changed, 47 insertions(+), 16 deletions(-) diff --git a/common/framework/DB.php b/common/framework/DB.php index f01339d36..3013fd803 100644 --- a/common/framework/DB.php +++ b/common/framework/DB.php @@ -1113,29 +1113,44 @@ class DB */ public function addPrefixes(string $query_string): string { + // Return early if no prefix is set. if (!$this->_prefix) { return $query_string; } + + // Generate a list of common table expressions (CTEs) to exclude from prefixing. + if (preg_match_all('/\bWITH(?:\s+RECURSIVE)?\s+`?(\w+)`?\s+AS\b/', $query_string, $matches)) + { + $exceptions = $matches[1]; + } else { - return preg_replace_callback('/((?:DELETE\s+)?FROM|JOIN|INTO|UPDATE)(?i)\s+((?:`?\w+\`?)(?:\s+AS\s+`?\w+`?)?(?:\s*,\s*(?:`?\w+\`?)(?:\s+AS\s+`?\w+`?)?)*)/', function($m) { - $type = strtoupper($m[1]); - $tables = array_map(function($str) use($type) { - return preg_replace_callback('/`?(\w+)`?(?:\s+AS\s+`?(\w+)`?)?/i', function($m) use($type) { - if ($type === 'FROM' || $type === 'JOIN') - { - return isset($m[2]) ? sprintf('`%s%s` AS `%s`', $this->_prefix, $m[1], $m[2]) : sprintf('`%s%s` AS `%s`', $this->_prefix, $m[1], $m[1]); - } - else - { - return isset($m[2]) ? sprintf('`%s%s` AS `%s`', $this->_prefix, $m[1], $m[2]) : sprintf('`%s%s`', $this->_prefix, $m[1]); - } - }, trim($str)); - }, explode(',', $m[2])); - return $m[1] . ' ' . implode(', ', $tables); - }, $query_string); + $exceptions = []; } + + // Add prefixes to all other table names in the query string. + return preg_replace_callback('/\b((?:DELETE\s+)?FROM|JOIN|INTO|(?_prefix, $m[1], $m[2]) : sprintf('`%s%s` AS `%s`', $this->_prefix, $m[1], $m[1]); + } + else + { + return isset($m[2]) ? sprintf('`%s%s` AS `%s`', $this->_prefix, $m[1], $m[2]) : sprintf('`%s%s`', $this->_prefix, $m[1]); + } + }, $str); + }, explode(',', $m[2])); + return $m[1] . ' ' . implode(', ', $tables); + }, $query_string); } /** diff --git a/tests/unit/framework/DBTest.php b/tests/unit/framework/DBTest.php index 41c5ccacc..cf1495224 100644 --- a/tests/unit/framework/DBTest.php +++ b/tests/unit/framework/DBTest.php @@ -159,6 +159,22 @@ class DBTest extends \Codeception\Test\Unit $target = 'DELETE FROM `' . $prefix . 'documents` WHERE d = ?'; $this->assertEquals($target, $oDB->addPrefixes($source)); + $source = 'WITH cte AS (SELECT * FROM documents) SELECT * FROM cte WHERE document_srl = ?'; + $target = 'WITH cte AS (SELECT * FROM `' . $prefix . 'documents` AS `documents`) SELECT * FROM cte WHERE document_srl = ?'; + $this->assertEquals($target, $oDB->addPrefixes($source)); + + $source = 'WITH RECURSIVE cte AS (SELECT * FROM documents INNER JOIN `cte`) SELECT * FROM cte JOIN member on cte.member_srl = member.member_srl'; + $target = 'WITH RECURSIVE cte AS (SELECT * FROM `' . $prefix . 'documents` AS `documents` INNER JOIN `cte`) SELECT * FROM cte JOIN `rx_member` AS `member` on cte.member_srl = member.member_srl'; + $this->assertEquals($target, $oDB->addPrefixes($source)); + + $source = 'WITH RECURSIVE `cte` AS (SELECT * FROM cte) SELECT * FROM cte WHERE a = ?'; + $target = 'WITH RECURSIVE `cte` AS (SELECT * FROM cte) SELECT * FROM cte WHERE a = ?'; + $this->assertEquals($target, $oDB->addPrefixes($source)); + + $source = 'INSERT INTO documents (a, b, c) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE b = ?, c = ?'; + $target = 'INSERT INTO `' . $prefix . 'documents` (a, b, c) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE b = ?, c = ?'; + $this->assertEquals($target, $oDB->addPrefixes($source)); + $source = 'update documents set a = ?, b = ? where c = ?'; $this->assertEquals($source, $oDB->addPrefixes($source));