From 802c500a650f4f58b0e3030037d1adbc9cf11a31 Mon Sep 17 00:00:00 2001 From: Marco Pfeiffer Date: Wed, 6 Jan 2021 12:39:16 +0100 Subject: [PATCH] resolve PREG_JIT_STACKLIMIT_ERROR error for larger queries it could happen that large emulated queries fail because the regular expression that checked if a question mark is outside a string used a capture group that went though the rest of the query character by character. I could optimize it to read larger batches at once. --- src/RdsDataParameterBag.php | 33 ++++++++++------- tests/RdsDataParameterBagTest.php | 59 ++++++++++++++++++++++++++----- 2 files changed, 70 insertions(+), 22 deletions(-) diff --git a/src/RdsDataParameterBag.php b/src/RdsDataParameterBag.php index 126b4ea..dfaf544 100644 --- a/src/RdsDataParameterBag.php +++ b/src/RdsDataParameterBag.php @@ -11,7 +11,7 @@ class RdsDataParameterBag * This expression can be used to find numeric parameters in an sql statement. * It understands strings and prevents matching within them. */ - private const NUMERIC_PARAMETER_EXPRESSION = '/\?(?=([^\'"`]|\'([^\']|\\\\\')*\'|"([^"]|\\\\")*"|`([^`]|\\\\`)*`)*$)/'; + private const NUMERIC_PARAMETER_EXPRESSION = '/\?(?=([^\'"`]+|\'([^\']|\\\\\')*\'|"([^"]|\\\\")*"|`([^`]|\\\\`)*`)*$)/'; /** * @var array @@ -89,20 +89,27 @@ public function getParameters(): array public function prepareSqlStatement(string $sql): string { $numericParameters = array_filter(array_keys($this->parameters), 'is_int'); + if (count($numericParameters) <= 0) { + return $sql; + } - if (count($numericParameters) > 0) { - - // it is valid to start numeric parameters 0 and 1 - $index = min($numericParameters); - if ($index !== 0 && $index !== 1) { - throw new \LogicException("Numeric parameters must start with 0 or 1."); - } - - $createParameter = function () use (&$index) { - return ':' . $index++; - }; + // it is valid to start numeric parameters 0 and 1 + $index = min($numericParameters); + if ($index !== 0 && $index !== 1) { + throw new \LogicException("Numeric parameters must start with 0 or 1."); + } - $sql = preg_replace_callback(self::NUMERIC_PARAMETER_EXPRESSION, $createParameter, $sql); + $createParameter = static function () use (&$index) { + return ':' . $index++; + }; + + $sql = preg_replace_callback(self::NUMERIC_PARAMETER_EXPRESSION, $createParameter, $sql); + if (!is_string($sql)) { + // snipped from https://www.php.net/manual/de/function.preg-last-error.php#124124 + $pregError = array_flip(array_filter(get_defined_constants(true)['pcre'], function ($value) { + return substr($value, -6) === '_ERROR'; + }, ARRAY_FILTER_USE_KEY))[preg_last_error()] ?? 'unknown error'; + throw new \RuntimeException("sql param replacement failed: $pregError"); } return $sql; diff --git a/tests/RdsDataParameterBagTest.php b/tests/RdsDataParameterBagTest.php index 92f8626..60a6a58 100644 --- a/tests/RdsDataParameterBagTest.php +++ b/tests/RdsDataParameterBagTest.php @@ -11,15 +11,56 @@ class RdsDataParameterBagTest extends TestCase public static function sqlPreparation() { return [ - [[0 => 1], 'SELECT * FROM x WHERE y = ?', 'SELECT * FROM x WHERE y = :0'], - [[1 => 1], 'SELECT * FROM x WHERE y = ?', 'SELECT * FROM x WHERE y = :1'], - [[1 => 1], "SELECT * FROM x WHERE x = '?' AND y = ?", "SELECT * FROM x WHERE x = '?' AND y = :1"], - [[1 => 1], "SELECT * FROM x WHERE x = ? AND y = '?'", "SELECT * FROM x WHERE x = :1 AND y = '?'"], - [[1 => 1], "SELECT * FROM x WHERE x = ? AND y = `?`", "SELECT * FROM x WHERE x = :1 AND y = `?`"], - [[1 => 1], 'SELECT * FROM x WHERE x = ? AND y = "?"', 'SELECT * FROM x WHERE x = :1 AND y = "?"'], - [[1 => 1], "SELECT * FROM x WHERE x = ? AND y = '\\'?'", "SELECT * FROM x WHERE x = :1 AND y = '\\'?'"], - [[1 => 1], "SELECT * FROM x WHERE x = '\\\\' AND y = ? AND z = '\\\\'", "SELECT * FROM x WHERE x = '\\\\' AND y = :1 AND z = '\\\\'"], - [['foo' => 1], 'SELECT * FROM x WHERE x = ? AND y = "?"', 'SELECT * FROM x WHERE x = ? AND y = "?"'], + [ + [0 => 1], + 'SELECT * FROM x WHERE y = ?', + 'SELECT * FROM x WHERE y = :0' + ], + [ + [1 => 1], + 'SELECT * FROM x WHERE y = ?', + 'SELECT * FROM x WHERE y = :1' + ], + [ + [1 => 1], + "SELECT * FROM x WHERE x = '?' AND y = ?", + "SELECT * FROM x WHERE x = '?' AND y = :1" + ], + [ + [1 => 1], + "SELECT * FROM x WHERE x = ? AND y = '?'", + "SELECT * FROM x WHERE x = :1 AND y = '?'" + ], + [ + [1 => 1], + "SELECT * FROM x WHERE x = ? AND y = `?`", + "SELECT * FROM x WHERE x = :1 AND y = `?`" + ], + [ + [1 => 1], + 'SELECT * FROM x WHERE x = ? AND y = "?"', + 'SELECT * FROM x WHERE x = :1 AND y = "?"' + ], + [ + [1 => 1], + "SELECT * FROM x WHERE x = ? AND y = '\\'?'", + "SELECT * FROM x WHERE x = :1 AND y = '\\'?'" + ], + [ + [1 => 1], + "SELECT * FROM x WHERE x = '\\\\' AND y = ? AND z = '\\\\'", + "SELECT * FROM x WHERE x = '\\\\' AND y = :1 AND z = '\\\\'" + ], + [ + ['foo' => 1], + 'SELECT * FROM x WHERE x = ? AND y = "?"', + 'SELECT * FROM x WHERE x = ? AND y = "?"' + ], + [ + [1 => 1], + 'SELECT * FROM x WHERE x = ? AND y IN (' . implode(',', range(0, 1500)) . ')', + 'SELECT * FROM x WHERE x = :1 AND y IN (' . implode(',', range(0, 1500)) . ')' + ], ]; }