From 17411deb0f1278de74edb34e5909f6032f2dc8ba Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 12 May 2020 00:55:52 +0200 Subject: [PATCH 01/11] 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 e9ba053cedb17a7e8cae5f84cad38ffba5507595 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 27 Mar 2020 12:36:58 +0100 Subject: [PATCH 02/11] 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 c3c0e877866149b23830a206cb8a14fe591743c7 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 27 Mar 2020 12:32:01 +0100 Subject: [PATCH 03/11] 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 85f91d6abf57503b575bfc82b443c1548ea505d6 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 26 Mar 2020 23:47:17 +0100 Subject: [PATCH 04/11] 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 5bd58bab8..c37d22f25 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 e603bd2e35d44f11bd519106be45a315fe185f4f Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 19 Mar 2020 18:28:39 +0100 Subject: [PATCH 05/11] 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 7e0aea2d8889d3c08381f1a9de09f7d150551edf Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 19 Mar 2020 18:45:08 +0100 Subject: [PATCH 06/11] 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 d733f4a5dde65aedcdb72ae10cc6c2dc1b013faa Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 19 Mar 2020 19:13:04 +0100 Subject: [PATCH 07/11] 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 d8d4a5258c587ce15218b472d278c69d8dde36c6 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 19 Mar 2020 19:09:19 +0100 Subject: [PATCH 08/11] 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 4f2d87e78328920d15503929baab4bf312fb0cc9 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 09/11] 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 c37d22f25..6d6392966 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; @@ -138,6 +141,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 d9b5f29c5bb1a467b2b3ce35c9dd660434d6e24a Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 8 May 2020 01:10:49 +0200 Subject: [PATCH 10/11] 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 dfb03315090061f9a8e20de4703fef571fede572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Bar=C3=A1=C5=A1ek?= Date: Wed, 1 Jul 2020 16:38:10 +0200 Subject: [PATCH 11/11] Extension: Added connection timeout --- src/Bridges/DatabaseDI/DatabaseExtension.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Bridges/DatabaseDI/DatabaseExtension.php b/src/Bridges/DatabaseDI/DatabaseExtension.php index 1272a1259..5b0737b6e 100644 --- a/src/Bridges/DatabaseDI/DatabaseExtension.php +++ b/src/Bridges/DatabaseDI/DatabaseExtension.php @@ -38,6 +38,7 @@ public function getConfigSchema(): Nette\Schema\Schema 'options' => Expect::array(), 'debugger' => Expect::bool(true), 'explain' => Expect::bool(true), + 'connectionTimeout' => Expect::int()->min(0), 'reflection' => Expect::string(), // BC 'conventions' => Expect::string('discovered'), // Nette\Database\Conventions\DiscoveredConventions 'autowired' => Expect::bool(), @@ -75,6 +76,10 @@ private function setupDatabase(\stdClass $config, string $name): void } } + if ($config->connectionTimeout !== null) { + $config->options[\PDO::ATTR_TIMEOUT] = $config->connectionTimeout; + } + $connection = $builder->addDefinition($this->prefix("$name.connection")) ->setFactory(Nette\Database\Connection::class, [$config->dsn, $config->user, $config->password, $config->options]) ->setAutowired($config->autowired);