|
4 | 4 |
|
5 | 5 | namespace Atk4\Data\Persistence\Sql\Mssql;
|
6 | 6 |
|
| 7 | +use Doctrine\DBAL\Exception\DriverException; |
| 8 | +use Doctrine\DBAL\Result as DbalResult; |
| 9 | + |
7 | 10 | trait ExpressionTrait
|
8 | 11 | {
|
9 | 12 | private function fixOpenEscapeChar(string $v): string
|
@@ -37,4 +40,73 @@ protected function hasNativeNamedParamSupport(): bool
|
37 | 40 | {
|
38 | 41 | return false;
|
39 | 42 | }
|
| 43 | + |
| 44 | + /** |
| 45 | + * Fix exception throwing for MSSQL TRY/CATCH SQL (for Query::$template_insert). |
| 46 | + * |
| 47 | + * Remove once https://github.com/microsoft/msphpsql/issues/1387 is fixed and released. |
| 48 | + */ |
| 49 | + public function execute(object $connection = null): DbalResult |
| 50 | + { |
| 51 | + $templateStr = preg_replace('~^\s*begin\s+(.+?)\s+end\s*$~is', '$1', $this->template ?? 'select...'); // @phpstan-ignore-line |
| 52 | + if (preg_match('~^(.*?)begin\s+try(.+?)end\s+try\s+begin\s+catch(.+)end\s+catch(.*?)$~is', $templateStr, $matches)) { |
| 53 | + $executeFx = function (string $template) use ($connection): DbalResult { |
| 54 | + $thisCloned = clone $this; |
| 55 | + $thisCloned->template = !str_contains(trim(trim($template), ';'), ';') |
| 56 | + ? $template |
| 57 | + : 'BEGIN' . "\n" . $template . "\n" . 'END'; |
| 58 | + |
| 59 | + return $thisCloned->execute($connection); |
| 60 | + }; |
| 61 | + |
| 62 | + $templateBefore = trim($matches[1]); |
| 63 | + $templateTry = trim($matches[2]); |
| 64 | + $templateAfter = trim($matches[4]); |
| 65 | + |
| 66 | + $expectedInsertTemplate = <<<'EOF' |
| 67 | + begin try |
| 68 | + insert[option] into [table_noalias] ([set_fields]) values ([set_values]); |
| 69 | + end try begin catch |
| 70 | + if ERROR_NUMBER() = 544 begin |
| 71 | + set IDENTITY_INSERT [table_noalias] on; |
| 72 | + begin try |
| 73 | + insert[option] into [table_noalias] ([set_fields]) values ([set_values]); |
| 74 | + set IDENTITY_INSERT [table_noalias] off; |
| 75 | + end try begin catch |
| 76 | + set IDENTITY_INSERT [table_noalias] off; |
| 77 | + throw; |
| 78 | + end catch |
| 79 | + end else begin |
| 80 | + throw; |
| 81 | + end |
| 82 | + end catch |
| 83 | + EOF; |
| 84 | + |
| 85 | + if ($templateBefore === '' && $templateAfter === '' && $templateStr === $expectedInsertTemplate) { |
| 86 | + $executeCatchFx = function (\Exception $e) use ($executeFx): DbalResult { |
| 87 | + $eDriver = $e->getPrevious(); |
| 88 | + if ($eDriver !== null && $eDriver instanceof DriverException && $eDriver->getCode() === 544) { |
| 89 | + try { |
| 90 | + return $executeFx('set IDENTITY_INSERT [table_noalias] on;' |
| 91 | + . "\n" . 'insert[option] into [table_noalias] ([set_fields]) values ([set_values]);'); |
| 92 | + } finally { |
| 93 | + $executeFx('set IDENTITY_INSERT [table_noalias] off;'); |
| 94 | + } |
| 95 | + } |
| 96 | + |
| 97 | + throw $e; |
| 98 | + }; |
| 99 | + } else { |
| 100 | + throw new \Error('Unexpected MSSQL TRY/CATCH SQL: ' . $templateStr); |
| 101 | + } |
| 102 | + |
| 103 | + try { |
| 104 | + return $executeFx($templateTry); |
| 105 | + } catch (\Exception $e) { |
| 106 | + return $executeCatchFx($e); |
| 107 | + } |
| 108 | + } |
| 109 | + |
| 110 | + return parent::execute($connection); |
| 111 | + } |
40 | 112 | }
|
0 commit comments