Commit 724a662f authored by Sergei Morozov's avatar Sergei Morozov

Optimize parameter conversion for Oracle. Handle quotes inside literals.

parent 87251a14
...@@ -121,27 +121,68 @@ class OCI8Statement implements \IteratorAggregate, Statement ...@@ -121,27 +121,68 @@ class OCI8Statement implements \IteratorAggregate, Statement
* @param string $statement The SQL statement to convert. * @param string $statement The SQL statement to convert.
* *
* @return string * @return string
* @throws \Doctrine\DBAL\Driver\OCI8\OCI8Exception
*/ */
static public function convertPositionalToNamedPlaceholders($statement) static public function convertPositionalToNamedPlaceholders($statement)
{ {
$count = 1;
$inLiteral = false; // a valid query never starts with quotes
$stmtLen = strlen($statement);
$paramMap = array(); $paramMap = array();
for ($i = 0; $i < $stmtLen; $i++) { $count = 1;
if ($statement[$i] == '?' && !$inLiteral) { $start = $offset = 0;
// real positional parameter detected $fragments = array();
$paramMap[$count] = ":param$count"; $quote = false;
$len = strlen($paramMap[$count]); $capture = function ($regex, $callback) use ($statement, $start, &$offset) {
$statement = substr_replace($statement, ":param$count", $i, 1); if (preg_match($regex, $statement, $matches, PREG_OFFSET_CAPTURE, $offset)) {
$i += $len-1; // jump ahead $offset = $matches[0][1];
$stmtLen = strlen($statement); // adjust statement length $callback($matches[0][0]);
++$count; return true;
} elseif ($statement[$i] == "'" || $statement[$i] == '"') { }
$inLiteral = ! $inLiteral; // switch state! return false;
};
do {
if (!$quote) {
$result = $capture('/[?\'"]/', function ($token) use (
$statement,
&$fragments,
&$start,
&$offset,
&$quote,
&$count,
&$paramMap
) {
if ($token == '?') {
$param = ':param' . $count;
$fragments[] = substr($statement, $start, $offset - $start);
$fragments[] = $param;
$paramMap[$count] = $param;
$start = ++$offset;
++$count;
} else {
$quote = $token;
++$offset;
}
});
} else {
$result = $capture('/(?<!\\\\)' . $quote . '/', function () use (&$offset, &$quote) {
$quote = false;
++$offset;
});
} }
} while ($result);
if ($quote) {
throw new OCI8Exception(sprintf(
'The statement contains non-terminated string literal starting at offset %d',
$offset - 1
));
}
if ($start < strlen($statement)) {
$fragments[] = substr($statement, $start);
} }
$statement = implode('', $fragments);
return array($statement, $paramMap); return array($statement, $paramMap);
} }
......
...@@ -2,7 +2,11 @@ ...@@ -2,7 +2,11 @@
namespace Doctrine\Tests\DBAL; namespace Doctrine\Tests\DBAL;
class OCI8StatementTest extends \Doctrine\Tests\DbalTestCase use Doctrine\DBAL\Driver\OCI8\OCI8Exception;
use Doctrine\DBAL\Driver\OCI8\OCI8Statement;
use Doctrine\Tests\DbalTestCase;
class OCI8StatementTest extends DbalTestCase
{ {
protected function setUp() protected function setUp()
{ {
...@@ -81,4 +85,52 @@ class OCI8StatementTest extends \Doctrine\Tests\DbalTestCase ...@@ -81,4 +85,52 @@ class OCI8StatementTest extends \Doctrine\Tests\DbalTestCase
); );
} }
/**
* @dataProvider convertSuccessProvider
*/
public function testConvertSuccess($statement, $expectedStatement, $expectedParamCount)
{
list($converted, $paramMap) = OCI8Statement::convertPositionalToNamedPlaceholders($statement);
$this->assertEquals($expectedStatement, $converted);
$this->assertCount($expectedParamCount, $paramMap);
}
public static function convertSuccessProvider()
{
return array(
'simple' => array(
'INSERT INTO table (column) VALUES(?)',
'INSERT INTO table (column) VALUES(:param1)',
1,
),
'literal-with-placeholder' => array(
"INSERT INTO table (col1, col2, col3) VALUES('?', ?, \"?\", ?)",
"INSERT INTO table (col1, col2, col3) VALUES('?', :param1, \"?\", :param2)",
2,
),
'literal-with-quotes' => array(
"INSERT INTO table (col1, col2, col3, col4) VALUES('?\"?\\'?', ?, \"?'?\\\"?\", ?)",
"INSERT INTO table (col1, col2, col3, col4) VALUES('?\"?\'?', :param1, \"?'?\\\"?\", :param2)",
2,
),
);
}
/**
* @dataProvider convertFailureProvider
*/
public function testConvertFailure($statement)
{
$this->expectException(OCI8Exception::class);
OCI8Statement::convertPositionalToNamedPlaceholders($statement);
}
public static function convertFailureProvider()
{
return array(
'non-terminated-literal' => array(
'SELECT "literal',
),
);
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment