From a71f78ed6709ada476b6bb6349451912c6234c01 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 12 May 2020 00:55:52 +0200 Subject: [PATCH 01/12] added funding.yml --- .github/funding.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/funding.yml diff --git a/.github/funding.yml b/.github/funding.yml new file mode 100644 index 000000000..25adc9520 --- /dev/null +++ b/.github/funding.yml @@ -0,0 +1,2 @@ +github: dg +custom: "https://nette.org/donate" From cdc4617be61004e6b0a1c96b4d5cfc9bc9085484 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 27 Mar 2020 12:36:58 +0100 Subject: [PATCH 02/12] opened 3.1-dev --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 46ebba32d..4380a83d9 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,7 @@ }, "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "3.1-dev" } } } From 367ae8a3f574fabf228311589f9704e04134825a Mon Sep 17 00:00:00 2001 From: David Grudl Date: Sun, 2 Aug 2020 18:54:19 +0200 Subject: [PATCH 03/12] requires PHP 7.2 --- .travis.yml | 1 - appveyor.yml | 2 +- composer.json | 2 +- readme.md | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3953da4f4..c963ec907 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: php php: - - 7.1 - 7.2 - 7.3 - 7.4 diff --git a/appveyor.yml b/appveyor.yml index 91ad14250..19c551fd4 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -22,7 +22,7 @@ install: - IF EXIST c:\php (SET PHP=0) ELSE (SET PHP=1) - IF %PHP%==1 mkdir c:\php - IF %PHP%==1 cd c:\php - - IF %PHP%==1 curl https://windows.php.net/downloads/releases/archives/php-7.1.5-Win32-VC14-x64.zip --output php.zip + - IF %PHP%==1 curl https://windows.php.net/downloads/releases/archives/php-7.2.28-Win32-VC15-x64.zip --output php.zip - IF %PHP%==1 7z x php.zip >nul - IF %PHP%==1 echo extension_dir=ext >> php.ini - IF %PHP%==1 echo extension=php_openssl.dll >> php.ini diff --git a/composer.json b/composer.json index 4380a83d9..2278b57c8 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": ">=7.1", + "php": ">=7.2", "ext-pdo": "*", "nette/caching": "^3.0", "nette/utils": "^3.1" diff --git a/readme.md b/readme.md index ed8367cdc..9d0f9ca41 100644 --- a/readme.md +++ b/readme.md @@ -33,7 +33,7 @@ The recommended way to install is via Composer: composer require nette/database ``` -It requires PHP version 7.1 and supports PHP up to 7.4. +It requires PHP version 7.2 and supports PHP up to 7.4. Usage From a215a87135a0be9d96c21efc217c701888358dd7 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 27 Mar 2020 12:32:01 +0100 Subject: [PATCH 04/12] Connection, Context: added transaction() --- src/Database/Connection.php | 17 +++++++++++++++++ src/Database/Context.php | 9 +++++++++ tests/Database/Context.transaction.phpt | 12 ++++++++++++ 3 files changed, 38 insertions(+) diff --git a/src/Database/Connection.php b/src/Database/Connection.php index 18ffe26ac..7b589983e 100644 --- a/src/Database/Connection.php +++ b/src/Database/Connection.php @@ -152,6 +152,23 @@ public function rollBack(): void } + /** + * @return mixed + */ + public function transaction(callable $callback) + { + $this->beginTransaction(); + try { + $res = $callback(); + } catch (\Throwable $e) { + $this->rollBack(); + throw $e; + } + $this->commit(); + return $res; + } + + /** * Generates and executes SQL query. */ diff --git a/src/Database/Context.php b/src/Database/Context.php index 280bd16f5..9cf68c1b4 100644 --- a/src/Database/Context.php +++ b/src/Database/Context.php @@ -60,6 +60,15 @@ public function rollBack(): void } + /** + * @return mixed + */ + public function transaction(callable $callback) + { + return $this->connection->transaction($callback); + } + + public function getInsertId(string $sequence = null): string { return $this->connection->getInsertId($sequence); diff --git a/tests/Database/Context.transaction.phpt b/tests/Database/Context.transaction.phpt index c5d293845..b5f9f4a3e 100644 --- a/tests/Database/Context.transaction.phpt +++ b/tests/Database/Context.transaction.phpt @@ -23,6 +23,18 @@ test(function () use ($context) { }); +test(function () use ($context) { + Assert::exception(function () use ($context) { + $context->transaction(function () use ($context) { + $context->query('DELETE FROM book'); + throw new Exception('my exception'); + }); + }, Exception::class, 'my exception'); + + Assert::same(3, $context->fetchField('SELECT id FROM book WHERE id = ', 3)); +}); + + test(function () use ($context) { $context->beginTransaction(); $context->query('DELETE FROM book'); From 9ba35d1ed2db46bf08c8f33554159c69535b426d Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 26 Mar 2020 23:47:17 +0100 Subject: [PATCH 05/12] some internal callbacks changed to private --- src/Bridges/DatabaseTracy/ConnectionPanel.php | 4 ++-- src/Database/SqlPreprocessor.php | 5 ++--- src/Database/Structure.php | 7 ++----- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/Bridges/DatabaseTracy/ConnectionPanel.php b/src/Bridges/DatabaseTracy/ConnectionPanel.php index 0297b7c8a..f1cdab212 100644 --- a/src/Bridges/DatabaseTracy/ConnectionPanel.php +++ b/src/Bridges/DatabaseTracy/ConnectionPanel.php @@ -46,11 +46,11 @@ class ConnectionPanel implements Tracy\IBarPanel public function __construct(Connection $connection) { - $connection->onQuery[] = [$this, 'logQuery']; + $connection->onQuery[] = \Closure::fromCallable([$this, 'logQuery']); } - public function logQuery(Connection $connection, $result): void + private function logQuery(Connection $connection, $result): void { if ($this->disabled) { return; diff --git a/src/Database/SqlPreprocessor.php b/src/Database/SqlPreprocessor.php index 0517f8dcc..a92f4b1f3 100644 --- a/src/Database/SqlPreprocessor.php +++ b/src/Database/SqlPreprocessor.php @@ -97,7 +97,7 @@ public function process(array $params, bool $useParams = false): array $res[] = Nette\Utils\Strings::replace( $param, '~\'[^\']*+\'|"[^"]*+"|\?[a-z]*|^\s*+(?:\(?\s*SELECT|INSERT|UPDATE|DELETE|REPLACE|EXPLAIN)\b|\b(?:SET|WHERE|HAVING|ORDER BY|GROUP BY|KEY UPDATE)(?=\s*$|\s*\?)|/\*.*?\*/|--[^\n]*~Dsi', - [$this, 'callback'] + \Closure::fromCallable([$this, 'callback']) ); } else { throw new Nette\InvalidArgumentException('There are more parameters than placeholders.'); @@ -108,8 +108,7 @@ public function process(array $params, bool $useParams = false): array } - /** @internal */ - public function callback(array $m): string + private function callback(array $m): string { $m = $m[0]; if ($m[0] === '?') { // placeholder diff --git a/src/Database/Structure.php b/src/Database/Structure.php index b2bc08ca4..5ec06790b 100644 --- a/src/Database/Structure.php +++ b/src/Database/Structure.php @@ -177,14 +177,11 @@ protected function needStructure(): void return; } - $this->structure = $this->cache->load('structure', [$this, 'loadStructure']); + $this->structure = $this->cache->load('structure', \Closure::fromCallable([$this, 'loadStructure'])); } - /** - * @internal - */ - public function loadStructure(): array + protected function loadStructure(): array { $driver = $this->connection->getSupplementalDriver(); From edffeb6896b630bc9e0af667dd4e6e08a88f9d5c Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 19 Mar 2020 18:28:39 +0100 Subject: [PATCH 06/12] SqlPreprocessor: added constants for modes --- src/Database/SqlPreprocessor.php | 49 ++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/src/Database/SqlPreprocessor.php b/src/Database/SqlPreprocessor.php index a92f4b1f3..1939c5e3c 100644 --- a/src/Database/SqlPreprocessor.php +++ b/src/Database/SqlPreprocessor.php @@ -19,18 +19,25 @@ class SqlPreprocessor { use Nette\SmartObject; - /** @var array */ - private const MODE_LIST = ['and', 'or', 'set', 'values', 'order']; + private const + MODE_AND = 'and', // (key [operator] value) AND ... + MODE_OR = 'or', // (key [operator] value) OR ... + MODE_SET = 'set', // key=value, key=value, ... + MODE_VALUES = 'values', // (key, key, ...) VALUES (value, value, ...) + MODE_ORDER = 'order', // key, key DESC, ... + MODE_AUTO = 'auto'; // arrayMode for arrays + + private const MODES = [self::MODE_AND, self::MODE_OR, self::MODE_SET, self::MODE_VALUES, self::MODE_ORDER]; private const ARRAY_MODES = [ - 'INSERT' => 'values', - 'REPLACE' => 'values', - 'KEY UPDATE' => 'set', - 'SET' => 'set', - 'WHERE' => 'and', - 'HAVING' => 'and', - 'ORDER BY' => 'order', - 'GROUP BY' => 'order', + 'INSERT' => self::MODE_VALUES, + 'REPLACE' => self::MODE_VALUES, + 'KEY UPDATE' => self::MODE_SET, + 'SET' => self::MODE_SET, + 'WHERE' => self::MODE_AND, + 'HAVING' => self::MODE_AND, + 'ORDER BY' => self::MODE_ORDER, + 'GROUP BY' => self::MODE_ORDER, ]; private const PARAMETRIC_COMMANDS = [ @@ -88,7 +95,7 @@ public function process(array $params, bool $useParams = false): array $param = $params[$this->counter++]; if (($this->counter === 2 && count($params) === 2) || !is_scalar($param)) { - $res[] = $this->formatValue($param, 'auto'); + $res[] = $this->formatValue($param, self::MODE_AUTO); $this->arrayMode = null; } elseif (is_string($param) && $this->counter > $prev + 1) { @@ -115,7 +122,7 @@ private function callback(array $m): string if ($this->counter >= count($this->params)) { throw new Nette\InvalidArgumentException('There are more placeholders than passed parameters.'); } - return $this->formatValue($this->params[$this->counter++], substr($m, 1) ?: 'auto'); + return $this->formatValue($this->params[$this->counter++], substr($m, 1) ?: self::MODE_AUTO); } elseif ($m[0] === "'" || $m[0] === '"' || $m[0] === '/' || $m[0] === '-') { // string or comment return $m; @@ -131,7 +138,7 @@ private function callback(array $m): string private function formatValue($value, string $mode = null): string { - if (!$mode || $mode === 'auto') { + if (!$mode || $mode === self::MODE_AUTO) { if (is_scalar($value) || is_resource($value)) { if ($this->useParams) { $this->remaining[] = $value; @@ -188,14 +195,14 @@ private function formatValue($value, string $mode = null): string if (is_array($value)) { $vx = $kx = []; - if ($mode === 'auto') { + if ($mode === self::MODE_AUTO) { $mode = $this->arrayMode; } - if ($mode === 'values') { // (key, key, ...) VALUES (value, value, ...) + if ($mode === self::MODE_VALUES) { // (key, key, ...) VALUES (value, value, ...) if (array_key_exists(0, $value)) { // multi-insert if (!is_array($value[0]) && !$value[0] instanceof Row) { - throw new Nette\InvalidArgumentException('Automaticaly detected multi-insert, but values aren\'t array. If you need try to change mode like "?[' . implode('|', self::MODE_LIST) . ']". Mode "' . $mode . '" was used.'); + throw new Nette\InvalidArgumentException('Automaticaly detected multi-insert, but values aren\'t array. If you need try to change mode like "?[' . implode('|', self::MODES) . ']". Mode "' . $mode . '" was used.'); } foreach ($value[0] as $k => $v) { $kx[] = $this->delimite($k); @@ -218,7 +225,7 @@ private function formatValue($value, string $mode = null): string } return '(' . implode(', ', $kx) . ') VALUES (' . implode(', ', $vx) . ')'; - } elseif (!$mode || $mode === 'set') { + } elseif (!$mode || $mode === self::MODE_SET) { foreach ($value as $k => $v) { if (is_int($k)) { // value, value, ... OR (1, 2), (3, 4) $vx[] = is_array($v) ? '(' . $this->formatValue($v) . ')' : $this->formatValue($v); @@ -231,7 +238,7 @@ private function formatValue($value, string $mode = null): string } return implode(', ', $vx); - } elseif ($mode === 'and' || $mode === 'or') { // (key [operator] value) AND ... + } elseif ($mode === self::MODE_AND || $mode === self::MODE_OR) { // (key [operator] value) AND ... foreach ($value as $k => $v) { if (is_int($k)) { $vx[] = $this->formatValue($v); @@ -256,7 +263,7 @@ private function formatValue($value, string $mode = null): string } return $value ? '(' . implode(') ' . strtoupper($mode) . ' (', $vx) . ')' : '1=1'; - } elseif ($mode === 'order') { // key, key DESC, ... + } elseif ($mode === self::MODE_ORDER) { // key, key DESC, ... foreach ($value as $k => $v) { $vx[] = $this->delimite($k) . ($v > 0 ? '' : ' DESC'); } @@ -266,11 +273,11 @@ private function formatValue($value, string $mode = null): string throw new Nette\InvalidArgumentException("Unknown placeholder ?$mode."); } - } elseif (in_array($mode, self::MODE_LIST, true)) { + } elseif (in_array($mode, self::MODES, true)) { $type = gettype($value); throw new Nette\InvalidArgumentException("Placeholder ?$mode expects array or Traversable object, $type given."); - } elseif ($mode && $mode !== 'auto') { + } elseif ($mode && $mode !== self::MODE_AUTO) { throw new Nette\InvalidArgumentException("Unknown placeholder ?$mode."); } else { From a0c8712dd9d6da5a107d2c4c5c4a17a7a358fcf5 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 19 Mar 2020 18:45:08 +0100 Subject: [PATCH 07/12] SqlPreprocessor: added mode ?list --- src/Database/SqlPreprocessor.php | 11 +++++++++-- tests/Database/SqlPreprocessor.phpt | 4 ++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Database/SqlPreprocessor.php b/src/Database/SqlPreprocessor.php index 1939c5e3c..f2fec466c 100644 --- a/src/Database/SqlPreprocessor.php +++ b/src/Database/SqlPreprocessor.php @@ -25,9 +25,10 @@ class SqlPreprocessor MODE_SET = 'set', // key=value, key=value, ... MODE_VALUES = 'values', // (key, key, ...) VALUES (value, value, ...) MODE_ORDER = 'order', // key, key DESC, ... + MODE_LIST = 'list', // value, value, ... | (tuple), (tuple), ... MODE_AUTO = 'auto'; // arrayMode for arrays - private const MODES = [self::MODE_AND, self::MODE_OR, self::MODE_SET, self::MODE_VALUES, self::MODE_ORDER]; + private const MODES = [self::MODE_AND, self::MODE_OR, self::MODE_SET, self::MODE_VALUES, self::MODE_ORDER, self::MODE_LIST]; private const ARRAY_MODES = [ 'INSERT' => self::MODE_VALUES, @@ -67,7 +68,7 @@ class SqlPreprocessor /** @var bool */ private $useParams; - /** @var string|null values|set|and|order */ + /** @var string|null values|set|and|order|items */ private $arrayMode; @@ -238,6 +239,12 @@ private function formatValue($value, string $mode = null): string } return implode(', ', $vx); + } elseif ($mode === self::MODE_LIST) { // value, value, ... | (tuple), (tuple), ... + foreach ($value as $k => $v) { + $vx[] = is_array($v) ? '(' . $this->formatValue($v, self::MODE_LIST) . ')' : $this->formatValue($v); + } + return implode(', ', $vx); + } elseif ($mode === self::MODE_AND || $mode === self::MODE_OR) { // (key [operator] value) AND ... foreach ($value as $k => $v) { if (is_int($k)) { diff --git a/tests/Database/SqlPreprocessor.phpt b/tests/Database/SqlPreprocessor.phpt index d6cf7314d..02fe938e8 100644 --- a/tests/Database/SqlPreprocessor.phpt +++ b/tests/Database/SqlPreprocessor.phpt @@ -347,10 +347,10 @@ test(function () use ($preprocessor) { // ?values }); -test(function () use ($preprocessor) { // automatic detection faild +test(function () use ($preprocessor) { // automatic detection failed Assert::exception(function () use ($preprocessor) { $preprocessor->process(['INSERT INTO author (name) SELECT name FROM user WHERE id IN (?)', [11, 12]]); - }, Nette\InvalidArgumentException::class, 'Automaticaly detected multi-insert, but values aren\'t array. If you need try to change mode like "?[and|or|set|values|order]". Mode "values" was used.'); + }, Nette\InvalidArgumentException::class, 'Automaticaly detected multi-insert, but values aren\'t array. If you need try to change mode like "?[and|or|set|values|order|list]". Mode "values" was used.'); }); From ac1451c7a3614c468b273e3b6d3c5676d4bc7c0d Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 19 Mar 2020 19:13:04 +0100 Subject: [PATCH 08/12] SqlPreprocessor::formatValue() rejects array unless they are explicitly allowed (possible BC break) --- src/Database/SqlPreprocessor.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Database/SqlPreprocessor.php b/src/Database/SqlPreprocessor.php index f2fec466c..6da87afe2 100644 --- a/src/Database/SqlPreprocessor.php +++ b/src/Database/SqlPreprocessor.php @@ -194,7 +194,7 @@ private function formatValue($value, string $mode = null): string $value = iterator_to_array($value); } - if (is_array($value)) { + if ($mode && is_array($value)) { $vx = $kx = []; if ($mode === self::MODE_AUTO) { $mode = $this->arrayMode; @@ -229,7 +229,7 @@ private function formatValue($value, string $mode = null): string } elseif (!$mode || $mode === self::MODE_SET) { foreach ($value as $k => $v) { if (is_int($k)) { // value, value, ... OR (1, 2), (3, 4) - $vx[] = is_array($v) ? '(' . $this->formatValue($v) . ')' : $this->formatValue($v); + $vx[] = is_array($v) ? '(' . $this->formatValue($v, self::MODE_LIST) . ')' : $this->formatValue($v); } elseif (substr($k, -1) === '=') { // key+=value, key-=value, ... $k2 = $this->delimite(substr($k, 0, -2)); $vx[] = $k2 . '=' . $k2 . ' ' . substr($k, -2, 1) . ' ' . $this->formatValue($v); @@ -255,7 +255,7 @@ private function formatValue($value, string $mode = null): string $k = $this->delimite($k); if (is_array($v)) { if ($v) { - $vx[] = $k . ' ' . ($operator ? $operator . ' ' : '') . 'IN (' . $this->formatValue(array_values($v)) . ')'; + $vx[] = $k . ' ' . ($operator ? $operator . ' ' : '') . 'IN (' . $this->formatValue(array_values($v), self::MODE_LIST) . ')'; } elseif ($operator === 'NOT') { } else { $vx[] = '1=0'; From 1ec4ca3ec223cbb16ffe879734b643e3b0460918 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 19 Mar 2020 19:09:19 +0100 Subject: [PATCH 09/12] SqlPreprocessor: default array mode is items (possible BC break) --- src/Database/SqlPreprocessor.php | 9 ++++----- tests/Database/SqlPreprocessor.phpt | 6 +++--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Database/SqlPreprocessor.php b/src/Database/SqlPreprocessor.php index 6da87afe2..b59eb1670 100644 --- a/src/Database/SqlPreprocessor.php +++ b/src/Database/SqlPreprocessor.php @@ -97,7 +97,6 @@ public function process(array $params, bool $useParams = false): array if (($this->counter === 2 && count($params) === 2) || !is_scalar($param)) { $res[] = $this->formatValue($param, self::MODE_AUTO); - $this->arrayMode = null; } elseif (is_string($param) && $this->counter > $prev + 1) { $prev = $this->counter; @@ -197,7 +196,7 @@ private function formatValue($value, string $mode = null): string if ($mode && is_array($value)) { $vx = $kx = []; if ($mode === self::MODE_AUTO) { - $mode = $this->arrayMode; + $mode = $this->arrayMode ?? self::MODE_LIST; } if ($mode === self::MODE_VALUES) { // (key, key, ...) VALUES (value, value, ...) @@ -226,10 +225,10 @@ private function formatValue($value, string $mode = null): string } return '(' . implode(', ', $kx) . ') VALUES (' . implode(', ', $vx) . ')'; - } elseif (!$mode || $mode === self::MODE_SET) { + } elseif ($mode === self::MODE_SET) { foreach ($value as $k => $v) { - if (is_int($k)) { // value, value, ... OR (1, 2), (3, 4) - $vx[] = is_array($v) ? '(' . $this->formatValue($v, self::MODE_LIST) . ')' : $this->formatValue($v); + if (is_int($k)) { // value, value, ... + $vx[] = $this->formatValue($v); } elseif (substr($k, -1) === '=') { // key+=value, key-=value, ... $k2 = $this->delimite(substr($k, 0, -2)); $vx[] = $k2 . '=' . $k2 . ' ' . substr($k, -2, 1) . ' ' . $this->formatValue($v); diff --git a/tests/Database/SqlPreprocessor.phpt b/tests/Database/SqlPreprocessor.phpt index 02fe938e8..db3385e4f 100644 --- a/tests/Database/SqlPreprocessor.phpt +++ b/tests/Database/SqlPreprocessor.phpt @@ -332,7 +332,7 @@ test(function () use ($preprocessor) { // insert [$sql, $params] = $preprocessor->process(['/* comment */ INSERT INTO author', ['name' => 'Catelyn Stark'], ]); - Assert::same(reformat("/* comment */ INSERT INTO author [name]='Catelyn Stark'"), $sql); // autodetection not used + Assert::same(reformat("/* comment */ INSERT INTO author 'Catelyn Stark'"), $sql); // autodetection not used Assert::same([], $params); }); @@ -440,10 +440,10 @@ test(function () use ($preprocessor) { // update Assert::same([12, 'John Doe'], $params); - [$sql, $params] = $preprocessor->process(['UPDATE author SET a=1,', + [$sql, $params] = $preprocessor->process(['UPDATE author SET a=1,', // autodetection not used ['id' => 12, 'name' => 'John Doe'], ]); - Assert::same(reformat('UPDATE author SET a=1, [id]=?, [name]=?'), $sql); + Assert::same(reformat('UPDATE author SET a=1, ?, ?'), $sql); Assert::same([12, 'John Doe'], $params); }); From 9ca16e715b97996274da30dab3e313b169e0f26b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Bar=C3=A1=C5=A1ek?= Date: Tue, 2 Jun 2020 11:40:58 +0200 Subject: [PATCH 10/12] ConnectionPanel: Added performance colors (#258) --- src/Bridges/DatabaseTracy/ConnectionPanel.php | 4 ++++ .../DatabaseTracy/templates/ConnectionPanel.panel.phtml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Bridges/DatabaseTracy/ConnectionPanel.php b/src/Bridges/DatabaseTracy/ConnectionPanel.php index f1cdab212..f2004a946 100644 --- a/src/Bridges/DatabaseTracy/ConnectionPanel.php +++ b/src/Bridges/DatabaseTracy/ConnectionPanel.php @@ -34,6 +34,9 @@ class ConnectionPanel implements Tracy\IBarPanel /** @var bool */ public $disabled = false; + /** @var float */ + public $performanceScale = 0.25; + /** @var float logged time */ private $totalTime = 0; @@ -137,6 +140,7 @@ public function getPanel(): ?string $name = $this->name; $count = $this->count; $totalTime = $this->totalTime; + $performanceScale = $this->performanceScale; require __DIR__ . '/templates/ConnectionPanel.panel.phtml'; }); } diff --git a/src/Bridges/DatabaseTracy/templates/ConnectionPanel.panel.phtml b/src/Bridges/DatabaseTracy/templates/ConnectionPanel.panel.phtml index ef83eddf9..a31f7b9cb 100644 --- a/src/Bridges/DatabaseTracy/templates/ConnectionPanel.panel.phtml +++ b/src/Bridges/DatabaseTracy/templates/ConnectionPanel.panel.phtml @@ -29,7 +29,7 @@ use Tracy\Helpers; [$connection, $sql, $params, $source, $time, $rows, $error, $command, $explain] = $query; ?> - + ERROR From 840858ab4dd9a051dfd0a5c7728b06fce1b85461 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 8 May 2020 01:10:49 +0200 Subject: [PATCH 11/12] SqlPreprocessor: support for IN (?) [Closes #256] --- src/Database/SqlPreprocessor.php | 8 +++++++- tests/Database/SqlPreprocessor.phpt | 7 ++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Database/SqlPreprocessor.php b/src/Database/SqlPreprocessor.php index b59eb1670..1fbdbd422 100644 --- a/src/Database/SqlPreprocessor.php +++ b/src/Database/SqlPreprocessor.php @@ -103,7 +103,7 @@ public function process(array $params, bool $useParams = false): array $this->arrayMode = null; $res[] = Nette\Utils\Strings::replace( $param, - '~\'[^\']*+\'|"[^"]*+"|\?[a-z]*|^\s*+(?:\(?\s*SELECT|INSERT|UPDATE|DELETE|REPLACE|EXPLAIN)\b|\b(?:SET|WHERE|HAVING|ORDER BY|GROUP BY|KEY UPDATE)(?=\s*$|\s*\?)|/\*.*?\*/|--[^\n]*~Dsi', + '~\'[^\']*+\'|"[^"]*+"|\?[a-z]*|^\s*+(?:\(?\s*SELECT|INSERT|UPDATE|DELETE|REPLACE|EXPLAIN)\b|\b(?:SET|WHERE|HAVING|ORDER BY|GROUP BY|KEY UPDATE)(?=\s*$|\s*\?)|\bIN\s+\(\?\)|/\*.*?\*/|--[^\n]*~Dsi', \Closure::fromCallable([$this, 'callback']) ); } else { @@ -127,6 +127,12 @@ private function callback(array $m): string } elseif ($m[0] === "'" || $m[0] === '"' || $m[0] === '/' || $m[0] === '-') { // string or comment return $m; + } elseif (substr($m, -3) === '(?)') { // IN (?) + if ($this->counter >= count($this->params)) { + throw new Nette\InvalidArgumentException('There are more placeholders than passed parameters.'); + } + return 'IN (' . $this->formatValue($this->params[$this->counter++], self::MODE_LIST) . ')'; + } else { // command $cmd = ltrim(strtoupper($m), "\t\n\r ("); $this->arrayMode = self::ARRAY_MODES[$cmd] ?? null; diff --git a/tests/Database/SqlPreprocessor.phpt b/tests/Database/SqlPreprocessor.phpt index db3385e4f..75392dae4 100644 --- a/tests/Database/SqlPreprocessor.phpt +++ b/tests/Database/SqlPreprocessor.phpt @@ -80,6 +80,11 @@ test(function () use ($preprocessor) { // IN Assert::same(reformat('SELECT id FROM author WHERE ([a] IN (NULL, ?, ?, ?)) AND (1=0) AND ([c] NOT IN (NULL, ?, ?, ?))'), $sql); Assert::same([1, 2, 3, 1, 2, 3], $params); + + + [$sql, $params] = $preprocessor->process(['SELECT * FROM table WHERE ? AND id IN (?) AND ?', ['a' => 111], [3, 4], ['b' => 222]]); + Assert::same(reformat('SELECT * FROM table WHERE ([a] = ?) AND id IN (?, ?) AND ([b] = ?)'), $sql); + Assert::same([111, 3, 4, 222], $params); }); @@ -349,7 +354,7 @@ test(function () use ($preprocessor) { // ?values test(function () use ($preprocessor) { // automatic detection failed Assert::exception(function () use ($preprocessor) { - $preprocessor->process(['INSERT INTO author (name) SELECT name FROM user WHERE id IN (?)', [11, 12]]); + dump($preprocessor->process(['INSERT INTO author (name) SELECT name FROM user WHERE id ?', [11, 12]])); // invalid sql }, Nette\InvalidArgumentException::class, 'Automaticaly detected multi-insert, but values aren\'t array. If you need try to change mode like "?[and|or|set|values|order|list]". Mode "values" was used.'); }); From d19a42ffb2a69b3ec6a559f94c3bc9d93448eefc Mon Sep 17 00:00:00 2001 From: Vlczech Date: Mon, 5 Oct 2020 18:07:24 +0200 Subject: [PATCH 12/12] Support for regclasses in PostgreSQL If data contains special char, table is named with quotation marks also. Thus regclass returns name with quotes and for example operation ->table() delimites such delimited regclass name obtained from SELECT for the second time. So for example $tbl='someschema."some-table"' is in $db->table() delimited to "someschema"."""some-table""". Note: Tested for PostgreSQL only. --- src/Database/Drivers/MsSqlDriver.php | 7 +++++++ src/Database/Drivers/MySqlDriver.php | 7 +++++++ src/Database/Drivers/OciDriver.php | 7 +++++++ src/Database/Drivers/OdbcDriver.php | 7 +++++++ src/Database/Drivers/PgSqlDriver.php | 7 +++++++ src/Database/Drivers/SqliteDriver.php | 8 ++++++++ src/Database/Drivers/SqlsrvDriver.php | 7 +++++++ src/Database/Helpers.php | 1 + src/Database/IStructure.php | 3 ++- src/Database/ISupplementalDriver.php | 5 +++++ src/Database/ResultSet.php | 3 +++ 11 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/Database/Drivers/MsSqlDriver.php b/src/Database/Drivers/MsSqlDriver.php index b5793b9ab..d4e898b1f 100644 --- a/src/Database/Drivers/MsSqlDriver.php +++ b/src/Database/Drivers/MsSqlDriver.php @@ -45,6 +45,13 @@ public function delimite(string $name): string } + public function undelimite(string $name): string + { + $name = preg_replace('#(?format("'Y-m-d H:i:s'"); diff --git a/src/Database/Drivers/MySqlDriver.php b/src/Database/Drivers/MySqlDriver.php index 717efbe06..b7f7e321e 100644 --- a/src/Database/Drivers/MySqlDriver.php +++ b/src/Database/Drivers/MySqlDriver.php @@ -78,6 +78,13 @@ public function delimite(string $name): string } + public function undelimite(string $name): string + { + $name = preg_replace('#(?format("'Y-m-d H:i:s'"); diff --git a/src/Database/Drivers/OciDriver.php b/src/Database/Drivers/OciDriver.php index ac5a0e120..917303c7c 100644 --- a/src/Database/Drivers/OciDriver.php +++ b/src/Database/Drivers/OciDriver.php @@ -61,6 +61,13 @@ public function delimite(string $name): string } + public function undelimite(string $name): string + { + $name = preg_replace('#(?format($this->fmtDateTime); diff --git a/src/Database/Drivers/OdbcDriver.php b/src/Database/Drivers/OdbcDriver.php index 3edda5e89..89755fa89 100644 --- a/src/Database/Drivers/OdbcDriver.php +++ b/src/Database/Drivers/OdbcDriver.php @@ -39,6 +39,13 @@ public function delimite(string $name): string } + public function undelimite(string $name): string + { + $name = preg_replace('#(?format('#m/d/Y H:i:s#'); diff --git a/src/Database/Drivers/PgSqlDriver.php b/src/Database/Drivers/PgSqlDriver.php index 0b3217e2e..8655a4172 100644 --- a/src/Database/Drivers/PgSqlDriver.php +++ b/src/Database/Drivers/PgSqlDriver.php @@ -63,6 +63,13 @@ public function delimite(string $name): string } + public function undelimite(string $name): string + { + $name = preg_replace('#(?format("'Y-m-d H:i:s'"); diff --git a/src/Database/Drivers/SqliteDriver.php b/src/Database/Drivers/SqliteDriver.php index 6bb700848..2f36082d3 100644 --- a/src/Database/Drivers/SqliteDriver.php +++ b/src/Database/Drivers/SqliteDriver.php @@ -74,6 +74,14 @@ public function delimite(string $name): string } + public function undelimite(string $name): string + { + $name = preg_replace('#(?:^|\.)\[#', '', $name); + // Can not be fully undelimited due to ambigious delimite() - was delimited "[x x]" name originally "x x" or "x[]x"? + return $name; + } + + public function formatDateTime(\DateTimeInterface $value): string { return $value->format($this->fmtDateTime); diff --git a/src/Database/Drivers/SqlsrvDriver.php b/src/Database/Drivers/SqlsrvDriver.php index a4f8661b9..beec8c3e8 100644 --- a/src/Database/Drivers/SqlsrvDriver.php +++ b/src/Database/Drivers/SqlsrvDriver.php @@ -49,6 +49,13 @@ public function delimite(string $name): string } + public function undelimite(string $name): string + { + $name = preg_replace('#(?:^|\.)\[#', '', $name); + return str_replace(']]', ']', $name); + } + + public function formatDateTime(\DateTimeInterface $value): string { /** @see https://msdn.microsoft.com/en-us/library/ms187819.aspx */ diff --git a/src/Database/Helpers.php b/src/Database/Helpers.php index 306b3e942..72ae81107 100644 --- a/src/Database/Helpers.php +++ b/src/Database/Helpers.php @@ -33,6 +33,7 @@ class Helpers 'DATE' => IStructure::FIELD_DATE, '(SMALL)?DATETIME(OFFSET)?\d*|TIME(STAMP.*)?' => IStructure::FIELD_DATETIME, 'BYTEA|(TINY|MEDIUM|LONG|)BLOB|(LONG )?(VAR)?BINARY|IMAGE' => IStructure::FIELD_BINARY, + 'REGCLASS' => IStructure::FIELD_TABLENAME, ]; diff --git a/src/Database/IStructure.php b/src/Database/IStructure.php index 212a08006..0bfbda1ed 100644 --- a/src/Database/IStructure.php +++ b/src/Database/IStructure.php @@ -25,7 +25,8 @@ interface IStructure FIELD_TIME = 'time', FIELD_DATETIME = 'datetime', FIELD_UNIX_TIMESTAMP = 'timestamp', - FIELD_TIME_INTERVAL = 'timeint'; + FIELD_TIME_INTERVAL = 'timeint', + FIELD_TABLENAME = 'regclass'; /** * Returns tables list. diff --git a/src/Database/ISupplementalDriver.php b/src/Database/ISupplementalDriver.php index 69ca84966..424e242bb 100644 --- a/src/Database/ISupplementalDriver.php +++ b/src/Database/ISupplementalDriver.php @@ -38,6 +38,11 @@ function convertException(\PDOException $e): DriverException; */ function delimite(string $name): string; + /** + * Undelimites identifier as reverse operation for delimite (for example to get not delimited table name). + */ + function undelimite(string $name): string; + /** * Formats date-time for use in a SQL statement. */ diff --git a/src/Database/ResultSet.php b/src/Database/ResultSet.php index 196a358fb..2d658909a 100644 --- a/src/Database/ResultSet.php +++ b/src/Database/ResultSet.php @@ -160,6 +160,9 @@ public function normalizeRow(array $row): array } elseif ($type === IStructure::FIELD_UNIX_TIMESTAMP) { $row[$key] = Nette\Utils\DateTime::from($value); + + } elseif ($type === IStructure::FIELD_TABLENAME) { + $row[$key] = $this->connection->getSupplementalDriver()->undelimite($value); } }