Skip to content

Commit

Permalink
resolve PREG_JIT_STACKLIMIT_ERROR error for larger queries
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Nemo64 committed Jan 6, 2021
1 parent 4d83370 commit 802c500
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 22 deletions.
33 changes: 20 additions & 13 deletions src/RdsDataParameterBag.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down
59 changes: 50 additions & 9 deletions tests/RdsDataParameterBagTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)) . ')'
],
];
}

Expand Down

0 comments on commit 802c500

Please sign in to comment.