-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
18 changed files
with
1,030 additions
and
788 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<?php | ||
|
||
namespace Doctrine\DBAL\ArrayParameters; | ||
|
||
use Throwable; | ||
|
||
/** | ||
* @internal | ||
*/ | ||
interface Exception extends Throwable | ||
{ | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<?php | ||
|
||
namespace Doctrine\DBAL\ArrayParameters\Exception; | ||
|
||
use Doctrine\DBAL\ArrayParameters\Exception; | ||
use LogicException; | ||
|
||
use function sprintf; | ||
|
||
/** | ||
* @psalm-immutable | ||
*/ | ||
class MissingNamedParameter extends LogicException implements Exception | ||
{ | ||
public static function new(string $name): self | ||
{ | ||
return new self( | ||
sprintf('Named parameter "%s" does not have a bound value.', $name) | ||
); | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
src/ArrayParameters/Exception/MissingPositionalParameter.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
<?php | ||
|
||
namespace Doctrine\DBAL\ArrayParameters\Exception; | ||
|
||
use Doctrine\DBAL\ArrayParameters\Exception; | ||
use LogicException; | ||
|
||
use function sprintf; | ||
|
||
/** | ||
* @internal | ||
* | ||
* @psalm-immutable | ||
*/ | ||
class MissingPositionalParameter extends LogicException implements Exception | ||
{ | ||
public static function new(int $index): self | ||
{ | ||
return new self( | ||
sprintf('Positional parameter at index %d does not have a bound value.', $index) | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
158 changes: 26 additions & 132 deletions
158
src/Driver/OCI8/ConvertPositionalToNamedPlaceholders.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,164 +1,58 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Doctrine\DBAL\Driver\OCI8; | ||
|
||
use Doctrine\DBAL\Driver\Exception; | ||
use Doctrine\DBAL\Driver\OCI8\Exception\NonTerminatedStringLiteral; | ||
use Doctrine\DBAL\SQL\Parser\Visitor; | ||
|
||
use function count; | ||
use function implode; | ||
use function preg_match; | ||
use function preg_quote; | ||
use function substr; | ||
|
||
use const PREG_OFFSET_CAPTURE; | ||
|
||
/** | ||
* Converts positional (?) into named placeholders (:param<num>). | ||
* | ||
* Oracle does not support positional parameters, hence this method converts all | ||
* positional parameters into artificially named parameters. Note that this conversion | ||
* is not perfect. All question marks (?) in the original statement are treated as | ||
* placeholders and converted to a named parameter. | ||
* positional parameters into artificially named parameters. | ||
* | ||
* @internal This class is not covered by the backward compatibility promise | ||
*/ | ||
final class ConvertPositionalToNamedPlaceholders | ||
final class ConvertPositionalToNamedPlaceholders implements Visitor | ||
{ | ||
/** | ||
* @param string $statement The SQL statement to convert. | ||
* | ||
* @return mixed[] [0] => the statement value (string), [1] => the paramMap value (array). | ||
* | ||
* @throws Exception | ||
*/ | ||
public function __invoke(string $statement): array | ||
{ | ||
$fragmentOffset = $tokenOffset = 0; | ||
$fragments = $paramMap = []; | ||
$currentLiteralDelimiter = null; | ||
|
||
do { | ||
if ($currentLiteralDelimiter === null) { | ||
$result = $this->findPlaceholderOrOpeningQuote( | ||
$statement, | ||
$tokenOffset, | ||
$fragmentOffset, | ||
$fragments, | ||
$currentLiteralDelimiter, | ||
$paramMap | ||
); | ||
} else { | ||
$result = $this->findClosingQuote($statement, $tokenOffset, $currentLiteralDelimiter); | ||
} | ||
} while ($result); | ||
/** @var list<string> */ | ||
private $buffer = []; | ||
|
||
if ($currentLiteralDelimiter !== null) { | ||
throw NonTerminatedStringLiteral::new($tokenOffset - 1); | ||
} | ||
/** @var array<int,string> */ | ||
private $parameterMap = []; | ||
|
||
$fragments[] = substr($statement, $fragmentOffset); | ||
$statement = implode('', $fragments); | ||
|
||
return [$statement, $paramMap]; | ||
public function acceptOther(string $sql): void | ||
{ | ||
$this->buffer[] = $sql; | ||
} | ||
|
||
/** | ||
* Finds next placeholder or opening quote. | ||
* | ||
* @param string $statement The SQL statement to parse | ||
* @param int $tokenOffset The offset to start searching from | ||
* @param int $fragmentOffset The offset to build the next fragment from | ||
* @param string[] $fragments Fragments of the original statement not containing placeholders | ||
* @param string|null $currentLiteralDelimiter The delimiter of the current string literal | ||
* or NULL if not currently in a literal | ||
* @param string[] $paramMap Mapping of the original parameter positions | ||
* to their named replacements | ||
* | ||
* @return bool Whether the token was found | ||
*/ | ||
private function findPlaceholderOrOpeningQuote( | ||
string $statement, | ||
int &$tokenOffset, | ||
int &$fragmentOffset, | ||
array &$fragments, | ||
?string &$currentLiteralDelimiter, | ||
array &$paramMap | ||
): bool { | ||
$token = $this->findToken($statement, $tokenOffset, '/[?\'"]/'); | ||
|
||
if ($token === null) { | ||
return false; | ||
} | ||
|
||
if ($token === '?') { | ||
$position = count($paramMap) + 1; | ||
$param = ':param' . $position; | ||
$fragments[] = substr($statement, $fragmentOffset, $tokenOffset - $fragmentOffset); | ||
$fragments[] = $param; | ||
$paramMap[$position] = $param; | ||
$tokenOffset += 1; | ||
$fragmentOffset = $tokenOffset; | ||
|
||
return true; | ||
} | ||
public function acceptPositionalParameter(string $sql): void | ||
{ | ||
$position = count($this->parameterMap) + 1; | ||
$param = ':param' . $position; | ||
|
||
$currentLiteralDelimiter = $token; | ||
++$tokenOffset; | ||
$this->parameterMap[$position] = $param; | ||
|
||
return true; | ||
$this->buffer[] = $param; | ||
} | ||
|
||
/** | ||
* Finds closing quote | ||
* | ||
* @param string $statement The SQL statement to parse | ||
* @param int $tokenOffset The offset to start searching from | ||
* @param string $currentLiteralDelimiter The delimiter of the current string literal | ||
* | ||
* @return bool Whether the token was found | ||
*/ | ||
private function findClosingQuote( | ||
string $statement, | ||
int &$tokenOffset, | ||
string &$currentLiteralDelimiter | ||
): bool { | ||
$token = $this->findToken( | ||
$statement, | ||
$tokenOffset, | ||
'/' . preg_quote($currentLiteralDelimiter, '/') . '/' | ||
); | ||
|
||
if ($token === null) { | ||
return false; | ||
} | ||
|
||
$currentLiteralDelimiter = null; | ||
++$tokenOffset; | ||
public function acceptNamedParameter(string $sql): void | ||
{ | ||
$this->buffer[] = $sql; | ||
} | ||
|
||
return true; | ||
public function getSQL(): string | ||
{ | ||
return implode('', $this->buffer); | ||
} | ||
|
||
/** | ||
* Finds the token described by regex starting from the given offset. Updates the offset with the position | ||
* where the token was found. | ||
* | ||
* @param string $statement The SQL statement to parse | ||
* @param int $offset The offset to start searching from | ||
* @param string $regex The regex containing token pattern | ||
* | ||
* @return string|null Token or NULL if not found | ||
* @return array<int,string> | ||
*/ | ||
private function findToken(string $statement, int &$offset, string $regex): ?string | ||
public function getParameterMap(): array | ||
{ | ||
if (preg_match($regex, $statement, $matches, PREG_OFFSET_CAPTURE, $offset) === 1) { | ||
$offset = $matches[0][1]; | ||
|
||
return $matches[0][0]; | ||
} | ||
|
||
return null; | ||
return $this->parameterMap; | ||
} | ||
} |
Oops, something went wrong.