Fix incorrect prefixing of CTE names and ON DUPLICATE KEY UPDATE ... #2587

This commit is contained in:
Kijin Sung 2025-07-21 00:57:47 +09:00
parent 8b8758f296
commit 796ecec247
2 changed files with 47 additions and 16 deletions

View file

@ -1113,15 +1113,31 @@ 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) {
$exceptions = [];
}
// Add prefixes to all other table names in the query string.
return preg_replace_callback('/\b((?:DELETE\s+)?FROM|JOIN|INTO|(?<!KEY\s)UPDATE)(?i)\s+((?:`?\w+`?)(?:\s+AS\s+`?\w+`?)?(?:\s*,\s*(?:`?\w+\`?)(?:\s+AS\s+`?\w+`?)?)*)/', function($m) use($exceptions) {
$type = strtoupper($m[1]);
$tables = array_map(function($str) use($type) {
$tables = array_map(function($str) use($type, $exceptions) {
$str = trim($str);
if (count($exceptions) && in_array(trim($str, '`'), $exceptions))
{
return $str;
}
return preg_replace_callback('/`?(\w+)`?(?:\s+AS\s+`?(\w+)`?)?/i', function($m) use($type) {
if ($type === 'FROM' || $type === 'JOIN')
{
@ -1131,12 +1147,11 @@ class DB
{
return isset($m[2]) ? sprintf('`%s%s` AS `%s`', $this->_prefix, $m[1], $m[2]) : sprintf('`%s%s`', $this->_prefix, $m[1]);
}
}, trim($str));
}, $str);
}, explode(',', $m[2]));
return $m[1] . ' ' . implode(', ', $tables);
}, $query_string);
}
}
/**
* Escape a string according to current DB settings.

View file

@ -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));