Skip to content

Commit 802c500

Browse files
committed
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.
1 parent 4d83370 commit 802c500

File tree

2 files changed

+70
-22
lines changed

2 files changed

+70
-22
lines changed

src/RdsDataParameterBag.php

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class RdsDataParameterBag
1111
* This expression can be used to find numeric parameters in an sql statement.
1212
* It understands strings and prevents matching within them.
1313
*/
14-
private const NUMERIC_PARAMETER_EXPRESSION = '/\?(?=([^\'"`]|\'([^\']|\\\\\')*\'|"([^"]|\\\\")*"|`([^`]|\\\\`)*`)*$)/';
14+
private const NUMERIC_PARAMETER_EXPRESSION = '/\?(?=([^\'"`]+|\'([^\']|\\\\\')*\'|"([^"]|\\\\")*"|`([^`]|\\\\`)*`)*$)/';
1515

1616
/**
1717
* @var array
@@ -89,20 +89,27 @@ public function getParameters(): array
8989
public function prepareSqlStatement(string $sql): string
9090
{
9191
$numericParameters = array_filter(array_keys($this->parameters), 'is_int');
92+
if (count($numericParameters) <= 0) {
93+
return $sql;
94+
}
9295

93-
if (count($numericParameters) > 0) {
94-
95-
// it is valid to start numeric parameters 0 and 1
96-
$index = min($numericParameters);
97-
if ($index !== 0 && $index !== 1) {
98-
throw new \LogicException("Numeric parameters must start with 0 or 1.");
99-
}
100-
101-
$createParameter = function () use (&$index) {
102-
return ':' . $index++;
103-
};
96+
// it is valid to start numeric parameters 0 and 1
97+
$index = min($numericParameters);
98+
if ($index !== 0 && $index !== 1) {
99+
throw new \LogicException("Numeric parameters must start with 0 or 1.");
100+
}
104101

105-
$sql = preg_replace_callback(self::NUMERIC_PARAMETER_EXPRESSION, $createParameter, $sql);
102+
$createParameter = static function () use (&$index) {
103+
return ':' . $index++;
104+
};
105+
106+
$sql = preg_replace_callback(self::NUMERIC_PARAMETER_EXPRESSION, $createParameter, $sql);
107+
if (!is_string($sql)) {
108+
// snipped from https://www.php.net/manual/de/function.preg-last-error.php#124124
109+
$pregError = array_flip(array_filter(get_defined_constants(true)['pcre'], function ($value) {
110+
return substr($value, -6) === '_ERROR';
111+
}, ARRAY_FILTER_USE_KEY))[preg_last_error()] ?? 'unknown error';
112+
throw new \RuntimeException("sql param replacement failed: $pregError");
106113
}
107114

108115
return $sql;

tests/RdsDataParameterBagTest.php

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,56 @@ class RdsDataParameterBagTest extends TestCase
1111
public static function sqlPreparation()
1212
{
1313
return [
14-
[[0 => 1], 'SELECT * FROM x WHERE y = ?', 'SELECT * FROM x WHERE y = :0'],
15-
[[1 => 1], 'SELECT * FROM x WHERE y = ?', 'SELECT * FROM x WHERE y = :1'],
16-
[[1 => 1], "SELECT * FROM x WHERE x = '?' AND y = ?", "SELECT * FROM x WHERE x = '?' AND y = :1"],
17-
[[1 => 1], "SELECT * FROM x WHERE x = ? AND y = '?'", "SELECT * FROM x WHERE x = :1 AND y = '?'"],
18-
[[1 => 1], "SELECT * FROM x WHERE x = ? AND y = `?`", "SELECT * FROM x WHERE x = :1 AND y = `?`"],
19-
[[1 => 1], 'SELECT * FROM x WHERE x = ? AND y = "?"', 'SELECT * FROM x WHERE x = :1 AND y = "?"'],
20-
[[1 => 1], "SELECT * FROM x WHERE x = ? AND y = '\\'?'", "SELECT * FROM x WHERE x = :1 AND y = '\\'?'"],
21-
[[1 => 1], "SELECT * FROM x WHERE x = '\\\\' AND y = ? AND z = '\\\\'", "SELECT * FROM x WHERE x = '\\\\' AND y = :1 AND z = '\\\\'"],
22-
[['foo' => 1], 'SELECT * FROM x WHERE x = ? AND y = "?"', 'SELECT * FROM x WHERE x = ? AND y = "?"'],
14+
[
15+
[0 => 1],
16+
'SELECT * FROM x WHERE y = ?',
17+
'SELECT * FROM x WHERE y = :0'
18+
],
19+
[
20+
[1 => 1],
21+
'SELECT * FROM x WHERE y = ?',
22+
'SELECT * FROM x WHERE y = :1'
23+
],
24+
[
25+
[1 => 1],
26+
"SELECT * FROM x WHERE x = '?' AND y = ?",
27+
"SELECT * FROM x WHERE x = '?' AND y = :1"
28+
],
29+
[
30+
[1 => 1],
31+
"SELECT * FROM x WHERE x = ? AND y = '?'",
32+
"SELECT * FROM x WHERE x = :1 AND y = '?'"
33+
],
34+
[
35+
[1 => 1],
36+
"SELECT * FROM x WHERE x = ? AND y = `?`",
37+
"SELECT * FROM x WHERE x = :1 AND y = `?`"
38+
],
39+
[
40+
[1 => 1],
41+
'SELECT * FROM x WHERE x = ? AND y = "?"',
42+
'SELECT * FROM x WHERE x = :1 AND y = "?"'
43+
],
44+
[
45+
[1 => 1],
46+
"SELECT * FROM x WHERE x = ? AND y = '\\'?'",
47+
"SELECT * FROM x WHERE x = :1 AND y = '\\'?'"
48+
],
49+
[
50+
[1 => 1],
51+
"SELECT * FROM x WHERE x = '\\\\' AND y = ? AND z = '\\\\'",
52+
"SELECT * FROM x WHERE x = '\\\\' AND y = :1 AND z = '\\\\'"
53+
],
54+
[
55+
['foo' => 1],
56+
'SELECT * FROM x WHERE x = ? AND y = "?"',
57+
'SELECT * FROM x WHERE x = ? AND y = "?"'
58+
],
59+
[
60+
[1 => 1],
61+
'SELECT * FROM x WHERE x = ? AND y IN (' . implode(',', range(0, 1500)) . ')',
62+
'SELECT * FROM x WHERE x = :1 AND y IN (' . implode(',', range(0, 1500)) . ')'
63+
],
2364
];
2465
}
2566

0 commit comments

Comments
 (0)