Skip to content

Commit 26832e7

Browse files
authored
Fix exception throwing for MSSQL insert queries (#993)
1 parent a647594 commit 26832e7

File tree

5 files changed

+84
-16
lines changed

5 files changed

+84
-16
lines changed

.github/workflows/test-unit.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ jobs:
221221
if [ -n "$LOG_COVERAGE" ]; then mv coverage/phpunit.cov coverage/phpunit-mssql.cov; fi
222222
223223
- name: "Run tests: Oracle - PDO (only for coverage or cron)"
224-
if: (success() || failure()) && env.LOG_COVERAGE || github.event_name == 'schedule'
224+
if: (success() || failure()) && (env.LOG_COVERAGE || github.event_name == 'schedule')
225225
env:
226226
DB_DSN: "pdo_oci:dbname=oracle/xe"
227227
DB_USER: system

composer.json

+3
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@
5757
"suggest": {
5858
"jdorn/sql-formatter": "*"
5959
},
60+
"conflict": {
61+
"jdorn/sql-formatter": "<1.2.9"
62+
},
6063
"minimum-stability": "dev",
6164
"prefer-stable": true,
6265
"autoload": {

src/Model/UserAction.php

+4-4
Original file line numberDiff line numberDiff line change
@@ -241,10 +241,10 @@ public function getConfirmation()
241241
if ($this->confirmation instanceof \Closure) {
242242
return ($this->confirmation)($this);
243243
} elseif ($this->confirmation === true) {
244-
$confirmation = 'Are you sure you wish to execute ';
245-
$confirmation .= $this->getCaption();
246-
$confirmation .= $this->getEntity()->getTitle() ? ' using ' . $this->getEntity()->getTitle() : '';
247-
$confirmation .= '?';
244+
$confirmation = 'Are you sure you wish to execute '
245+
. $this->getCaption()
246+
. ($this->getEntity()->getTitle() ? ' using ' . $this->getEntity()->getTitle() : '')
247+
. '?';
248248

249249
return $confirmation;
250250
}

src/Persistence/Sql/Mssql/ExpressionTrait.php

+72
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
namespace Atk4\Data\Persistence\Sql\Mssql;
66

7+
use Doctrine\DBAL\Exception\DriverException;
8+
use Doctrine\DBAL\Result as DbalResult;
9+
710
trait ExpressionTrait
811
{
912
private function fixOpenEscapeChar(string $v): string
@@ -37,4 +40,73 @@ protected function hasNativeNamedParamSupport(): bool
3740
{
3841
return false;
3942
}
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+
}
40112
}

src/Persistence/Sql/Query.php

+4-11
Original file line numberDiff line numberDiff line change
@@ -435,17 +435,10 @@ public function _render_join(): ?string
435435
}
436436
$joins = [];
437437
foreach ($this->args['join'] as $j) {
438-
$jj = '';
439-
440-
$jj .= $j['t'] . ' join ';
441-
442-
$jj .= $this->escapeIdentifierSoft($j['f1']);
443-
444-
if ($j['fa'] !== null) {
445-
$jj .= ' ' . $this->escapeIdentifier($j['fa']);
446-
}
447-
448-
$jj .= ' on ';
438+
$jj = $j['t'] . ' join '
439+
. $this->escapeIdentifierSoft($j['f1'])
440+
. ($j['fa'] !== null ? ' ' . $this->escapeIdentifier($j['fa']) : '')
441+
. ' on ';
449442

450443
if (isset($j['expr'])) {
451444
$jj .= $this->consume($j['expr']);

0 commit comments

Comments
 (0)