| 1: | <?php |
| 2: | |
| 3: | declare(strict_types=1); |
| 4: | |
| 5: | namespace Atk4\Data\Persistence\Sql\Mssql; |
| 6: | |
| 7: | use Atk4\Data\Persistence\Sql\ExecuteException; |
| 8: | use Doctrine\DBAL\Connection as DbalConnection; |
| 9: | use Doctrine\DBAL\Driver\PDO\Exception as DbalDriverPdoException; |
| 10: | use Doctrine\DBAL\Driver\PDO\Result as DbalDriverPdoResult; |
| 11: | use Doctrine\DBAL\Result as DbalResult; |
| 12: | |
| 13: | trait ExpressionTrait |
| 14: | { |
| 15: | #[\Override] |
| 16: | public function render(): array |
| 17: | { |
| 18: | [$sql, $params] = parent::render(); |
| 19: | |
| 20: | |
| 21: | $sql = preg_replace_callback( |
| 22: | '~(?!\')' . self::QUOTED_TOKEN_REGEX . '\K|N?' . self::QUOTED_TOKEN_REGEX . '~', |
| 23: | static function ($matches) { |
| 24: | if ($matches[0] === '') { |
| 25: | return ''; |
| 26: | } |
| 27: | |
| 28: | return (substr($matches[0], 0, 1) === 'N' ? '' : 'N') . $matches[0]; |
| 29: | }, |
| 30: | $sql |
| 31: | ); |
| 32: | |
| 33: | return [$sql, $params]; |
| 34: | } |
| 35: | |
| 36: | #[\Override] |
| 37: | protected function hasNativeNamedParamSupport(): bool |
| 38: | { |
| 39: | return false; |
| 40: | } |
| 41: | |
| 42: | #[\Override] |
| 43: | protected function updateRenderBeforeExecute(array $render): array |
| 44: | { |
| 45: | [$sql, $params] = $render; |
| 46: | |
| 47: | $sql = preg_replace_callback( |
| 48: | '~' . self::QUOTED_TOKEN_REGEX . '\K|:\w+~', |
| 49: | static function ($matches) use ($params) { |
| 50: | if ($matches[0] === '') { |
| 51: | return ''; |
| 52: | } |
| 53: | |
| 54: | $sql = $matches[0]; |
| 55: | $value = $params[$sql]; |
| 56: | |
| 57: | |
| 58: | |
| 59: | if (is_float($value)) { |
| 60: | $sql = 'cast(' . $sql . ' as DOUBLE PRECISION)'; |
| 61: | } |
| 62: | |
| 63: | return $sql; |
| 64: | }, |
| 65: | $sql |
| 66: | ); |
| 67: | |
| 68: | return parent::updateRenderBeforeExecute([$sql, $params]); |
| 69: | } |
| 70: | |
| 71: | #[\Override] |
| 72: | protected function _execute(?object $connection, bool $fromExecuteStatement) |
| 73: | { |
| 74: | |
| 75: | |
| 76: | if ($fromExecuteStatement && $connection instanceof DbalConnection) { |
| 77: | |
| 78: | $result = $this->_execute($connection, false); |
| 79: | |
| 80: | $driverResult = \Closure::bind(static fn (): DbalDriverPdoResult => $result->result, null, DbalResult::class)(); |
| 81: | $driverPdoResult = \Closure::bind(static fn () => $driverResult->statement, null, DbalDriverPdoResult::class)(); |
| 82: | try { |
| 83: | while ($driverPdoResult->nextRowset()); |
| 84: | } catch (\PDOException $e) { |
| 85: | $e = $connection->convertException(DbalDriverPdoException::new($e)); |
| 86: | |
| 87: | $firstException = $e; |
| 88: | while ($firstException->getPrevious() !== null) { |
| 89: | $firstException = $firstException->getPrevious(); |
| 90: | } |
| 91: | $errorInfo = $firstException instanceof \PDOException ? $firstException->errorInfo : null; |
| 92: | |
| 93: | $eNew = (new ExecuteException('Dsql execute error', $errorInfo[1] ?? $e->getCode(), $e)); |
| 94: | if ($errorInfo !== null && $errorInfo !== []) { |
| 95: | $eNew->addMoreInfo('error', $errorInfo[2] ?? 'n/a (' . $errorInfo[0] . ')'); |
| 96: | } |
| 97: | $eNew->addMoreInfo('query', $this->getDebugQuery()); |
| 98: | |
| 99: | throw $eNew; |
| 100: | } |
| 101: | |
| 102: | return $result->rowCount(); |
| 103: | } |
| 104: | |
| 105: | return parent::_execute($connection, $fromExecuteStatement); |
| 106: | } |
| 107: | } |
| 108: | |