From f950ea171eeb41a24e7a8a1def8c66ce4004b7dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Fri, 27 May 2022 00:31:18 +0200 Subject: [PATCH 1/6] simplify concat str --- src/Model/UserAction.php | 8 ++++---- src/Persistence/Sql/Query.php | 15 ++++----------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/Model/UserAction.php b/src/Model/UserAction.php index 6e6601efd..9d86bfe79 100644 --- a/src/Model/UserAction.php +++ b/src/Model/UserAction.php @@ -241,10 +241,10 @@ public function getConfirmation() if ($this->confirmation instanceof \Closure) { return ($this->confirmation)($this); } elseif ($this->confirmation === true) { - $confirmation = 'Are you sure you wish to execute '; - $confirmation .= $this->getCaption(); - $confirmation .= $this->getEntity()->getTitle() ? ' using ' . $this->getEntity()->getTitle() : ''; - $confirmation .= '?'; + $confirmation = 'Are you sure you wish to execute ' + . $this->getCaption() + . ($this->getEntity()->getTitle() ? ' using ' . $this->getEntity()->getTitle() : '') + . '?'; return $confirmation; } diff --git a/src/Persistence/Sql/Query.php b/src/Persistence/Sql/Query.php index 7df84bd12..58dc06f80 100644 --- a/src/Persistence/Sql/Query.php +++ b/src/Persistence/Sql/Query.php @@ -435,17 +435,10 @@ public function _render_join(): ?string } $joins = []; foreach ($this->args['join'] as $j) { - $jj = ''; - - $jj .= $j['t'] . ' join '; - - $jj .= $this->escapeIdentifierSoft($j['f1']); - - if ($j['fa'] !== null) { - $jj .= ' ' . $this->escapeIdentifier($j['fa']); - } - - $jj .= ' on '; + $jj = $j['t'] . ' join ' + . $this->escapeIdentifierSoft($j['f1']) + . ($j['fa'] !== null ? ' ' . $this->escapeIdentifier($j['fa']) : '') + . ' on '; if (isset($j['expr'])) { $jj .= $this->consume($j['expr']); From 78f6131be74dcd46ca1d3d8b614dcb583c000afa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Fri, 27 May 2022 10:58:39 +0200 Subject: [PATCH 2/6] fix jdorn/sql-formatter for insert SQL --- composer.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/composer.json b/composer.json index 45dd4b641..afb9af6e0 100644 --- a/composer.json +++ b/composer.json @@ -57,6 +57,9 @@ "suggest": { "jdorn/sql-formatter": "*" }, + "conflict": { + "jdorn/sql-formatter": "<1.2.9" + }, "minimum-stability": "dev", "prefer-stable": true, "autoload": { From 773b82fb5540f3231506a1ceb0eee14fd10ea3c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Fri, 27 May 2022 00:30:01 +0200 Subject: [PATCH 3/6] fix GH workflow typo --- .github/workflows/test-unit.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml index 5db9b2594..ccc53671e 100644 --- a/.github/workflows/test-unit.yml +++ b/.github/workflows/test-unit.yml @@ -221,7 +221,7 @@ jobs: if [ -n "$LOG_COVERAGE" ]; then mv coverage/phpunit.cov coverage/phpunit-mssql.cov; fi - name: "Run tests: Oracle - PDO (only for coverage or cron)" - if: (success() || failure()) && env.LOG_COVERAGE || github.event_name == 'schedule' + if: (success() || failure()) && (env.LOG_COVERAGE || github.event_name == 'schedule') env: DB_DSN: "pdo_oci:dbname=oracle/xe" DB_USER: system From 2ab7f65d37dadb4409c0b2873c1978f063054902 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Fri, 27 May 2022 00:55:01 +0200 Subject: [PATCH 4/6] Fix exception throwing for MSSQL TRY/CATCH SQL --- src/Persistence/Sql/Mssql/ExpressionTrait.php | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/Persistence/Sql/Mssql/ExpressionTrait.php b/src/Persistence/Sql/Mssql/ExpressionTrait.php index 38710d2ba..7c8bcbb6b 100644 --- a/src/Persistence/Sql/Mssql/ExpressionTrait.php +++ b/src/Persistence/Sql/Mssql/ExpressionTrait.php @@ -37,4 +37,41 @@ protected function hasNativeNamedParamSupport(): bool { return false; } + + /** + * Fix exception throwing for MSSQL TRY/CATCH SQL (for Query::$template_insert). + * + * Remove once https://github.com/microsoft/msphpsql/issues/1387 is fixed and released. + */ + public function execute(object $connection = null): \Doctrine\DBAL\Result + { + $templateStr = $this->template ?? 'select...'; // @phpstan-ignore-line + if (preg_match('~^\s*begin\s+try(.+?)end\s+try\s+begin\s+catch(.+)end\s+catch\s*$~is', $templateStr, $matches)) { + $templateTry = trim($matches[1]); + if (preg_match('~^\s+if ERROR_NUMBER\(\)\s*=\s*544\s+begin(.+)end\s+else\s+begin\s+throw;\s*end\s+$~is', $matches[2], $matches)) { + $templateCatch = trim($matches[1]); + } else { + throw new \Error('Unexpected MSSQL TRY/CATCH SQL'); + } + + try { + $thisCloned = clone $this; + $thisCloned->template = $templateTry; + + return $thisCloned->execute($connection); + } catch (\Exception $e) { + $eDbal = $e->getPrevious(); + if ($eDbal !== null && $eDbal instanceof \Doctrine\DBAL\Exception\DriverException && $eDbal->getCode() === 544) { + $thisCloned = clone $this; + $thisCloned->template = $templateCatch; + + return $thisCloned->execute($connection); + } + + throw $e; + } + } + + return parent::execute($connection); + } } From 1ef57e6ae53d8641772f0d5ab1d669614db0059d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Fri, 27 May 2022 02:51:01 +0200 Subject: [PATCH 5/6] also for inner --- src/Persistence/Sql/Mssql/ExpressionTrait.php | 58 +++++++++++++------ 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/src/Persistence/Sql/Mssql/ExpressionTrait.php b/src/Persistence/Sql/Mssql/ExpressionTrait.php index 7c8bcbb6b..7b7d3a21b 100644 --- a/src/Persistence/Sql/Mssql/ExpressionTrait.php +++ b/src/Persistence/Sql/Mssql/ExpressionTrait.php @@ -4,6 +4,9 @@ namespace Atk4\Data\Persistence\Sql\Mssql; +use Doctrine\DBAL\Exception\DriverException; +use Doctrine\DBAL\Result as DbalResult; + trait ExpressionTrait { private function fixOpenEscapeChar(string $v): string @@ -43,32 +46,49 @@ protected function hasNativeNamedParamSupport(): bool * * Remove once https://github.com/microsoft/msphpsql/issues/1387 is fixed and released. */ - public function execute(object $connection = null): \Doctrine\DBAL\Result + public function execute(object $connection = null): DbalResult { - $templateStr = $this->template ?? 'select...'; // @phpstan-ignore-line - if (preg_match('~^\s*begin\s+try(.+?)end\s+try\s+begin\s+catch(.+)end\s+catch\s*$~is', $templateStr, $matches)) { - $templateTry = trim($matches[1]); - if (preg_match('~^\s+if ERROR_NUMBER\(\)\s*=\s*544\s+begin(.+)end\s+else\s+begin\s+throw;\s*end\s+$~is', $matches[2], $matches)) { - $templateCatch = trim($matches[1]); + $templateStr = preg_replace('~^\s*begin\s+(.+)\s+end\s*$~is', '$1', $this->template ?? 'select...'); // @phpstan-ignore-line + if (preg_match('~^(.*?)begin\s+try(.+?)end\s+try\s+begin\s+catch(.+)end\s+catch(.*?)$~is', $templateStr, $matches)) { + $executeFx = function (string $template) use ($connection): DbalResult { + $thisCloned = clone $this; + $thisCloned->template = !str_contains(trim(trim($template), ';'), ';') + ? $template + : 'BEGIN' . "\n" . $template . "\n" . 'END'; + + return $thisCloned->execute($connection); + }; + + $templateBefore = trim($matches[1]); + $templateTry = trim($matches[2]); + $templateAfter = trim($matches[4]); + + if ($templateBefore === '' && $templateAfter === '' && preg_match('~^\s+if ERROR_NUMBER\(\)\s*=\s*544\s+begin\s*(.+?)\s*end\s+else\s+begin\s+throw;\s*end\s+$~is', $matches[3], $matches2)) { + $templateCatch = 'set IDENTITY_INSERT [table_noalias] on;' + . "\n" . 'insert[option] into [table_noalias] ([set_fields]) values ([set_values]);'; + $templateCatchFinally = 'set IDENTITY_INSERT [table_noalias] off;'; + $executeCatchFx = function (\Exception $e) use ($executeFx, $templateCatch, $templateCatchFinally): DbalResult { + $eDriver = $e->getPrevious(); + if ($eDriver !== null && $eDriver instanceof DriverException && $eDriver->getCode() === 544) { + try { + return $executeFx($templateCatch); + } finally { + $executeFx($templateCatchFinally); + } + } + + throw $e; + }; } else { - throw new \Error('Unexpected MSSQL TRY/CATCH SQL'); + throw new \Error('Unexpected MSSQL TRY/CATCH SQL: ' . $templateStr); } try { - $thisCloned = clone $this; - $thisCloned->template = $templateTry; + $res = $executeFx($templateTry); - return $thisCloned->execute($connection); + return $res; } catch (\Exception $e) { - $eDbal = $e->getPrevious(); - if ($eDbal !== null && $eDbal instanceof \Doctrine\DBAL\Exception\DriverException && $eDbal->getCode() === 544) { - $thisCloned = clone $this; - $thisCloned->template = $templateCatch; - - return $thisCloned->execute($connection); - } - - throw $e; + return $executeCatchFx($e); } } From 162ce88289535c6133b559dbd14665d5534f66bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Fri, 27 May 2022 11:27:43 +0200 Subject: [PATCH 6/6] cleanup --- src/Persistence/Sql/Mssql/ExpressionTrait.php | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/src/Persistence/Sql/Mssql/ExpressionTrait.php b/src/Persistence/Sql/Mssql/ExpressionTrait.php index 7b7d3a21b..3bc41de1b 100644 --- a/src/Persistence/Sql/Mssql/ExpressionTrait.php +++ b/src/Persistence/Sql/Mssql/ExpressionTrait.php @@ -48,7 +48,7 @@ protected function hasNativeNamedParamSupport(): bool */ public function execute(object $connection = null): DbalResult { - $templateStr = preg_replace('~^\s*begin\s+(.+)\s+end\s*$~is', '$1', $this->template ?? 'select...'); // @phpstan-ignore-line + $templateStr = preg_replace('~^\s*begin\s+(.+?)\s+end\s*$~is', '$1', $this->template ?? 'select...'); // @phpstan-ignore-line if (preg_match('~^(.*?)begin\s+try(.+?)end\s+try\s+begin\s+catch(.+)end\s+catch(.*?)$~is', $templateStr, $matches)) { $executeFx = function (string $template) use ($connection): DbalResult { $thisCloned = clone $this; @@ -63,17 +63,34 @@ public function execute(object $connection = null): DbalResult $templateTry = trim($matches[2]); $templateAfter = trim($matches[4]); - if ($templateBefore === '' && $templateAfter === '' && preg_match('~^\s+if ERROR_NUMBER\(\)\s*=\s*544\s+begin\s*(.+?)\s*end\s+else\s+begin\s+throw;\s*end\s+$~is', $matches[3], $matches2)) { - $templateCatch = 'set IDENTITY_INSERT [table_noalias] on;' - . "\n" . 'insert[option] into [table_noalias] ([set_fields]) values ([set_values]);'; - $templateCatchFinally = 'set IDENTITY_INSERT [table_noalias] off;'; - $executeCatchFx = function (\Exception $e) use ($executeFx, $templateCatch, $templateCatchFinally): DbalResult { + $expectedInsertTemplate = <<<'EOF' + begin try + insert[option] into [table_noalias] ([set_fields]) values ([set_values]); + end try begin catch + if ERROR_NUMBER() = 544 begin + set IDENTITY_INSERT [table_noalias] on; + begin try + insert[option] into [table_noalias] ([set_fields]) values ([set_values]); + set IDENTITY_INSERT [table_noalias] off; + end try begin catch + set IDENTITY_INSERT [table_noalias] off; + throw; + end catch + end else begin + throw; + end + end catch + EOF; + + if ($templateBefore === '' && $templateAfter === '' && $templateStr === $expectedInsertTemplate) { + $executeCatchFx = function (\Exception $e) use ($executeFx): DbalResult { $eDriver = $e->getPrevious(); if ($eDriver !== null && $eDriver instanceof DriverException && $eDriver->getCode() === 544) { try { - return $executeFx($templateCatch); + return $executeFx('set IDENTITY_INSERT [table_noalias] on;' + . "\n" . 'insert[option] into [table_noalias] ([set_fields]) values ([set_values]);'); } finally { - $executeFx($templateCatchFinally); + $executeFx('set IDENTITY_INSERT [table_noalias] off;'); } } @@ -84,9 +101,7 @@ public function execute(object $connection = null): DbalResult } try { - $res = $executeFx($templateTry); - - return $res; + return $executeFx($templateTry); } catch (\Exception $e) { return $executeCatchFx($e); }