1
0
mirror of https://github.com/dg/dibi.git synced 2025-08-30 17:29:53 +02:00

Compare commits

...

31 Commits

Author SHA1 Message Date
David Grudl
09a974ed7a sorting [WIP] 2022-12-20 19:04:31 +01:00
Miloslav Hůla
124d52139c added ObjectTranslator (#420)
Object translator allows to translate any object passed to Translator into Dibi\Expression.
2022-12-20 19:00:13 +01:00
Miloslav Hůla
78646e1790 tests: remove dependency on dibi_test database name 2022-11-22 10:46:30 +01:00
David Grudl
cab1c5b5e6 used native PHP 8 functions 2022-11-18 04:46:47 +01:00
David Grudl
94df6db03d added PHP 8 typehints 2022-11-18 04:46:47 +01:00
David Grudl
0575f9ea17 added property typehints 2022-11-18 04:45:32 +01:00
David Grudl
76b8ed2108 removed support for PHP 7 2022-11-18 04:45:32 +01:00
David Grudl
4b1a2faa76 coding style 2022-11-18 04:45:32 +01:00
David Grudl
b931dbe13b composer: updated dependencies 2022-11-18 04:45:32 +01:00
David Grudl
a55e2a0cf8 requires PHP 8.0 2022-11-18 04:45:32 +01:00
David Grudl
6356f9f7a4 opened 5.0-dev 2022-11-18 04:45:32 +01:00
Marek Štípek
7fa05f381b Event: detecting source without filesystem check (#428)
Co-authored-by: Marek Stipek <stipek@shoptet.cz>
2022-11-18 04:40:51 +01:00
David Grudl
3a962de553 cs 2022-10-13 03:58:51 +02:00
David Grudl
96e370b8fe updated github workflow 2022-10-13 03:51:11 +02:00
Jirka Hrazdil
ec0455ae00 Panel: typo (#421) 2022-09-06 04:32:10 +02:00
David Grudl
b61737311e support for PHP 8.2 2022-09-06 04:26:58 +02:00
David Grudl
9d5d430d3d Released version 4.2.6 2022-01-19 18:38:15 +01:00
Miloslav Hůla
04bb5ede3d Translator: convert BackedEnum to scalar
also closes (#412)
2022-01-18 16:15:30 +01:00
Andrej Rypo
7d82ce2ff6 MySqliDriver::getResource() fixed access to resource being closed prior to the call in PHP 8 (#410) 2021-12-12 18:00:04 +01:00
David Grudl
82150d120d cs nullable typehints 2021-12-12 17:46:56 +01:00
David Grudl
0f045c0986 cs whitespace 2021-12-12 03:52:44 +01:00
David Grudl
5646884899 cs 2021-12-12 03:52:44 +01:00
David Grudl
af33a354d6 removed ecs.php 2021-12-12 03:52:44 +01:00
David Grudl
a0c86747dc GitHub Actions updates 2021-12-06 19:05:06 +01:00
David Grudl
d70e274244 Released version 4.2.5 2021-11-29 13:48:16 +01:00
David Grudl
e05eb01233 fixed PHP 7.2 compatibility 2021-11-28 15:09:41 +01:00
David Grudl
2ac618ffff Date 0000-01-01 is valid [Closes #402] 2021-11-24 18:34:20 +01:00
Milan Otáhal
1881fea0e5 Profiler is not used in CLI mode 2021-11-24 18:34:20 +01:00
Jan Rössler
cb82357cfb Helpers::detectType(): detect PostgreSQL range types as Type::TEXT 2021-11-24 18:34:20 +01:00
Jan Rössler
0a29fcb502 PostgreReflector: fix reflection of matview columns on PostgreSQL 12+ 2021-11-24 18:34:20 +01:00
David Grudl
8270b7c1c3 support for PHP 8.1 2021-11-24 18:34:20 +01:00
97 changed files with 1206 additions and 958 deletions

View File

@@ -7,7 +7,7 @@ jobs:
name: Nette Code Checker name: Nette Code Checker
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: shivammathur/setup-php@v2 - uses: shivammathur/setup-php@v2
with: with:
php-version: 8.0 php-version: 8.0
@@ -21,11 +21,11 @@ jobs:
name: Nette Coding Standard name: Nette Coding Standard
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: shivammathur/setup-php@v2 - uses: shivammathur/setup-php@v2
with: with:
php-version: 8.0 php-version: 8.0
coverage: none coverage: none
- run: composer create-project nette/coding-standard temp/coding-standard ^3 --no-progress --ignore-platform-reqs - run: composer create-project nette/coding-standard temp/coding-standard ^3 --no-progress
- run: php temp/coding-standard/ecs check - run: php temp/coding-standard/ecs check

View File

@@ -10,7 +10,7 @@ jobs:
name: PHPStan name: PHPStan
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: shivammathur/setup-php@v2 - uses: shivammathur/setup-php@v2
with: with:
php-version: 8.0 php-version: 8.0

View File

@@ -3,7 +3,7 @@ name: Tests
on: [push, pull_request] on: [push, pull_request]
env: env:
php-extensions: mbstring, intl, mysqli, pgsql, sqlsrv-5.9.0preview1, pdo_sqlsrv-5.9.0preview1 php-extensions: mbstring, intl, mysqli, pgsql, sqlsrv-5.10.0beta2, pdo_sqlsrv-5.10.0beta2
php-tools: "composer:v2, pecl" php-tools: "composer:v2, pecl"
jobs: jobs:
@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
php: ['7.2', '7.3', '7.4', '8.0'] php: ['8.0', '8.1', '8.2']
fail-fast: false fail-fast: false
@@ -89,7 +89,7 @@ jobs:
--health-retries 5 --health-retries 5
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: shivammathur/setup-php@v2 - uses: shivammathur/setup-php@v2
with: with:
php-version: ${{ matrix.php }} php-version: ${{ matrix.php }}
@@ -106,7 +106,7 @@ jobs:
- run: composer install --no-progress --prefer-dist - run: composer install --no-progress --prefer-dist
- run: vendor/bin/tester -p phpdbg tests -s -C --coverage ./coverage.xml --coverage-src ./src - run: vendor/bin/tester -p phpdbg tests -s -C --coverage ./coverage.xml --coverage-src ./src
- if: failure() - if: failure()
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v3
with: with:
name: output name: output
path: tests/**/output path: tests/**/output

View File

@@ -1,31 +1,34 @@
build: off build: off
cache: cache:
- c:\php7 -> appveyor.yml - c:\php -> appveyor.yml
- '%LOCALAPPDATA%\Composer\files -> appveyor.yml' - '%LOCALAPPDATA%\Composer\files -> appveyor.yml'
clone_folder: c:\projects\dibi clone_folder: c:\projects\dibi
environment:
MYSQL_PWD: Password12!
services: services:
- mssql2012sp1 - mssql2012sp1
# - mssql2014 # - mssql2014
- mysql - mysql
init: init:
- SET PATH=c:\php7;%PATH% - SET PATH=c:\php;c:\Program Files\MySQL\MySQL Server 5.7\bin;%PATH%
- SET ANSICON=121x90 (121x90) - SET ANSICON=121x90 (121x90)
install: install:
# Install PHP 7.2 # Install PHP 8.0
- IF EXIST c:\php7 (SET PHP=0) ELSE (SET PHP=1) - IF EXIST c:\php (SET PHP=0) ELSE (SET PHP=1)
- IF %PHP%==1 mkdir c:\php7 - IF %PHP%==1 mkdir c:\php
- IF %PHP%==1 cd c:\php7 - IF %PHP%==1 cd c:\php
- IF %PHP%==1 curl https://windows.php.net/downloads/releases/archives/php-7.2.18-Win32-VC15-x64.zip --output php.zip - IF %PHP%==1 curl https://windows.php.net/downloads/releases/archives/php-8.0.1-Win32-vs16-x64.zip --output php.zip
- IF %PHP%==1 7z x php.zip >nul - IF %PHP%==1 7z x php.zip >nul
- IF %PHP%==1 echo extension_dir=ext >> php.ini - IF %PHP%==1 echo extension_dir=ext >> php.ini
- IF %PHP%==1 echo extension=php_openssl.dll >> php.ini - IF %PHP%==1 echo extension=php_openssl.dll >> php.ini
- IF %PHP%==1 curl https://github.com/microsoft/msphpsql/releases/download/v5.8.0/Windows-7.2.zip -L --output sqlsrv.zip - IF %PHP%==1 curl https://github.com/microsoft/msphpsql/releases/download/v5.9.0/Windows-8.0.zip -L --output sqlsrv.zip
- IF %PHP%==1 7z x sqlsrv.zip >nul - IF %PHP%==1 7z x sqlsrv.zip >nul
- IF %PHP%==1 copy Windows-7.2\x64\php_sqlsrv_72_ts.dll ext\php_sqlsrv_ts.dll - IF %PHP%==1 copy Windows-8.0\x64\php_sqlsrv_80_ts.dll ext\php_sqlsrv_ts.dll
- IF %PHP%==1 del /Q *.zip - IF %PHP%==1 del /Q *.zip
# Install Microsoft Access Database Engine x64 # Install Microsoft Access Database Engine x64
@@ -40,8 +43,12 @@ install:
# Create databases.ini # Create databases.ini
- copy tests\databases.appveyor.ini tests\databases.ini - copy tests\databases.appveyor.ini tests\databases.ini
before_test:
# Create MySQL database
- mysql --user=root -e "CREATE DATABASE dibi_test"
test_script: test_script:
- vendor\bin\tester tests -s -p c:\php7\php -c tests\php-win.ini - vendor\bin\tester tests -s -p c:\php\php -c tests\php-win.ini
on_failure: on_failure:
# Print *.actual content # Print *.actual content

View File

@@ -11,11 +11,11 @@
} }
], ],
"require": { "require": {
"php": ">=7.2" "php": ">=8.0 <8.3"
}, },
"require-dev": { "require-dev": {
"tracy/tracy": "~2.2", "tracy/tracy": "^2.8",
"nette/tester": "~2.0", "nette/tester": "^2.4",
"nette/di": "^3.0", "nette/di": "^3.0",
"phpstan/phpstan": "^0.12" "phpstan/phpstan": "^0.12"
}, },
@@ -31,7 +31,7 @@
}, },
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "4.2-dev" "dev-master": "5.0-dev"
} }
} }
} }

21
ecs.php
View File

@@ -1,21 +0,0 @@
<?php
/**
* Rules for Nette Coding Standard
* https://github.com/nette/coding-standard
*/
declare(strict_types=1);
return function (Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator $containerConfigurator): void {
$containerConfigurator->import(PRESET_DIR . '/php71.php');
$parameters = $containerConfigurator->parameters();
$parameters->set('skip', [
// issue #260
PhpCsFixer\Fixer\Operator\TernaryToNullCoalescingFixer::class => ['src/Dibi/HashMap.php'],
SlevomatCodingStandard\Sniffs\ControlStructures\RequireNullCoalesceOperatorSniff::class => ['src/Dibi/HashMap.php'],
]);
};

View File

@@ -31,7 +31,7 @@ $name = $cond1 ? 'K%' : null;
$dibi->test(' $dibi->test('
SELECT * SELECT *
FROM customers FROM customers
%if', isset($name), 'WHERE name LIKE ?', $name, '%end' %if', isset($name), 'WHERE name LIKE ?', $name, '%end',
); );
// -> SELECT * FROM customers WHERE name LIKE 'K%' // -> SELECT * FROM customers WHERE name LIKE 'K%'
@@ -54,7 +54,7 @@ $dibi->test('
WHERE WHERE
%if', isset($name), 'name LIKE ?', $name, ' %if', isset($name), 'name LIKE ?', $name, '
%if', $cond2, 'AND admin=1 %end %if', $cond2, 'AND admin=1 %end
%else 1 LIMIT 10 %end' %else 1 LIMIT 10 %end',
); );
// -> SELECT * FROM customers WHERE LIMIT 10 // -> SELECT * FROM customers WHERE LIMIT 10

View File

@@ -28,7 +28,7 @@ $dibi->test('
SELECT COUNT(*) as [count] SELECT COUNT(*) as [count]
FROM [comments] FROM [comments]
WHERE [ip] LIKE ?', $ipMask, ' WHERE [ip] LIKE ?', $ipMask, '
AND [date] > ', new Dibi\DateTime($timestamp) AND [date] > ', new Dibi\DateTime($timestamp),
); );
// -> SELECT COUNT(*) as [count] FROM [comments] WHERE [ip] LIKE '192.168.%' AND [date] > 876693600 // -> SELECT COUNT(*) as [count] FROM [comments] WHERE [ip] LIKE '192.168.%' AND [date] > 876693600
@@ -69,7 +69,7 @@ $array = [1, 2, 3];
$dibi->test(' $dibi->test('
SELECT * SELECT *
FROM people FROM people
WHERE id IN (?)', $array WHERE id IN (?)', $array,
); );
// -> SELECT * FROM people WHERE id IN ( 1, 2, 3 ) // -> SELECT * FROM people WHERE id IN ( 1, 2, 3 )

View File

@@ -29,6 +29,6 @@ $dibi->test('
'id' => 123, 'id' => 123,
'date' => new DateTime('12.3.2007'), 'date' => new DateTime('12.3.2007'),
'stamp' => new DateTime('23.1.2007 10:23'), 'stamp' => new DateTime('23.1.2007 10:23'),
] ],
); );
// -> INSERT INTO [mytable] ([id], [date], [stamp]) VALUES (123, '2007-03-12', '2007-01-23 10-23-00') // -> INSERT INTO [mytable] ([id], [date], [stamp]) VALUES (123, '2007-03-12', '2007-01-23 10-23-00')

View File

@@ -54,6 +54,6 @@ define('SUBST_ACTIVE', 7);
$dibi->test(" $dibi->test("
UPDATE :account:user UPDATE :account:user
SET name='John Doe', status=:active: SET name='John Doe', status=:active:
WHERE id=", 7 WHERE id=", 7,
); );
// -> UPDATE eshop_user SET name='John Doe', status=7 WHERE id= 7 // -> UPDATE eshop_user SET name='John Doe', status=7 WHERE id= 7

View File

@@ -34,7 +34,7 @@ Install Dibi via Composer:
composer require dibi/dibi composer require dibi/dibi
``` ```
The Dibi 4.2 requires PHP version 7.2 and supports PHP up to 8.0. The Dibi 5.0 requires PHP version 8.0 and supports PHP up to 8.2.
Usage Usage

View File

@@ -19,13 +19,15 @@ use Tracy;
*/ */
class DibiExtension22 extends Nette\DI\CompilerExtension class DibiExtension22 extends Nette\DI\CompilerExtension
{ {
/** @var bool|null */ private ?bool $debugMode;
private $debugMode;
private ?bool $cliMode;
public function __construct(bool $debugMode = null) public function __construct(?bool $debugMode = null, ?bool $cliMode = null)
{ {
$this->debugMode = $debugMode; $this->debugMode = $debugMode;
$this->cliMode = $cliMode;
} }
@@ -38,7 +40,11 @@ class DibiExtension22 extends Nette\DI\CompilerExtension
$this->debugMode = $container->parameters['debugMode']; $this->debugMode = $container->parameters['debugMode'];
} }
$useProfiler = $config['profiler'] ?? (class_exists(Tracy\Debugger::class) && $this->debugMode); if ($this->cliMode === null) {
$this->cliMode = $container->parameters['consoleMode'];
}
$useProfiler = $config['profiler'] ?? (class_exists(Tracy\Debugger::class) && $this->debugMode && !$this->cliMode);
unset($config['profiler']); unset($config['profiler']);
@@ -47,6 +53,7 @@ class DibiExtension22 extends Nette\DI\CompilerExtension
foreach ((array) $config['flags'] as $flag) { foreach ((array) $config['flags'] as $flag) {
$flags |= constant($flag); $flags |= constant($flag);
} }
$config['flags'] = $flags; $config['flags'] = $flags;
} }
@@ -57,9 +64,10 @@ class DibiExtension22 extends Nette\DI\CompilerExtension
if (class_exists(Tracy\Debugger::class)) { if (class_exists(Tracy\Debugger::class)) {
$connection->addSetup( $connection->addSetup(
[new Nette\DI\Statement('Tracy\Debugger::getBlueScreen'), 'addPanel'], [new Nette\DI\Statement('Tracy\Debugger::getBlueScreen'), 'addPanel'],
[[Dibi\Bridges\Tracy\Panel::class, 'renderException']] [[Dibi\Bridges\Tracy\Panel::class, 'renderException']],
); );
} }
if ($useProfiler) { if ($useProfiler) {
$panel = $container->addDefinition($this->prefix('panel')) $panel = $container->addDefinition($this->prefix('panel'))
->setFactory(Dibi\Bridges\Tracy\Panel::class, [ ->setFactory(Dibi\Bridges\Tracy\Panel::class, [

View File

@@ -22,20 +22,17 @@ class Panel implements Tracy\IBarPanel
{ {
use Dibi\Strict; use Dibi\Strict;
/** @var int maximum SQL length */ /** maximum SQL length */
public static $maxLength = 1000; public static int $maxLength = 1000;
/** @var bool|string explain queries? */ public bool|string $explain;
public $explain;
/** @var int */ public int $filter;
public $filter;
/** @var array */ private array $events = [];
private $events = [];
public function __construct($explain = true, int $filter = null) public function __construct(bool $explain = true, ?int $filter = null)
{ {
$this->filter = $filter ?: Event::QUERY; $this->filter = $filter ?: Event::QUERY;
$this->explain = $explain; $this->explain = $explain;
@@ -58,6 +55,7 @@ class Panel implements Tracy\IBarPanel
if (($event->type & $this->filter) === 0) { if (($event->type & $this->filter) === 0) {
return; return;
} }
$this->events[] = $event; $this->events[] = $event;
} }
@@ -88,9 +86,10 @@ class Panel implements Tracy\IBarPanel
foreach ($this->events as $event) { foreach ($this->events as $event) {
$totalTime += $event->time; $totalTime += $event->time;
} }
return '<span title="dibi"><svg viewBox="0 0 2048 2048" style="vertical-align: bottom; width:1.23em; height:1.55em"><path fill="' . ($count ? '#b079d6' : '#aaa') . '" d="M1024 896q237 0 443-43t325-127v170q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128v-170q119 84 325 127t443 43zm0 768q237 0 443-43t325-127v170q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128v-170q119 84 325 127t443 43zm0-384q237 0 443-43t325-127v170q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128v-170q119 84 325 127t443 43zm0-1152q208 0 385 34.5t280 93.5 103 128v128q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128v-128q0-69 103-128t280-93.5 385-34.5z"/></svg><span class="tracy-label">' return '<span title="dibi"><svg viewBox="0 0 2048 2048" style="vertical-align: bottom; width:1.23em; height:1.55em"><path fill="' . ($count ? '#b079d6' : '#aaa') . '" d="M1024 896q237 0 443-43t325-127v170q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128v-170q119 84 325 127t443 43zm0 768q237 0 443-43t325-127v170q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128v-170q119 84 325 127t443 43zm0-384q237 0 443-43t325-127v170q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128v-170q119 84 325 127t443 43zm0-1152q208 0 385 34.5t280 93.5 103 128v128q0 69-103 128t-280 93.5-385 34.5-385-34.5-280-93.5-103-128v-128q0-69 103-128t280-93.5 385-34.5z"/></svg><span class="tracy-label">'
. $count . ' queries' . $count . "\u{a0}queries"
. ($totalTime ? ' / ' . number_format($totalTime * 1000, 1, '.', '') . 'ms' : '') . ($totalTime ? ' / ' . number_format($totalTime * 1000, 1, '.', "\u{202f}") . "\u{202f}ms" : '')
. '</span></span>'; . '</span></span>';
} }
@@ -128,10 +127,11 @@ class Panel implements Tracy\IBarPanel
$explain = @Helpers::dump($connection->nativeQuery("$cmd $event->sql"), true); $explain = @Helpers::dump($connection->nativeQuery("$cmd $event->sql"), true);
} catch (Dibi\Exception $e) { } catch (Dibi\Exception $e) {
} }
[$connection->onEvent, \dibi::$numOfQueries, \dibi::$totalTime] = $backup; [$connection->onEvent, \dibi::$numOfQueries, \dibi::$totalTime] = $backup;
} }
$s .= '<tr><td data-order="' . $event->time . '">' . number_format($event->time * 1000, 3, '.', ''); $s .= '<tr><td data-order="' . $event->time . '">' . number_format($event->time * 1000, 3, '.', "\u{202f}");
if ($explain) { if ($explain) {
static $counter; static $counter;
$counter++; $counter++;
@@ -142,6 +142,7 @@ class Panel implements Tracy\IBarPanel
if ($explain) { if ($explain) {
$s .= "<div id='tracy-debug-DibiProfiler-row-$counter' class='tracy-collapsed'>{$explain}</div>"; $s .= "<div id='tracy-debug-DibiProfiler-row-$counter' class='tracy-collapsed'>{$explain}</div>";
} }
if ($event->source) { if ($event->source) {
$s .= Tracy\Helpers::editorLink($event->source[0], $event->source[1]); //->class('tracy-DibiProfiler-source'); $s .= Tracy\Helpers::editorLink($event->source[0], $event->source[1]); //->class('tracy-DibiProfiler-source');
} }
@@ -155,8 +156,8 @@ class Panel implements Tracy\IBarPanel
return '<style> #tracy-debug td.tracy-DibiProfiler-sql { background: white !important } return '<style> #tracy-debug td.tracy-DibiProfiler-sql { background: white !important }
#tracy-debug .tracy-DibiProfiler-source { color: #999 !important } #tracy-debug .tracy-DibiProfiler-source { color: #999 !important }
#tracy-debug tracy-DibiProfiler tr table { margin: 8px 0; max-height: 150px; overflow:auto } </style> #tracy-debug tracy-DibiProfiler tr table { margin: 8px 0; max-height: 150px; overflow:auto } </style>
<h1>Queries: ' . count($this->events) <h1>Queries:' . "\u{a0}" . count($this->events)
. ($totalTime === null ? '' : ', time: ' . number_format($totalTime * 1000, 1, '.', '') . 'ms') . ', ' . ($totalTime === null ? '' : ", time:\u{a0}" . number_format($totalTime * 1000, 1, '.', "\u{202f}") . "\u{202f}ms") . ', '
. htmlspecialchars($this->getConnectionName($singleConnection)) . '</h1> . htmlspecialchars($this->getConnectionName($singleConnection)) . '</h1>
<div class="tracy-inner tracy-DibiProfiler"> <div class="tracy-inner tracy-DibiProfiler">
<table class="tracy-sortable"> <table class="tracy-sortable">
@@ -170,8 +171,8 @@ class Panel implements Tracy\IBarPanel
private function getConnectionName(Dibi\Connection $connection): string private function getConnectionName(Dibi\Connection $connection): string
{ {
$driver = $connection->getConfig('driver'); $driver = $connection->getConfig('driver');
return (is_object($driver) ? get_class($driver) : $driver) return (is_object($driver) ? $driver::class : $driver)
. ($connection->getConfig('name') ? '/' . $connection->getConfig('name') : '') . ($connection->getConfig('name') ? '/' . $connection->getConfig('name') : '')
. ($connection->getConfig('host') ? '@' . $connection->getConfig('host') : ''); . ($connection->getConfig('host') ? "\u{202f}@\u{202f}" . $connection->getConfig('host') : '');
} }
} }

View File

@@ -22,25 +22,25 @@ class Connection implements IConnection
{ {
use Strict; use Strict;
/** @var array of function (Event $event); Occurs after query is executed */ /** function (Event $event); Occurs after query is executed */
public $onEvent = []; public ?array $onEvent = [];
/** @var array Current connection configuration */ /** Current connection configuration */
private $config; private array $config;
/** @var string[] resultset formats */ /** @var string[] resultset formats */
private $formats; private array $formats;
/** @var Driver|null */ private ?Driver $driver = null;
private $driver;
/** @var Translator|null */ private ?Translator $translator = null;
private $translator;
/** @var HashMap Substitutes for identifiers */ /** @var array<string, callable(object): Expression> */
private $substitutes; private array $translators = [];
private $transactionDepth = 0; private HashMap $substitutes;
private int $transactionDepth = 0;
/** /**
@@ -68,14 +68,14 @@ class Connection implements IConnection
* - onConnect (array) => list of SQL queries to execute (by Connection::query()) after connection is established * - onConnect (array) => list of SQL queries to execute (by Connection::query()) after connection is established
* @throws Exception * @throws Exception
*/ */
public function __construct(array $config, string $name = null) public function __construct(array $config, ?string $name = null)
{ {
Helpers::alias($config, 'username', 'user'); Helpers::alias($config, 'username', 'user');
Helpers::alias($config, 'password', 'pass'); Helpers::alias($config, 'password', 'pass');
Helpers::alias($config, 'host', 'hostname'); Helpers::alias($config, 'host', 'hostname');
Helpers::alias($config, 'result|formatDate', 'resultDate'); Helpers::alias($config, 'result|formatDate', 'resultDate');
Helpers::alias($config, 'result|formatDateTime', 'resultDateTime'); Helpers::alias($config, 'result|formatDateTime', 'resultDateTime');
$config['driver'] = $config['driver'] ?? 'mysqli'; $config['driver'] ??= 'mysqli';
$config['name'] = $name; $config['name'] = $name;
$this->config = $config; $this->config = $config;
@@ -93,7 +93,7 @@ class Connection implements IConnection
$this->onEvent[] = [new Loggers\FileLogger($config['profiler']['file'], $filter, $errorsOnly), 'logEvent']; $this->onEvent[] = [new Loggers\FileLogger($config['profiler']['file'], $filter, $errorsOnly), 'logEvent'];
} }
$this->substitutes = new HashMap(function (string $expr) { return ":$expr:"; }); $this->substitutes = new HashMap(fn(string $expr) => ":$expr:");
if (!empty($config['substitutes'])) { if (!empty($config['substitutes'])) {
foreach ($config['substitutes'] as $key => $value) { foreach ($config['substitutes'] as $key => $value) {
$this->substitutes->$key = $value; $this->substitutes->$key = $value;
@@ -150,16 +150,17 @@ class Connection implements IConnection
if ($event) { if ($event) {
$this->onEvent($event->done()); $this->onEvent($event->done());
} }
if (isset($this->config['onConnect'])) { if (isset($this->config['onConnect'])) {
foreach ($this->config['onConnect'] as $sql) { foreach ($this->config['onConnect'] as $sql) {
$this->query($sql); $this->query($sql);
} }
} }
} catch (DriverException $e) { } catch (DriverException $e) {
if ($event) { if ($event) {
$this->onEvent($event->done($e)); $this->onEvent($event->done($e));
} }
throw $e; throw $e;
} }
} }
@@ -189,9 +190,8 @@ class Connection implements IConnection
/** /**
* Returns configuration variable. If no $key is passed, returns the entire array. * Returns configuration variable. If no $key is passed, returns the entire array.
* @see self::__construct * @see self::__construct
* @return mixed
*/ */
final public function getConfig(string $key = null, $default = null) final public function getConfig(?string $key = null, $default = null): mixed
{ {
return $key === null return $key === null
? $this->config ? $this->config
@@ -207,16 +207,16 @@ class Connection implements IConnection
if (!$this->driver) { if (!$this->driver) {
$this->connect(); $this->connect();
} }
return $this->driver; return $this->driver;
} }
/** /**
* Generates (translates) and executes SQL query. * Generates (translates) and executes SQL query.
* @param mixed ...$args
* @throws Exception * @throws Exception
*/ */
final public function query(...$args): Result final public function query(mixed ...$args): Result
{ {
return $this->nativeQuery($this->translate(...$args)); return $this->nativeQuery($this->translate(...$args));
} }
@@ -224,23 +224,22 @@ class Connection implements IConnection
/** /**
* Generates SQL query. * Generates SQL query.
* @param mixed ...$args
* @throws Exception * @throws Exception
*/ */
final public function translate(...$args): string final public function translate(mixed ...$args): string
{ {
if (!$this->driver) { if (!$this->driver) {
$this->connect(); $this->connect();
} }
return (clone $this->translator)->translate($args); return (clone $this->translator)->translate($args);
} }
/** /**
* Generates and prints SQL query. * Generates and prints SQL query.
* @param mixed ...$args
*/ */
final public function test(...$args): bool final public function test(mixed ...$args): bool
{ {
try { try {
Helpers::dump($this->translate(...$args)); Helpers::dump($this->translate(...$args));
@@ -250,8 +249,9 @@ class Connection implements IConnection
if ($e->getSql()) { if ($e->getSql()) {
Helpers::dump($e->getSql()); Helpers::dump($e->getSql());
} else { } else {
echo get_class($e) . ': ' . $e->getMessage() . (PHP_SAPI === 'cli' ? "\n" : '<br>'); echo $e::class . ': ' . $e->getMessage() . (PHP_SAPI === 'cli' ? "\n" : '<br>');
} }
return false; return false;
} }
} }
@@ -259,10 +259,9 @@ class Connection implements IConnection
/** /**
* Generates (translates) and returns SQL query as DataSource. * Generates (translates) and returns SQL query as DataSource.
* @param mixed ...$args
* @throws Exception * @throws Exception
*/ */
final public function dataSource(...$args): DataSource final public function dataSource(mixed ...$args): DataSource
{ {
return new DataSource($this->translate(...$args), $this); return new DataSource($this->translate(...$args), $this);
} }
@@ -287,6 +286,7 @@ class Connection implements IConnection
if ($event) { if ($event) {
$this->onEvent($event->done($e)); $this->onEvent($event->done($e));
} }
throw $e; throw $e;
} }
@@ -294,6 +294,7 @@ class Connection implements IConnection
if ($event) { if ($event) {
$this->onEvent($event->done($res)); $this->onEvent($event->done($res));
} }
return $res; return $res;
} }
@@ -307,10 +308,12 @@ class Connection implements IConnection
if (!$this->driver) { if (!$this->driver) {
$this->connect(); $this->connect();
} }
$rows = $this->driver->getAffectedRows(); $rows = $this->driver->getAffectedRows();
if ($rows === null || $rows < 0) { if ($rows === null || $rows < 0) {
throw new Exception('Cannot retrieve number of affected rows.'); throw new Exception('Cannot retrieve number of affected rows.');
} }
return $rows; return $rows;
} }
@@ -319,15 +322,17 @@ class Connection implements IConnection
* Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query. * Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
* @throws Exception * @throws Exception
*/ */
public function getInsertId(string $sequence = null): int public function getInsertId(?string $sequence = null): int
{ {
if (!$this->driver) { if (!$this->driver) {
$this->connect(); $this->connect();
} }
$id = $this->driver->getInsertId($sequence); $id = $this->driver->getInsertId($sequence);
if ($id === null) { if ($id === null) {
throw new Exception('Cannot retrieve last generated ID.'); throw new Exception('Cannot retrieve last generated ID.');
} }
return $id; return $id;
} }
@@ -335,7 +340,7 @@ class Connection implements IConnection
/** /**
* Begins a transaction (if supported). * Begins a transaction (if supported).
*/ */
public function begin(string $savepoint = null): void public function begin(?string $savepoint = null): void
{ {
if ($this->transactionDepth !== 0) { if ($this->transactionDepth !== 0) {
throw new \LogicException(__METHOD__ . '() call is forbidden inside a transaction() callback'); throw new \LogicException(__METHOD__ . '() call is forbidden inside a transaction() callback');
@@ -344,17 +349,18 @@ class Connection implements IConnection
if (!$this->driver) { if (!$this->driver) {
$this->connect(); $this->connect();
} }
$event = $this->onEvent ? new Event($this, Event::BEGIN, $savepoint) : null; $event = $this->onEvent ? new Event($this, Event::BEGIN, $savepoint) : null;
try { try {
$this->driver->begin($savepoint); $this->driver->begin($savepoint);
if ($event) { if ($event) {
$this->onEvent($event->done()); $this->onEvent($event->done());
} }
} catch (DriverException $e) { } catch (DriverException $e) {
if ($event) { if ($event) {
$this->onEvent($event->done($e)); $this->onEvent($event->done($e));
} }
throw $e; throw $e;
} }
} }
@@ -363,7 +369,7 @@ class Connection implements IConnection
/** /**
* Commits statements in a transaction. * Commits statements in a transaction.
*/ */
public function commit(string $savepoint = null): void public function commit(?string $savepoint = null): void
{ {
if ($this->transactionDepth !== 0) { if ($this->transactionDepth !== 0) {
throw new \LogicException(__METHOD__ . '() call is forbidden inside a transaction() callback'); throw new \LogicException(__METHOD__ . '() call is forbidden inside a transaction() callback');
@@ -372,17 +378,18 @@ class Connection implements IConnection
if (!$this->driver) { if (!$this->driver) {
$this->connect(); $this->connect();
} }
$event = $this->onEvent ? new Event($this, Event::COMMIT, $savepoint) : null; $event = $this->onEvent ? new Event($this, Event::COMMIT, $savepoint) : null;
try { try {
$this->driver->commit($savepoint); $this->driver->commit($savepoint);
if ($event) { if ($event) {
$this->onEvent($event->done()); $this->onEvent($event->done());
} }
} catch (DriverException $e) { } catch (DriverException $e) {
if ($event) { if ($event) {
$this->onEvent($event->done($e)); $this->onEvent($event->done($e));
} }
throw $e; throw $e;
} }
} }
@@ -391,7 +398,7 @@ class Connection implements IConnection
/** /**
* Rollback changes in a transaction. * Rollback changes in a transaction.
*/ */
public function rollback(string $savepoint = null): void public function rollback(?string $savepoint = null): void
{ {
if ($this->transactionDepth !== 0) { if ($this->transactionDepth !== 0) {
throw new \LogicException(__METHOD__ . '() call is forbidden inside a transaction() callback'); throw new \LogicException(__METHOD__ . '() call is forbidden inside a transaction() callback');
@@ -400,26 +407,24 @@ class Connection implements IConnection
if (!$this->driver) { if (!$this->driver) {
$this->connect(); $this->connect();
} }
$event = $this->onEvent ? new Event($this, Event::ROLLBACK, $savepoint) : null; $event = $this->onEvent ? new Event($this, Event::ROLLBACK, $savepoint) : null;
try { try {
$this->driver->rollback($savepoint); $this->driver->rollback($savepoint);
if ($event) { if ($event) {
$this->onEvent($event->done()); $this->onEvent($event->done());
} }
} catch (DriverException $e) { } catch (DriverException $e) {
if ($event) { if ($event) {
$this->onEvent($event->done($e)); $this->onEvent($event->done($e));
} }
throw $e; throw $e;
} }
} }
/** public function transaction(callable $callback): mixed
* @return mixed
*/
public function transaction(callable $callback)
{ {
if ($this->transactionDepth === 0) { if ($this->transactionDepth === 0) {
$this->begin(); $this->begin();
@@ -433,6 +438,7 @@ class Connection implements IConnection
if ($this->transactionDepth === 0) { if ($this->transactionDepth === 0) {
$this->rollback(); $this->rollback();
} }
throw $e; throw $e;
} }
@@ -484,6 +490,7 @@ class Connection implements IConnection
if ($args instanceof Traversable) { if ($args instanceof Traversable) {
$args = iterator_to_array($args); $args = iterator_to_array($args);
} }
return $this->command()->insert() return $this->command()->insert()
->into('%n', $table, '(%n)', array_keys($args))->values('%l', $args); ->into('%n', $table, '(%n)', array_keys($args))->values('%l', $args);
} }
@@ -512,9 +519,28 @@ class Connection implements IConnection
*/ */
public function substitute(string $value): string public function substitute(string $value): string
{ {
return strpos($value, ':') === false return str_contains($value, ':')
? $value ? preg_replace_callback('#:([^:\s]*):#', fn(array $m) => $this->substitutes->{$m[1]}, $value)
: preg_replace_callback('#:([^:\s]*):#', function (array $m) { return $this->substitutes->{$m[1]}; }, $value); : $value;
}
/********************* value objects translation ****************d*g**/
/** @param callable(object): Expression $translator */
public function addObjectTranslator(string $class, callable $translator): self
{
$this->translators[$class] = $translator;
uksort($this->translators, fn($a, $b) => class_exists($a, false) && is_subclass_of($a, $b) ? -1 : 1);
return $this;
}
/** @return array<string, callable(object): Expression> */
public function getObjectTranslators(): array
{
return $this->translators;
} }
@@ -523,10 +549,9 @@ class Connection implements IConnection
/** /**
* Executes SQL query and fetch result - shortcut for query() & fetch(). * Executes SQL query and fetch result - shortcut for query() & fetch().
* @param mixed ...$args
* @throws Exception * @throws Exception
*/ */
public function fetch(...$args): ?Row public function fetch(mixed ...$args): ?Row
{ {
return $this->query($args)->fetch(); return $this->query($args)->fetch();
} }
@@ -534,11 +559,10 @@ class Connection implements IConnection
/** /**
* Executes SQL query and fetch results - shortcut for query() & fetchAll(). * Executes SQL query and fetch results - shortcut for query() & fetchAll().
* @param mixed ...$args
* @return Row[]|array[] * @return Row[]|array[]
* @throws Exception * @throws Exception
*/ */
public function fetchAll(...$args): array public function fetchAll(mixed ...$args): array
{ {
return $this->query($args)->fetchAll(); return $this->query($args)->fetchAll();
} }
@@ -546,11 +570,9 @@ class Connection implements IConnection
/** /**
* Executes SQL query and fetch first column - shortcut for query() & fetchSingle(). * Executes SQL query and fetch first column - shortcut for query() & fetchSingle().
* @param mixed ...$args
* @return mixed
* @throws Exception * @throws Exception
*/ */
public function fetchSingle(...$args) public function fetchSingle(mixed ...$args): mixed
{ {
return $this->query($args)->fetchSingle(); return $this->query($args)->fetchSingle();
} }
@@ -558,10 +580,9 @@ class Connection implements IConnection
/** /**
* Executes SQL query and fetch pairs - shortcut for query() & fetchPairs(). * Executes SQL query and fetch pairs - shortcut for query() & fetchPairs().
* @param mixed ...$args
* @throws Exception * @throws Exception
*/ */
public function fetchPairs(...$args): array public function fetchPairs(mixed ...$args): array
{ {
return $this->query($args)->fetchPairs(); return $this->query($args)->fetchPairs();
} }
@@ -587,7 +608,7 @@ class Connection implements IConnection
* @param callable $onProgress function (int $count, ?float $percent): void * @param callable $onProgress function (int $count, ?float $percent): void
* @return int count of sql commands * @return int count of sql commands
*/ */
public function loadFile(string $file, callable $onProgress = null): int public function loadFile(string $file, ?callable $onProgress = null): int
{ {
return Helpers::loadFromFile($this, $file, $onProgress); return Helpers::loadFromFile($this, $file, $onProgress);
} }
@@ -601,6 +622,7 @@ class Connection implements IConnection
if (!$this->driver) { if (!$this->driver) {
$this->connect(); $this->connect();
} }
return new Reflection\Database($this->driver->getReflector(), $this->config['database'] ?? null); return new Reflection\Database($this->driver->getReflector(), $this->config['database'] ?? null);
} }

View File

@@ -17,35 +17,25 @@ class DataSource implements IDataSource
{ {
use Strict; use Strict;
/** @var Connection */ private Connection $connection;
private $connection;
/** @var string */ private string $sql;
private $sql;
/** @var Result|null */ private ?Result $result = null;
private $result;
/** @var int|null */ private ?int $count = null;
private $count;
/** @var int|null */ private ?int $totalCount = null;
private $totalCount;
/** @var array */ private array $cols = [];
private $cols = [];
/** @var array */ private array $sorting = [];
private $sorting = [];
/** @var array */ private array $conds = [];
private $conds = [];
/** @var int|null */ private ?int $offset = null;
private $offset;
/** @var int|null */ private ?int $limit = null;
private $limit;
/** /**
@@ -65,13 +55,14 @@ class DataSource implements IDataSource
* @param string|array $col column name or array of column names * @param string|array $col column name or array of column names
* @param string $as column alias * @param string $as column alias
*/ */
public function select($col, string $as = null): self public function select(string|array $col, ?string $as = null): static
{ {
if (is_array($col)) { if (is_array($col)) {
$this->cols = $col; $this->cols = $col;
} else { } else {
$this->cols[$col] = $as; $this->cols[$col] = $as;
} }
$this->result = null; $this->result = null;
return $this; return $this;
} }
@@ -80,7 +71,7 @@ class DataSource implements IDataSource
/** /**
* Adds conditions to query. * Adds conditions to query.
*/ */
public function where($cond): self public function where($cond): static
{ {
$this->conds[] = is_array($cond) $this->conds[] = is_array($cond)
? $cond // TODO: not consistent with select and orderBy ? $cond // TODO: not consistent with select and orderBy
@@ -94,13 +85,14 @@ class DataSource implements IDataSource
* Selects columns to order by. * Selects columns to order by.
* @param string|array $row column name or array of column names * @param string|array $row column name or array of column names
*/ */
public function orderBy($row, string $direction = 'ASC'): self public function orderBy(string|array $row, string $direction = 'ASC'): static
{ {
if (is_array($row)) { if (is_array($row)) {
$this->sorting = $row; $this->sorting = $row;
} else { } else {
$this->sorting[$row] = $direction; $this->sorting[$row] = $direction;
} }
$this->result = null; $this->result = null;
return $this; return $this;
} }
@@ -109,7 +101,7 @@ class DataSource implements IDataSource
/** /**
* Limits number of rows. * Limits number of rows.
*/ */
public function applyLimit(int $limit, int $offset = null): self public function applyLimit(int $limit, ?int $offset = null): static
{ {
$this->limit = $limit; $this->limit = $limit;
$this->offset = $offset; $this->offset = $offset;
@@ -135,6 +127,7 @@ class DataSource implements IDataSource
if ($this->result === null) { if ($this->result === null) {
$this->result = $this->connection->nativeQuery($this->__toString()); $this->result = $this->connection->nativeQuery($this->__toString());
} }
return $this->result; return $this->result;
} }
@@ -158,7 +151,7 @@ class DataSource implements IDataSource
* Like fetch(), but returns only first field. * Like fetch(), but returns only first field.
* @return mixed value on success, null if no next record * @return mixed value on success, null if no next record
*/ */
public function fetchSingle() public function fetchSingle(): mixed
{ {
return $this->getResult()->fetchSingle(); return $this->getResult()->fetchSingle();
} }
@@ -185,7 +178,7 @@ class DataSource implements IDataSource
/** /**
* Fetches all records from table like $key => $value pairs. * Fetches all records from table like $key => $value pairs.
*/ */
public function fetchPairs(string $key = null, string $value = null): array public function fetchPairs(?string $key = null, ?string $value = null): array
{ {
return $this->getResult()->fetchPairs($key, $value); return $this->getResult()->fetchPairs($key, $value);
} }
@@ -226,24 +219,19 @@ class DataSource implements IDataSource
*/ */
public function __toString(): string public function __toString(): string
{ {
try { return $this->connection->translate(
return $this->connection->translate( "\nSELECT %n",
"\nSELECT %n", (empty($this->cols) ? '*' : $this->cols),
(empty($this->cols) ? '*' : $this->cols), "\nFROM %SQL",
"\nFROM %SQL", $this->sql,
$this->sql, "\n%ex",
"\n%ex", $this->conds ? ['WHERE %and', $this->conds] : null,
$this->conds ? ['WHERE %and', $this->conds] : null, "\n%ex",
"\n%ex", $this->sorting ? ['ORDER BY %by', $this->sorting] : null,
$this->sorting ? ['ORDER BY %by', $this->sorting] : null, "\n%ofs %lmt",
"\n%ofs %lmt", $this->offset,
$this->offset, $this->limit,
$this->limit );
);
} catch (\Throwable $e) {
trigger_error($e->getMessage(), E_USER_ERROR);
return '';
}
} }
@@ -258,10 +246,11 @@ class DataSource implements IDataSource
if ($this->count === null) { if ($this->count === null) {
$this->count = $this->conds || $this->offset || $this->limit $this->count = $this->conds || $this->offset || $this->limit
? Helpers::intVal($this->connection->nativeQuery( ? Helpers::intVal($this->connection->nativeQuery(
'SELECT COUNT(*) FROM (' . $this->__toString() . ') t' 'SELECT COUNT(*) FROM (' . $this->__toString() . ') t',
)->fetchSingle()) )->fetchSingle())
: $this->getTotalCount(); : $this->getTotalCount();
} }
return $this->count; return $this->count;
} }
@@ -273,9 +262,10 @@ class DataSource implements IDataSource
{ {
if ($this->totalCount === null) { if ($this->totalCount === null) {
$this->totalCount = Helpers::intVal($this->connection->nativeQuery( $this->totalCount = Helpers::intVal($this->connection->nativeQuery(
'SELECT COUNT(*) FROM ' . $this->sql 'SELECT COUNT(*) FROM ' . $this->sql,
)->fetchSingle()); )->fetchSingle());
} }
return $this->totalCount; return $this->totalCount;
} }
} }

View File

@@ -17,10 +17,7 @@ class DateTime extends \DateTimeImmutable
{ {
use Strict; use Strict;
/** public function __construct(string|int $time = 'now', ?\DateTimeZone $timezone = null)
* @param string|int $time
*/
public function __construct($time = 'now', \DateTimeZone $timezone = null)
{ {
$timezone = $timezone ?: new \DateTimeZone(date_default_timezone_get()); $timezone = $timezone ?: new \DateTimeZone(date_default_timezone_get());
if (is_numeric($time)) { if (is_numeric($time)) {

View File

@@ -42,22 +42,22 @@ class DummyDriver implements Dibi\Driver, Dibi\ResultDriver, Dibi\Reflector
} }
public function begin(string $savepoint = null): void public function begin(?string $savepoint = null): void
{ {
} }
public function commit(string $savepoint = null): void public function commit(?string $savepoint = null): void
{ {
} }
public function rollback(string $savepoint = null): void public function rollback(?string $savepoint = null): void
{ {
} }
public function getResource() public function getResource(): mixed
{ {
return null; return null;
} }
@@ -171,8 +171,9 @@ class DummyDriver implements Dibi\Driver, Dibi\ResultDriver, Dibi\Reflector
} }
public function getResultResource() public function getResultResource(): mixed
{ {
return null;
} }

View File

@@ -33,11 +33,10 @@ class FirebirdDriver implements Dibi\Driver
/** @var resource */ /** @var resource */
private $connection; private $connection;
/** @var resource|null */ /** @var ?resource */
private $transaction; private $transaction;
/** @var bool */ private bool $inTransaction = false;
private $inTransaction = false;
/** @throws Dibi\NotSupportedException */ /** @throws Dibi\NotSupportedException */
@@ -101,10 +100,10 @@ class FirebirdDriver implements Dibi\Driver
} else { } else {
throw new Dibi\DriverException(ibase_errmsg(), ibase_errcode(), $sql); throw new Dibi\DriverException(ibase_errmsg(), ibase_errcode(), $sql);
} }
} elseif (is_resource($res)) { } elseif (is_resource($res)) {
return $this->createResultDriver($res); return $this->createResultDriver($res);
} }
return null; return null;
} }
@@ -131,11 +130,12 @@ class FirebirdDriver implements Dibi\Driver
* Begins a transaction (if supported). * Begins a transaction (if supported).
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function begin(string $savepoint = null): void public function begin(?string $savepoint = null): void
{ {
if ($savepoint !== null) { if ($savepoint !== null) {
throw new Dibi\NotSupportedException('Savepoints are not supported in Firebird/Interbase.'); throw new Dibi\NotSupportedException('Savepoints are not supported in Firebird/Interbase.');
} }
$this->transaction = ibase_trans($this->getResource()); $this->transaction = ibase_trans($this->getResource());
$this->inTransaction = true; $this->inTransaction = true;
} }
@@ -145,7 +145,7 @@ class FirebirdDriver implements Dibi\Driver
* Commits statements in a transaction. * Commits statements in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function commit(string $savepoint = null): void public function commit(?string $savepoint = null): void
{ {
if ($savepoint !== null) { if ($savepoint !== null) {
throw new Dibi\NotSupportedException('Savepoints are not supported in Firebird/Interbase.'); throw new Dibi\NotSupportedException('Savepoints are not supported in Firebird/Interbase.');
@@ -163,7 +163,7 @@ class FirebirdDriver implements Dibi\Driver
* Rollback changes in a transaction. * Rollback changes in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function rollback(string $savepoint = null): void public function rollback(?string $savepoint = null): void
{ {
if ($savepoint !== null) { if ($savepoint !== null) {
throw new Dibi\NotSupportedException('Savepoints are not supported in Firebird/Interbase.'); throw new Dibi\NotSupportedException('Savepoints are not supported in Firebird/Interbase.');
@@ -190,7 +190,7 @@ class FirebirdDriver implements Dibi\Driver
* Returns the connection resource. * Returns the connection resource.
* @return resource|null * @return resource|null
*/ */
public function getResource() public function getResource(): mixed
{ {
return is_resource($this->connection) ? $this->connection : null; return is_resource($this->connection) ? $this->connection : null;
} }

View File

@@ -19,8 +19,7 @@ class FirebirdReflector implements Dibi\Reflector
{ {
use Dibi\Strict; use Dibi\Strict;
/** @var Dibi\Driver */ private Dibi\Driver $driver;
private $driver;
public function __construct(Dibi\Driver $driver) public function __construct(Dibi\Driver $driver)
@@ -47,6 +46,7 @@ class FirebirdReflector implements Dibi\Reflector
'view' => $row[1] === 'TRUE', 'view' => $row[1] === 'TRUE',
]; ];
} }
return $tables; return $tables;
} }
@@ -99,6 +99,7 @@ class FirebirdReflector implements Dibi\Reflector
'autoincrement' => false, 'autoincrement' => false,
]; ];
} }
return $columns; return $columns;
} }
@@ -131,6 +132,7 @@ class FirebirdReflector implements Dibi\Reflector
$indexes[$key]['table'] = $table; $indexes[$key]['table'] = $table;
$indexes[$key]['columns'][$row['FIELD_POSITION']] = $row['FIELD_NAME']; $indexes[$key]['columns'][$row['FIELD_POSITION']] = $row['FIELD_NAME'];
} }
return $indexes; return $indexes;
} }
@@ -159,6 +161,7 @@ class FirebirdReflector implements Dibi\Reflector
'table' => $table, 'table' => $table,
]; ];
} }
return $keys; return $keys;
} }
@@ -179,6 +182,7 @@ class FirebirdReflector implements Dibi\Reflector
while ($row = $res->fetch(false)) { while ($row = $res->fetch(false)) {
$indices[] = $row[0]; $indices[] = $row[0];
} }
return $indices; return $indices;
} }
@@ -201,6 +205,7 @@ class FirebirdReflector implements Dibi\Reflector
while ($row = $res->fetch(false)) { while ($row = $res->fetch(false)) {
$constraints[] = $row[0]; $constraints[] = $row[0];
} }
return $constraints; return $constraints;
} }
@@ -209,7 +214,7 @@ class FirebirdReflector implements Dibi\Reflector
* Returns metadata for all triggers in a table or database. * Returns metadata for all triggers in a table or database.
* (Only if user has permissions on ALTER TABLE, INSERT/UPDATE/DELETE record in table) * (Only if user has permissions on ALTER TABLE, INSERT/UPDATE/DELETE record in table)
*/ */
public function getTriggersMeta(string $table = null): array public function getTriggersMeta(?string $table = null): array
{ {
$res = $this->driver->query( $res = $this->driver->query(
" "
@@ -236,7 +241,7 @@ class FirebirdReflector implements Dibi\Reflector
END AS TRIGGER_ENABLED END AS TRIGGER_ENABLED
FROM RDB\$TRIGGERS FROM RDB\$TRIGGERS
WHERE RDB\$SYSTEM_FLAG = 0" WHERE RDB\$SYSTEM_FLAG = 0"
. ($table === null ? ';' : " AND RDB\$RELATION_NAME = UPPER('$table');") . ($table === null ? ';' : " AND RDB\$RELATION_NAME = UPPER('$table');"),
); );
$triggers = []; $triggers = [];
while ($row = $res->fetch(true)) { while ($row = $res->fetch(true)) {
@@ -248,6 +253,7 @@ class FirebirdReflector implements Dibi\Reflector
'enabled' => trim($row['TRIGGER_ENABLED']) === 'TRUE', 'enabled' => trim($row['TRIGGER_ENABLED']) === 'TRUE',
]; ];
} }
return $triggers; return $triggers;
} }
@@ -256,7 +262,7 @@ class FirebirdReflector implements Dibi\Reflector
* Returns list of triggers for given table. * Returns list of triggers for given table.
* (Only if user has permissions on ALTER TABLE, INSERT/UPDATE/DELETE record in table) * (Only if user has permissions on ALTER TABLE, INSERT/UPDATE/DELETE record in table)
*/ */
public function getTriggers(string $table = null): array public function getTriggers(?string $table = null): array
{ {
$q = 'SELECT TRIM(RDB$TRIGGER_NAME) $q = 'SELECT TRIM(RDB$TRIGGER_NAME)
FROM RDB$TRIGGERS FROM RDB$TRIGGERS
@@ -270,6 +276,7 @@ class FirebirdReflector implements Dibi\Reflector
while ($row = $res->fetch(false)) { while ($row = $res->fetch(false)) {
$triggers[] = $row[0]; $triggers[] = $row[0];
} }
return $triggers; return $triggers;
} }
@@ -321,6 +328,7 @@ class FirebirdReflector implements Dibi\Reflector
$procedures[$key]['params'][$io][$num]['type'] = trim($row['FIELD_TYPE']); $procedures[$key]['params'][$io][$num]['type'] = trim($row['FIELD_TYPE']);
$procedures[$key]['params'][$io][$num]['size'] = $row['FIELD_LENGTH']; $procedures[$key]['params'][$io][$num]['size'] = $row['FIELD_LENGTH'];
} }
return $procedures; return $procedures;
} }
@@ -338,6 +346,7 @@ class FirebirdReflector implements Dibi\Reflector
while ($row = $res->fetch(false)) { while ($row = $res->fetch(false)) {
$procedures[] = $row[0]; $procedures[] = $row[0];
} }
return $procedures; return $procedures;
} }
@@ -356,6 +365,7 @@ class FirebirdReflector implements Dibi\Reflector
while ($row = $res->fetch(false)) { while ($row = $res->fetch(false)) {
$generators[] = $row[0]; $generators[] = $row[0];
} }
return $generators; return $generators;
} }
@@ -374,6 +384,7 @@ class FirebirdReflector implements Dibi\Reflector
while ($row = $res->fetch(false)) { while ($row = $res->fetch(false)) {
$functions[] = $row[0]; $functions[] = $row[0];
} }
return $functions; return $functions;
} }
} }

View File

@@ -23,8 +23,7 @@ class FirebirdResult implements Dibi\ResultDriver
/** @var resource */ /** @var resource */
private $resultSet; private $resultSet;
/** @var bool */ private bool $autoFree = true;
private $autoFree = true;
/** /**
@@ -103,7 +102,7 @@ class FirebirdResult implements Dibi\ResultDriver
* Returns the result set resource. * Returns the result set resource.
* @return resource|null * @return resource|null
*/ */
public function getResultResource() public function getResultResource(): mixed
{ {
$this->autoFree = false; $this->autoFree = false;
return is_resource($this->resultSet) ? $this->resultSet : null; return is_resource($this->resultSet) ? $this->resultSet : null;
@@ -126,6 +125,7 @@ class FirebirdResult implements Dibi\ResultDriver
'nativetype' => $row['type'], 'nativetype' => $row['type'],
]; ];
} }
return $columns; return $columns;
} }

View File

@@ -20,8 +20,7 @@ class MySqlReflector implements Dibi\Reflector
{ {
use Dibi\Strict; use Dibi\Strict;
/** @var Dibi\Driver */ private Dibi\Driver $driver;
private $driver;
public function __construct(Dibi\Driver $driver) public function __construct(Dibi\Driver $driver)
@@ -43,6 +42,7 @@ class MySqlReflector implements Dibi\Reflector
'view' => isset($row[1]) && $row[1] === 'VIEW', 'view' => isset($row[1]) && $row[1] === 'VIEW',
]; ];
} }
return $tables; return $tables;
} }
@@ -67,6 +67,7 @@ class MySqlReflector implements Dibi\Reflector
'vendor' => $row, 'vendor' => $row,
]; ];
} }
return $columns; return $columns;
} }
@@ -84,6 +85,7 @@ class MySqlReflector implements Dibi\Reflector
$indexes[$row['Key_name']]['primary'] = $row['Key_name'] === 'PRIMARY'; $indexes[$row['Key_name']]['primary'] = $row['Key_name'] === 'PRIMARY';
$indexes[$row['Key_name']]['columns'][$row['Seq_in_index'] - 1] = $row['Column_name']; $indexes[$row['Key_name']]['columns'][$row['Seq_in_index'] - 1] = $row['Column_name'];
} }
return array_values($indexes); return array_values($indexes);
} }
@@ -123,6 +125,7 @@ class MySqlReflector implements Dibi\Reflector
$foreignKeys[$keyName]['onDelete'] = $row['DELETE_RULE']; $foreignKeys[$keyName]['onDelete'] = $row['DELETE_RULE'];
$foreignKeys[$keyName]['onUpdate'] = $row['UPDATE_RULE']; $foreignKeys[$keyName]['onUpdate'] = $row['UPDATE_RULE'];
} }
return array_values($foreignKeys); return array_values($foreignKeys);
} }
} }

View File

@@ -40,11 +40,10 @@ class MySqliDriver implements Dibi\Driver
public const ERROR_DATA_TRUNCATED = 1265; public const ERROR_DATA_TRUNCATED = 1265;
/** @var \mysqli */ private \mysqli $connection;
private $connection;
/** @var bool Is buffered (seekable and countable)? */ /** Is buffered (seekable and countable)? */
private $buffered; private bool $buffered = false;
/** @throws Dibi\NotSupportedException */ /** @throws Dibi\NotSupportedException */
@@ -88,6 +87,7 @@ class MySqliDriver implements Dibi\Driver
$this->connection->options($key, $value); $this->connection->options($key, $value);
} }
} }
@$this->connection->real_connect( // intentionally @ @$this->connection->real_connect( // intentionally @
(empty($config['persistent']) ? '' : 'p:') . $config['host'], (empty($config['persistent']) ? '' : 'p:') . $config['host'],
$config['username'], $config['username'],
@@ -95,7 +95,7 @@ class MySqliDriver implements Dibi\Driver
$config['database'] ?? '', $config['database'] ?? '',
$config['port'] ?? 0, $config['port'] ?? 0,
$config['socket'], $config['socket'],
$config['flags'] ?? 0 $config['flags'] ?? 0,
); );
if ($this->connection->connect_errno) { if ($this->connection->connect_errno) {
@@ -153,14 +153,12 @@ class MySqliDriver implements Dibi\Driver
} elseif ($res instanceof \mysqli_result) { } elseif ($res instanceof \mysqli_result) {
return $this->createResultDriver($res); return $this->createResultDriver($res);
} }
return null; return null;
} }
/** public static function createException(string $message, int|string $code, string $sql): Dibi\DriverException
* @param int|string $code
*/
public static function createException(string $message, $code, string $sql): Dibi\DriverException
{ {
if (in_array($code, [1216, 1217, 1451, 1452, 1701], true)) { if (in_array($code, [1216, 1217, 1451, 1452, 1701], true)) {
return new Dibi\ForeignKeyConstraintViolationException($message, $code, $sql); return new Dibi\ForeignKeyConstraintViolationException($message, $code, $sql);
@@ -191,6 +189,7 @@ class MySqliDriver implements Dibi\Driver
foreach ($matches as $m) { foreach ($matches as $m) {
$res[$m[1]] = (int) $m[2]; $res[$m[1]] = (int) $m[2];
} }
return $res; return $res;
} }
@@ -219,7 +218,7 @@ class MySqliDriver implements Dibi\Driver
* Begins a transaction (if supported). * Begins a transaction (if supported).
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function begin(string $savepoint = null): void public function begin(?string $savepoint = null): void
{ {
$this->query($savepoint ? "SAVEPOINT $savepoint" : 'START TRANSACTION'); $this->query($savepoint ? "SAVEPOINT $savepoint" : 'START TRANSACTION');
} }
@@ -229,7 +228,7 @@ class MySqliDriver implements Dibi\Driver
* Commits statements in a transaction. * Commits statements in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function commit(string $savepoint = null): void public function commit(?string $savepoint = null): void
{ {
$this->query($savepoint ? "RELEASE SAVEPOINT $savepoint" : 'COMMIT'); $this->query($savepoint ? "RELEASE SAVEPOINT $savepoint" : 'COMMIT');
} }
@@ -239,7 +238,7 @@ class MySqliDriver implements Dibi\Driver
* Rollback changes in a transaction. * Rollback changes in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function rollback(string $savepoint = null): void public function rollback(?string $savepoint = null): void
{ {
$this->query($savepoint ? "ROLLBACK TO SAVEPOINT $savepoint" : 'ROLLBACK'); $this->query($savepoint ? "ROLLBACK TO SAVEPOINT $savepoint" : 'ROLLBACK');
} }
@@ -250,7 +249,11 @@ class MySqliDriver implements Dibi\Driver
*/ */
public function getResource(): ?\mysqli public function getResource(): ?\mysqli
{ {
return @$this->connection->thread_id ? $this->connection : null; try {
return @$this->connection->thread_id ? $this->connection : null;
} catch (\Throwable $e) {
return null;
}
} }
@@ -319,6 +322,7 @@ class MySqliDriver implements Dibi\Driver
if ($value->y || $value->m || $value->d) { if ($value->y || $value->m || $value->d) {
throw new Dibi\NotSupportedException('Only time interval is supported.'); throw new Dibi\NotSupportedException('Only time interval is supported.');
} }
return $value->format("'%r%H:%I:%S.%f'"); return $value->format("'%r%H:%I:%S.%f'");
} }

View File

@@ -19,14 +19,12 @@ class MySqliResult implements Dibi\ResultDriver
{ {
use Dibi\Strict; use Dibi\Strict;
/** @var \mysqli_result */ private \mysqli_result $resultSet;
private $resultSet;
/** @var bool */ private bool $autoFree = true;
private $autoFree = true;
/** @var bool Is buffered (seekable and countable)? */ /** Is buffered (seekable and countable)? */
private $buffered; private bool $buffered;
public function __construct(\mysqli_result $resultSet, bool $buffered) public function __construct(\mysqli_result $resultSet, bool $buffered)
@@ -55,6 +53,7 @@ class MySqliResult implements Dibi\ResultDriver
if (!$this->buffered) { if (!$this->buffered) {
throw new Dibi\NotSupportedException('Row count is not available for unbuffered queries.'); throw new Dibi\NotSupportedException('Row count is not available for unbuffered queries.');
} }
return $this->resultSet->num_rows; return $this->resultSet->num_rows;
} }
@@ -80,6 +79,7 @@ class MySqliResult implements Dibi\ResultDriver
if (!$this->buffered) { if (!$this->buffered) {
throw new Dibi\NotSupportedException('Cannot seek an unbuffered result set.'); throw new Dibi\NotSupportedException('Cannot seek an unbuffered result set.');
} }
return $this->resultSet->data_seek($row); return $this->resultSet->data_seek($row);
} }
@@ -107,6 +107,7 @@ class MySqliResult implements Dibi\ResultDriver
$types[$value] = substr($key, 12); $types[$value] = substr($key, 12);
} }
} }
$types[MYSQLI_TYPE_TINY] = $types[MYSQLI_TYPE_SHORT] = $types[MYSQLI_TYPE_LONG] = 'INT'; $types[MYSQLI_TYPE_TINY] = $types[MYSQLI_TYPE_SHORT] = $types[MYSQLI_TYPE_LONG] = 'INT';
} }
@@ -123,6 +124,7 @@ class MySqliResult implements Dibi\ResultDriver
'vendor' => $row, 'vendor' => $row,
]; ];
} }
return $columns; return $columns;
} }

View File

@@ -19,8 +19,7 @@ class NoDataResult implements Dibi\ResultDriver
{ {
use Dibi\Strict; use Dibi\Strict;
/** @var int */ private int $rows;
private $rows;
public function __construct(int $rows) public function __construct(int $rows)
@@ -61,7 +60,7 @@ class NoDataResult implements Dibi\ResultDriver
} }
public function getResultResource() public function getResultResource(): mixed
{ {
return null; return null;
} }

View File

@@ -30,11 +30,9 @@ class OdbcDriver implements Dibi\Driver
/** @var resource */ /** @var resource */
private $connection; private $connection;
/** @var int|null Affected rows */ private ?int $affectedRows;
private $affectedRows;
/** @var bool */ private bool $microseconds = true;
private $microseconds = true;
/** @throws Dibi\NotSupportedException */ /** @throws Dibi\NotSupportedException */
@@ -96,6 +94,7 @@ class OdbcDriver implements Dibi\Driver
? $this->createResultDriver($res) ? $this->createResultDriver($res)
: null; : null;
} }
return null; return null;
} }
@@ -122,9 +121,9 @@ class OdbcDriver implements Dibi\Driver
* Begins a transaction (if supported). * Begins a transaction (if supported).
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function begin(string $savepoint = null): void public function begin(?string $savepoint = null): void
{ {
if (!odbc_autocommit($this->connection, PHP_VERSION_ID < 80000 ? 0 : false)) { if (!odbc_autocommit($this->connection, false)) {
throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection)); throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection));
} }
} }
@@ -134,12 +133,13 @@ class OdbcDriver implements Dibi\Driver
* Commits statements in a transaction. * Commits statements in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function commit(string $savepoint = null): void public function commit(?string $savepoint = null): void
{ {
if (!odbc_commit($this->connection)) { if (!odbc_commit($this->connection)) {
throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection)); throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection));
} }
odbc_autocommit($this->connection, PHP_VERSION_ID < 80000 ? 1 : true);
odbc_autocommit($this->connection, true);
} }
@@ -147,12 +147,13 @@ class OdbcDriver implements Dibi\Driver
* Rollback changes in a transaction. * Rollback changes in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function rollback(string $savepoint = null): void public function rollback(?string $savepoint = null): void
{ {
if (!odbc_rollback($this->connection)) { if (!odbc_rollback($this->connection)) {
throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection)); throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection));
} }
odbc_autocommit($this->connection, PHP_VERSION_ID < 80000 ? 1 : true);
odbc_autocommit($this->connection, true);
} }
@@ -169,7 +170,7 @@ class OdbcDriver implements Dibi\Driver
* Returns the connection resource. * Returns the connection resource.
* @return resource|null * @return resource|null
*/ */
public function getResource() public function getResource(): mixed
{ {
return is_resource($this->connection) ? $this->connection : null; return is_resource($this->connection) ? $this->connection : null;
} }

View File

@@ -19,8 +19,7 @@ class OdbcReflector implements Dibi\Reflector
{ {
use Dibi\Strict; use Dibi\Strict;
/** @var Dibi\Driver */ private Dibi\Driver $driver;
private $driver;
public function __construct(Dibi\Driver $driver) public function __construct(Dibi\Driver $driver)
@@ -44,6 +43,7 @@ class OdbcReflector implements Dibi\Reflector
]; ];
} }
} }
odbc_free_result($res); odbc_free_result($res);
return $tables; return $tables;
} }
@@ -68,6 +68,7 @@ class OdbcReflector implements Dibi\Reflector
]; ];
} }
} }
odbc_free_result($res); odbc_free_result($res);
return $columns; return $columns;
} }

View File

@@ -22,11 +22,9 @@ class OdbcResult implements Dibi\ResultDriver
/** @var resource */ /** @var resource */
private $resultSet; private $resultSet;
/** @var bool */ private bool $autoFree = true;
private $autoFree = true;
/** @var int Cursor */ private int $row = 0;
private $row = 0;
/** /**
@@ -72,11 +70,13 @@ class OdbcResult implements Dibi\ResultDriver
if (!odbc_fetch_row($set, ++$this->row)) { if (!odbc_fetch_row($set, ++$this->row)) {
return null; return null;
} }
$count = odbc_num_fields($set); $count = odbc_num_fields($set);
$cols = []; $cols = [];
for ($i = 1; $i <= $count; $i++) { for ($i = 1; $i <= $count; $i++) {
$cols[] = odbc_result($set, $i); $cols[] = odbc_result($set, $i);
} }
return $cols; return $cols;
} }
} }
@@ -116,6 +116,7 @@ class OdbcResult implements Dibi\ResultDriver
'nativetype' => odbc_field_type($this->resultSet, $i), 'nativetype' => odbc_field_type($this->resultSet, $i),
]; ];
} }
return $columns; return $columns;
} }
@@ -124,7 +125,7 @@ class OdbcResult implements Dibi\ResultDriver
* Returns the result set resource. * Returns the result set resource.
* @return resource|null * @return resource|null
*/ */
public function getResultResource() public function getResultResource(): mixed
{ {
$this->autoFree = false; $this->autoFree = false;
return is_resource($this->resultSet) ? $this->resultSet : null; return is_resource($this->resultSet) ? $this->resultSet : null;

View File

@@ -32,14 +32,12 @@ class OracleDriver implements Dibi\Driver
/** @var resource */ /** @var resource */
private $connection; private $connection;
/** @var bool */ private bool $autocommit = true;
private $autocommit = true;
/** @var bool use native datetime format */ /** use native datetime format */
private $nativeDate; private bool $nativeDate;
/** @var int|null Number of affected rows */ private ?int $affectedRows;
private $affectedRows;
/** @throws Dibi\NotSupportedException */ /** @throws Dibi\NotSupportedException */
@@ -104,6 +102,7 @@ class OracleDriver implements Dibi\Driver
$err = oci_error($this->connection); $err = oci_error($this->connection);
throw new Dibi\DriverException($err['message'], $err['code'], $sql); throw new Dibi\DriverException($err['message'], $err['code'], $sql);
} }
return null; return null;
} }
@@ -147,7 +146,7 @@ class OracleDriver implements Dibi\Driver
/** /**
* Begins a transaction (if supported). * Begins a transaction (if supported).
*/ */
public function begin(string $savepoint = null): void public function begin(?string $savepoint = null): void
{ {
$this->autocommit = false; $this->autocommit = false;
} }
@@ -157,12 +156,13 @@ class OracleDriver implements Dibi\Driver
* Commits statements in a transaction. * Commits statements in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function commit(string $savepoint = null): void public function commit(?string $savepoint = null): void
{ {
if (!oci_commit($this->connection)) { if (!oci_commit($this->connection)) {
$err = oci_error($this->connection); $err = oci_error($this->connection);
throw new Dibi\DriverException($err['message'], $err['code']); throw new Dibi\DriverException($err['message'], $err['code']);
} }
$this->autocommit = true; $this->autocommit = true;
} }
@@ -171,12 +171,13 @@ class OracleDriver implements Dibi\Driver
* Rollback changes in a transaction. * Rollback changes in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function rollback(string $savepoint = null): void public function rollback(?string $savepoint = null): void
{ {
if (!oci_rollback($this->connection)) { if (!oci_rollback($this->connection)) {
$err = oci_error($this->connection); $err = oci_error($this->connection);
throw new Dibi\DriverException($err['message'], $err['code']); throw new Dibi\DriverException($err['message'], $err['code']);
} }
$this->autocommit = true; $this->autocommit = true;
} }
@@ -185,7 +186,7 @@ class OracleDriver implements Dibi\Driver
* Returns the connection resource. * Returns the connection resource.
* @return resource|null * @return resource|null
*/ */
public function getResource() public function getResource(): mixed
{ {
return is_resource($this->connection) ? $this->connection : null; return is_resource($this->connection) ? $this->connection : null;
} }

View File

@@ -19,8 +19,7 @@ class OracleReflector implements Dibi\Reflector
{ {
use Dibi\Strict; use Dibi\Strict;
/** @var Dibi\Driver */ private Dibi\Driver $driver;
private $driver;
public function __construct(Dibi\Driver $driver) public function __construct(Dibi\Driver $driver)
@@ -44,6 +43,7 @@ class OracleReflector implements Dibi\Reflector
]; ];
} }
} }
return $tables; return $tables;
} }
@@ -66,6 +66,7 @@ class OracleReflector implements Dibi\Reflector
'vendor' => $row, 'vendor' => $row,
]; ];
} }
return $columns; return $columns;
} }

View File

@@ -22,8 +22,7 @@ class OracleResult implements Dibi\ResultDriver
/** @var resource */ /** @var resource */
private $resultSet; private $resultSet;
/** @var bool */ private bool $autoFree = true;
private $autoFree = true;
/** /**
@@ -100,6 +99,7 @@ class OracleResult implements Dibi\ResultDriver
'nativetype' => $type === 'NUMBER' && oci_field_scale($this->resultSet, $i) === 0 ? 'INTEGER' : $type, 'nativetype' => $type === 'NUMBER' && oci_field_scale($this->resultSet, $i) === 0 ? 'INTEGER' : $type,
]; ];
} }
return $columns; return $columns;
} }
@@ -108,7 +108,7 @@ class OracleResult implements Dibi\ResultDriver
* Returns the result set resource. * Returns the result set resource.
* @return resource|null * @return resource|null
*/ */
public function getResultResource() public function getResultResource(): mixed
{ {
$this->autoFree = false; $this->autoFree = false;
return is_resource($this->resultSet) ? $this->resultSet : null; return is_resource($this->resultSet) ? $this->resultSet : null;

View File

@@ -29,17 +29,13 @@ class PdoDriver implements Dibi\Driver
{ {
use Dibi\Strict; use Dibi\Strict;
/** @var PDO|null Connection resource */ private ?PDO $connection;
private $connection;
/** @var int|null Affected rows */ private ?int $affectedRows;
private $affectedRows;
/** @var string */ private string $driverName;
private $driverName;
/** @var string */ private string $serverVersion = '';
private $serverVersion = '';
/** @throws Dibi\NotSupportedException */ /** @throws Dibi\NotSupportedException */
@@ -60,7 +56,6 @@ class PdoDriver implements Dibi\Driver
if ($this->connection->getAttribute(PDO::ATTR_ERRMODE) !== PDO::ERRMODE_SILENT) { if ($this->connection->getAttribute(PDO::ATTR_ERRMODE) !== PDO::ERRMODE_SILENT) {
throw new Dibi\DriverException('PDO connection in exception or warning error mode is not supported.'); throw new Dibi\DriverException('PDO connection in exception or warning error mode is not supported.');
} }
} else { } else {
try { try {
$this->connection = new PDO($config['dsn'], $config['username'], $config['password'], $config['options']); $this->connection = new PDO($config['dsn'], $config['username'], $config['password'], $config['options']);
@@ -69,6 +64,7 @@ class PdoDriver implements Dibi\Driver
if ($e->getMessage() === 'could not find driver') { if ($e->getMessage() === 'could not find driver') {
throw new Dibi\NotSupportedException('PHP extension for PDO is not loaded.'); throw new Dibi\NotSupportedException('PHP extension for PDO is not loaded.');
} }
throw new Dibi\DriverException($e->getMessage(), $e->getCode()); throw new Dibi\DriverException($e->getMessage(), $e->getCode());
} }
} }
@@ -144,7 +140,7 @@ class PdoDriver implements Dibi\Driver
* Begins a transaction (if supported). * Begins a transaction (if supported).
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function begin(string $savepoint = null): void public function begin(?string $savepoint = null): void
{ {
if (!$this->connection->beginTransaction()) { if (!$this->connection->beginTransaction()) {
$err = $this->connection->errorInfo(); $err = $this->connection->errorInfo();
@@ -157,7 +153,7 @@ class PdoDriver implements Dibi\Driver
* Commits statements in a transaction. * Commits statements in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function commit(string $savepoint = null): void public function commit(?string $savepoint = null): void
{ {
if (!$this->connection->commit()) { if (!$this->connection->commit()) {
$err = $this->connection->errorInfo(); $err = $this->connection->errorInfo();
@@ -170,7 +166,7 @@ class PdoDriver implements Dibi\Driver
* Rollback changes in a transaction. * Rollback changes in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function rollback(string $savepoint = null): void public function rollback(?string $savepoint = null): void
{ {
if (!$this->connection->rollBack()) { if (!$this->connection->rollBack()) {
$err = $this->connection->errorInfo(); $err = $this->connection->errorInfo();
@@ -366,15 +362,18 @@ class PdoDriver implements Dibi\Driver
$sql .= ' LIMIT ' . ($limit ?? '18446744073709551615') $sql .= ' LIMIT ' . ($limit ?? '18446744073709551615')
. ($offset ? ' OFFSET ' . $offset : ''); . ($offset ? ' OFFSET ' . $offset : '');
} }
break; break;
case 'pgsql': case 'pgsql':
if ($limit !== null) { if ($limit !== null) {
$sql .= ' LIMIT ' . $limit; $sql .= ' LIMIT ' . $limit;
} }
if ($offset) { if ($offset) {
$sql .= ' OFFSET ' . $offset; $sql .= ' OFFSET ' . $offset;
} }
break; break;
case 'sqlite': case 'sqlite':
@@ -382,6 +381,7 @@ class PdoDriver implements Dibi\Driver
$sql .= ' LIMIT ' . ($limit ?? '-1') $sql .= ' LIMIT ' . ($limit ?? '-1')
. ($offset ? ' OFFSET ' . $offset : ''); . ($offset ? ' OFFSET ' . $offset : '');
} }
break; break;
case 'oci': case 'oci':
@@ -394,6 +394,7 @@ class PdoDriver implements Dibi\Driver
} elseif ($limit !== null) { } elseif ($limit !== null) {
$sql = 'SELECT * FROM (' . $sql . ') WHERE ROWNUM <= ' . $limit; $sql = 'SELECT * FROM (' . $sql . ') WHERE ROWNUM <= ' . $limit;
} }
break; break;
case 'mssql': case 'mssql':
@@ -406,10 +407,10 @@ class PdoDriver implements Dibi\Driver
} elseif ($offset) { } elseif ($offset) {
$sql = sprintf('%s OFFSET %d ROWS', rtrim($sql), $offset); $sql = sprintf('%s OFFSET %d ROWS', rtrim($sql), $offset);
} }
break; break;
} }
// break omitted // break omitted
case 'odbc': case 'odbc':
if ($offset) { if ($offset) {
throw new Dibi\NotSupportedException('Offset is not supported by this database.'); throw new Dibi\NotSupportedException('Offset is not supported by this database.');
@@ -419,7 +420,6 @@ class PdoDriver implements Dibi\Driver
break; break;
} }
// break omitted // break omitted
default: default:
throw new Dibi\NotSupportedException('PDO or driver does not support applying limit or offset.'); throw new Dibi\NotSupportedException('PDO or driver does not support applying limit or offset.');
} }

View File

@@ -21,11 +21,9 @@ class PdoResult implements Dibi\ResultDriver
{ {
use Dibi\Strict; use Dibi\Strict;
/** @var \PDOStatement|null */ private ?\PDOStatement $resultSet;
private $resultSet;
/** @var string */ private string $driverName;
private $driverName;
public function __construct(\PDOStatement $resultSet, string $driverName) public function __construct(\PDOStatement $resultSet, string $driverName)
@@ -85,6 +83,7 @@ class PdoResult implements Dibi\ResultDriver
if ($row === false) { if ($row === false) {
throw new Dibi\NotSupportedException('Driver does not support meta data.'); throw new Dibi\NotSupportedException('Driver does not support meta data.');
} }
$row += [ $row += [
'table' => null, 'table' => null,
'native_type' => 'VAR_STRING', 'native_type' => 'VAR_STRING',
@@ -99,6 +98,7 @@ class PdoResult implements Dibi\ResultDriver
'vendor' => $row, 'vendor' => $row,
]; ];
} }
return $columns; return $columns;
} }

View File

@@ -11,6 +11,7 @@ namespace Dibi\Drivers;
use Dibi; use Dibi;
use Dibi\Helpers; use Dibi\Helpers;
use PgSql;
/** /**
@@ -29,11 +30,10 @@ class PostgreDriver implements Dibi\Driver
{ {
use Dibi\Strict; use Dibi\Strict;
/** @var resource */ /** @var resource|PgSql\Connection */
private $connection; private $connection;
/** @var int|null Affected rows */ private ?int $affectedRows;
private $affectedRows;
/** @throws Dibi\NotSupportedException */ /** @throws Dibi\NotSupportedException */
@@ -63,6 +63,7 @@ class PostgreDriver implements Dibi\Driver
} }
} }
} }
$connectType = $config['connect_type'] ?? PGSQL_CONNECT_FORCE_NEW; $connectType = $config['connect_type'] ?? PGSQL_CONNECT_FORCE_NEW;
set_error_handler(function (int $severity, string $message) use (&$error) { set_error_handler(function (int $severity, string $message) use (&$error) {
@@ -74,7 +75,7 @@ class PostgreDriver implements Dibi\Driver
restore_error_handler(); restore_error_handler();
} }
if (!is_resource($this->connection)) { if (!is_resource($this->connection) && !$this->connection instanceof PgSql\Connection) {
throw new Dibi\DriverException($error ?: 'Connecting error.'); throw new Dibi\DriverException($error ?: 'Connecting error.');
} }
@@ -120,24 +121,25 @@ class PostgreDriver implements Dibi\Driver
if ($res === false) { if ($res === false) {
throw static::createException(pg_last_error($this->connection), null, $sql); throw static::createException(pg_last_error($this->connection), null, $sql);
} elseif (is_resource($res)) { } elseif (is_resource($res) || $res instanceof PgSql\Result) {
$this->affectedRows = Helpers::false2Null(pg_affected_rows($res)); $this->affectedRows = Helpers::false2Null(pg_affected_rows($res));
if (pg_num_fields($res)) { if (pg_num_fields($res)) {
return $this->createResultDriver($res); return $this->createResultDriver($res);
} }
} }
return null; return null;
} }
public static function createException(string $message, $code = null, string $sql = null): Dibi\DriverException public static function createException(string $message, $code = null, ?string $sql = null): Dibi\DriverException
{ {
if ($code === null && preg_match('#^ERROR:\s+(\S+):\s*#', $message, $m)) { if ($code === null && preg_match('#^ERROR:\s+(\S+):\s*#', $message, $m)) {
$code = $m[1]; $code = $m[1];
$message = substr($message, strlen($m[0])); $message = substr($message, strlen($m[0]));
} }
if ($code === '0A000' && strpos($message, 'truncate') !== false) { if ($code === '0A000' && str_contains($message, 'truncate')) {
return new Dibi\ForeignKeyConstraintViolationException($message, $code, $sql); return new Dibi\ForeignKeyConstraintViolationException($message, $code, $sql);
} elseif ($code === '23502') { } elseif ($code === '23502') {
@@ -186,7 +188,7 @@ class PostgreDriver implements Dibi\Driver
* Begins a transaction (if supported). * Begins a transaction (if supported).
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function begin(string $savepoint = null): void public function begin(?string $savepoint = null): void
{ {
$this->query($savepoint ? "SAVEPOINT {$this->escapeIdentifier($savepoint)}" : 'START TRANSACTION'); $this->query($savepoint ? "SAVEPOINT {$this->escapeIdentifier($savepoint)}" : 'START TRANSACTION');
} }
@@ -196,7 +198,7 @@ class PostgreDriver implements Dibi\Driver
* Commits statements in a transaction. * Commits statements in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function commit(string $savepoint = null): void public function commit(?string $savepoint = null): void
{ {
$this->query($savepoint ? "RELEASE SAVEPOINT {$this->escapeIdentifier($savepoint)}" : 'COMMIT'); $this->query($savepoint ? "RELEASE SAVEPOINT {$this->escapeIdentifier($savepoint)}" : 'COMMIT');
} }
@@ -206,7 +208,7 @@ class PostgreDriver implements Dibi\Driver
* Rollback changes in a transaction. * Rollback changes in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function rollback(string $savepoint = null): void public function rollback(?string $savepoint = null): void
{ {
$this->query($savepoint ? "ROLLBACK TO SAVEPOINT {$this->escapeIdentifier($savepoint)}" : 'ROLLBACK'); $this->query($savepoint ? "ROLLBACK TO SAVEPOINT {$this->escapeIdentifier($savepoint)}" : 'ROLLBACK');
} }
@@ -225,9 +227,11 @@ class PostgreDriver implements Dibi\Driver
* Returns the connection resource. * Returns the connection resource.
* @return resource|null * @return resource|null
*/ */
public function getResource() public function getResource(): mixed
{ {
return is_resource($this->connection) ? $this->connection : null; return is_resource($this->connection) || $this->connection instanceof PgSql\Connection
? $this->connection
: null;
} }
@@ -258,18 +262,20 @@ class PostgreDriver implements Dibi\Driver
*/ */
public function escapeText(string $value): string public function escapeText(string $value): string
{ {
if (!is_resource($this->connection)) { if (!$this->getResource()) {
throw new Dibi\Exception('Lost connection to server.'); throw new Dibi\Exception('Lost connection to server.');
} }
return "'" . pg_escape_string($this->connection, $value) . "'"; return "'" . pg_escape_string($this->connection, $value) . "'";
} }
public function escapeBinary(string $value): string public function escapeBinary(string $value): string
{ {
if (!is_resource($this->connection)) { if (!$this->getResource()) {
throw new Dibi\Exception('Lost connection to server.'); throw new Dibi\Exception('Lost connection to server.');
} }
return "'" . pg_escape_bytea($this->connection, $value) . "'"; return "'" . pg_escape_bytea($this->connection, $value) . "'";
} }
@@ -325,9 +331,11 @@ class PostgreDriver implements Dibi\Driver
if ($limit < 0 || $offset < 0) { if ($limit < 0 || $offset < 0) {
throw new Dibi\NotSupportedException('Negative offset or limit.'); throw new Dibi\NotSupportedException('Negative offset or limit.');
} }
if ($limit !== null) { if ($limit !== null) {
$sql .= ' LIMIT ' . $limit; $sql .= ' LIMIT ' . $limit;
} }
if ($offset) { if ($offset) {
$sql .= ' OFFSET ' . $offset; $sql .= ' OFFSET ' . $offset;
} }

View File

@@ -19,11 +19,9 @@ class PostgreReflector implements Dibi\Reflector
{ {
use Dibi\Strict; use Dibi\Strict;
/** @var Dibi\Driver */ private Dibi\Driver $driver;
private $driver;
/** @var string */ private string $version;
private $version;
public function __construct(Dibi\Driver $driver, string $version) public function __construct(Dibi\Driver $driver, string $version)
@@ -66,6 +64,7 @@ class PostgreReflector implements Dibi\Reflector
while ($row = $res->fetch(true)) { while ($row = $res->fetch(true)) {
$tables[] = $row; $tables[] = $row;
} }
return $tables; return $tables;
} }
@@ -102,7 +101,7 @@ class PostgreReflector implements Dibi\Reflector
a.atttypmod-4 AS character_maximum_length, a.atttypmod-4 AS character_maximum_length,
NOT a.attnotnull AS is_nullable, NOT a.attnotnull AS is_nullable,
a.attnum AS ordinal_position, a.attnum AS ordinal_position,
adef.adsrc AS column_default pg_get_expr(adef.adbin, adef.adrelid) AS column_default
FROM FROM
pg_attribute a pg_attribute a
JOIN pg_type ON a.atttypid = pg_type.oid JOIN pg_type ON a.atttypid = pg_type.oid
@@ -127,10 +126,11 @@ class PostgreReflector implements Dibi\Reflector
'size' => $size > 0 ? $size : null, 'size' => $size > 0 ? $size : null,
'nullable' => $row['is_nullable'] === 'YES' || $row['is_nullable'] === 't' || $row['is_nullable'] === true, 'nullable' => $row['is_nullable'] === 'YES' || $row['is_nullable'] === 't' || $row['is_nullable'] === true,
'default' => $row['column_default'], 'default' => $row['column_default'],
'autoincrement' => (int) $row['ordinal_position'] === $primary && substr($row['column_default'] ?? '', 0, 7) === 'nextval', 'autoincrement' => (int) $row['ordinal_position'] === $primary && str_starts_with($row['column_default'] ?? '', 'nextval'),
'vendor' => $row, 'vendor' => $row,
]; ];
} }
return $columns; return $columns;
} }
@@ -180,6 +180,7 @@ class PostgreReflector implements Dibi\Reflector
} }
} }
} }
return array_values($indexes); return array_values($indexes);
} }

View File

@@ -11,6 +11,7 @@ namespace Dibi\Drivers;
use Dibi; use Dibi;
use Dibi\Helpers; use Dibi\Helpers;
use PgSql;
/** /**
@@ -20,15 +21,14 @@ class PostgreResult implements Dibi\ResultDriver
{ {
use Dibi\Strict; use Dibi\Strict;
/** @var resource */ /** @var resource|PgSql\Result */
private $resultSet; private $resultSet;
/** @var bool */ private bool $autoFree = true;
private $autoFree = true;
/** /**
* @param resource $resultSet * @param resource|PgSql\Result $resultSet
*/ */
public function __construct($resultSet) public function __construct($resultSet)
{ {
@@ -102,18 +102,21 @@ class PostgreResult implements Dibi\ResultDriver
: $row['name']; : $row['name'];
$columns[] = $row; $columns[] = $row;
} }
return $columns; return $columns;
} }
/** /**
* Returns the result set resource. * Returns the result set resource.
* @return resource|null * @return resource|PgSql\Result|null
*/ */
public function getResultResource() public function getResultResource(): mixed
{ {
$this->autoFree = false; $this->autoFree = false;
return is_resource($this->resultSet) ? $this->resultSet : null; return is_resource($this->resultSet) || $this->resultSet instanceof PgSql\Result
? $this->resultSet
: null;
} }

View File

@@ -27,14 +27,11 @@ class SqliteDriver implements Dibi\Driver
{ {
use Dibi\Strict; use Dibi\Strict;
/** @var SQLite3 */ private SQLite3 $connection;
private $connection;
/** @var string Date format */ private string $fmtDate;
private $fmtDate;
/** @var string Datetime format */ private string $fmtDateTime;
private $fmtDateTime;
/** @throws Dibi\NotSupportedException */ /** @throws Dibi\NotSupportedException */
@@ -57,7 +54,7 @@ class SqliteDriver implements Dibi\Driver
} else { } else {
try { try {
$this->connection = new SQLite3($config['database']); $this->connection = new SQLite3($config['database']);
} catch (\Exception $e) { } catch (\Throwable $e) {
throw new Dibi\DriverException($e->getMessage(), $e->getCode()); throw new Dibi\DriverException($e->getMessage(), $e->getCode());
} }
} }
@@ -92,6 +89,7 @@ class SqliteDriver implements Dibi\Driver
} elseif ($res instanceof \SQLite3Result && $res->numColumns()) { } elseif ($res instanceof \SQLite3Result && $res->numColumns()) {
return $this->createResultDriver($res); return $this->createResultDriver($res);
} }
return null; return null;
} }
@@ -101,19 +99,19 @@ class SqliteDriver implements Dibi\Driver
if ($code !== 19) { if ($code !== 19) {
return new Dibi\DriverException($message, $code, $sql); return new Dibi\DriverException($message, $code, $sql);
} elseif (strpos($message, 'must be unique') !== false } elseif (str_contains($message, 'must be unique')
|| strpos($message, 'is not unique') !== false || str_contains($message, 'is not unique')
|| strpos($message, 'UNIQUE constraint failed') !== false || str_contains($message, 'UNIQUE constraint failed')
) { ) {
return new Dibi\UniqueConstraintViolationException($message, $code, $sql); return new Dibi\UniqueConstraintViolationException($message, $code, $sql);
} elseif (strpos($message, 'may not be null') !== false } elseif (str_contains($message, 'may not be null')
|| strpos($message, 'NOT NULL constraint failed') !== false || str_contains($message, 'NOT NULL constraint failed')
) { ) {
return new Dibi\NotNullConstraintViolationException($message, $code, $sql); return new Dibi\NotNullConstraintViolationException($message, $code, $sql);
} elseif (strpos($message, 'foreign key constraint failed') !== false } elseif (str_contains($message, 'foreign key constraint failed')
|| strpos($message, 'FOREIGN KEY constraint failed') !== false || str_contains($message, 'FOREIGN KEY constraint failed')
) { ) {
return new Dibi\ForeignKeyConstraintViolationException($message, $code, $sql); return new Dibi\ForeignKeyConstraintViolationException($message, $code, $sql);
@@ -145,7 +143,7 @@ class SqliteDriver implements Dibi\Driver
* Begins a transaction (if supported). * Begins a transaction (if supported).
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function begin(string $savepoint = null): void public function begin(?string $savepoint = null): void
{ {
$this->query($savepoint ? "SAVEPOINT $savepoint" : 'BEGIN'); $this->query($savepoint ? "SAVEPOINT $savepoint" : 'BEGIN');
} }
@@ -155,7 +153,7 @@ class SqliteDriver implements Dibi\Driver
* Commits statements in a transaction. * Commits statements in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function commit(string $savepoint = null): void public function commit(?string $savepoint = null): void
{ {
$this->query($savepoint ? "RELEASE SAVEPOINT $savepoint" : 'COMMIT'); $this->query($savepoint ? "RELEASE SAVEPOINT $savepoint" : 'COMMIT');
} }
@@ -165,7 +163,7 @@ class SqliteDriver implements Dibi\Driver
* Rollback changes in a transaction. * Rollback changes in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function rollback(string $savepoint = null): void public function rollback(?string $savepoint = null): void
{ {
$this->query($savepoint ? "ROLLBACK TO SAVEPOINT $savepoint" : 'ROLLBACK'); $this->query($savepoint ? "ROLLBACK TO SAVEPOINT $savepoint" : 'ROLLBACK');
} }
@@ -290,8 +288,9 @@ class SqliteDriver implements Dibi\Driver
string $name, string $name,
callable $rowCallback, callable $rowCallback,
callable $agrCallback, callable $agrCallback,
int $numArgs = -1 int $numArgs = -1,
): void { ): void
{
$this->connection->createAggregate($name, $rowCallback, $agrCallback, $numArgs); $this->connection->createAggregate($name, $rowCallback, $agrCallback, $numArgs);
} }
} }

View File

@@ -19,8 +19,7 @@ class SqliteReflector implements Dibi\Reflector
{ {
use Dibi\Strict; use Dibi\Strict;
/** @var Dibi\Driver */ private Dibi\Driver $driver;
private $driver;
public function __construct(Dibi\Driver $driver) public function __construct(Dibi\Driver $driver)
@@ -44,6 +43,7 @@ class SqliteReflector implements Dibi\Reflector
while ($row = $res->fetch(true)) { while ($row = $res->fetch(true)) {
$tables[] = $row; $tables[] = $row;
} }
return $tables; return $tables;
} }
@@ -70,6 +70,7 @@ class SqliteReflector implements Dibi\Reflector
'vendor' => $row, 'vendor' => $row,
]; ];
} }
return $columns; return $columns;
} }
@@ -103,8 +104,10 @@ class SqliteReflector implements Dibi\Reflector
break; break;
} }
} }
$indexes[$index]['primary'] = (bool) $primary; $indexes[$index]['primary'] = (bool) $primary;
} }
if (!$indexes) { // @see http://www.sqlite.org/lang_createtable.html#rowid if (!$indexes) { // @see http://www.sqlite.org/lang_createtable.html#rowid
foreach ($columns as $column) { foreach ($columns as $column) {
if ($column['vendor']['pk']) { if ($column['vendor']['pk']) {
@@ -142,6 +145,7 @@ class SqliteReflector implements Dibi\Reflector
$keys[$row['id']]['foreign'] = null; $keys[$row['id']]['foreign'] = null;
} }
} }
return array_values($keys); return array_values($keys);
} }
} }

View File

@@ -20,11 +20,9 @@ class SqliteResult implements Dibi\ResultDriver
{ {
use Dibi\Strict; use Dibi\Strict;
/** @var \SQLite3Result */ private \SQLite3Result $resultSet;
private $resultSet;
/** @var bool */ private bool $autoFree = true;
private $autoFree = true;
public function __construct(\SQLite3Result $resultSet) public function __construct(\SQLite3Result $resultSet)
@@ -90,7 +88,7 @@ class SqliteResult implements Dibi\ResultDriver
{ {
$count = $this->resultSet->numColumns(); $count = $this->resultSet->numColumns();
$columns = []; $columns = [];
static $types = [SQLITE3_INTEGER => 'int', SQLITE3_FLOAT => 'float', SQLITE3_TEXT => 'text', SQLITE3_BLOB => 'blob', SQLITE3_NULL => 'null']; $types = [SQLITE3_INTEGER => 'int', SQLITE3_FLOAT => 'float', SQLITE3_TEXT => 'text', SQLITE3_BLOB => 'blob', SQLITE3_NULL => 'null'];
for ($i = 0; $i < $count; $i++) { for ($i = 0; $i < $count; $i++) {
$columns[] = [ $columns[] = [
'name' => $this->resultSet->columnName($i), 'name' => $this->resultSet->columnName($i),
@@ -99,6 +97,7 @@ class SqliteResult implements Dibi\ResultDriver
'nativetype' => $types[$this->resultSet->columnType($i)] ?? null, // buggy in PHP 7.4.4 & 7.3.16, bug 79414 'nativetype' => $types[$this->resultSet->columnType($i)] ?? null, // buggy in PHP 7.4.4 & 7.3.16, bug 79414
]; ];
} }
return $columns; return $columns;
} }

View File

@@ -32,11 +32,9 @@ class SqlsrvDriver implements Dibi\Driver
/** @var resource */ /** @var resource */
private $connection; private $connection;
/** @var int|null Affected rows */ private ?int $affectedRows;
private $affectedRows;
/** @var string */ private string $version = '';
private $version = '';
/** @throws Dibi\NotSupportedException */ /** @throws Dibi\NotSupportedException */
@@ -56,12 +54,11 @@ class SqlsrvDriver implements Dibi\Driver
if (!is_resource($this->connection)) { if (!is_resource($this->connection)) {
throw new \InvalidArgumentException("Configuration option 'resource' is not resource."); throw new \InvalidArgumentException("Configuration option 'resource' is not resource.");
} }
} else { } else {
$options = $config['options']; $options = $config['options'];
// Default values // Default values
$options['CharacterSet'] = $options['CharacterSet'] ?? 'UTF-8'; $options['CharacterSet'] ??= 'UTF-8';
$options['PWD'] = (string) $options['PWD']; $options['PWD'] = (string) $options['PWD'];
$options['UID'] = (string) $options['UID']; $options['UID'] = (string) $options['UID'];
$options['Database'] = (string) $options['Database']; $options['Database'] = (string) $options['Database'];
@@ -72,6 +69,7 @@ class SqlsrvDriver implements Dibi\Driver
$info = sqlsrv_errors(SQLSRV_ERR_ERRORS); $info = sqlsrv_errors(SQLSRV_ERR_ERRORS);
throw new Dibi\DriverException($info[0]['message'], $info[0]['code']); throw new Dibi\DriverException($info[0]['message'], $info[0]['code']);
} }
sqlsrv_configure('WarningsReturnAsErrors', 1); sqlsrv_configure('WarningsReturnAsErrors', 1);
} }
@@ -107,6 +105,7 @@ class SqlsrvDriver implements Dibi\Driver
? $this->createResultDriver($res) ? $this->createResultDriver($res)
: null; : null;
} }
return null; return null;
} }
@@ -130,6 +129,7 @@ class SqlsrvDriver implements Dibi\Driver
$row = sqlsrv_fetch_array($res, SQLSRV_FETCH_NUMERIC); $row = sqlsrv_fetch_array($res, SQLSRV_FETCH_NUMERIC);
return Dibi\Helpers::intVal($row[0]); return Dibi\Helpers::intVal($row[0]);
} }
return null; return null;
} }
@@ -138,7 +138,7 @@ class SqlsrvDriver implements Dibi\Driver
* Begins a transaction (if supported). * Begins a transaction (if supported).
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function begin(string $savepoint = null): void public function begin(?string $savepoint = null): void
{ {
sqlsrv_begin_transaction($this->connection); sqlsrv_begin_transaction($this->connection);
} }
@@ -148,7 +148,7 @@ class SqlsrvDriver implements Dibi\Driver
* Commits statements in a transaction. * Commits statements in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function commit(string $savepoint = null): void public function commit(?string $savepoint = null): void
{ {
sqlsrv_commit($this->connection); sqlsrv_commit($this->connection);
} }
@@ -158,7 +158,7 @@ class SqlsrvDriver implements Dibi\Driver
* Rollback changes in a transaction. * Rollback changes in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function rollback(string $savepoint = null): void public function rollback(?string $savepoint = null): void
{ {
sqlsrv_rollback($this->connection); sqlsrv_rollback($this->connection);
} }
@@ -168,7 +168,7 @@ class SqlsrvDriver implements Dibi\Driver
* Returns the connection resource. * Returns the connection resource.
* @return resource|null * @return resource|null
*/ */
public function getResource() public function getResource(): mixed
{ {
return is_resource($this->connection) ? $this->connection : null; return is_resource($this->connection) ? $this->connection : null;
} }
@@ -267,7 +267,6 @@ class SqlsrvDriver implements Dibi\Driver
} elseif ($limit !== null) { } elseif ($limit !== null) {
$sql = sprintf('SELECT TOP (%d) * FROM (%s) t', $limit, $sql); $sql = sprintf('SELECT TOP (%d) * FROM (%s) t', $limit, $sql);
} }
} elseif ($limit !== null) { } elseif ($limit !== null) {
// requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx // requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx
$sql = sprintf('%s OFFSET %d ROWS FETCH NEXT %d ROWS ONLY', rtrim($sql), $offset, $limit); $sql = sprintf('%s OFFSET %d ROWS FETCH NEXT %d ROWS ONLY', rtrim($sql), $offset, $limit);

View File

@@ -19,8 +19,7 @@ class SqlsrvReflector implements Dibi\Reflector
{ {
use Dibi\Strict; use Dibi\Strict;
/** @var Dibi\Driver */ private Dibi\Driver $driver;
private $driver;
public function __construct(Dibi\Driver $driver) public function __construct(Dibi\Driver $driver)
@@ -42,6 +41,7 @@ class SqlsrvReflector implements Dibi\Reflector
'view' => isset($row[1]) && $row[1] === 'VIEW', 'view' => isset($row[1]) && $row[1] === 'VIEW',
]; ];
} }
return $tables; return $tables;
} }
@@ -91,6 +91,7 @@ class SqlsrvReflector implements Dibi\Reflector
'vendor' => $row, 'vendor' => $row,
]; ];
} }
return $columns; return $columns;
} }
@@ -114,6 +115,7 @@ class SqlsrvReflector implements Dibi\Reflector
$indexes[$row['name']]['primary'] = $row['is_primary_key'] === 1; $indexes[$row['name']]['primary'] = $row['is_primary_key'] === 1;
$indexes[$row['name']]['columns'] = $keyUsages[$row['name']] ?? []; $indexes[$row['name']]['columns'] = $keyUsages[$row['name']] ?? [];
} }
return array_values($indexes); return array_values($indexes);
} }

View File

@@ -22,8 +22,7 @@ class SqlsrvResult implements Dibi\ResultDriver
/** @var resource */ /** @var resource */
private $resultSet; private $resultSet;
/** @var bool */ private bool $autoFree = true;
private $autoFree = true;
/** /**
@@ -96,6 +95,7 @@ class SqlsrvResult implements Dibi\ResultDriver
'nativetype' => $fieldMetadata['Type'], 'nativetype' => $fieldMetadata['Type'],
]; ];
} }
return $columns; return $columns;
} }
@@ -104,7 +104,7 @@ class SqlsrvResult implements Dibi\ResultDriver
* Returns the result set resource. * Returns the result set resource.
* @return resource|null * @return resource|null
*/ */
public function getResultResource() public function getResultResource(): mixed
{ {
$this->autoFree = false; $this->autoFree = false;
return is_resource($this->resultSet) ? $this->resultSet : null; return is_resource($this->resultSet) ? $this->resultSet : null;

View File

@@ -31,29 +31,22 @@ class Event
TRANSACTION = 448, // BEGIN | COMMIT | ROLLBACK TRANSACTION = 448, // BEGIN | COMMIT | ROLLBACK
ALL = 1023; ALL = 1023;
/** @var Connection */ public Connection $connection;
public $connection;
/** @var int */ public int $type;
public $type;
/** @var string */ public string $sql;
public $sql;
/** @var Result|DriverException|null */ public Result|DriverException|null $result;
public $result;
/** @var float */ public float $time;
public $time;
/** @var int|null */ public ?int $count = null;
public $count;
/** @var array|null */ public ?array $source = null;
public $source;
public function __construct(Connection $connection, int $type, string $sql = null) public function __construct(Connection $connection, int $type, ?string $sql = null)
{ {
$this->connection = $connection; $this->connection = $connection;
$this->type = $type; $this->type = $type;
@@ -61,7 +54,7 @@ class Event
$this->time = -microtime(true); $this->time = -microtime(true);
if ($type === self::QUERY && preg_match('#\(?\s*(SELECT|UPDATE|INSERT|DELETE)#iA', $this->sql, $matches)) { if ($type === self::QUERY && preg_match('#\(?\s*(SELECT|UPDATE|INSERT|DELETE)#iA', $this->sql, $matches)) {
static $types = [ $types = [
'SELECT' => self::SELECT, 'UPDATE' => self::UPDATE, 'SELECT' => self::SELECT, 'UPDATE' => self::UPDATE,
'INSERT' => self::INSERT, 'DELETE' => self::DELETE, 'INSERT' => self::INSERT, 'DELETE' => self::DELETE,
]; ];
@@ -70,7 +63,11 @@ class Event
$dibiDir = dirname((new \ReflectionClass('dibi'))->getFileName()) . DIRECTORY_SEPARATOR; $dibiDir = dirname((new \ReflectionClass('dibi'))->getFileName()) . DIRECTORY_SEPARATOR;
foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) as $row) { foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) as $row) {
if (isset($row['file']) && is_file($row['file']) && strpos($row['file'], $dibiDir) !== 0) { if (
isset($row['file'])
&& preg_match('~\.(php.?|phtml)$~', $row['file'])
&& !str_starts_with($row['file'], $dibiDir)
) {
$this->source = [$row['file'], (int) $row['line']]; $this->source = [$row['file'], (int) $row['line']];
break; break;
} }
@@ -82,10 +79,7 @@ class Event
} }
/** public function done(Result|DriverException|null $result = null): static
* @param Result|DriverException|null $result
*/
public function done($result = null): self
{ {
$this->result = $result; $this->result = $result;
try { try {

View File

@@ -17,8 +17,7 @@ class Expression
{ {
use Strict; use Strict;
/** @var array */ private array $values;
private $values;
public function __construct(...$values) public function __construct(...$values)

View File

@@ -47,8 +47,7 @@ class Fluent implements IDataSource
public const REMOVE = false; public const REMOVE = false;
/** @var array */ public static array $masks = [
public static $masks = [
'SELECT' => ['SELECT', 'DISTINCT', 'FROM', 'WHERE', 'GROUP BY', 'SELECT' => ['SELECT', 'DISTINCT', 'FROM', 'WHERE', 'GROUP BY',
'HAVING', 'ORDER BY', 'LIMIT', 'OFFSET', ], 'HAVING', 'ORDER BY', 'LIMIT', 'OFFSET', ],
'UPDATE' => ['UPDATE', 'SET', 'WHERE', 'ORDER BY', 'LIMIT'], 'UPDATE' => ['UPDATE', 'SET', 'WHERE', 'ORDER BY', 'LIMIT'],
@@ -56,8 +55,8 @@ class Fluent implements IDataSource
'DELETE' => ['DELETE', 'FROM', 'USING', 'WHERE', 'ORDER BY', 'LIMIT'], 'DELETE' => ['DELETE', 'FROM', 'USING', 'WHERE', 'ORDER BY', 'LIMIT'],
]; ];
/** @var array default modifiers for arrays */ /** default modifiers for arrays */
public static $modifiers = [ public static array $modifiers = [
'SELECT' => '%n', 'SELECT' => '%n',
'FROM' => '%n', 'FROM' => '%n',
'IN' => '%in', 'IN' => '%in',
@@ -69,8 +68,8 @@ class Fluent implements IDataSource
'GROUP BY' => '%by', 'GROUP BY' => '%by',
]; ];
/** @var array clauses separators */ /** clauses separators */
public static $separators = [ public static array $separators = [
'SELECT' => ',', 'SELECT' => ',',
'FROM' => ',', 'FROM' => ',',
'WHERE' => 'AND', 'WHERE' => 'AND',
@@ -84,41 +83,35 @@ class Fluent implements IDataSource
'INTO' => false, 'INTO' => false,
]; ];
/** @var array clauses */ /** clauses */
public static $clauseSwitches = [ public static array $clauseSwitches = [
'JOIN' => 'FROM', 'JOIN' => 'FROM',
'INNER JOIN' => 'FROM', 'INNER JOIN' => 'FROM',
'LEFT JOIN' => 'FROM', 'LEFT JOIN' => 'FROM',
'RIGHT JOIN' => 'FROM', 'RIGHT JOIN' => 'FROM',
]; ];
/** @var Connection */ private Connection $connection;
private $connection;
/** @var array */ private array $setups = [];
private $setups = [];
/** @var string|null */ private ?string $command = null;
private $command;
/** @var array */ private array $clauses = [];
private $clauses = [];
/** @var array */ private array $flags = [];
private $flags = [];
/** @var array|null */
private $cursor; private $cursor;
/** @var HashMap normalized clauses */ /** normalized clauses */
private static $normalizer; private static HashMap $normalizer;
public function __construct(Connection $connection) public function __construct(Connection $connection)
{ {
$this->connection = $connection; $this->connection = $connection;
if (self::$normalizer === null) { if (!isset(self::$normalizer)) {
self::$normalizer = new HashMap([self::class, '_formatClause']); self::$normalizer = new HashMap([self::class, '_formatClause']);
} }
} }
@@ -127,7 +120,7 @@ class Fluent implements IDataSource
/** /**
* Appends new argument to the clause. * Appends new argument to the clause.
*/ */
public function __call(string $clause, array $args): self public function __call(string $clause, array $args): static
{ {
$clause = self::$normalizer->$clause; $clause = self::$normalizer->$clause;
@@ -136,6 +129,7 @@ class Fluent implements IDataSource
if (isset(self::$masks[$clause])) { if (isset(self::$masks[$clause])) {
$this->clauses = array_fill_keys(self::$masks[$clause], null); $this->clauses = array_fill_keys(self::$masks[$clause], null);
} }
$this->cursor = &$this->clauses[$clause]; $this->cursor = &$this->clauses[$clause];
$this->cursor = []; $this->cursor = [];
$this->command = $clause; $this->command = $clause;
@@ -165,7 +159,6 @@ class Fluent implements IDataSource
$this->cursor[] = $sep; $this->cursor[] = $sep;
} }
} }
} else { } else {
// append to currect flow // append to currect flow
if ($args === [self::REMOVE]) { if ($args === [self::REMOVE]) {
@@ -203,6 +196,7 @@ class Fluent implements IDataSource
if ($arg instanceof self) { if ($arg instanceof self) {
$arg = new Literal("($arg)"); $arg = new Literal("($arg)");
} }
$this->cursor[] = $arg; $this->cursor[] = $arg;
} }
@@ -213,7 +207,7 @@ class Fluent implements IDataSource
/** /**
* Switch to a clause. * Switch to a clause.
*/ */
public function clause(string $clause): self public function clause(string $clause): static
{ {
$this->cursor = &$this->clauses[self::$normalizer->$clause]; $this->cursor = &$this->clauses[self::$normalizer->$clause];
if ($this->cursor === null) { if ($this->cursor === null) {
@@ -227,7 +221,7 @@ class Fluent implements IDataSource
/** /**
* Removes a clause. * Removes a clause.
*/ */
public function removeClause(string $clause): self public function removeClause(string $clause): static
{ {
$this->clauses[self::$normalizer->$clause] = null; $this->clauses[self::$normalizer->$clause] = null;
return $this; return $this;
@@ -237,7 +231,7 @@ class Fluent implements IDataSource
/** /**
* Change a SQL flag. * Change a SQL flag.
*/ */
public function setFlag(string $flag, bool $value = true): self public function setFlag(string $flag, bool $value = true): static
{ {
$flag = strtoupper($flag); $flag = strtoupper($flag);
if ($value) { if ($value) {
@@ -245,6 +239,7 @@ class Fluent implements IDataSource
} else { } else {
unset($this->flags[$flag]); unset($this->flags[$flag]);
} }
return $this; return $this;
} }
@@ -276,7 +271,7 @@ class Fluent implements IDataSource
/** /**
* Adds Result setup. * Adds Result setup.
*/ */
public function setupResult(string $method): self public function setupResult(string $method): static
{ {
$this->setups[] = func_get_args(); $this->setups[] = func_get_args();
return $this; return $this;
@@ -288,10 +283,10 @@ class Fluent implements IDataSource
/** /**
* Generates and executes SQL query. * Generates and executes SQL query.
* @return Result|int|null result set or number of affected rows * Returns result set or number of affected rows
* @throws Exception * @throws Exception
*/ */
public function execute(string $return = null) public function execute(?string $return = null): Result|int|null
{ {
$res = $this->query($this->_export()); $res = $this->query($this->_export());
switch ($return) { switch ($return) {
@@ -307,9 +302,8 @@ class Fluent implements IDataSource
/** /**
* Generates, executes SQL query and fetches the single row. * Generates, executes SQL query and fetches the single row.
* @return Row|array|null
*/ */
public function fetch() public function fetch(): Row|array|null
{ {
return $this->command === 'SELECT' && !$this->clauses['LIMIT'] return $this->command === 'SELECT' && !$this->clauses['LIMIT']
? $this->query($this->_export(null, ['%lmt', 1]))->fetch() ? $this->query($this->_export(null, ['%lmt', 1]))->fetch()
@@ -319,9 +313,9 @@ class Fluent implements IDataSource
/** /**
* Like fetch(), but returns only first field. * Like fetch(), but returns only first field.
* @return mixed value on success, null if no next record * Returns value on success, null if no next record
*/ */
public function fetchSingle() public function fetchSingle(): mixed
{ {
return $this->command === 'SELECT' && !$this->clauses['LIMIT'] return $this->command === 'SELECT' && !$this->clauses['LIMIT']
? $this->query($this->_export(null, ['%lmt', 1]))->fetchSingle() ? $this->query($this->_export(null, ['%lmt', 1]))->fetchSingle()
@@ -332,7 +326,7 @@ class Fluent implements IDataSource
/** /**
* Fetches all records from table. * Fetches all records from table.
*/ */
public function fetchAll(int $offset = null, int $limit = null): array public function fetchAll(?int $offset = null, ?int $limit = null): array
{ {
return $this->query($this->_export(null, ['%ofs %lmt', $offset, $limit]))->fetchAll(); return $this->query($this->_export(null, ['%ofs %lmt', $offset, $limit]))->fetchAll();
} }
@@ -351,7 +345,7 @@ class Fluent implements IDataSource
/** /**
* Fetches all records from table like $key => $value pairs. * Fetches all records from table like $key => $value pairs.
*/ */
public function fetchPairs(string $key = null, string $value = null): array public function fetchPairs(?string $key = null, ?string $value = null): array
{ {
return $this->query($this->_export())->fetchPairs($key, $value); return $this->query($this->_export())->fetchPairs($key, $value);
} }
@@ -360,7 +354,7 @@ class Fluent implements IDataSource
/** /**
* Required by the IteratorAggregate interface. * Required by the IteratorAggregate interface.
*/ */
public function getIterator(int $offset = null, int $limit = null): ResultIterator public function getIterator(?int $offset = null, ?int $limit = null): ResultIterator
{ {
return $this->query($this->_export(null, ['%ofs %lmt', $offset, $limit]))->getIterator(); return $this->query($this->_export(null, ['%ofs %lmt', $offset, $limit]))->getIterator();
} }
@@ -369,7 +363,7 @@ class Fluent implements IDataSource
/** /**
* Generates and prints SQL query or it's part. * Generates and prints SQL query or it's part.
*/ */
public function test(string $clause = null): bool public function test(?string $clause = null): bool
{ {
return $this->connection->test($this->_export($clause)); return $this->connection->test($this->_export($clause));
} }
@@ -390,6 +384,7 @@ class Fluent implements IDataSource
$method = array_shift($setup); $method = array_shift($setup);
$res->$method(...$setup); $res->$method(...$setup);
} }
return $res; return $res;
} }
@@ -408,19 +403,14 @@ class Fluent implements IDataSource
*/ */
final public function __toString(): string final public function __toString(): string
{ {
try { return $this->connection->translate($this->_export());
return $this->connection->translate($this->_export());
} catch (\Throwable $e) {
trigger_error($e->getMessage(), E_USER_ERROR);
return '';
}
} }
/** /**
* Generates parameters for Translator. * Generates parameters for Translator.
*/ */
protected function _export(string $clause = null, array $args = []): array protected function _export(?string $clause = null, array $args = []): array
{ {
if ($clause === null) { if ($clause === null) {
$data = $this->clauses; $data = $this->clauses;
@@ -428,7 +418,6 @@ class Fluent implements IDataSource
$args = array_merge(['%lmt %ofs', $data['LIMIT'][0] ?? null, $data['OFFSET'][0] ?? null], $args); $args = array_merge(['%lmt %ofs', $data['LIMIT'][0] ?? null, $data['OFFSET'][0] ?? null], $args);
unset($data['LIMIT'], $data['OFFSET']); unset($data['LIMIT'], $data['OFFSET']);
} }
} else { } else {
$clause = self::$normalizer->$clause; $clause = self::$normalizer->$clause;
if (array_key_exists($clause, $this->clauses)) { if (array_key_exists($clause, $this->clauses)) {
@@ -444,6 +433,7 @@ class Fluent implements IDataSource
if ($clause === $this->command && $this->flags) { if ($clause === $this->command && $this->flags) {
$args[] = implode(' ', array_keys($this->flags)); $args[] = implode(' ', array_keys($this->flags));
} }
foreach ($statement as $arg) { foreach ($statement as $arg) {
$args[] = $arg; $args[] = $arg;
} }
@@ -464,6 +454,7 @@ class Fluent implements IDataSource
$s .= 'By'; $s .= 'By';
trigger_error("Did you mean '$s'?", E_USER_NOTICE); trigger_error("Did you mean '$s'?", E_USER_NOTICE);
} }
return strtoupper(preg_replace('#[a-z](?=[A-Z])#', '$0 ', $s)); return strtoupper(preg_replace('#[a-z](?=[A-Z])#', '$0 ', $s));
} }
@@ -475,6 +466,7 @@ class Fluent implements IDataSource
$this->clauses[$clause] = &$val; $this->clauses[$clause] = &$val;
unset($val); unset($val);
} }
$this->cursor = &$foo; $this->cursor = &$foo;
} }
} }

View File

@@ -14,6 +14,7 @@ namespace Dibi;
* Lazy cached storage. * Lazy cached storage.
* @internal * @internal
*/ */
#[\AllowDynamicProperties]
abstract class HashMapBase abstract class HashMapBase
{ {
/** @var callable */ /** @var callable */
@@ -50,6 +51,7 @@ final class HashMap extends HashMapBase
if ($nm === '') { if ($nm === '') {
$nm = "\xFF"; $nm = "\xFF";
} }
$this->$nm = $val; $this->$nm = $val;
} }
@@ -58,7 +60,7 @@ final class HashMap extends HashMapBase
{ {
if ($nm === '') { if ($nm === '') {
$nm = "\xFF"; $nm = "\xFF";
return isset($this->$nm) ? $this->$nm : $this->$nm = $this->getCallback()(''); return isset($this->$nm) && true ? $this->$nm : $this->$nm = $this->getCallback()('');
} else { } else {
return $this->$nm = $this->getCallback()($nm); return $this->$nm = $this->getCallback()($nm);
} }

View File

@@ -14,19 +14,17 @@ class Helpers
{ {
use Strict; use Strict;
/** @var HashMap */ private static HashMap $types;
private static $types;
/** /**
* Prints out a syntax highlighted version of the SQL command or Result. * Prints out a syntax highlighted version of the SQL command or Result.
* @param string|Result $sql
*/ */
public static function dump($sql = null, bool $return = false): ?string public static function dump(string|Result|null $sql = null, bool $return = false): ?string
{ {
ob_start(); ob_start();
if ($sql instanceof Result && PHP_SAPI === 'cli') { if ($sql instanceof Result && PHP_SAPI === 'cli') {
$hasColors = (substr((string) getenv('TERM'), 0, 5) === 'xterm'); $hasColors = (str_starts_with((string) getenv('TERM'), 'xterm'));
$maxLen = 0; $maxLen = 0;
foreach ($sql as $i => $row) { foreach ($sql as $i => $row) {
if ($i === 0) { if ($i === 0) {
@@ -41,6 +39,7 @@ class Helpers
$spaces = $maxLen - mb_strlen($col) + 2; $spaces = $maxLen - mb_strlen($col) + 2;
echo "$col" . str_repeat(' ', $spaces) . "$val\n"; echo "$col" . str_repeat(' ', $spaces) . "$val\n";
} }
echo "\n"; echo "\n";
} }
@@ -53,6 +52,7 @@ class Helpers
foreach ($row as $col => $foo) { foreach ($row as $col => $foo) {
echo "\t\t<th>" . htmlspecialchars((string) $col) . "</th>\n"; echo "\t\t<th>" . htmlspecialchars((string) $col) . "</th>\n";
} }
echo "\t</tr>\n</thead>\n<tbody>\n"; echo "\t</tr>\n</thead>\n<tbody>\n";
} }
@@ -60,6 +60,7 @@ class Helpers
foreach ($row as $col) { foreach ($row as $col) {
echo "\t\t<td>", htmlspecialchars((string) $col), "</td>\n"; echo "\t\t<td>", htmlspecialchars((string) $col), "</td>\n";
} }
echo "\t</tr>\n"; echo "\t</tr>\n";
} }
@@ -72,8 +73,8 @@ class Helpers
$sql = \dibi::$sql; $sql = \dibi::$sql;
} }
static $keywords1 = 'SELECT|(?:ON\s+DUPLICATE\s+KEY)?UPDATE|INSERT(?:\s+INTO)?|REPLACE(?:\s+INTO)?|DELETE|CALL|UNION|FROM|WHERE|HAVING|GROUP\s+BY|ORDER\s+BY|LIMIT|OFFSET|FETCH\s+NEXT|SET|VALUES|LEFT\s+JOIN|INNER\s+JOIN|TRUNCATE|START\s+TRANSACTION|BEGIN|COMMIT|ROLLBACK(?:\s+TO\s+SAVEPOINT)?|(?:RELEASE\s+)?SAVEPOINT'; $keywords1 = 'SELECT|(?:ON\s+DUPLICATE\s+KEY)?UPDATE|INSERT(?:\s+INTO)?|REPLACE(?:\s+INTO)?|DELETE|CALL|UNION|FROM|WHERE|HAVING|GROUP\s+BY|ORDER\s+BY|LIMIT|OFFSET|FETCH\s+NEXT|SET|VALUES|LEFT\s+JOIN|INNER\s+JOIN|TRUNCATE|START\s+TRANSACTION|BEGIN|COMMIT|ROLLBACK(?:\s+TO\s+SAVEPOINT)?|(?:RELEASE\s+)?SAVEPOINT';
static $keywords2 = 'ALL|DISTINCT|DISTINCTROW|IGNORE|AS|USING|ON|AND|OR|IN|IS|NOT|NULL|LIKE|RLIKE|REGEXP|TRUE|FALSE'; $keywords2 = 'ALL|DISTINCT|DISTINCTROW|IGNORE|AS|USING|ON|AND|OR|IN|IS|NOT|NULL|LIKE|RLIKE|REGEXP|TRUE|FALSE';
// insert new lines // insert new lines
$sql = " $sql "; $sql = " $sql ";
@@ -88,7 +89,7 @@ class Helpers
// syntax highlight // syntax highlight
$highlighter = "#(/\\*.+?\\*/)|(\\*\\*.+?\\*\\*)|(?<=[\\s,(])($keywords1)(?=[\\s,)])|(?<=[\\s,(=])($keywords2)(?=[\\s,)=])#is"; $highlighter = "#(/\\*.+?\\*/)|(\\*\\*.+?\\*\\*)|(?<=[\\s,(])($keywords1)(?=[\\s,)])|(?<=[\\s,(=])($keywords2)(?=[\\s,)=])#is";
if (PHP_SAPI === 'cli') { if (PHP_SAPI === 'cli') {
if (substr((string) getenv('TERM'), 0, 5) === 'xterm') { if (str_starts_with((string) getenv('TERM'), 'xterm')) {
$sql = preg_replace_callback($highlighter, function (array $m) { $sql = preg_replace_callback($highlighter, function (array $m) {
if (!empty($m[1])) { // comment if (!empty($m[1])) { // comment
return "\033[1;30m" . $m[1] . "\033[0m"; return "\033[1;30m" . $m[1] . "\033[0m";
@@ -104,6 +105,7 @@ class Helpers
} }
}, $sql); }, $sql);
} }
echo trim($sql) . "\n\n"; echo trim($sql) . "\n\n";
} else { } else {
@@ -150,6 +152,7 @@ class Helpers
$best = $item; $best = $item;
} }
} }
return $best; return $best;
} }
@@ -157,7 +160,7 @@ class Helpers
/** @internal */ /** @internal */
public static function escape(Driver $driver, $value, string $type): string public static function escape(Driver $driver, $value, string $type): string
{ {
static $types = [ $types = [
Type::TEXT => 'text', Type::TEXT => 'text',
Type::BINARY => 'binary', Type::BINARY => 'binary',
Type::BOOL => 'bool', Type::BOOL => 'bool',
@@ -179,8 +182,9 @@ class Helpers
*/ */
public static function detectType(string $type): ?string public static function detectType(string $type): ?string
{ {
static $patterns = [ $patterns = [
'^_' => Type::TEXT, // PostgreSQL arrays '^_' => Type::TEXT, // PostgreSQL arrays
'RANGE$' => Type::TEXT, // PostgreSQL range types
'BYTEA|BLOB|BIN' => Type::BINARY, 'BYTEA|BLOB|BIN' => Type::BINARY,
'TEXT|CHAR|POINT|INTERVAL|STRING' => Type::TEXT, 'TEXT|CHAR|POINT|INTERVAL|STRING' => Type::TEXT,
'YEAR|BYTE|COUNTER|SERIAL|INT|LONG|SHORT|^TINY$' => Type::INTEGER, 'YEAR|BYTE|COUNTER|SERIAL|INT|LONG|SHORT|^TINY$' => Type::INTEGER,
@@ -197,6 +201,7 @@ class Helpers
return $val; return $val;
} }
} }
return null; return null;
} }
@@ -204,9 +209,10 @@ class Helpers
/** @internal */ /** @internal */
public static function getTypeCache(): HashMap public static function getTypeCache(): HashMap
{ {
if (self::$types === null) { if (!isset(self::$types)) {
self::$types = new HashMap([self::class, 'detectType']); self::$types = new HashMap([self::class, 'detectType']);
} }
return self::$types; return self::$types;
} }
@@ -230,9 +236,9 @@ class Helpers
/** /**
* Import SQL dump from file. * Import SQL dump from file.
* @return int count of sql commands * Returns count of sql commands
*/ */
public static function loadFromFile(Connection $connection, string $file, callable $onProgress = null): int public static function loadFromFile(Connection $connection, string $file, ?callable $onProgress = null): int
{ {
@set_time_limit(0); // intentionally @ @set_time_limit(0); // intentionally @
@@ -251,7 +257,7 @@ class Helpers
if (strtoupper(substr($s, 0, 10)) === 'DELIMITER ') { if (strtoupper(substr($s, 0, 10)) === 'DELIMITER ') {
$delimiter = trim(substr($s, 10)); $delimiter = trim(substr($s, 10));
} elseif (substr($ts = rtrim($s), -strlen($delimiter)) === $delimiter) { } elseif (str_ends_with($ts = rtrim($s), $delimiter)) {
$sql .= substr($ts, 0, -strlen($delimiter)); $sql .= substr($ts, 0, -strlen($delimiter));
$driver->query($sql); $driver->query($sql);
$sql = ''; $sql = '';
@@ -259,7 +265,6 @@ class Helpers
if ($onProgress) { if ($onProgress) {
$onProgress($count, isset($stat['size']) ? $size * 100 / $stat['size'] : null); $onProgress($count, isset($stat['size']) ? $size * 100 / $stat['size'] : null);
} }
} else { } else {
$sql .= $s; $sql .= $s;
} }
@@ -272,20 +277,21 @@ class Helpers
$onProgress($count, isset($stat['size']) ? 100 : null); $onProgress($count, isset($stat['size']) ? 100 : null);
} }
} }
fclose($handle); fclose($handle);
return $count; return $count;
} }
/** @internal */ /** @internal */
public static function false2Null($val) public static function false2Null(mixed $val): mixed
{ {
return $val === false ? null : $val; return $val === false ? null : $val;
} }
/** @internal */ /** @internal */
public static function intVal($value): int public static function intVal(mixed $value): int
{ {
if (is_int($value)) { if (is_int($value)) {
return $value; return $value;
@@ -293,6 +299,7 @@ class Helpers
if (is_float($value * 1)) { if (is_float($value * 1)) {
throw new Exception("Number $value is greater than integer."); throw new Exception("Number $value is greater than integer.");
} }
return (int) $value; return (int) $value;
} else { } else {
throw new Exception("Expected number, '$value' given."); throw new Exception("Expected number, '$value' given.");

View File

@@ -17,8 +17,7 @@ class Literal
{ {
use Strict; use Strict;
/** @var string */ private string $value;
private $value;
public function __construct($value) public function __construct($value)

View File

@@ -19,17 +19,15 @@ class FileLogger
{ {
use Dibi\Strict; use Dibi\Strict;
/** @var string Name of the file where SQL errors should be logged */ /** Name of the file where SQL errors should be logged */
public $file; public string $file;
/** @var int */ public int $filter;
public $filter;
/** @var bool */ private bool $errorsOnly;
private $errorsOnly;
public function __construct(string $file, int $filter = null, bool $errorsOnly = false) public function __construct(string $file, ?int $filter = null, bool $errorsOnly = false)
{ {
$this->file = $file; $this->file = $file;
$this->filter = $filter ?: Dibi\Event::QUERY; $this->filter = $filter ?: Dibi\Event::QUERY;
@@ -54,10 +52,11 @@ class FileLogger
if ($code = $event->result->getCode()) { if ($code = $event->result->getCode()) {
$message = "[$code] $message"; $message = "[$code] $message";
} }
$this->writeToFile( $this->writeToFile(
$event, $event,
"ERROR: $message" "ERROR: $message"
. "\n-- SQL: " . $event->sql . "\n-- SQL: " . $event->sql,
); );
} else { } else {
$this->writeToFile( $this->writeToFile(
@@ -65,7 +64,7 @@ class FileLogger
'OK: ' . $event->sql 'OK: ' . $event->sql
. ($event->count ? ";\n-- rows: " . $event->count : '') . ($event->count ? ";\n-- rows: " . $event->count : '')
. "\n-- takes: " . sprintf('%0.3f ms', $event->time * 1000) . "\n-- takes: " . sprintf('%0.3f ms', $event->time * 1000)
. "\n-- source: " . implode(':', $event->source) . "\n-- source: " . implode(':', $event->source),
); );
} }
} }
@@ -75,7 +74,7 @@ class FileLogger
{ {
$driver = $event->connection->getConfig('driver'); $driver = $event->connection->getConfig('driver');
$message .= $message .=
"\n-- driver: " . (is_object($driver) ? get_class($driver) : $driver) . '/' . $event->connection->getConfig('name') "\n-- driver: " . (is_object($driver) ? $driver::class : $driver) . '/' . $event->connection->getConfig('name')
. "\n-- " . date('Y-m-d H:i:s') . "\n-- " . date('Y-m-d H:i:s')
. "\n\n"; . "\n\n";
file_put_contents($this->file, $message, FILE_APPEND | LOCK_EX); file_put_contents($this->file, $message, FILE_APPEND | LOCK_EX);

View File

@@ -29,14 +29,14 @@ class Column
{ {
use Dibi\Strict; use Dibi\Strict;
/** @var Dibi\Reflector|null when created by Result */ /** when created by Result */
private $reflector; private ?Dibi\Reflector $reflector;
/** @var array (name, nativetype, [table], [fullname], [size], [nullable], [default], [autoincrement], [vendor]) */ /** @var array (name, nativetype, [table], [fullname], [size], [nullable], [default], [autoincrement], [vendor]) */
private $info; private array $info;
public function __construct(Dibi\Reflector $reflector = null, array $info) public function __construct(?Dibi\Reflector $reflector, array $info)
{ {
$this->reflector = $reflector; $this->reflector = $reflector;
$this->info = $info; $this->info = $info;
@@ -66,6 +66,7 @@ class Column
if (empty($this->info['table']) || !$this->reflector) { if (empty($this->info['table']) || !$this->reflector) {
throw new Dibi\Exception('Table is unknown or not available.'); throw new Dibi\Exception('Table is unknown or not available.');
} }
return new Table($this->reflector, ['name' => $this->info['table']]); return new Table($this->reflector, ['name' => $this->info['table']]);
} }
@@ -108,15 +109,13 @@ class Column
} }
/** @return mixed */ public function getDefault(): mixed
public function getDefault()
{ {
return $this->info['default'] ?? null; return $this->info['default'] ?? null;
} }
/** @return mixed */ public function getVendorInfo(string $key): mixed
public function getVendorInfo(string $key)
{ {
return $this->info['vendor'][$key] ?? null; return $this->info['vendor'][$key] ?? null;
} }

View File

@@ -23,17 +23,15 @@ class Database
{ {
use Dibi\Strict; use Dibi\Strict;
/** @var Dibi\Reflector */ private Dibi\Reflector $reflector;
private $reflector;
/** @var string|null */ private ?string $name;
private $name;
/** @var Table[]|null */ /** @var Table[] */
private $tables; private array $tables;
public function __construct(Dibi\Reflector $reflector, string $name = null) public function __construct(Dibi\Reflector $reflector, ?string $name = null)
{ {
$this->reflector = $reflector; $this->reflector = $reflector;
$this->name = $name; $this->name = $name;
@@ -62,6 +60,7 @@ class Database
foreach ($this->tables as $table) { foreach ($this->tables as $table) {
$res[] = $table->getName(); $res[] = $table->getName();
} }
return $res; return $res;
} }
@@ -88,7 +87,7 @@ class Database
protected function init(): void protected function init(): void
{ {
if ($this->tables === null) { if (!isset($this->tables)) {
$this->tables = []; $this->tables = [];
foreach ($this->reflector->getTables() as $info) { foreach ($this->reflector->getTables() as $info) {
$this->tables[strtolower($info['name'])] = new Table($this->reflector, $info); $this->tables[strtolower($info['name'])] = new Table($this->reflector, $info);

View File

@@ -22,11 +22,10 @@ class ForeignKey
{ {
use Dibi\Strict; use Dibi\Strict;
/** @var string */ private string $name;
private $name;
/** @var array of [local, foreign, onDelete, onUpdate] */ /** @var array of [local, foreign, onDelete, onUpdate] */
private $references; private array $references;
public function __construct(string $name, array $references) public function __construct(string $name, array $references)

View File

@@ -25,7 +25,7 @@ class Index
use Dibi\Strict; use Dibi\Strict;
/** @var array (name, columns, [unique], [primary]) */ /** @var array (name, columns, [unique], [primary]) */
private $info; private array $info;
public function __construct(array $info) public function __construct(array $info)

View File

@@ -22,14 +22,13 @@ class Result
{ {
use Dibi\Strict; use Dibi\Strict;
/** @var Dibi\ResultDriver */ private Dibi\ResultDriver $driver;
private $driver;
/** @var Column[]|null */ /** @var Column[]|null */
private $columns; private ?array $columns;
/** @var Column[]|null */ /** @var Column[]|null */
private $names; private ?array $names;
public function __construct(Dibi\ResultDriver $driver) public function __construct(Dibi\ResultDriver $driver)
@@ -54,6 +53,7 @@ class Result
foreach ($this->columns as $column) { foreach ($this->columns as $column) {
$res[] = $fullNames ? $column->getFullName() : $column->getName(); $res[] = $fullNames ? $column->getFullName() : $column->getName();
} }
return $res; return $res;
} }
@@ -80,7 +80,7 @@ class Result
protected function initColumns(): void protected function initColumns(): void
{ {
if ($this->columns === null) { if (!isset($this->columns)) {
$this->columns = []; $this->columns = [];
$reflector = $this->driver instanceof Dibi\Reflector $reflector = $this->driver instanceof Dibi\Reflector
? $this->driver ? $this->driver

View File

@@ -27,26 +27,22 @@ class Table
{ {
use Dibi\Strict; use Dibi\Strict;
/** @var Dibi\Reflector */ private Dibi\Reflector $reflector;
private $reflector;
/** @var string */ private string $name;
private $name;
/** @var bool */ private bool $view;
private $view;
/** @var Column[]|null */ /** @var Column[] */
private $columns; private array $columns;
/** @var ForeignKey[]|null */ /** @var ForeignKey[] */
private $foreignKeys; private array $foreignKeys;
/** @var Index[]|null */ /** @var Index[] */
private $indexes; private array $indexes;
/** @var Index|null */ private ?Index $primaryKey;
private $primaryKey;
public function __construct(Dibi\Reflector $reflector, array $info) public function __construct(Dibi\Reflector $reflector, array $info)
@@ -85,6 +81,7 @@ class Table
foreach ($this->columns as $column) { foreach ($this->columns as $column) {
$res[] = $column->getName(); $res[] = $column->getName();
} }
return $res; return $res;
} }
@@ -134,7 +131,7 @@ class Table
protected function initColumns(): void protected function initColumns(): void
{ {
if ($this->columns === null) { if (!isset($this->columns)) {
$this->columns = []; $this->columns = [];
foreach ($this->reflector->getColumns($this->name) as $info) { foreach ($this->reflector->getColumns($this->name) as $info) {
$this->columns[strtolower($info['name'])] = new Column($this->reflector, $info); $this->columns[strtolower($info['name'])] = new Column($this->reflector, $info);
@@ -145,13 +142,14 @@ class Table
protected function initIndexes(): void protected function initIndexes(): void
{ {
if ($this->indexes === null) { if (!isset($this->indexes)) {
$this->initColumns(); $this->initColumns();
$this->indexes = []; $this->indexes = [];
foreach ($this->reflector->getIndexes($this->name) as $info) { foreach ($this->reflector->getIndexes($this->name) as $info) {
foreach ($info['columns'] as $key => $name) { foreach ($info['columns'] as $key => $name) {
$info['columns'][$key] = $this->columns[strtolower($name)]; $info['columns'][$key] = $this->columns[strtolower($name)];
} }
$this->indexes[strtolower($info['name'])] = new Index($info); $this->indexes[strtolower($info['name'])] = new Index($info);
if (!empty($info['primary'])) { if (!empty($info['primary'])) {
$this->primaryKey = $this->indexes[strtolower($info['name'])]; $this->primaryKey = $this->indexes[strtolower($info['name'])];

View File

@@ -19,26 +19,23 @@ class Result implements IDataSource
{ {
use Strict; use Strict;
/** @var ResultDriver|null */ private ?ResultDriver $driver;
private $driver;
/** @var array Translate table */ /** Translate table */
private $types = []; private array $types = [];
/** @var Reflection\Result|null */ private ?Reflection\Result $meta;
private $meta;
/** @var bool Already fetched? Used for allowance for first seek(0) */ /** Already fetched? Used for allowance for first seek(0) */
private $fetched = false; private bool $fetched = false;
/** @var string|null returned object class */ /** returned object class */
private $rowClass = Row::class; private ?string $rowClass = Row::class;
/** @var callable|null returned object factory */ /** @var callable|null returned object factory */
private $rowFactory; private $rowFactory;
/** @var array format */ private array $formats = [];
private $formats = [];
public function __construct(ResultDriver $driver, bool $normalize = true) public function __construct(ResultDriver $driver, bool $normalize = true)
@@ -133,7 +130,7 @@ class Result implements IDataSource
/** /**
* Set fetched object class. This class should extend the Row class. * Set fetched object class. This class should extend the Row class.
*/ */
public function setRowClass(?string $class): self public function setRowClass(?string $class): static
{ {
$this->rowClass = $class; $this->rowClass = $class;
return $this; return $this;
@@ -152,7 +149,7 @@ class Result implements IDataSource
/** /**
* Set a factory to create fetched object instances. These should extend the Row class. * Set a factory to create fetched object instances. These should extend the Row class.
*/ */
public function setRowFactory(callable $callback): self public function setRowFactory(callable $callback): static
{ {
$this->rowFactory = $callback; $this->rowFactory = $callback;
return $this; return $this;
@@ -162,14 +159,14 @@ class Result implements IDataSource
/** /**
* Fetches the row at current position, process optional type conversion. * Fetches the row at current position, process optional type conversion.
* and moves the internal cursor to the next position * and moves the internal cursor to the next position
* @return Row|array|null
*/ */
final public function fetch() final public function fetch(): mixed
{ {
$row = $this->getResultDriver()->fetch(true); $row = $this->getResultDriver()->fetch(true);
if ($row === null) { if ($row === null) {
return null; return null;
} }
$this->fetched = true; $this->fetched = true;
$this->normalize($row); $this->normalize($row);
if ($this->rowFactory) { if ($this->rowFactory) {
@@ -177,20 +174,22 @@ class Result implements IDataSource
} elseif ($this->rowClass) { } elseif ($this->rowClass) {
return new $this->rowClass($row); return new $this->rowClass($row);
} }
return $row; return $row;
} }
/** /**
* Like fetch(), but returns only first field. * Like fetch(), but returns only first field.
* @return mixed value on success, null if no next record * Returns value on success, null if no next record
*/ */
final public function fetchSingle() final public function fetchSingle(): mixed
{ {
$row = $this->getResultDriver()->fetch(true); $row = $this->getResultDriver()->fetch(true);
if ($row === null) { if ($row === null) {
return null; return null;
} }
$this->fetched = true; $this->fetched = true;
$this->normalize($row); $this->normalize($row);
return reset($row); return reset($row);
@@ -201,9 +200,9 @@ class Result implements IDataSource
* Fetches all records from table. * Fetches all records from table.
* @return Row[]|array[] * @return Row[]|array[]
*/ */
final public function fetchAll(int $offset = null, int $limit = null): array final public function fetchAll(?int $offset = null, ?int $limit = null): array
{ {
$limit = $limit ?? -1; $limit ??= -1;
$this->seek($offset ?: 0); $this->seek($offset ?: 0);
$row = $this->fetch(); $row = $this->fetch();
if (!$row) { if (!$row) {
@@ -215,6 +214,7 @@ class Result implements IDataSource
if ($limit === 0) { if ($limit === 0) {
break; break;
} }
$limit--; $limit--;
$data[] = $row; $data[] = $row;
} while ($row = $this->fetch()); } while ($row = $this->fetch());
@@ -234,7 +234,7 @@ class Result implements IDataSource
*/ */
final public function fetchAssoc(string $assoc): array final public function fetchAssoc(string $assoc): array
{ {
if (strpos($assoc, ',') !== false) { if (str_contains($assoc, ',')) {
return $this->oldFetchAssoc($assoc); return $this->oldFetchAssoc($assoc);
} }
@@ -287,7 +287,6 @@ class Result implements IDataSource
} else { } else {
$x = &$x->{$assoc[$i + 1]}; $x = &$x->{$assoc[$i + 1]};
} }
} elseif ($as !== '|') { // associative-array node } elseif ($as !== '|') { // associative-array node
$x = &$x[(string) $row->$as]; $x = &$x[(string) $row->$as];
} }
@@ -345,7 +344,6 @@ class Result implements IDataSource
} else { } else {
$x = &$x[$assoc[$i + 1]]; $x = &$x[$assoc[$i + 1]];
} }
} elseif ($as === '@') { // "object" node } elseif ($as === '@') { // "object" node
if ($x === null) { if ($x === null) {
$x = clone $row; $x = clone $row;
@@ -354,7 +352,6 @@ class Result implements IDataSource
} else { } else {
$x = &$x->{$assoc[$i + 1]}; $x = &$x->{$assoc[$i + 1]};
} }
} else { // associative-array node } else { // associative-array node
$x = &$x[(string) $row->$as]; $x = &$x[(string) $row->$as];
} }
@@ -376,7 +373,7 @@ class Result implements IDataSource
* Fetches all records from table like $key => $value pairs. * Fetches all records from table like $key => $value pairs.
* @throws \InvalidArgumentException * @throws \InvalidArgumentException
*/ */
final public function fetchPairs(string $key = null, string $value = null): array final public function fetchPairs(?string $key = null, ?string $value = null): array
{ {
$this->seek(0); $this->seek(0);
$row = $this->fetch(); $row = $this->fetch();
@@ -398,6 +395,7 @@ class Result implements IDataSource
do { do {
$data[] = $row[$key]; $data[] = $row[$key];
} while ($row = $this->fetch()); } while ($row = $this->fetch());
return $data; return $data;
} }
@@ -412,6 +410,7 @@ class Result implements IDataSource
do { do {
$data[] = $row[$value]; $data[] = $row[$value];
} while ($row = $this->fetch()); } while ($row = $this->fetch());
return $data; return $data;
} }
@@ -455,6 +454,7 @@ class Result implements IDataSource
if (!isset($row[$key])) { // null if (!isset($row[$key])) { // null
continue; continue;
} }
$value = $row[$key]; $value = $row[$key];
$format = $this->formats[$type] ?? null; $format = $this->formats[$type] ?? null;
@@ -478,9 +478,11 @@ class Result implements IDataSource
} elseif ($p !== false && $e !== false) { } elseif ($p !== false && $e !== false) {
$value = rtrim($value, '.'); $value = rtrim($value, '.');
} }
if ($value === '' || $value[0] === '.') { if ($value === '' || $value[0] === '.') {
$value = '0' . $value; $value = '0' . $value;
} }
$row[$key] = $value === str_replace(',', '.', (string) ($float = (float) $value)) $row[$key] = $value === str_replace(',', '.', (string) ($float = (float) $value))
? $float ? $float
: $value; : $value;
@@ -489,13 +491,12 @@ class Result implements IDataSource
$row[$key] = ((bool) $value) && $value !== 'f' && $value !== 'F'; $row[$key] = ((bool) $value) && $value !== 'f' && $value !== 'F';
} elseif ($type === Type::DATETIME || $type === Type::DATE || $type === Type::TIME) { } elseif ($type === Type::DATETIME || $type === Type::DATE || $type === Type::TIME) {
if ($value && substr((string) $value, 0, 3) !== '000') { // '', null, false, '0000-00-00', ... if ($value && !str_starts_with((string) $value, '0000-00')) { // '', null, false, '0000-00-00', ...
$value = new DateTime($value); $value = new DateTime($value);
$row[$key] = $format ? $value->format($format) : $value; $row[$key] = $format ? $value->format($format) : $value;
} else { } else {
$row[$key] = null; $row[$key] = null;
} }
} elseif ($type === Type::TIME_INTERVAL) { } elseif ($type === Type::TIME_INTERVAL) {
preg_match('#^(-?)(\d+)\D(\d+)\D(\d+)\z#', $value, $m); preg_match('#^(-?)(\d+)\D(\d+)\D(\d+)\z#', $value, $m);
$value = new \DateInterval("PT$m[2]H$m[3]M$m[4]S"); $value = new \DateInterval("PT$m[2]H$m[3]M$m[4]S");
@@ -513,7 +514,6 @@ class Result implements IDataSource
} else { } else {
$row[$key] = json_decode($value, $format === 'array'); $row[$key] = json_decode($value, $format === 'array');
} }
} else { } else {
throw new \RuntimeException('Unexpected type ' . $type); throw new \RuntimeException('Unexpected type ' . $type);
} }
@@ -525,7 +525,7 @@ class Result implements IDataSource
* Define column type. * Define column type.
* @param string|null $type use constant Type::* * @param string|null $type use constant Type::*
*/ */
final public function setType(string $column, ?string $type): self final public function setType(string $column, ?string $type): static
{ {
$this->types[$column] = $type; $this->types[$column] = $type;
return $this; return $this;
@@ -553,7 +553,7 @@ class Result implements IDataSource
/** /**
* Sets type format. * Sets type format.
*/ */
final public function setFormat(string $type, ?string $format): self final public function setFormat(string $type, ?string $format): static
{ {
$this->formats[$type] = $format; $this->formats[$type] = $format;
return $this; return $this;
@@ -563,7 +563,7 @@ class Result implements IDataSource
/** /**
* Sets type formats. * Sets type formats.
*/ */
final public function setFormats(array $formats): self final public function setFormats(array $formats): static
{ {
$this->formats = $formats; $this->formats = $formats;
return $this; return $this;
@@ -587,9 +587,10 @@ class Result implements IDataSource
*/ */
public function getInfo(): Reflection\Result public function getInfo(): Reflection\Result
{ {
if ($this->meta === null) { if (!isset($this->meta)) {
$this->meta = new Reflection\Result($this->getResultDriver()); $this->meta = new Reflection\Result($this->getResultDriver());
} }
return $this->meta; return $this->meta;
} }

View File

@@ -17,14 +17,11 @@ class ResultIterator implements \Iterator, \Countable
{ {
use Strict; use Strict;
/** @var Result */ private Result $result;
private $result;
/** @var mixed */ private mixed $row;
private $row;
/** @var int */ private int $pointer = 0;
private $pointer = 0;
public function __construct(Result $result) public function __construct(Result $result)
@@ -46,9 +43,8 @@ class ResultIterator implements \Iterator, \Countable
/** /**
* Returns the key of the current element. * Returns the key of the current element.
* @return mixed
*/ */
public function key() public function key(): mixed
{ {
return $this->pointer; return $this->pointer;
} }
@@ -56,9 +52,8 @@ class ResultIterator implements \Iterator, \Countable
/** /**
* Returns the current element. * Returns the current element.
* @return mixed
*/ */
public function current() public function current(): mixed
{ {
return $this->row; return $this->row;
} }

View File

@@ -13,6 +13,7 @@ namespace Dibi;
/** /**
* Result set single row. * Result set single row.
*/ */
#[\AllowDynamicProperties]
class Row implements \ArrayAccess, \IteratorAggregate, \Countable class Row implements \ArrayAccess, \IteratorAggregate, \Countable
{ {
public function __construct(array $arr) public function __construct(array $arr)
@@ -31,17 +32,18 @@ class Row implements \ArrayAccess, \IteratorAggregate, \Countable
/** /**
* Converts value to DateTime object. * Converts value to DateTime object.
* @return DateTime|string|null
*/ */
public function asDateTime(string $key, string $format = null) public function asDateTime(string $key, ?string $format = null): DateTime|string|null
{ {
$time = $this[$key]; $time = $this[$key];
if (!$time instanceof DateTime) { if (!$time instanceof DateTime) {
if (!$time || substr((string) $time, 0, 3) === '000') { // '', null, false, '0000-00-00', ... if (!$time || str_starts_with((string) $time, '0000-00')) { // '', null, false, '0000-00-00', ...
return null; return null;
} }
$time = new DateTime($time); $time = new DateTime($time);
} }
return $format === null ? $time : $time->format($format); return $format === null ? $time : $time->format($format);
} }
@@ -62,37 +64,37 @@ class Row implements \ArrayAccess, \IteratorAggregate, \Countable
/********************* interfaces ArrayAccess, Countable & IteratorAggregate ****************d*g**/ /********************* interfaces ArrayAccess, Countable & IteratorAggregate ****************d*g**/
final public function count() final public function count(): int
{ {
return count((array) $this); return count((array) $this);
} }
final public function getIterator() final public function getIterator(): \ArrayIterator
{ {
return new \ArrayIterator($this); return new \ArrayIterator($this);
} }
final public function offsetSet($nm, $val) final public function offsetSet($nm, $val): void
{ {
$this->$nm = $val; $this->$nm = $val;
} }
final public function offsetGet($nm) final public function offsetGet($nm): mixed
{ {
return $this->$nm; return $this->$nm;
} }
final public function offsetExists($nm) final public function offsetExists($nm): bool
{ {
return isset($this->$nm); return isset($this->$nm);
} }
final public function offsetUnset($nm) final public function offsetUnset($nm): void
{ {
unset($this->$nm); unset($this->$nm);
} }

View File

@@ -20,7 +20,7 @@ use ReflectionProperty;
trait Strict trait Strict
{ {
/** @var array [method => [type => callback]] */ /** @var array [method => [type => callback]] */
private static $extMethods; private static array $extMethods;
/** /**
@@ -31,7 +31,7 @@ trait Strict
{ {
$class = method_exists($this, $name) ? 'parent' : static::class; $class = method_exists($this, $name) ? 'parent' : static::class;
$items = (new ReflectionClass($this))->getMethods(ReflectionMethod::IS_PUBLIC); $items = (new ReflectionClass($this))->getMethods(ReflectionMethod::IS_PUBLIC);
$items = array_map(function ($item) { return $item->getName(); }, $items); $items = array_map(fn($item) => $item->getName(), $items);
$hint = ($t = Helpers::getSuggestion($items, $name)) $hint = ($t = Helpers::getSuggestion($items, $name))
? ", did you mean $t()?" ? ", did you mean $t()?"
: '.'; : '.';
@@ -46,8 +46,8 @@ trait Strict
public static function __callStatic(string $name, array $args) public static function __callStatic(string $name, array $args)
{ {
$rc = new ReflectionClass(static::class); $rc = new ReflectionClass(static::class);
$items = array_filter($rc->getMethods(\ReflectionMethod::IS_STATIC), function ($m) { return $m->isPublic(); }); $items = array_filter($rc->getMethods(\ReflectionMethod::IS_STATIC), fn($m) => $m->isPublic());
$items = array_map(function ($item) { return $item->getName(); }, $items); $items = array_map(fn($item) => $item->getName(), $items);
$hint = ($t = Helpers::getSuggestion($items, $name)) $hint = ($t = Helpers::getSuggestion($items, $name))
? ", did you mean $t()?" ? ", did you mean $t()?"
: '.'; : '.';
@@ -67,9 +67,10 @@ trait Strict
$ret = $this->$m(); $ret = $this->$m();
return $ret; return $ret;
} }
$rc = new ReflectionClass($this); $rc = new ReflectionClass($this);
$items = array_filter($rc->getProperties(ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); }); $items = array_filter($rc->getProperties(ReflectionProperty::IS_PUBLIC), fn($p) => !$p->isStatic());
$items = array_map(function ($item) { return $item->getName(); }, $items); $items = array_map(fn($item) => $item->getName(), $items);
$hint = ($t = Helpers::getSuggestion($items, $name)) $hint = ($t = Helpers::getSuggestion($items, $name))
? ", did you mean $$t?" ? ", did you mean $$t?"
: '.'; : '.';
@@ -84,8 +85,8 @@ trait Strict
public function __set(string $name, $value) public function __set(string $name, $value)
{ {
$rc = new ReflectionClass($this); $rc = new ReflectionClass($this);
$items = array_filter($rc->getProperties(ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); }); $items = array_filter($rc->getProperties(ReflectionProperty::IS_PUBLIC), fn($p) => !$p->isStatic());
$items = array_map(function ($item) { return $item->getName(); }, $items); $items = array_map(fn($item) => $item->getName(), $items);
$hint = ($t = Helpers::getSuggestion($items, $name)) $hint = ($t = Helpers::getSuggestion($items, $name))
? ", did you mean $$t?" ? ", did you mean $$t?"
: '.'; : '.';

View File

@@ -17,38 +17,28 @@ final class Translator
{ {
use Strict; use Strict;
/** @var Connection */ private Connection $connection;
private $connection;
/** @var Driver */ private Driver $driver;
private $driver;
/** @var int */ private int $cursor = 0;
private $cursor = 0;
/** @var array */ private array $args;
private $args;
/** @var string[] */ /** @var string[] */
private $errors; private array $errors;
/** @var bool */ private bool $comment = false;
private $comment = false;
/** @var int */ private int $ifLevel = 0;
private $ifLevel = 0;
/** @var int */ private int $ifLevelStart = 0;
private $ifLevelStart = 0;
/** @var int|null */ private ?int $limit = null;
private $limit;
/** @var int|null */ private ?int $offset = null;
private $offset;
/** @var HashMap */ private HashMap $identifiers;
private $identifiers;
public function __construct(Connection $connection) public function __construct(Connection $connection)
@@ -69,6 +59,7 @@ final class Translator
while (count($args) === 1 && is_array($args[0])) { // implicit array expansion while (count($args) === 1 && is_array($args[0])) { // implicit array expansion
$args = array_values($args[0]); $args = array_values($args[0]);
} }
$this->args = $args; $this->args = $args;
$this->errors = []; $this->errors = [];
@@ -95,27 +86,27 @@ final class Translator
// note: this can change $this->args & $this->cursor & ... // note: this can change $this->args & $this->cursor & ...
. preg_replace_callback( . preg_replace_callback(
<<<'XX' <<<'XX'
/ /
(?=[`['":%?]) ## speed-up (?=[`['":%?]) ## speed-up
(?: (?:
`(.+?)`| ## 1) `identifier` `(.+?)`| ## 1) `identifier`
\[(.+?)\]| ## 2) [identifier] \[(.+?)\]| ## 2) [identifier]
(')((?:''|[^'])*)'| ## 3,4) string (')((?:''|[^'])*)'| ## 3,4) string
(")((?:""|[^"])*)"| ## 5,6) "string" (")((?:""|[^"])*)"| ## 5,6) "string"
('|")| ## 7) lone quote ('|")| ## 7) lone quote
:(\S*?:)([a-zA-Z0-9._]?)| ## 8,9) :substitution: :(\S*?:)([a-zA-Z0-9._]?)| ## 8,9) :substitution:
%([a-zA-Z~][a-zA-Z0-9~]{0,5})| ## 10) modifier %([a-zA-Z~][a-zA-Z0-9~]{0,5})| ## 10) modifier
(\?) ## 11) placeholder (\?) ## 11) placeholder
)/xs )/xs
XX XX,
,
[$this, 'cb'], [$this, 'cb'],
substr($arg, $toSkip) substr($arg, $toSkip),
); );
if (preg_last_error()) { if (preg_last_error()) {
throw new PcreException; throw new PcreException;
} }
} }
continue; continue;
} }
@@ -138,8 +129,10 @@ XX
if ($lastArr === $cursor - 1) { if ($lastArr === $cursor - 1) {
$sql[] = ','; $sql[] = ',';
} }
$sql[] = $this->formatValue($arg, $commandIns ? 'l' : 'a'); $sql[] = $this->formatValue($arg, $commandIns ? 'l' : 'a');
} }
$lastArr = $cursor; $lastArr = $cursor;
continue; continue;
} }
@@ -148,7 +141,6 @@ XX
$sql[] = $this->formatValue($arg, null); $sql[] = $this->formatValue($arg, null);
} // while } // while
if ($comment) { if ($comment) {
$sql[] = '*/'; $sql[] = '*/';
} }
@@ -170,9 +162,8 @@ XX
/** /**
* Apply modifier to single value. * Apply modifier to single value.
* @param mixed $value
*/ */
public function formatValue($value, ?string $modifier): string public function formatValue(mixed $value, ?string $modifier): string
{ {
if ($this->comment) { if ($this->comment) {
return '...'; return '...';
@@ -207,20 +198,21 @@ XX
$v = $this->formatValue($v, $pair[1]); $v = $this->formatValue($v, $pair[1]);
if ($pair[1] === 'l' || $pair[1] === 'in') { if ($pair[1] === 'l' || $pair[1] === 'in') {
$op = 'IN '; $op = 'IN ';
} elseif (strpos($pair[1], 'like') !== false) { } elseif (str_contains($pair[1], 'like')) {
$op = 'LIKE '; $op = 'LIKE ';
} elseif ($v === 'NULL') { } elseif ($v === 'NULL') {
$op = 'IS '; $op = 'IS ';
} else { } else {
$op = '= '; $op = '= ';
} }
$vx[] = $k . $op . $v; $vx[] = $k . $op . $v;
} }
} else { } else {
$vx[] = $this->formatValue($v, 'ex'); $vx[] = $this->formatValue($v, 'ex');
} }
} }
return '(' . implode(') ' . strtoupper($modifier) . ' (', $vx) . ')'; return '(' . implode(') ' . strtoupper($modifier) . ' (', $vx) . ')';
case 'n': // key, key, ... identifier names case 'n': // key, key, ... identifier names
@@ -232,6 +224,7 @@ XX
$vx[] = $this->identifiers->{$pair[0]}; $vx[] = $this->identifiers->{$pair[0]};
} }
} }
return implode(', ', $vx); return implode(', ', $vx);
@@ -241,6 +234,7 @@ XX
$vx[] = $this->identifiers->{$pair[0]} . '=' $vx[] = $this->identifiers->{$pair[0]} . '='
. $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null)); . $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null));
} }
return implode(', ', $vx); return implode(', ', $vx);
@@ -250,6 +244,7 @@ XX
$pair = explode('%', (string) $k, 2); // split into identifier & modifier $pair = explode('%', (string) $k, 2); // split into identifier & modifier
$vx[] = $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null)); $vx[] = $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null));
} }
return '(' . (($vx || $modifier === 'l') ? implode(', ', $vx) : 'NULL') . ')'; return '(' . (($vx || $modifier === 'l') ? implode(', ', $vx) : 'NULL') . ')';
@@ -259,6 +254,7 @@ XX
$kx[] = $this->identifiers->{$pair[0]}; $kx[] = $this->identifiers->{$pair[0]};
$vx[] = $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null)); $vx[] = $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null));
} }
return '(' . implode(', ', $kx) . ') VALUES (' . implode(', ', $vx) . ')'; return '(' . implode(', ', $kx) . ') VALUES (' . implode(', ', $vx) . ')';
case 'm': // (key, key, ...) VALUES (val, val, ...), (val, val, ...), ... case 'm': // (key, key, ...) VALUES (val, val, ...), (val, val, ...), ...
@@ -272,7 +268,7 @@ XX
$proto = array_keys($v); $proto = array_keys($v);
} }
} else { } else {
return $this->errors[] = '**Unexpected type ' . (is_object($v) ? get_class($v) : gettype($v)) . '**'; return $this->errors[] = '**Unexpected type ' . (is_object($v) ? $v::class : gettype($v)) . '**';
} }
$pair = explode('%', $k, 2); // split into identifier & modifier $pair = explode('%', $k, 2); // split into identifier & modifier
@@ -281,9 +277,11 @@ XX
$vx[$k2][] = $this->formatValue($v2, $pair[1] ?? (is_array($v2) ? 'ex!' : null)); $vx[$k2][] = $this->formatValue($v2, $pair[1] ?? (is_array($v2) ? 'ex!' : null));
} }
} }
foreach ($vx as $k => $v) { foreach ($vx as $k => $v) {
$vx[$k] = '(' . implode(', ', $v) . ')'; $vx[$k] = '(' . implode(', ', $v) . ')';
} }
return '(' . implode(', ', $kx) . ') VALUES ' . implode(', ', $vx); return '(' . implode(', ', $kx) . ') VALUES ' . implode(', ', $vx);
case 'by': // key ASC, key DESC case 'by': // key ASC, key DESC
@@ -297,6 +295,7 @@ XX
$vx[] = $this->identifiers->$v; $vx[] = $this->identifiers->$v;
} }
} }
return implode(', ', $vx); return implode(', ', $vx);
case 'ex!': case 'ex!':
@@ -310,10 +309,28 @@ XX
foreach ($value as $v) { foreach ($value as $v) {
$vx[] = $this->formatValue($v, $modifier); $vx[] = $this->formatValue($v, $modifier);
} }
return implode(', ', $vx); return implode(', ', $vx);
} }
} }
if (is_object($value)
&& $modifier === null
&& !$value instanceof Literal
&& !$value instanceof Expression
) {
foreach ($this->connection->getObjectTranslators() as $class => $translator) {
if ($value instanceof $class) {
$value = $translator($value);
return $this->connection->translate(...$value->getValues());
}
}
}
// object-to-scalar procession
if ($value instanceof \BackedEnum && is_scalar($value->value)) {
$value = $value->value;
}
// with modifier procession // with modifier procession
if ($modifier) { if ($modifier) {
@@ -333,7 +350,7 @@ XX
) { ) {
// continue // continue
} else { } else {
$type = is_object($value) ? get_class($value) : gettype($value); $type = is_object($value) ? $value::class : gettype($value);
return $this->errors[] = "**Invalid combination of type $type and modifier %$modifier**"; return $this->errors[] = "**Invalid combination of type $type and modifier %$modifier**";
} }
} }
@@ -400,6 +417,7 @@ XX
} elseif (!$value instanceof \DateTimeInterface) { } elseif (!$value instanceof \DateTimeInterface) {
$value = new DateTime($value); $value = new DateTime($value);
} }
return $modifier === 'd' return $modifier === 'd'
? $this->driver->escapeDate($value) ? $this->driver->escapeDate($value)
: $this->driver->escapeDateTime($value); : $this->driver->escapeDateTime($value);
@@ -420,25 +438,26 @@ XX
$value = substr($value, 0, $toSkip) $value = substr($value, 0, $toSkip)
. preg_replace_callback( . preg_replace_callback(
<<<'XX' <<<'XX'
/ /
(?=[`['":]) (?=[`['":])
(?: (?:
`(.+?)`| `(.+?)`|
\[(.+?)\]| \[(.+?)\]|
(')((?:''|[^'])*)'| (')((?:''|[^'])*)'|
(")((?:""|[^"])*)"| (")((?:""|[^"])*)"|
('|")| ('|")|
:(\S*?:)([a-zA-Z0-9._]?) :(\S*?:)([a-zA-Z0-9._]?)
)/sx )/sx
XX XX
, ,
[$this, 'cb'], [$this, 'cb'],
substr($value, $toSkip) substr($value, $toSkip),
); );
if (preg_last_error()) { if (preg_last_error()) {
throw new PcreException; throw new PcreException;
} }
} }
return $value; return $value;
case 'SQL': // preserve as real SQL (TODO: rename to %sql) case 'SQL': // preserve as real SQL (TODO: rename to %sql)
@@ -469,7 +488,6 @@ XX
} }
} }
// without modifier procession // without modifier procession
if (is_string($value)) { if (is_string($value)) {
return $this->driver->escapeText($value); return $this->driver->escapeText($value);
@@ -499,7 +517,7 @@ XX
return $this->connection->translate(...$value->getValues()); return $this->connection->translate(...$value->getValues());
} else { } else {
$type = is_object($value) ? get_class($value) : gettype($value); $type = is_object($value) ? $value::class : gettype($value);
return $this->errors[] = "**Unexpected $type**"; return $this->errors[] = "**Unexpected $type**";
} }
} }
@@ -551,6 +569,7 @@ XX
$this->comment = true; $this->comment = true;
return '/*'; return '/*';
} }
return ''; return '';
} elseif ($mod === 'else') { } elseif ($mod === 'else') {
@@ -563,7 +582,6 @@ XX
$this->comment = true; $this->comment = true;
return '/*'; return '/*';
} }
} elseif ($mod === 'end') { } elseif ($mod === 'end') {
$this->ifLevel--; $this->ifLevel--;
if ($this->ifLevelStart === $this->ifLevel + 1) { if ($this->ifLevelStart === $this->ifLevel + 1) {
@@ -572,6 +590,7 @@ XX
$this->comment = false; $this->comment = false;
return '*/'; return '*/';
} }
return ''; return '';
} elseif ($mod === 'ex') { // array expansion } elseif ($mod === 'ex') { // array expansion
@@ -586,6 +605,7 @@ XX
} else { } else {
$this->limit = Helpers::intVal($arg); $this->limit = Helpers::intVal($arg);
} }
return ''; return '';
} elseif ($mod === 'ofs') { // apply offset } elseif ($mod === 'ofs') { // apply offset
@@ -596,6 +616,7 @@ XX
} else { } else {
$this->offset = Helpers::intVal($arg); $this->offset = Helpers::intVal($arg);
} }
return ''; return '';
} else { // default processing } else { // default processing
@@ -649,6 +670,7 @@ XX
$v = $this->driver->escapeIdentifier($v); $v = $this->driver->escapeIdentifier($v);
} }
} }
return implode('.', $parts); return implode('.', $parts);
} }
} }

View File

@@ -44,31 +44,30 @@ class dibi
IDENTIFIER = 'n'; IDENTIFIER = 'n';
/** version */ /** version */
public const public const VERSION = '5.0-dev';
VERSION = '4.2.3';
/** sorting order */ /** sorting order */
public const public const
ASC = 'ASC', ASC = 'ASC',
DESC = 'DESC'; DESC = 'DESC';
/** @var string|null Last SQL command @see dibi::query() */ /** Last SQL command @see dibi::query() */
public static $sql; public static ?string $sql = null;
/** @var float|null Elapsed time for last query */ /** Elapsed time for last query */
public static $elapsedTime; public static ?float $elapsedTime = null;
/** @var float Elapsed time for all queries */ /** Elapsed time for all queries */
public static $totalTime; public static float $totalTime = 0;
/** @var int Number or queries */ /** Number or queries */
public static $numOfQueries = 0; public static int $numOfQueries = 0;
/** @var Dibi\Connection[] Connection registry storage for Dibi\Connection objects */ /** @var Dibi\Connection[] Connection registry storage for Dibi\Connection objects */
private static $registry = []; private static array $registry = [];
/** @var Dibi\Connection Current connection */ /** Current connection */
private static $connection; private static Dibi\Connection $connection;
/** /**
@@ -88,7 +87,7 @@ class dibi
* @param array $config connection parameters * @param array $config connection parameters
* @throws Dibi\Exception * @throws Dibi\Exception
*/ */
public static function connect($config = [], string $name = '0'): Dibi\Connection public static function connect(array $config = [], string $name = '0'): Dibi\Connection
{ {
return self::$connection = self::$registry[$name] = new Dibi\Connection($config, $name); return self::$connection = self::$registry[$name] = new Dibi\Connection($config, $name);
} }
@@ -107,7 +106,7 @@ class dibi
* Retrieve active connection. * Retrieve active connection.
* @throws Dibi\Exception * @throws Dibi\Exception
*/ */
public static function getConnection(string $name = null): Dibi\Connection public static function getConnection(?string $name = null): Dibi\Connection
{ {
if ($name === null) { if ($name === null) {
if (self::$connection === null) { if (self::$connection === null) {
@@ -151,10 +150,9 @@ class dibi
/** /**
* Prints out a syntax highlighted version of the SQL command or Result. * Prints out a syntax highlighted version of the SQL command or Result.
* @param string|Dibi\Result $sql
* @param bool $return return output instead of printing it? * @param bool $return return output instead of printing it?
*/ */
public static function dump($sql = null, bool $return = false): ?string public static function dump(string|Dibi\Result|null $sql = null, bool $return = false): ?string
{ {
return Dibi\Helpers::dump($sql, $return); return Dibi\Helpers::dump($sql, $return);
} }
@@ -163,9 +161,9 @@ class dibi
/** /**
* Strips microseconds part. * Strips microseconds part.
*/ */
public static function stripMicroseconds(\DateTimeInterface $dt): \DateTimeInterface public static function stripMicroseconds(DateTimeInterface $dt): DateTimeInterface
{ {
$class = get_class($dt); $class = $dt::class;
return new $class($dt->format('Y-m-d H:i:s'), $dt->getTimezone()); return new $class($dt->format('Y-m-d H:i:s'), $dt->getTimezone());
} }
} }

View File

@@ -15,15 +15,15 @@ namespace Dibi;
*/ */
class Exception extends \Exception class Exception extends \Exception
{ {
/** @var string|null */ private ?string $sql;
private $sql;
/** public function __construct(
* @param int|string $code string $message = '',
*/ int|string $code = 0,
public function __construct(string $message = '', $code = 0, string $sql = null, \Throwable $previous = null) ?string $sql = null,
{ ?\Throwable $previous = null,
) {
parent::__construct($message, 0, $previous); parent::__construct($message, 0, $previous);
$this->code = $code; $this->code = $code;
$this->sql = $sql; $this->sql = $sql;
@@ -56,17 +56,9 @@ class DriverException extends Exception
*/ */
class PcreException extends Exception class PcreException extends Exception
{ {
public function __construct(string $message = '%msg.') public function __construct()
{ {
static $messages = [ parent::__construct(preg_last_error_msg(), preg_last_error());
PREG_INTERNAL_ERROR => 'Internal error',
PREG_BACKTRACK_LIMIT_ERROR => 'Backtrack limit was exhausted',
PREG_RECURSION_LIMIT_ERROR => 'Recursion limit was exhausted',
PREG_BAD_UTF8_ERROR => 'Malformed UTF-8 data',
5 => 'Offset didn\'t correspond to the begin of a valid UTF-8 code point', // PREG_BAD_UTF8_OFFSET_ERROR
];
$code = preg_last_error();
parent::__construct(str_replace('%msg', $messages[$code] ?? 'Unknown error', $message), $code);
} }
} }
@@ -86,14 +78,13 @@ class NotSupportedException extends Exception
*/ */
class ProcedureException extends Exception class ProcedureException extends Exception
{ {
/** @var string */ protected string $severity;
protected $severity;
/** /**
* Construct the exception. * Construct the exception.
*/ */
public function __construct(string $message = '', int $code = 0, string $severity = '', string $sql = null) public function __construct(string $message = '', int $code = 0, string $severity = '', ?string $sql = null)
{ {
parent::__construct($message, $code, $sql); parent::__construct($message, $code, $sql);
$this->severity = $severity; $this->severity = $severity;

View File

@@ -51,25 +51,24 @@ interface Driver
* Begins a transaction (if supported). * Begins a transaction (if supported).
* @throws DriverException * @throws DriverException
*/ */
function begin(string $savepoint = null): void; function begin(?string $savepoint = null): void;
/** /**
* Commits statements in a transaction. * Commits statements in a transaction.
* @throws DriverException * @throws DriverException
*/ */
function commit(string $savepoint = null): void; function commit(?string $savepoint = null): void;
/** /**
* Rollback changes in a transaction. * Rollback changes in a transaction.
* @throws DriverException * @throws DriverException
*/ */
function rollback(string $savepoint = null): void; function rollback(?string $savepoint = null): void;
/** /**
* Returns the connection resource. * Returns the connection resource.
* @return mixed
*/ */
function getResource(); function getResource(): mixed;
/** /**
* Returns the connection reflector. * Returns the connection reflector.
@@ -117,7 +116,6 @@ interface ResultDriver
/** /**
* Moves cursor position without fetching row. * Moves cursor position without fetching row.
* @return bool true on success, false if unable to seek to specified record
* @throws Exception * @throws Exception
*/ */
function seek(int $row): bool; function seek(int $row): bool;
@@ -142,9 +140,8 @@ interface ResultDriver
/** /**
* Returns the result set resource. * Returns the result set resource.
* @return mixed
*/ */
function getResultResource(); function getResultResource(): mixed;
/** /**
* Decodes data from result set. * Decodes data from result set.
@@ -224,20 +221,20 @@ interface IConnection
* Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query. * Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
* @throws Exception * @throws Exception
*/ */
function getInsertId(string $sequence = null): int; function getInsertId(?string $sequence = null): int;
/** /**
* Begins a transaction (if supported). * Begins a transaction (if supported).
*/ */
function begin(string $savepoint = null): void; function begin(?string $savepoint = null): void;
/** /**
* Commits statements in a transaction. * Commits statements in a transaction.
*/ */
function commit(string $savepoint = null): void; function commit(?string $savepoint = null): void;
/** /**
* Rollback changes in a transaction. * Rollback changes in a transaction.
*/ */
function rollback(string $savepoint = null): void; function rollback(?string $savepoint = null): void;
} }

View File

@@ -13,12 +13,13 @@ driver = mysqli
host = 127.0.0.1 host = 127.0.0.1
username = root username = root
password = "Password12!" password = "Password12!"
database = dibi_test
charset = utf8 charset = utf8
system = mysql system = mysql
[mysql pdo] [mysql pdo]
driver = pdo driver = pdo
dsn = "mysql:host=127.0.0.1" dsn = "mysql:host=127.0.0.1;dbname=dibi_test"
username = root username = root
password = "Password12!" password = "Password12!"
system = mysql system = mysql

View File

@@ -13,12 +13,13 @@ driver = mysqli
host = 127.0.0.1 host = 127.0.0.1
username = root username = root
password = password =
database = dibi_test
charset = utf8 charset = utf8
system = mysql system = mysql
[mysql pdo] [mysql pdo]
driver = pdo driver = pdo
dsn = "mysql:host=127.0.0.1" dsn = "mysql:host=127.0.0.1;dbname=dibi_test"
username = root username = root
password = password =
system = mysql system = mysql

View File

@@ -63,6 +63,15 @@ test('', function () use ($config) {
}); });
test('', function () use ($config) {
$conn = new Connection($config);
Assert::true($conn->isConnected());
$conn->__destruct();
Assert::false($conn->isConnected());
});
test('', function () use ($config) { test('', function () use ($config) {
Assert::exception(function () use ($config) { Assert::exception(function () use ($config) {
new Connection($config + ['onConnect' => '']); new Connection($config + ['onConnect' => '']);

View File

@@ -33,13 +33,13 @@ Assert::equal([
$res = $conn->query('SELECT * FROM [products] ORDER BY product_id'); $res = $conn->query('SELECT * FROM [products] ORDER BY product_id');
Assert::same( Assert::same(
[1 => 'Chair', 'Table', 'Computer'], [1 => 'Chair', 'Table', 'Computer'],
$res->fetchPairs('product_id', 'title') $res->fetchPairs('product_id', 'title'),
); );
$res = $conn->query('SELECT * FROM [products] ORDER BY product_id'); $res = $conn->query('SELECT * FROM [products] ORDER BY product_id');
Assert::same( Assert::same(
[1 => 'Chair', 'Table', 'Computer'], [1 => 'Chair', 'Table', 'Computer'],
$res->fetchPairs() $res->fetchPairs(),
); );

View File

@@ -0,0 +1,119 @@
<?php
/**
* @dataProvider ../databases.ini
*/
declare(strict_types=1);
use Tester\Assert;
require __DIR__ . '/bootstrap.php';
$conn = new Dibi\Connection($config + ['formatDateTime' => "'Y-m-d H:i:s.u'", 'formatDate' => "'Y-m-d'"]);
class Email
{
public $address = 'address@example.com';
}
class Time extends DateTimeImmutable
{
}
test('Without object translator', function () use ($conn) {
Assert::exception(function () use ($conn) {
$conn->translate('?', new Email);
}, Dibi\Exception::class, 'SQL translate error: Unexpected Email');
});
test('Basics', function () use ($conn) {
$conn->addObjectTranslator(Email::class, fn($object) => new Dibi\Expression('?', $object->address));
Assert::same(
reformat([
'sqlsrv' => "N'address@example.com'",
"'address@example.com'",
]),
$conn->translate('?', new Email),
);
});
test('DateTime', function () use ($conn) {
$stamp = Time::createFromFormat('Y-m-d H:i:s', '2022-11-22 12:13:14');
// Without object translator, DateTime child is translated by driver
Assert::same(
$conn->getDriver()->escapeDateTime($stamp),
$conn->translate('?', $stamp),
);
// With object translator
$conn->addObjectTranslator(Time::class, fn($object) => new Dibi\Expression('OwnTime(?)', $object->format('H:i:s')));
Assert::same(
reformat([
'sqlsrv' => "OwnTime(N'12:13:14')",
"OwnTime('12:13:14')",
]),
$conn->translate('?', $stamp),
);
// With modifier, it is still translated by driver
Assert::same(
$conn->getDriver()->escapeDateTime($stamp),
$conn->translate('%dt', $stamp),
);
Assert::same(
$conn->getDriver()->escapeDateTime($stamp),
$conn->translate('%t', $stamp),
);
Assert::same(
$conn->getDriver()->escapeDate($stamp),
$conn->translate('%d', $stamp),
);
// DateTimeImmutable as a Time parent is not affected and still translated by driver
$dt = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', '2022-11-22 12:13:14');
Assert::same(
$conn->getDriver()->escapeDateTime($dt),
$conn->translate('?', $dt),
);
// But DateTime translation can be overloaded
$conn->addObjectTranslator(DateTimeInterface::class, fn() => new Dibi\Expression('OwnDateTime'));
Assert::same(
'OwnDateTime',
$conn->translate('?', $dt),
);
});
test('Complex structures', function () use ($conn) {
$conn->addObjectTranslator(Email::class, fn($object) => new Dibi\Expression('?', $object->address));
$conn->addObjectTranslator(Time::class, fn($object) => new Dibi\Expression('OwnTime(?)', $object->format('H:i:s')));
$conn->addObjectTranslator(Time::class, fn($object) => new Dibi\Expression('OwnTime(?)', $object->format('H:i:s')));
$time = Time::createFromFormat('Y-m-d H:i:s', '2022-11-22 12:13:14');
Assert::same(
reformat([
'sqlsrv' => "([a], [b], [c], [d], [e], [f], [g]) VALUES (OwnTime(N'12:13:14'), '2022-11-22', CONVERT(DATETIME2(7), '2022-11-22 12:13:14.000000'), CONVERT(DATETIME2(7), '2022-11-22 12:13:14.000000'), N'address@example.com', OwnDateTime, OwnDateTime)",
'odbc' => "([a], [b], [c], [d], [e], [f], [g]) VALUES (OwnTime('12:13:14'), #11/22/2022#, #11/22/2022 12:13:14.000000#, #11/22/2022 12:13:14.000000#, 'address@example.com', OwnDateTime, OwnDateTime)",
"([a], [b], [c], [d], [e], [f], [g]) VALUES (OwnTime('12:13:14'), '2022-11-22', '2022-11-22 12:13:14.000000', '2022-11-22 12:13:14.000000', 'address@example.com', OwnDateTime, OwnDateTime)",
]),
$conn->translate('%v', [
'a' => $time,
'b%d' => $time,
'c%t' => $time,
'd%dt' => $time,
'e' => new Email,
'f' => new DateTime,
'g' => new DateTimeImmutable,
]),
);
});

View File

@@ -1,4 +1,5 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
use Tester\Assert; use Tester\Assert;
@@ -13,27 +14,27 @@ $conn->getSubstitutes()->blog = 'wp_';
Assert::same( Assert::same(
reformat('UPDATE wp_items SET [val]=1'), reformat('UPDATE wp_items SET [val]=1'),
$conn->translate('UPDATE :blog:items SET [val]=1') $conn->translate('UPDATE :blog:items SET [val]=1'),
); );
Assert::same( Assert::same(
reformat('UPDATE [wp_items] SET [val]=1'), reformat('UPDATE [wp_items] SET [val]=1'),
$conn->translate('UPDATE [:blog:items] SET [val]=1') $conn->translate('UPDATE [:blog:items] SET [val]=1'),
); );
Assert::same( Assert::same(
reformat("UPDATE 'wp_' SET [val]=1"), reformat("UPDATE 'wp_' SET [val]=1"),
$conn->translate('UPDATE :blog: SET [val]=1') $conn->translate('UPDATE :blog: SET [val]=1'),
); );
Assert::same( Assert::same(
reformat("UPDATE ':blg:' SET [val]=1"), reformat("UPDATE ':blg:' SET [val]=1"),
$conn->translate('UPDATE :blg: SET [val]=1') $conn->translate('UPDATE :blg: SET [val]=1'),
); );
Assert::same( Assert::same(
reformat("UPDATE table SET [text]=':blog:a'"), reformat("UPDATE table SET [text]=':blog:a'"),
$conn->translate("UPDATE table SET [text]=':blog:a'") $conn->translate("UPDATE table SET [text]=':blog:a'"),
); );
@@ -42,16 +43,14 @@ $conn->getSubstitutes()->{''} = 'my_';
Assert::same( Assert::same(
reformat('UPDATE my_table SET [val]=1'), reformat('UPDATE my_table SET [val]=1'),
$conn->translate('UPDATE ::table SET [val]=1') $conn->translate('UPDATE ::table SET [val]=1'),
); );
// create substitutions using fallback callback // create substitutions using fallback callback
$conn->getSubstitutes()->setCallback(function ($expr) { $conn->getSubstitutes()->setCallback(fn($expr) => '_' . $expr . '_');
return '_' . $expr . '_';
});
Assert::same( Assert::same(
reformat('UPDATE _account_user SET [val]=1'), reformat('UPDATE _account_user SET [val]=1'),
$conn->translate('UPDATE :account:user SET [val]=1') $conn->translate('UPDATE :account:user SET [val]=1'),
); );

View File

@@ -60,7 +60,7 @@ test('transaction() fail', function () use ($conn) {
]); ]);
throw new Exception('my exception'); throw new Exception('my exception');
}); });
}, \Throwable::class, 'my exception'); }, Throwable::class, 'my exception');
Assert::same(4, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle()); Assert::same(4, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
}); });
@@ -89,7 +89,7 @@ test('nested transaction() call fail', function () use ($conn) {
throw new Exception('my exception'); throw new Exception('my exception');
}); });
}); });
}, \Throwable::class, 'my exception'); }, Throwable::class, 'my exception');
Assert::same(5, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle()); Assert::same(5, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
}); });
@@ -115,17 +115,17 @@ test('begin(), commit() & rollback() calls are forbidden in transaction()', func
$conn->transaction(function (Dibi\Connection $connection) { $conn->transaction(function (Dibi\Connection $connection) {
$connection->begin(); $connection->begin();
}); });
}, \LogicException::class, Dibi\Connection::class . '::begin() call is forbidden inside a transaction() callback'); }, LogicException::class, Dibi\Connection::class . '::begin() call is forbidden inside a transaction() callback');
Assert::exception(function () use ($conn) { Assert::exception(function () use ($conn) {
$conn->transaction(function (Dibi\Connection $connection) { $conn->transaction(function (Dibi\Connection $connection) {
$connection->commit(); $connection->commit();
}); });
}, \LogicException::class, Dibi\Connection::class . '::commit() call is forbidden inside a transaction() callback'); }, LogicException::class, Dibi\Connection::class . '::commit() call is forbidden inside a transaction() callback');
Assert::exception(function () use ($conn) { Assert::exception(function () use ($conn) {
$conn->transaction(function (Dibi\Connection $connection) { $conn->transaction(function (Dibi\Connection $connection) {
$connection->rollback(); $connection->rollback();
}); });
}, \LogicException::class, Dibi\Connection::class . '::rollback() call is forbidden inside a transaction() callback'); }, LogicException::class, Dibi\Connection::class . '::rollback() call is forbidden inside a transaction() callback');
}); });

View File

@@ -1,4 +1,5 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
use Dibi\Row; use Dibi\Row;
@@ -16,7 +17,7 @@ Assert::match(
reformat(' reformat('
SELECT * SELECT *
FROM (SELECT * FROM products) t'), FROM (SELECT * FROM products) t'),
(string) $ds (string) $ds,
); );
@@ -24,7 +25,7 @@ Assert::same(3, $ds->count());
Assert::same(3, $ds->getTotalCount()); Assert::same(3, $ds->getTotalCount());
Assert::same( Assert::same(
reformat('SELECT COUNT(*) FROM (SELECT * FROM products) t'), reformat('SELECT COUNT(*) FROM (SELECT * FROM products) t'),
dibi::$sql dibi::$sql,
); );
@@ -38,7 +39,7 @@ FROM (SELECT * FROM products) t
WHERE (title like '%a%') WHERE (title like '%a%')
ORDER BY [title] DESC ORDER BY [title] DESC
"), "),
(string) $ds (string) $ds,
); );
@@ -52,7 +53,7 @@ FROM (SELECT * FROM products) t
WHERE (title like '%a%') AND (product_id = 1) WHERE (title like '%a%') AND (product_id = 1)
ORDER BY [title] DESC, [product_id] ASC ORDER BY [title] DESC, [product_id] ASC
"), "),
(string) $ds (string) $ds,
); );
@@ -66,7 +67,7 @@ FROM (SELECT * FROM products) t
WHERE (title like '%a%') AND (product_id = 1) AND (product_id = 1) WHERE (title like '%a%') AND (product_id = 1) AND (product_id = 1)
ORDER BY [product_id] ASC ORDER BY [product_id] ASC
"), "),
(string) $ds (string) $ds,
); );
@@ -94,7 +95,7 @@ FROM (SELECT * FROM products) t
WHERE (title like '%a%') AND (product_id = 1) AND (product_id = 1) WHERE (title like '%a%') AND (product_id = 1) AND (product_id = 1)
ORDER BY [product_id] ASC ORDER BY [product_id] ASC
"), "),
dibi::$sql dibi::$sql,
); );
@@ -107,7 +108,7 @@ FROM (SELECT * FROM products) t
WHERE (title like '%a%') AND (product_id = 1) AND (product_id = 1) WHERE (title like '%a%') AND (product_id = 1) AND (product_id = 1)
ORDER BY [product_id] ASC ORDER BY [product_id] ASC
) t"), ) t"),
(string) $fluent (string) $fluent,
); );
@@ -116,7 +117,7 @@ Assert::match(
reformat(' reformat('
SELECT * SELECT *
FROM (SELECT [title] FROM [products]) t'), FROM (SELECT [title] FROM [products]) t'),
(string) $ds (string) $ds,
); );
Assert::equal(new Row([ Assert::equal(new Row([
@@ -128,7 +129,7 @@ Assert::same(1, $conn->dataSource('SELECT * FROM products ORDER BY product_id')-
Assert::same( Assert::same(
[1 => 'Chair', 'Table', 'Computer'], [1 => 'Chair', 'Table', 'Computer'],
$conn->dataSource('SELECT * FROM products ORDER BY product_id')->fetchPairs() $conn->dataSource('SELECT * FROM products ORDER BY product_id')->fetchPairs(),
); );
Assert::equal([ Assert::equal([
@@ -153,7 +154,7 @@ Assert::match(
reformat(' reformat('
SELECT * SELECT *
FROM [products]'), FROM [products]'),
(string) $ds (string) $ds,
); );
Assert::same(3, $ds->count()); Assert::same(3, $ds->count());

View File

@@ -10,12 +10,10 @@ require __DIR__ . '/bootstrap.php';
date_default_timezone_set('Europe/Prague'); date_default_timezone_set('Europe/Prague');
Assert::same('1978-01-23 11:40:00.000000', (string) new DateTime(254400000)); Assert::same('1978-01-23 11:40:00.000000', (string) new DateTime(254_400_000));
Assert::same('1978-01-23 11:40:00.000000', (string) (new DateTime)->setTimestamp(254400000)); Assert::same('1978-01-23 11:40:00.000000', (string) (new DateTime)->setTimestamp(254_400_000));
Assert::same(254400000, (new DateTime(254400000))->getTimestamp()); Assert::same(254_400_000, (new DateTime(254_400_000))->getTimestamp());
Assert::same('2050-08-13 11:40:00.000000', (string) new DateTime(2544000000)); Assert::same(is_int(2_544_000_000) ? 2_544_000_000 : '2544000000', (new DateTime(2_544_000_000))->getTimestamp()); // 64 bit
Assert::same('2050-08-13 11:40:00.000000', (string) (new DateTime)->setTimestamp(2544000000));
Assert::same(is_int(2544000000) ? 2544000000 : '2544000000', (new DateTime(2544000000))->getTimestamp()); // 64 bit
Assert::same('1978-05-05 00:00:00.000000', (string) new DateTime('1978-05-05')); Assert::same('1978-05-05 00:00:00.000000', (string) new DateTime('1978-05-05'));

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
use Tester\Assert;
require __DIR__ . '/bootstrap.php';
$conn = new Dibi\Connection($config);
$event = new Dibi\Event($conn, Dibi\Event::CONNECT);
Assert::same([__FILE__, __LINE__ - 1], $event->source);
eval('$event = new Dibi\Event($conn, Dibi\Event::CONNECT);');
Assert::same([__FILE__, __LINE__ - 1], $event->source);
array_map(function () use ($conn) {
$event = new Dibi\Event($conn, Dibi\Event::CONNECT);
Assert::same([__FILE__, __LINE__ - 1], $event->source);
}, [null]);

View File

@@ -1,4 +1,5 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
use Dibi\Fluent; use Dibi\Fluent;

View File

@@ -1,4 +1,5 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
use Tester\Assert; use Tester\Assert;
@@ -14,33 +15,33 @@ $fluent = $conn->delete('table')->as('bAlias')
Assert::same( Assert::same(
reformat('DELETE IGNORE FROM [table] AS [bAlias]'), reformat('DELETE IGNORE FROM [table] AS [bAlias]'),
(string) $fluent (string) $fluent,
); );
$fluent->removeClause('from')->from('anotherTable'); $fluent->removeClause('from')->from('anotherTable');
Assert::same( Assert::same(
reformat('DELETE IGNORE FROM [anotherTable]'), reformat('DELETE IGNORE FROM [anotherTable]'),
(string) $fluent (string) $fluent,
); );
$fluent->using('thirdTable'); $fluent->using('thirdTable');
Assert::same( Assert::same(
reformat('DELETE IGNORE FROM [anotherTable] USING [thirdTable]'), reformat('DELETE IGNORE FROM [anotherTable] USING [thirdTable]'),
(string) $fluent (string) $fluent,
); );
$fluent->setFlag('IGNORE', false); $fluent->setFlag('IGNORE', false);
Assert::same( Assert::same(
reformat('DELETE FROM [anotherTable] USING [thirdTable]'), reformat('DELETE FROM [anotherTable] USING [thirdTable]'),
(string) $fluent (string) $fluent,
); );
$fluent->limit(10); $fluent->limit(10);
Assert::same( Assert::same(
reformat('DELETE FROM [anotherTable] USING [thirdTable] LIMIT 10'), reformat('DELETE FROM [anotherTable] USING [thirdTable] LIMIT 10'),
(string) $fluent (string) $fluent,
); );

View File

@@ -1,4 +1,5 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
use Tester\Assert; use Tester\Assert;
@@ -57,28 +58,28 @@ $fluent = $conn->select('*')
Assert::same( Assert::same(
reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'), reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
(string) $fluent (string) $fluent,
); );
$fluent->fetch(); $fluent->fetch();
Assert::same( Assert::same(
'SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t', 'SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t',
dibi::$sql dibi::$sql,
); );
$fluent->fetchSingle(); $fluent->fetchSingle();
Assert::same( Assert::same(
reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'), reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
dibi::$sql dibi::$sql,
); );
$fluent->fetchAll(0, 3); $fluent->fetchAll(0, 3);
Assert::same( Assert::same(
reformat('SELECT TOP (3) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'), reformat('SELECT TOP (3) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
dibi::$sql dibi::$sql,
); );
Assert::same( Assert::same(
reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'), reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
(string) $fluent (string) $fluent,
); );
@@ -86,16 +87,16 @@ $fluent->limit(0);
$fluent->fetch(); $fluent->fetch();
Assert::same( Assert::same(
reformat('SELECT TOP (0) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'), reformat('SELECT TOP (0) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
dibi::$sql dibi::$sql,
); );
$fluent->fetchSingle(); $fluent->fetchSingle();
Assert::same( Assert::same(
reformat('SELECT TOP (0) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'), reformat('SELECT TOP (0) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
dibi::$sql dibi::$sql,
); );
Assert::same( Assert::same(
reformat('SELECT TOP (0) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'), reformat('SELECT TOP (0) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
(string) $fluent (string) $fluent,
); );
@@ -104,14 +105,14 @@ $fluent->removeClause('offset');
$fluent->fetch(); $fluent->fetch();
Assert::same( Assert::same(
reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'), reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
dibi::$sql dibi::$sql,
); );
$fluent->fetchSingle(); $fluent->fetchSingle();
Assert::same( Assert::same(
reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'), reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
dibi::$sql dibi::$sql,
); );
Assert::same( Assert::same(
reformat('SELECT * FROM [customers] ORDER BY [customer_id]'), reformat('SELECT * FROM [customers] ORDER BY [customer_id]'),
(string) $fluent (string) $fluent,
); );

View File

@@ -23,28 +23,28 @@ $fluent = $conn->select('*')
Assert::same( Assert::same(
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'), reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
(string) $fluent (string) $fluent,
); );
$fluent->fetch(); $fluent->fetch();
Assert::same( Assert::same(
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'), reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
dibi::$sql dibi::$sql,
); );
$fluent->fetchSingle(); $fluent->fetchSingle();
Assert::same( Assert::same(
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'), reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
dibi::$sql dibi::$sql,
); );
$fluent->fetchAll(2, 3); $fluent->fetchAll(2, 3);
Assert::same( Assert::same(
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 3 OFFSET 2'), reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 3 OFFSET 2'),
dibi::$sql dibi::$sql,
); );
Assert::same( Assert::same(
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'), reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
(string) $fluent (string) $fluent,
); );
@@ -52,16 +52,16 @@ $fluent->limit(0);
$fluent->fetch(); $fluent->fetch();
Assert::same( Assert::same(
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 0 OFFSET 3'), reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 0 OFFSET 3'),
dibi::$sql dibi::$sql,
); );
$fluent->fetchSingle(); $fluent->fetchSingle();
Assert::same( Assert::same(
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 0 OFFSET 3'), reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 0 OFFSET 3'),
dibi::$sql dibi::$sql,
); );
Assert::same( Assert::same(
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 0 OFFSET 3'), reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 0 OFFSET 3'),
(string) $fluent (string) $fluent,
); );
@@ -69,16 +69,16 @@ $fluent->removeClause('limit');
$fluent->fetch(); $fluent->fetch();
Assert::same( Assert::same(
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'), reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
dibi::$sql dibi::$sql,
); );
$fluent->fetchSingle(); $fluent->fetchSingle();
Assert::same( Assert::same(
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'), reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
dibi::$sql dibi::$sql,
); );
Assert::same( Assert::same(
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 18446744073709551615 OFFSET 3'), reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 18446744073709551615 OFFSET 3'),
(string) $fluent (string) $fluent,
); );
@@ -86,14 +86,14 @@ $fluent->removeClause('offset');
$fluent->fetch(); $fluent->fetch();
Assert::same( Assert::same(
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1'), reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1'),
dibi::$sql dibi::$sql,
); );
$fluent->fetchSingle(); $fluent->fetchSingle();
Assert::same( Assert::same(
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1'), reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1'),
dibi::$sql dibi::$sql,
); );
Assert::same( Assert::same(
reformat('SELECT * FROM [customers] ORDER BY [customer_id]'), reformat('SELECT * FROM [customers] ORDER BY [customer_id]'),
(string) $fluent (string) $fluent,
); );

View File

@@ -1,4 +1,5 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
use Tester\Assert; use Tester\Assert;
@@ -20,40 +21,40 @@ $fluent = $conn->insert('table', $arr)
Assert::same( Assert::same(
reformat('INSERT IGNORE DELAYED INTO [table] ([title], [price], [brand]) VALUES (\'Super Product\', 12, NULL)'), reformat('INSERT IGNORE DELAYED INTO [table] ([title], [price], [brand]) VALUES (\'Super Product\', 12, NULL)'),
(string) $fluent (string) $fluent,
); );
$fluent->setFlag('IGNORE', false); $fluent->setFlag('IGNORE', false);
Assert::same( Assert::same(
reformat('INSERT DELAYED INTO [table] ([title], [price], [brand]) VALUES (\'Super Product\', 12, NULL)'), reformat('INSERT DELAYED INTO [table] ([title], [price], [brand]) VALUES (\'Super Product\', 12, NULL)'),
(string) $fluent (string) $fluent,
); );
$fluent->setFlag('HIGH_priority'); $fluent->setFlag('HIGH_priority');
Assert::same( Assert::same(
reformat('INSERT DELAYED HIGH_PRIORITY INTO [table] ([title], [price], [brand]) VALUES (\'Super Product\', 12, NULL)'), reformat('INSERT DELAYED HIGH_PRIORITY INTO [table] ([title], [price], [brand]) VALUES (\'Super Product\', 12, NULL)'),
(string) $fluent (string) $fluent,
); );
$fluent->into('anotherTable'); $fluent->into('anotherTable');
Assert::same( Assert::same(
reformat('INSERT DELAYED HIGH_PRIORITY INTO [anotherTable] VALUES (\'Super Product\', 12, NULL)'), reformat('INSERT DELAYED HIGH_PRIORITY INTO [anotherTable] VALUES (\'Super Product\', 12, NULL)'),
(string) $fluent (string) $fluent,
); );
$fluent->values('%l', $arr); $fluent->values('%l', $arr);
Assert::same( Assert::same(
reformat('INSERT DELAYED HIGH_PRIORITY INTO [anotherTable] VALUES (\'Super Product\', 12, NULL) , (\'Super Product\', 12, NULL)'), reformat('INSERT DELAYED HIGH_PRIORITY INTO [anotherTable] VALUES (\'Super Product\', 12, NULL) , (\'Super Product\', 12, NULL)'),
(string) $fluent (string) $fluent,
); );
$fluent->values($arr); $fluent->values($arr);
Assert::same( Assert::same(
reformat('INSERT DELAYED HIGH_PRIORITY INTO [anotherTable] VALUES (\'Super Product\', 12, NULL) , (\'Super Product\', 12, NULL) , (\'Super Product\', 12, NULL)'), reformat('INSERT DELAYED HIGH_PRIORITY INTO [anotherTable] VALUES (\'Super Product\', 12, NULL) , (\'Super Product\', 12, NULL) , (\'Super Product\', 12, NULL)'),
(string) $fluent (string) $fluent,
); );

View File

@@ -25,7 +25,7 @@ $fluent = $conn->select('*')
Assert::same( Assert::same(
reformat('SELECT * , [a] , [b] AS [bAlias] , [c], [d], [e] , [d]'), reformat('SELECT * , [a] , [b] AS [bAlias] , [c], [d], [e] , [d]'),
(string) $fluent (string) $fluent,
); );
$fluent->from('table')->as('table.Alias') $fluent->from('table')->as('table.Alias')
@@ -34,21 +34,21 @@ $fluent->from('table')->as('table.Alias')
Assert::same( Assert::same(
reformat('SELECT * , [a] , [b] AS [bAlias] , [c], [d], [e] , [d] FROM [table] AS [table.Alias] INNER JOIN [table1] ON table.col = table1.col INNER JOIN [table2] ON table.col = table2.col'), reformat('SELECT * , [a] , [b] AS [bAlias] , [c], [d], [e] , [d] FROM [table] AS [table.Alias] INNER JOIN [table1] ON table.col = table1.col INNER JOIN [table2] ON table.col = table2.col'),
(string) $fluent (string) $fluent,
); );
$fluent->from('anotherTable'); $fluent->from('anotherTable');
Assert::same( Assert::same(
reformat('SELECT * , [a] , [b] AS [bAlias] , [c], [d], [e] , [d] FROM [table] AS [table.Alias] INNER JOIN [table1] ON table.col = table1.col INNER JOIN [table2] ON table.col = table2.col , [anotherTable]'), reformat('SELECT * , [a] , [b] AS [bAlias] , [c], [d], [e] , [d] FROM [table] AS [table.Alias] INNER JOIN [table1] ON table.col = table1.col INNER JOIN [table2] ON table.col = table2.col , [anotherTable]'),
(string) $fluent (string) $fluent,
); );
$fluent->removeClause('from')->from('anotherTable'); $fluent->removeClause('from')->from('anotherTable');
Assert::same( Assert::same(
reformat('SELECT * , [a] , [b] AS [bAlias] , [c], [d], [e] , [d] FROM [anotherTable]'), reformat('SELECT * , [a] , [b] AS [bAlias] , [c], [d], [e] , [d] FROM [anotherTable]'),
(string) $fluent (string) $fluent,
); );
$fluent->as('anotherAlias') $fluent->as('anotherAlias')
@@ -58,7 +58,7 @@ $fluent->as('anotherAlias')
Assert::same( Assert::same(
reformat('SELECT * , [a] , [b] AS [bAlias] , [c], [d], [e] , [d] FROM [anotherTable] AS [anotherAlias] INNER JOIN [table3] ON table.col = table3.col'), reformat('SELECT * , [a] , [b] AS [bAlias] , [c], [d], [e] , [d] FROM [anotherTable] AS [anotherAlias] INNER JOIN [table3] ON table.col = table3.col'),
(string) $fluent (string) $fluent,
); );
$fluent->where('col > %i', $max) $fluent->where('col > %i', $max)
@@ -71,14 +71,14 @@ $fluent->where('col > %i', $max)
Assert::same( Assert::same(
reformat('SELECT * , [a] , [b] AS [bAlias] , [c], [d], [e] , [d] FROM [anotherTable] AS [anotherAlias] INNER JOIN [table3] ON table.col = table3.col WHERE col > 10 OR col < 5 AND active = 1 AND [col] IN (1, 2, 3) ORDER BY [val] ASC , [val2] DESC , [val3] DESC'), reformat('SELECT * , [a] , [b] AS [bAlias] , [c], [d], [e] , [d] FROM [anotherTable] AS [anotherAlias] INNER JOIN [table3] ON table.col = table3.col WHERE col > 10 OR col < 5 AND active = 1 AND [col] IN (1, 2, 3) ORDER BY [val] ASC , [val2] DESC , [val3] DESC'),
(string) $fluent (string) $fluent,
); );
$fluent->orderBy(Dibi\Fluent::REMOVE); $fluent->orderBy(Dibi\Fluent::REMOVE);
Assert::same( Assert::same(
reformat('SELECT * , [a] , [b] AS [bAlias] , [c], [d], [e] , [d] FROM [anotherTable] AS [anotherAlias] INNER JOIN [table3] ON table.col = table3.col WHERE col > 10 OR col < 5 AND active = 1 AND [col] IN (1, 2, 3)'), reformat('SELECT * , [a] , [b] AS [bAlias] , [c], [d], [e] , [d] FROM [anotherTable] AS [anotherAlias] INNER JOIN [table3] ON table.col = table3.col WHERE col > 10 OR col < 5 AND active = 1 AND [col] IN (1, 2, 3)'),
(string) $fluent (string) $fluent,
); );
@@ -86,7 +86,7 @@ $fluent = $conn->select('*')
->select( ->select(
$conn->select('count(*)') $conn->select('count(*)')
->from('precteni')->as('P') ->from('precteni')->as('P')
->where('P.id_clanku', '=', 'C.id_clanku') ->where('P.id_clanku', '=', 'C.id_clanku'),
) )
->from('clanky')->as('C') ->from('clanky')->as('C')
->where('id_clanku=%i', 123) ->where('id_clanku=%i', 123)
@@ -99,7 +99,7 @@ Assert::same(
'sqlsrv' => 'SELECT * , (SELECT count(*) FROM [precteni] AS [P] WHERE P.id_clanku = C.id_clanku) FROM [clanky] AS [C] WHERE id_clanku=123 OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY', 'sqlsrv' => 'SELECT * , (SELECT count(*) FROM [precteni] AS [P] WHERE P.id_clanku = C.id_clanku) FROM [clanky] AS [C] WHERE id_clanku=123 OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY',
'SELECT * , (SELECT count(*) FROM [precteni] AS [P] WHERE P.id_clanku = C.id_clanku) FROM [clanky] AS [C] WHERE id_clanku=123 LIMIT 1', 'SELECT * , (SELECT count(*) FROM [precteni] AS [P] WHERE P.id_clanku = C.id_clanku) FROM [clanky] AS [C] WHERE id_clanku=123 LIMIT 1',
]), ]),
(string) $fluent (string) $fluent,
); );
@@ -112,7 +112,7 @@ $fluent = $conn->select('*')
Assert::same( Assert::same(
reformat('SELECT * , [x] AS [xAlias] FROM [products] INNER JOIN [orders] USING (product_id) INNER JOIN [customers] USING ([customer_id]) INNER JOIN [items] USING ([customer_id], [order_id])'), reformat('SELECT * , [x] AS [xAlias] FROM [products] INNER JOIN [orders] USING (product_id) INNER JOIN [customers] USING ([customer_id]) INNER JOIN [items] USING ([customer_id], [order_id])'),
(string) $fluent (string) $fluent,
); );
@@ -124,7 +124,7 @@ $fluent = $conn->command()->select()
Assert::same( Assert::same(
reformat('SELECT * FROM [products] INNER JOIN [orders] USING (product_id)'), reformat('SELECT * FROM [products] INNER JOIN [orders] USING (product_id)'),
(string) $fluent (string) $fluent,
); );
@@ -138,7 +138,7 @@ Assert::same(
'sqlsrv' => "SELECT * FROM [me] AS [t] WHERE col > 10 AND ([x] = N'a') AND (b) AND (c)", 'sqlsrv' => "SELECT * FROM [me] AS [t] WHERE col > 10 AND ([x] = N'a') AND (b) AND (c)",
"SELECT * FROM [me] AS [t] WHERE col > 10 AND ([x] = 'a') AND (b) AND (c)", "SELECT * FROM [me] AS [t] WHERE col > 10 AND ([x] = 'a') AND (b) AND (c)",
]), ]),
(string) $fluent (string) $fluent,
); );
@@ -147,9 +147,9 @@ if ($config['system'] === 'mysql') {
->limit(' 1; DROP TABLE users') ->limit(' 1; DROP TABLE users')
->offset(' 1; DROP TABLE users'); ->offset(' 1; DROP TABLE users');
Assert::error(function () use ($fluent) { Assert::exception(function () use ($fluent) {
(string) $fluent; (string) $fluent;
}, E_USER_ERROR, "Expected number, ' 1; DROP TABLE users' given."); }, Dibi\Exception::class, "Expected number, ' 1; DROP TABLE users' given.");
} }
@@ -158,5 +158,5 @@ $fluent = $conn->select('*')->from('abc')
Assert::same( Assert::same(
reformat('SELECT * FROM [abc] WHERE x IN ((SELECT [id] FROM [xyz]))'), reformat('SELECT * FROM [abc] WHERE x IN ((SELECT [id] FROM [xyz]))'),
(string) $fluent (string) $fluent,
); );

View File

@@ -1,4 +1,5 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
use Tester\Assert; use Tester\Assert;
@@ -20,14 +21,14 @@ $fluent = $conn->update('table', $arr)
Assert::same( Assert::same(
reformat('UPDATE IGNORE DELAYED [table] SET [title]=\'Super Product\', [price]=12, [brand]=NULL'), reformat('UPDATE IGNORE DELAYED [table] SET [title]=\'Super Product\', [price]=12, [brand]=NULL'),
(string) $fluent (string) $fluent,
); );
$fluent->set(['another' => 123]); $fluent->set(['another' => 123]);
Assert::same( Assert::same(
reformat('UPDATE IGNORE DELAYED [table] SET [title]=\'Super Product\', [price]=12, [brand]=NULL , [another]=123'), reformat('UPDATE IGNORE DELAYED [table] SET [title]=\'Super Product\', [price]=12, [brand]=NULL , [another]=123'),
(string) $fluent (string) $fluent,
); );
@@ -39,5 +40,5 @@ $arr = [
$fluent = $conn->update(['table1', 'table2'], $arr); $fluent = $conn->update(['table1', 'table2'], $arr);
Assert::same( Assert::same(
reformat('UPDATE [table1], [table2] SET [table1].[title]=\'Super Product\', [table2].[price]=12, [table2].[brand]=NULL'), reformat('UPDATE [table1], [table2] SET [table1].[title]=\'Super Product\', [table2].[price]=12, [table2].[brand]=NULL'),
(string) $fluent (string) $fluent,
); );

View File

@@ -6,9 +6,7 @@ use Tester\Assert;
require __DIR__ . '/bootstrap.php'; require __DIR__ . '/bootstrap.php';
$hash = new Dibi\HashMap(function ($v) { $hash = new Dibi\HashMap(fn($v) => "b-$v-e");
return "b-$v-e";
});
Assert::same('b-X-e', $hash->{'X'}); Assert::same('b-X-e', $hash->{'X'});
Assert::same('b--e', $hash->{''}); Assert::same('b--e', $hash->{''});

View File

@@ -1,4 +1,5 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
use Dibi\Helpers; use Dibi\Helpers;

View File

@@ -1,4 +1,5 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
use Dibi\Helpers; use Dibi\Helpers;

View File

@@ -13,6 +13,7 @@ function buildPdoDriver(?int $errorMode)
if ($errorMode !== null) { if ($errorMode !== null) {
$pdo->setAttribute(PDO::ATTR_ERRMODE, $errorMode); $pdo->setAttribute(PDO::ATTR_ERRMODE, $errorMode);
} }
new Dibi\Drivers\PdoDriver(['resource' => $pdo]); new Dibi\Drivers\PdoDriver(['resource' => $pdo]);
} }

View File

@@ -28,14 +28,14 @@ Assert::same(4, $res->getColumnCount());
Assert::same( Assert::same(
['product_id', 'order_id', 'name', 'xXx'], ['product_id', 'order_id', 'name', 'xXx'],
$info->getColumnNames() $info->getColumnNames(),
); );
if (!in_array($config['driver'], ['sqlite', 'sqlite3', 'pdo', 'sqlsrv'], true)) { if (!in_array($config['driver'], ['sqlite', 'sqlite3', 'pdo', 'sqlsrv'], true)) {
Assert::same( Assert::same(
['products.product_id', 'orders.order_id', 'customers.name', 'xXx'], ['products.product_id', 'orders.order_id', 'customers.name', 'xXx'],
$info->getColumnNames(true) $info->getColumnNames(true),
); );
} }

View File

@@ -1,4 +1,5 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
use Dibi\Type; use Dibi\Type;

View File

@@ -1,4 +1,5 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
use Tester\Assert; use Tester\Assert;

View File

@@ -25,7 +25,7 @@ $conn->query(
'CREATE TRIGGER %n ON %n AFTER INSERT AS INSERT INTO %n DEFAULT VALUES', 'CREATE TRIGGER %n ON %n AFTER INSERT AS INSERT INTO %n DEFAULT VALUES',
'UpdAAB', 'UpdAAB',
'aab', 'aab',
'aaa' 'aaa',
); );
$conn->query('INSERT INTO %n DEFAULT VALUES', 'aab'); $conn->query('INSERT INTO %n DEFAULT VALUES', 'aab');

View File

@@ -21,19 +21,19 @@ $tests = function ($conn) {
// Limit and offset // Limit and offset
Assert::same( Assert::same(
'SELECT 1 OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY', 'SELECT 1 OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY',
$conn->translate('SELECT 1 %ofs %lmt', 10, 10) $conn->translate('SELECT 1 %ofs %lmt', 10, 10),
); );
// Limit only // Limit only
Assert::same( Assert::same(
'SELECT 1 OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY', 'SELECT 1 OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY',
$conn->translate('SELECT 1 %lmt', 10) $conn->translate('SELECT 1 %lmt', 10),
); );
// Offset only // Offset only
Assert::same( Assert::same(
'SELECT 1 OFFSET 10 ROWS', 'SELECT 1 OFFSET 10 ROWS',
$conn->translate('SELECT 1 %ofs', 10) $conn->translate('SELECT 1 %ofs', 10),
); );
// Offset invalid // Offset invalid
@@ -42,7 +42,7 @@ $tests = function ($conn) {
$conn->translate('SELECT 1 %ofs', -10); $conn->translate('SELECT 1 %ofs', -10);
}, },
Dibi\NotSupportedException::class, Dibi\NotSupportedException::class,
'Negative offset or limit.' 'Negative offset or limit.',
); );
// Limit invalid // Limit invalid
@@ -51,7 +51,7 @@ $tests = function ($conn) {
$conn->translate('SELECT 1 %lmt', -10); $conn->translate('SELECT 1 %lmt', -10);
}, },
Dibi\NotSupportedException::class, Dibi\NotSupportedException::class,
'Negative offset or limit.' 'Negative offset or limit.',
); );
// Limit invalid, offset valid // Limit invalid, offset valid
@@ -60,7 +60,7 @@ $tests = function ($conn) {
$conn->translate('SELECT 1 %ofs %lmt', 10, -10); $conn->translate('SELECT 1 %ofs %lmt', 10, -10);
}, },
Dibi\NotSupportedException::class, Dibi\NotSupportedException::class,
'Negative offset or limit.' 'Negative offset or limit.',
); );
// Limit valid, offset invalid // Limit valid, offset invalid
@@ -69,22 +69,22 @@ $tests = function ($conn) {
$conn->translate('SELECT 1 %ofs %lmt', -10, 10); $conn->translate('SELECT 1 %ofs %lmt', -10, 10);
}, },
Dibi\NotSupportedException::class, Dibi\NotSupportedException::class,
'Negative offset or limit.' 'Negative offset or limit.',
); );
} else { } else {
Assert::same( Assert::same(
'SELECT TOP (1) * FROM (SELECT 1) t', 'SELECT TOP (1) * FROM (SELECT 1) t',
$conn->translate('SELECT 1 %lmt', 1) $conn->translate('SELECT 1 %lmt', 1),
); );
Assert::same( Assert::same(
'SELECT 1', 'SELECT 1',
$conn->translate('SELECT 1 %lmt', -10) $conn->translate('SELECT 1 %lmt', -10),
); );
Assert::exception( Assert::exception(
$conn->translate('SELECT 1 %ofs %lmt', 10, 10), $conn->translate('SELECT 1 %ofs %lmt', 10, 10),
Dibi\NotSupportedException::class Dibi\NotSupportedException::class,
); );
} }
}; };

View File

@@ -1,4 +1,5 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
use Tester\Assert; use Tester\Assert;

View File

@@ -27,8 +27,8 @@ FROM [customers]
isset($name), isset($name),
'WHERE [name] LIKE %s', 'WHERE [name] LIKE %s',
'xxx', 'xxx',
'%end' '%end',
) ),
); );
@@ -42,8 +42,8 @@ FROM [customers] /* ... */'),
SELECT * SELECT *
FROM %if', FROM %if',
true, true,
'[customers] %else [products]' '[customers] %else [products]',
) ),
); );
@@ -62,7 +62,7 @@ FROM [people]
WHERE [id] > 0 WHERE [id] > 0
%if', false, 'AND [foo]=%i', 1, ' %if', false, 'AND [foo]=%i', 1, '
%else %if', true, 'AND [bar]=%i', 1, ' %else %if', true, 'AND [bar]=%i', 1, '
') '),
); );
@@ -97,8 +97,8 @@ WHERE
%if', %if',
false, false,
'AND [admin]=1 %end 'AND [admin]=1 %end
%else 1 LIMIT 10 %end' %else 1 LIMIT 10 %end',
) ),
); );
@@ -113,6 +113,6 @@ Assert::same(
3, 3,
'%ofs', '%ofs',
5, 5,
'%end' '%end',
) ),
); );

View File

@@ -0,0 +1,41 @@
<?php
/**
* @phpVersion 8.1
* @dataProvider ../databases.ini
*/
declare(strict_types=1);
use Tester\Assert;
require __DIR__ . '/bootstrap.php';
$conn = new Dibi\Connection($config);
$translator = new Dibi\Translator($conn);
enum EnumInt: int
{
case One = 1;
}
enum EnumString: string
{
case One = 'one';
}
enum PureEnum
{
case One;
}
Assert::equal('1', $translator->formatValue(EnumInt::One, null));
Assert::equal(match ($config['driver']) {
'sqlsrv' => "N'one'",
default => "'one'",
}, $translator->formatValue(EnumString::One, null));
Assert::equal('**Unexpected PureEnum**', $translator->formatValue(PureEnum::One, null));

View File

@@ -15,35 +15,35 @@ $conn = new Dibi\Connection($config);
Assert::same( Assert::same(
reformat('SELECT * FROM where WHERE select < 2'), reformat('SELECT * FROM where WHERE select < 2'),
$conn->translate('SELECT * FROM where WHERE select < 2') $conn->translate('SELECT * FROM where WHERE select < 2'),
); );
Assert::same( Assert::same(
reformat('SELECT * FROM [where] WHERE where.select < 2'), reformat('SELECT * FROM [where] WHERE where.select < 2'),
$conn->translate('SELECT * FROM [where] WHERE where.select < 2') $conn->translate('SELECT * FROM [where] WHERE where.select < 2'),
); );
Assert::same( Assert::same(
reformat('SELECT * FROM [where] WHERE [where].[select] < 2'), reformat('SELECT * FROM [where] WHERE [where].[select] < 2'),
$conn->translate('SELECT * FROM [where] WHERE [where.select] < 2') $conn->translate('SELECT * FROM [where] WHERE [where.select] < 2'),
); );
Assert::same( Assert::same(
reformat('SELECT * FROM [where] as [temp] WHERE [temp].[select] < 2'), reformat('SELECT * FROM [where] as [temp] WHERE [temp].[select] < 2'),
$conn->translate('SELECT * FROM [where] as [temp] WHERE [temp.select] < 2') $conn->translate('SELECT * FROM [where] as [temp] WHERE [temp.select] < 2'),
); );
Assert::same( Assert::same(
reformat('SELECT * FROM [where] WHERE [quot\'n\' space] > 2'), reformat('SELECT * FROM [where] WHERE [quot\'n\' space] > 2'),
$conn->translate("SELECT * FROM [where] WHERE [quot'n' space] > 2") $conn->translate("SELECT * FROM [where] WHERE [quot'n' space] > 2"),
); );
Assert::same( Assert::same(
reformat('SELECT * FROM [where] WHERE [where].[quot\'n\' space] > 2'), reformat('SELECT * FROM [where] WHERE [where].[quot\'n\' space] > 2'),
$conn->translate("SELECT * FROM [where] WHERE [where.quot'n' space] > 2") $conn->translate("SELECT * FROM [where] WHERE [where.quot'n' space] > 2"),
); );

View File

@@ -23,7 +23,7 @@ Assert::same(
$conn->translate('REPLACE INTO [products]', [ $conn->translate('REPLACE INTO [products]', [
'title' => 'Drticka', 'title' => 'Drticka',
'price' => 318, 'price' => 318,
]) ]),
); );
@@ -38,7 +38,7 @@ Assert::same(
'sqlsrv' => "INSERT INTO [products] ([title], [price], [brand]) VALUES (N'Super Product', 12, NULL) , (N'Super Product', 12, NULL) , (N'Super Product', 12, NULL)", 'sqlsrv' => "INSERT INTO [products] ([title], [price], [brand]) VALUES (N'Super Product', 12, NULL) , (N'Super Product', 12, NULL) , (N'Super Product', 12, NULL)",
"INSERT INTO [products] ([title], [price], [brand]) VALUES ('Super Product', 12, NULL) , ('Super Product', 12, NULL) , ('Super Product', 12, NULL)", "INSERT INTO [products] ([title], [price], [brand]) VALUES ('Super Product', 12, NULL) , ('Super Product', 12, NULL) , ('Super Product', 12, NULL)",
]), ]),
$conn->translate('INSERT INTO [products]', $array, $array, $array) $conn->translate('INSERT INTO [products]', $array, $array, $array),
); );
@@ -53,7 +53,7 @@ Assert::same(
'sqlsrv' => "INSERT INTO [products] ([pole], [bit]) VALUES (N'hodnota1', 1) , (N'hodnota2', 1) , (N'hodnota3', 1)", 'sqlsrv' => "INSERT INTO [products] ([pole], [bit]) VALUES (N'hodnota1', 1) , (N'hodnota2', 1) , (N'hodnota3', 1)",
"INSERT INTO [products] ([pole], [bit]) VALUES ('hodnota1', 1) , ('hodnota2', 1) , ('hodnota3', 1)", "INSERT INTO [products] ([pole], [bit]) VALUES ('hodnota1', 1) , ('hodnota2', 1) , ('hodnota3', 1)",
]), ]),
$conn->translate('INSERT INTO [products] %ex', $array) $conn->translate('INSERT INTO [products] %ex', $array),
); );
@@ -66,7 +66,7 @@ Assert::same(
$conn->translate('UPDATE [colors] SET', [ $conn->translate('UPDATE [colors] SET', [
'color' => 'blue', 'color' => 'blue',
'order' => 12, 'order' => 12,
], 'WHERE [id]=%i', 123) ], 'WHERE [id]=%i', 123),
); );
@@ -74,20 +74,20 @@ Assert::same(
$array = [1, 2, 3]; $array = [1, 2, 3];
Assert::same( Assert::same(
reformat('SELECT * FROM [people] WHERE [id] IN ( 1, 2, 3 )'), reformat('SELECT * FROM [people] WHERE [id] IN ( 1, 2, 3 )'),
$conn->translate('SELECT * FROM [people] WHERE [id] IN (', $array, ')') $conn->translate('SELECT * FROM [people] WHERE [id] IN (', $array, ')'),
); );
// long numbers // long numbers
Assert::same( Assert::same(
reformat('SELECT -123456789123456789123456789'), reformat('SELECT -123456789123456789123456789'),
$conn->translate('SELECT %i', '-123456789123456789123456789') $conn->translate('SELECT %i', '-123456789123456789123456789'),
); );
// long float numbers // long float numbers
Assert::same( Assert::same(
reformat('SELECT -.12345678912345678912345678e10'), reformat('SELECT -.12345678912345678912345678e10'),
$conn->translate('SELECT %f', '-.12345678912345678912345678e10') $conn->translate('SELECT %f', '-.12345678912345678912345678e10'),
); );
// invalid input // invalid input
@@ -101,7 +101,7 @@ Assert::same(
'sqlsrv' => "SELECT * FROM [table] WHERE id=10 AND name=N'ahoj'", 'sqlsrv' => "SELECT * FROM [table] WHERE id=10 AND name=N'ahoj'",
"SELECT * FROM [table] WHERE id=10 AND name='ahoj'", "SELECT * FROM [table] WHERE id=10 AND name='ahoj'",
]), ]),
$conn->translate('SELECT * FROM [table] WHERE id=%i AND name=%s', 10, 'ahoj') $conn->translate('SELECT * FROM [table] WHERE id=%i AND name=%s', 10, 'ahoj'),
); );
Assert::same( Assert::same(
@@ -109,7 +109,7 @@ Assert::same(
'sqlsrv' => "TEST ([cond] > 2) OR ([cond2] = N'3') OR (cond3 < RAND())", 'sqlsrv' => "TEST ([cond] > 2) OR ([cond2] = N'3') OR (cond3 < RAND())",
"TEST ([cond] > 2) OR ([cond2] = '3') OR (cond3 < RAND())", "TEST ([cond] > 2) OR ([cond2] = '3') OR (cond3 < RAND())",
]), ]),
$conn->translate('TEST %or', ['[cond] > 2', '[cond2] = "3"', 'cond3 < RAND()']) $conn->translate('TEST %or', ['[cond] > 2', '[cond2] = "3"', 'cond3 < RAND()']),
); );
Assert::same( Assert::same(
@@ -117,7 +117,7 @@ Assert::same(
'sqlsrv' => "TEST ([cond] > 2) AND ([cond2] = N'3') AND (cond3 < RAND())", 'sqlsrv' => "TEST ([cond] > 2) AND ([cond2] = N'3') AND (cond3 < RAND())",
"TEST ([cond] > 2) AND ([cond2] = '3') AND (cond3 < RAND())", "TEST ([cond] > 2) AND ([cond2] = '3') AND (cond3 < RAND())",
]), ]),
$conn->translate('TEST %and', ['[cond] > 2', '[cond2] = "3"', 'cond3 < RAND()']) $conn->translate('TEST %and', ['[cond] > 2', '[cond2] = "3"', 'cond3 < RAND()']),
); );
@@ -126,7 +126,7 @@ $where[] = '[age] > 20';
$where[] = '[email] IS NOT NULL'; $where[] = '[email] IS NOT NULL';
Assert::same( Assert::same(
reformat('SELECT * FROM [table] WHERE ([age] > 20) AND ([email] IS NOT NULL)'), reformat('SELECT * FROM [table] WHERE ([age] > 20) AND ([email] IS NOT NULL)'),
$conn->translate('SELECT * FROM [table] WHERE %and', $where) $conn->translate('SELECT * FROM [table] WHERE %and', $where),
); );
@@ -139,14 +139,14 @@ Assert::same(
'sqlsrv' => "SELECT * FROM [table] WHERE ([age] IS NULL) AND ([email] = N'ahoj') AND ([id] IN (10, 20, 30))", 'sqlsrv' => "SELECT * FROM [table] WHERE ([age] IS NULL) AND ([email] = N'ahoj') AND ([id] IN (10, 20, 30))",
"SELECT * FROM [table] WHERE ([age] IS NULL) AND ([email] = 'ahoj') AND ([id] IN (10, 20, 30))", "SELECT * FROM [table] WHERE ([age] IS NULL) AND ([email] = 'ahoj') AND ([id] IN (10, 20, 30))",
]), ]),
$conn->translate('SELECT * FROM [table] WHERE %and', $where) $conn->translate('SELECT * FROM [table] WHERE %and', $where),
); );
$where = []; $where = [];
Assert::same( Assert::same(
reformat('SELECT * FROM [table] WHERE 1=1'), reformat('SELECT * FROM [table] WHERE 1=1'),
$conn->translate('SELECT * FROM [table] WHERE %and', $where) $conn->translate('SELECT * FROM [table] WHERE %and', $where),
); );
@@ -161,7 +161,7 @@ $order = [
]; ];
Assert::same( Assert::same(
reformat('SELECT * FROM [people] ORDER BY [field1] ASC, [field2] DESC, [field3] ASC, [field4] DESC, [field5] ASC, [field6] DESC'), reformat('SELECT * FROM [people] ORDER BY [field1] ASC, [field2] DESC, [field3] ASC, [field4] DESC, [field5] ASC, [field6] DESC'),
$conn->translate('SELECT * FROM [people] ORDER BY %by', $order) $conn->translate('SELECT * FROM [people] ORDER BY %by', $order),
); );
@@ -172,7 +172,7 @@ Assert::same(
'sqlsrv' => 'SELECT * FROM [products] OFFSET 0 ROWS FETCH NEXT 2 ROWS ONLY', 'sqlsrv' => 'SELECT * FROM [products] OFFSET 0 ROWS FETCH NEXT 2 ROWS ONLY',
'SELECT * FROM [products] LIMIT 2', 'SELECT * FROM [products] LIMIT 2',
]), ]),
$conn->translate('SELECT * FROM [products] %lmt', 2) $conn->translate('SELECT * FROM [products] %lmt', 2),
); );
if ($config['system'] === 'odbc') { if ($config['system'] === 'odbc') {
@@ -186,7 +186,7 @@ if ($config['system'] === 'odbc') {
'sqlsrv' => 'SELECT * FROM [products] OFFSET 1 ROWS FETCH NEXT 2 ROWS ONLY', 'sqlsrv' => 'SELECT * FROM [products] OFFSET 1 ROWS FETCH NEXT 2 ROWS ONLY',
'SELECT * FROM [products] LIMIT 2 OFFSET 1', 'SELECT * FROM [products] LIMIT 2 OFFSET 1',
]), ]),
$conn->translate('SELECT * FROM [products] %lmt %ofs', 2, 1) $conn->translate('SELECT * FROM [products] %lmt %ofs', 2, 1),
); );
// with offset = 50 // with offset = 50
@@ -197,7 +197,7 @@ if ($config['system'] === 'odbc') {
'sqlsrv' => 'SELECT * FROM [products] OFFSET 50 ROWS', 'sqlsrv' => 'SELECT * FROM [products] OFFSET 50 ROWS',
'SELECT * FROM [products] LIMIT -1 OFFSET 50', 'SELECT * FROM [products] LIMIT -1 OFFSET 50',
]), ]),
$conn->translate('SELECT * FROM [products] %ofs', 50) $conn->translate('SELECT * FROM [products] %ofs', 50),
); );
} }
@@ -223,7 +223,7 @@ Assert::same(
'b8%d' => null, 'b8%d' => null,
'b9%t' => null, 'b9%t' => null,
'c1%t' => new DateTime('1212-09-26 16:51:34.0124'), 'c1%t' => new DateTime('1212-09-26 16:51:34.0124'),
]) ]),
); );
Assert::exception(function () use ($conn) { Assert::exception(function () use ($conn) {
@@ -247,13 +247,13 @@ if ($config['system'] === 'postgre') {
$conn->query('SET standard_conforming_strings = off'); $conn->query('SET standard_conforming_strings = off');
Assert::same( Assert::same(
"SELECT * FROM products WHERE (title LIKE 'C%' AND title LIKE '%r') OR title LIKE '%a\n\\\\%\\\\_\\\\\\\\''\"%'", "SELECT * FROM products WHERE (title LIKE 'C%' AND title LIKE '%r') OR title LIKE '%a\n\\\\%\\\\_\\\\\\\\''\"%'",
$conn->translate($args[0], $args[1], $args[2], $args[3]) $conn->translate($args[0], $args[1], $args[2], $args[3]),
); );
$conn->query('SET standard_conforming_strings = on'); $conn->query('SET standard_conforming_strings = on');
Assert::same( Assert::same(
"SELECT * FROM products WHERE (title LIKE 'C%' AND title LIKE '%r') OR title LIKE '%a\n\\%\\_\\\\''\"%'", "SELECT * FROM products WHERE (title LIKE 'C%' AND title LIKE '%r') OR title LIKE '%a\n\\%\\_\\\\''\"%'",
$conn->translate($args[0], $args[1], $args[2], $args[3]) $conn->translate($args[0], $args[1], $args[2], $args[3]),
); );
} elseif ($config['driver'] !== 'sqlite') { // sqlite2 } elseif ($config['driver'] !== 'sqlite') { // sqlite2
Assert::same( Assert::same(
@@ -263,7 +263,7 @@ if ($config['system'] === 'postgre') {
'sqlsrv' => "SELECT * FROM products WHERE (title LIKE 'C%' AND title LIKE '%r') OR title LIKE '%a\n[%][_]\\''\"%'", 'sqlsrv' => "SELECT * FROM products WHERE (title LIKE 'C%' AND title LIKE '%r') OR title LIKE '%a\n[%][_]\\''\"%'",
"SELECT * FROM products WHERE (title LIKE 'C%' AND title LIKE '%r') OR title LIKE '%a\\n\\%\\_\\\\\\\\\\'\"%'", "SELECT * FROM products WHERE (title LIKE 'C%' AND title LIKE '%r') OR title LIKE '%a\\n\\%\\_\\\\\\\\\\'\"%'",
]), ]),
$conn->translate($args[0], $args[1], $args[2], $args[3]) $conn->translate($args[0], $args[1], $args[2], $args[3]),
); );
} }
@@ -274,38 +274,43 @@ $e = Assert::exception(function () use ($conn) {
Assert::same('SELECT **Alone quote**', $e->getSql()); Assert::same('SELECT **Alone quote**', $e->getSql());
Assert::match( Assert::match(
reformat([ pattern: reformat([
'mysql' => "SELECT DISTINCT HIGH_PRIORITY SQL_BUFFER_RESULT 'mysql' => <<<'XX'
CONCAT(last_name, ', ', first_name) AS full_name SELECT DISTINCT HIGH_PRIORITY SQL_BUFFER_RESULT
GROUP BY `user` CONCAT(last_name, ', ', first_name) AS full_name
HAVING MAX(salary) > %i 123 GROUP BY `user`
INTO OUTFILE '/tmp/result\\'.txt' HAVING MAX(salary) > %i 123
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\\\"' INTO OUTFILE '/tmp/result\'.txt'
LINES TERMINATED BY '\\\\n' FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"'
", LINES TERMINATED BY '\\n'
'sqlsrv' => "SELECT DISTINCT HIGH_PRIORITY SQL_BUFFER_RESULT XX,
CONCAT(last_name, N', ', first_name) AS full_name 'sqlsrv' => <<<'XX'
GROUP BY [user] SELECT DISTINCT HIGH_PRIORITY SQL_BUFFER_RESULT
HAVING MAX(salary) > %i 123 CONCAT(last_name, N', ', first_name) AS full_name
INTO OUTFILE N'/tmp/result''.txt' GROUP BY [user]
FIELDS TERMINATED BY N',' OPTIONALLY ENCLOSED BY N'\"' HAVING MAX(salary) > %i 123
LINES TERMINATED BY N'\\n'", "SELECT DISTINCT HIGH_PRIORITY SQL_BUFFER_RESULT INTO OUTFILE N'/tmp/result''.txt'
CONCAT(last_name, ', ', first_name) AS full_name FIELDS TERMINATED BY N',' OPTIONALLY ENCLOSED BY N'"'
GROUP BY [user] LINES TERMINATED BY N'\n'
HAVING MAX(salary) > %i 123 XX,
INTO OUTFILE '/tmp/result''.txt' <<<'XX'
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"' SELECT DISTINCT HIGH_PRIORITY SQL_BUFFER_RESULT
LINES TERMINATED BY '\\n' CONCAT(last_name, ', ', first_name) AS full_name
", GROUP BY [user]
HAVING MAX(salary) > %i 123
INTO OUTFILE '/tmp/result''.txt'
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
LINES TERMINATED BY '\n'
XX,
]), ]),
$conn->translate('%sql', 'SELECT DISTINCT HIGH_PRIORITY SQL_BUFFER_RESULT actual: $conn->translate('%sql', 'SELECT DISTINCT HIGH_PRIORITY SQL_BUFFER_RESULT
CONCAT(last_name, ", ", first_name) AS full_name CONCAT(last_name, ", ", first_name) AS full_name
GROUP BY [user] GROUP BY [user]
HAVING MAX(salary) > %i', 123, " HAVING MAX(salary) > %i', 123, "
INTO OUTFILE '/tmp/result''.txt' INTO OUTFILE '/tmp/result''.txt'
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"' FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"'
LINES TERMINATED BY '\\n' LINES TERMINATED BY '\\n'
") "),
); );
@@ -330,114 +335,124 @@ $array5 = ['RAND()', '[col1] > [col2]'];
Assert::match( Assert::match(
reformat([ pattern: reformat([
'mysql' => "SELECT * 'mysql' => <<<'XX'
FROM `db`.`table` SELECT *
WHERE (`test`.`a` LIKE '1995-03-01' FROM `db`.`table`
OR `b1` IN ( 1, 2, 3 ) WHERE (`test`.`a` LIKE '1995-03-01'
OR `b2` IN ('1', '2', '3' ) OR `b1` IN ( 1, 2, 3 )
OR `b3` IN ( ) OR `b2` IN ('1', '2', '3' )
OR `b4` IN ( 'one', 'two', 'three' ) OR `b3` IN ( )
OR `b5` IN (`col1` AS `one`, `col2` AS `two`, `col3` AS `thr.ee` ) OR `b4` IN ( 'one', 'two', 'three' )
OR `b6` IN ('one', 'two', 'thr.ee') OR `b5` IN (`col1` AS `one`, `col2` AS `two`, `col3` AS `thr.ee` )
OR `b7` IN (NULL) OR `b6` IN ('one', 'two', 'thr.ee')
OR `b8` IN (RAND() `col1` > `col2` ) OR `b7` IN (NULL)
OR `b9` IN (RAND(), [col1] > [col2] ) OR `b8` IN (RAND() `col1` > `col2` )
OR `b10` IN ( ) OR `b9` IN (RAND(), [col1] > [col2] )
AND `c` = 'embedded \\' string' OR `b10` IN ( )
OR `d`=10 AND `c` = 'embedded \' string'
OR `e`=NULL OR `d`=10
OR `true`= 1 OR `e`=NULL
OR `false`= 0 OR `true`= 1
OR `str_null`=NULL OR `false`= 0
OR `str_not_null`='hello' OR `str_null`=NULL
LIMIT 10", OR `str_not_null`='hello'
'sqlsrv' => "SELECT * LIMIT 10
FROM [db].[table] XX,
WHERE ([test].[a] LIKE '1995-03-01' 'sqlsrv' => <<<'XX'
OR [b1] IN ( 1, 2, 3 ) SELECT *
OR [b2] IN (N'1', N'2', N'3' ) FROM [db].[table]
OR [b3] IN ( ) WHERE ([test].[a] LIKE '1995-03-01'
OR [b4] IN ( N'one', N'two', N'three' ) OR [b1] IN ( 1, 2, 3 )
OR [b5] IN ([col1] AS [one], [col2] AS [two], [col3] AS [thr.ee] ) OR [b2] IN (N'1', N'2', N'3' )
OR [b6] IN (N'one', N'two', N'thr.ee') OR [b3] IN ( )
OR [b7] IN (NULL) OR [b4] IN ( N'one', N'two', N'three' )
OR [b8] IN (RAND() [col1] > [col2] ) OR [b5] IN ([col1] AS [one], [col2] AS [two], [col3] AS [thr.ee] )
OR [b9] IN (RAND(), [col1] > [col2] ) OR [b6] IN (N'one', N'two', N'thr.ee')
OR [b10] IN ( ) OR [b7] IN (NULL)
AND [c] = N'embedded '' string' OR [b8] IN (RAND() [col1] > [col2] )
OR [d]=10 OR [b9] IN (RAND(), [col1] > [col2] )
OR [e]=NULL OR [b10] IN ( )
OR [true]= 1 AND [c] = N'embedded '' string'
OR [false]= 0 OR [d]=10
OR [str_null]=NULL OR [e]=NULL
OR [str_not_null]=N'hello' OR [true]= 1
LIMIT 10", OR [false]= 0
'postgre' => 'SELECT * OR [str_null]=NULL
FROM "db"."table" OR [str_not_null]=N'hello'
WHERE ("test"."a" LIKE \'1995-03-01\' LIMIT 10
OR "b1" IN ( 1, 2, 3 ) XX,
OR "b2" IN (\'1\', \'2\', \'3\' ) 'postgre' => <<<'XX'
OR "b3" IN ( ) SELECT *
OR "b4" IN ( \'one\', \'two\', \'three\' ) FROM "db"."table"
OR "b5" IN ("col1" AS "one", "col2" AS "two", "col3" AS "thr.ee" ) WHERE ("test"."a" LIKE '1995-03-01'
OR "b6" IN (\'one\', \'two\', \'thr.ee\') OR "b1" IN ( 1, 2, 3 )
OR "b7" IN (NULL) OR "b2" IN ('1', '2', '3' )
OR "b8" IN (RAND() "col1" > "col2" ) OR "b3" IN ( )
OR "b9" IN (RAND(), [col1] > [col2] ) OR "b4" IN ( 'one', 'two', 'three' )
OR "b10" IN ( ) OR "b5" IN ("col1" AS "one", "col2" AS "two", "col3" AS "thr.ee" )
AND "c" = \'embedded \'\' string\' OR "b6" IN ('one', 'two', 'thr.ee')
OR "d"=10 OR "b7" IN (NULL)
OR "e"=NULL OR "b8" IN (RAND() "col1" > "col2" )
OR "true"= TRUE OR "b9" IN (RAND(), [col1] > [col2] )
OR "false"= FALSE OR "b10" IN ( )
OR "str_null"=NULL AND "c" = 'embedded '' string'
OR "str_not_null"=\'hello\' OR "d"=10
LIMIT 10', OR "e"=NULL
'odbc' => "SELECT * OR "true"= TRUE
FROM [db].[table] OR "false"= FALSE
WHERE ([test].[a] LIKE #03/01/1995# OR "str_null"=NULL
OR [b1] IN ( 1, 2, 3 ) OR "str_not_null"='hello'
OR [b2] IN ('1', '2', '3' ) LIMIT 10
OR [b3] IN ( ) XX,
OR [b4] IN ( 'one', 'two', 'three' ) 'odbc' => <<<'XX'
OR [b5] IN ([col1] AS [one], [col2] AS [two], [col3] AS [thr.ee] ) SELECT *
OR [b6] IN ('one', 'two', 'thr.ee') FROM [db].[table]
OR [b7] IN (NULL) WHERE ([test].[a] LIKE #03/01/1995#
OR [b8] IN (RAND() [col1] > [col2] ) OR [b1] IN ( 1, 2, 3 )
OR [b9] IN (RAND(), [col1] > [col2] ) OR [b2] IN ('1', '2', '3' )
OR [b10] IN ( ) OR [b3] IN ( )
AND [c] = 'embedded '' string' OR [b4] IN ( 'one', 'two', 'three' )
OR [d]=10 OR [b5] IN ([col1] AS [one], [col2] AS [two], [col3] AS [thr.ee] )
OR [e]=NULL OR [b6] IN ('one', 'two', 'thr.ee')
OR [true]= 1 OR [b7] IN (NULL)
OR [false]= 0 OR [b8] IN (RAND() [col1] > [col2] )
OR [str_null]=NULL OR [b9] IN (RAND(), [col1] > [col2] )
OR [str_not_null]='hello' OR [b10] IN ( )
LIMIT 10", AND [c] = 'embedded '' string'
"SELECT * OR [d]=10
FROM [db].[table] OR [e]=NULL
WHERE ([test].[a] LIKE '1995-03-01' OR [true]= 1
OR [b1] IN ( 1, 2, 3 ) OR [false]= 0
OR [b2] IN ('1', '2', '3' ) OR [str_null]=NULL
OR [b3] IN ( ) OR [str_not_null]='hello'
OR [b4] IN ( 'one', 'two', 'three' ) LIMIT 10
OR [b5] IN ([col1] AS [one], [col2] AS [two], [col3] AS [thr.ee] ) XX,
OR [b6] IN ('one', 'two', 'thr.ee') <<<'XX'
OR [b7] IN (NULL) SELECT *
OR [b8] IN (RAND() [col1] > [col2] ) FROM [db].[table]
OR [b9] IN (RAND(), [col1] > [col2] ) WHERE ([test].[a] LIKE '1995-03-01'
OR [b10] IN ( ) OR [b1] IN ( 1, 2, 3 )
AND [c] = 'embedded '' string' OR [b2] IN ('1', '2', '3' )
OR [d]=10 OR [b3] IN ( )
OR [e]=NULL OR [b4] IN ( 'one', 'two', 'three' )
OR [true]= 1 OR [b5] IN ([col1] AS [one], [col2] AS [two], [col3] AS [thr.ee] )
OR [false]= 0 OR [b6] IN ('one', 'two', 'thr.ee')
OR [str_null]=NULL OR [b7] IN (NULL)
OR [str_not_null]='hello' OR [b8] IN (RAND() [col1] > [col2] )
LIMIT 10", OR [b9] IN (RAND(), [col1] > [col2] )
OR [b10] IN ( )
AND [c] = 'embedded '' string'
OR [d]=10
OR [e]=NULL
OR [true]= 1
OR [false]= 0
OR [str_null]=NULL
OR [str_not_null]='hello'
LIMIT 10
XX,
]), ]),
$conn->translate('SELECT * actual: $conn->translate('SELECT *
FROM [db.table] FROM [db.table]
WHERE ([test.a] LIKE %d', '1995-03-01', ' WHERE ([test.a] LIKE %d', '1995-03-01', '
OR [b1] IN (', $array1, ') OR [b1] IN (', $array1, ')
@@ -457,7 +472,7 @@ WHERE ([test.a] LIKE %d', '1995-03-01', '
OR [false]=', false, ' OR [false]=', false, '
OR [str_null]=%sn', '', ' OR [str_null]=%sn', '', '
OR [str_not_null]=%sn', 'hello', ' OR [str_not_null]=%sn', 'hello', '
LIMIT 10') LIMIT 10'),
); );
@@ -466,19 +481,19 @@ Assert::same(
'sqlsrv' => "TEST [cond] > 2 [cond2] = N'3' cond3 < RAND() 123", 'sqlsrv' => "TEST [cond] > 2 [cond2] = N'3' cond3 < RAND() 123",
"TEST [cond] > 2 [cond2] = '3' cond3 < RAND() 123", "TEST [cond] > 2 [cond2] = '3' cond3 < RAND() 123",
]), ]),
$conn->translate('TEST %ex', ['[cond] > 2', '[cond2] = "3"', 'cond3 < RAND()'], 123) $conn->translate('TEST %ex', ['[cond] > 2', '[cond2] = "3"', 'cond3 < RAND()'], 123),
); );
Assert::same( Assert::same(
reformat('TEST ([cond] > 2) OR ([cond2] > 3) OR ([cond3] = 10 + 1)'), reformat('TEST ([cond] > 2) OR ([cond2] > 3) OR ([cond3] = 10 + 1)'),
$conn->translate('TEST %or', ['`cond` > 2', ['[cond2] > %i', '3'], 'cond3%sql' => ['10 + 1']]) $conn->translate('TEST %or', ['`cond` > 2', ['[cond2] > %i', '3'], 'cond3%sql' => ['10 + 1']]),
); );
Assert::same( Assert::same(
reformat('TEST ([cond] = 2) OR ([cond3] = RAND())'), reformat('TEST ([cond] = 2) OR ([cond3] = RAND())'),
$conn->translate('TEST %or', ['cond' => 2, 'cond3%sql' => 'RAND()']) $conn->translate('TEST %or', ['cond' => 2, 'cond3%sql' => 'RAND()']),
); );
@@ -487,7 +502,7 @@ Assert::same(
'sqlsrv' => "TEST ([cond1] 3) OR ([cond2] RAND()) OR ([cond3] LIKE N'string')", 'sqlsrv' => "TEST ([cond1] 3) OR ([cond2] RAND()) OR ([cond3] LIKE N'string')",
"TEST ([cond1] 3) OR ([cond2] RAND()) OR ([cond3] LIKE 'string')", "TEST ([cond1] 3) OR ([cond2] RAND()) OR ([cond3] LIKE 'string')",
]), ]),
$conn->translate('TEST %or', ['cond1%ex' => 3, 'cond2%ex' => 'RAND()', 'cond3%ex' => ['LIKE %s', 'string']]) $conn->translate('TEST %or', ['cond1%ex' => 3, 'cond2%ex' => 'RAND()', 'cond3%ex' => ['LIKE %s', 'string']]),
); );
@@ -497,7 +512,7 @@ Assert::same(
'sqlsrv' => 'SELECT * FROM [test] WHERE [id] LIKE N\'%d%t\' OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY', 'sqlsrv' => 'SELECT * FROM [test] WHERE [id] LIKE N\'%d%t\' OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY',
'SELECT * FROM [test] WHERE [id] LIKE \'%d%t\' LIMIT 10', 'SELECT * FROM [test] WHERE [id] LIKE \'%d%t\' LIMIT 10',
]), ]),
$conn->translate("SELECT * FROM [test] WHERE %n LIKE '%d%t' %lmt", 'id', 10) $conn->translate("SELECT * FROM [test] WHERE %n LIKE '%d%t' %lmt", 'id', 10),
); );
@@ -506,13 +521,13 @@ $where = [
]; ];
Assert::same( Assert::same(
reformat('SELECT * FROM [tablename] WHERE ([tablename].[column] = 1)'), reformat('SELECT * FROM [tablename] WHERE ([tablename].[column] = 1)'),
$conn->translate('SELECT * FROM [tablename] WHERE %and', $where) $conn->translate('SELECT * FROM [tablename] WHERE %and', $where),
); );
Assert::same( Assert::same(
reformat('SELECT FROM ...'), reformat('SELECT FROM ...'),
$conn->translate('SELECT FROM ... %lmt', null) $conn->translate('SELECT FROM ... %lmt', null),
); );
Assert::same( Assert::same(
@@ -520,7 +535,7 @@ Assert::same(
'sqlsrv' => "SELECT N'%i'", 'sqlsrv' => "SELECT N'%i'",
"SELECT '%i'", "SELECT '%i'",
]), ]),
$conn->translate("SELECT '%i'") $conn->translate("SELECT '%i'"),
); );
Assert::same( Assert::same(
@@ -528,7 +543,7 @@ Assert::same(
'sqlsrv' => "SELECT N'%i'", 'sqlsrv' => "SELECT N'%i'",
"SELECT '%i'", "SELECT '%i'",
]), ]),
$conn->translate('SELECT "%i"') $conn->translate('SELECT "%i"'),
); );
@@ -543,7 +558,7 @@ Assert::same(
], [ ], [
'product_id' => 1, 'product_id' => 1,
'title' => new Dibi\Expression('SHA1(%s)', 'Test product'), 'title' => new Dibi\Expression('SHA1(%s)', 'Test product'),
]) ]),
); );
Assert::same( Assert::same(
@@ -554,7 +569,7 @@ Assert::same(
$conn->translate('UPDATE [products]', [ $conn->translate('UPDATE [products]', [
'product_id' => 1, 'product_id' => 1,
'title' => new Dibi\Expression('SHA1(%s)', 'Test product'), 'title' => new Dibi\Expression('SHA1(%s)', 'Test product'),
]) ]),
); );
Assert::same( Assert::same(
@@ -565,7 +580,7 @@ Assert::same(
$conn->translate('UPDATE [products]', [ $conn->translate('UPDATE [products]', [
'product_id' => 1, 'product_id' => 1,
'title' => new Dibi\Expression('SHA1(%s)', 'Test product'), 'title' => new Dibi\Expression('SHA1(%s)', 'Test product'),
]) ]),
); );
Assert::same( Assert::same(
@@ -576,7 +591,7 @@ Assert::same(
$conn->translate('SELECT * FROM [products] WHERE', [ $conn->translate('SELECT * FROM [products] WHERE', [
'product_id' => 1, 'product_id' => 1,
'title' => new Dibi\Expression('SHA1(%s)', 'Test product'), 'title' => new Dibi\Expression('SHA1(%s)', 'Test product'),
]) ]),
); );
@@ -588,7 +603,7 @@ Assert::same(
'top' => 2, 'top' => 2,
]), ]),
new Dibi\Expression('number < %i', 100), new Dibi\Expression('number < %i', 100),
]) ]),
); );
@@ -613,7 +628,7 @@ Assert::same(
'sqlsrv' => "INSERT INTO test ([id], [text], [num]) VALUES (1, N'ahoj', 1), (2, N'jak', -1), (3, N'se', 10), (4, SUM(5), 1)", 'sqlsrv' => "INSERT INTO test ([id], [text], [num]) VALUES (1, N'ahoj', 1), (2, N'jak', -1), (3, N'se', 10), (4, SUM(5), 1)",
"INSERT INTO test ([id], [text], [num]) VALUES (1, 'ahoj', 1), (2, 'jak', -1), (3, 'se', 10), (4, SUM(5), 1)", "INSERT INTO test ([id], [text], [num]) VALUES (1, 'ahoj', 1), (2, 'jak', -1), (3, 'se', 10), (4, SUM(5), 1)",
]), ]),
$conn->translate('INSERT INTO test %m', $array6) $conn->translate('INSERT INTO test %m', $array6),
); );
@@ -624,12 +639,12 @@ $by = [
Assert::same( Assert::same(
reformat('SELECT * FROM table ORDER BY funkce(nazev_pole) ASC, [jine_pole] DESC'), reformat('SELECT * FROM table ORDER BY funkce(nazev_pole) ASC, [jine_pole] DESC'),
$conn->translate('SELECT * FROM table ORDER BY %by', $by) $conn->translate('SELECT * FROM table ORDER BY %by', $by),
); );
Assert::same( Assert::same(
reformat('INSERT INTO [test].*'), reformat('INSERT INTO [test].*'),
$conn->translate('INSERT INTO [test.*]') $conn->translate('INSERT INTO [test.*]'),
); );
Assert::exception(function () use ($conn) { Assert::exception(function () use ($conn) {
@@ -643,23 +658,23 @@ Assert::exception(function () use ($conn) {
Assert::same( Assert::same(
reformat('SELECT * FROM table'), reformat('SELECT * FROM table'),
$conn->translate('SELECT', new Dibi\Literal('* FROM table')) $conn->translate('SELECT', new Dibi\Literal('* FROM table')),
); );
Assert::same( Assert::same(
reformat('SELECT * FROM table'), reformat('SELECT * FROM table'),
$conn->translate('SELECT %SQL', new Dibi\Literal('* FROM table')) $conn->translate('SELECT %SQL', new Dibi\Literal('* FROM table')),
); );
Assert::same( Assert::same(
reformat('SELECT * FROM table'), reformat('SELECT * FROM table'),
$conn->translate(new Dibi\Literal('SELECT * FROM table')) $conn->translate(new Dibi\Literal('SELECT * FROM table')),
); );
Assert::same( Assert::same(
reformat('SELECT [a].[b] AS [c.d]'), reformat('SELECT [a].[b] AS [c.d]'),
$conn->translate('SELECT %n AS %N', 'a.b', 'c.d') $conn->translate('SELECT %n AS %N', 'a.b', 'c.d'),
); );
@@ -677,5 +692,5 @@ Assert::same(
'spec2%f' => 1000.00, 'spec2%f' => 1000.00,
'spec3%i' => 10000, 'spec3%i' => 10000,
'spec4' => 10000, 'spec4' => 10000,
], 'WHERE [price]=%f', 123.5) ], 'WHERE [price]=%f', 123.5),
); );

View File

@@ -1,4 +1,5 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
// The Nette Tester command-line runner can be // The Nette Tester command-line runner can be
@@ -18,7 +19,7 @@ date_default_timezone_set('Europe/Prague');
// load connection // load connection
try { try {
$config = Tester\Environment::loadData(); $config = Tester\Environment::loadData();
} catch (Exception $e) { } catch (Throwable $e) {
$config = parse_ini_file(__DIR__ . '/../databases.ini', true); $config = parse_ini_file(__DIR__ . '/../databases.ini', true);
$config = reset($config); $config = reset($config);
} }
@@ -50,7 +51,7 @@ function test(string $title, Closure $function): void
/** Replaces [] with driver-specific quotes */ /** Replaces [] with driver-specific quotes */
function reformat($s) function reformat($s)
{ {
global $config; $config = $GLOBALS['config'];
if (is_array($s)) { if (is_array($s)) {
if (isset($s[$config['system']])) { if (isset($s[$config['system']])) {
return $s[$config['system']]; return $s[$config['system']];
@@ -71,7 +72,7 @@ function reformat($s)
function num($n) function num($n)
{ {
global $config; $config = $GLOBALS['config'];
if (substr($config['dsn'] ?? '', 0, 5) === 'odbc:') { if (substr($config['dsn'] ?? '', 0, 5) === 'odbc:') {
$n = is_float($n) ? "$n.0" : (string) $n; $n = is_float($n) ? "$n.0" : (string) $n;
} }

View File

@@ -1,8 +1,7 @@
DROP DATABASE IF EXISTS dibi_test; DROP TABLE IF EXISTS `orders`;
CREATE DATABASE dibi_test;
USE dibi_test;
DROP TABLE IF EXISTS `products`; DROP TABLE IF EXISTS `products`;
DROP TABLE IF EXISTS `customers`;
CREATE TABLE `products` ( CREATE TABLE `products` (
`product_id` int(11) NOT NULL AUTO_INCREMENT, `product_id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(100) NOT NULL, `title` varchar(100) NOT NULL,
@@ -15,7 +14,6 @@ INSERT INTO `products` (`product_id`, `title`) VALUES
(3, 'Computer'), (3, 'Computer'),
(2, 'Table'); (2, 'Table');
DROP TABLE IF EXISTS `customers`;
CREATE TABLE `customers` ( CREATE TABLE `customers` (
`customer_id` int(11) NOT NULL AUTO_INCREMENT, `customer_id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL, `name` varchar(100) NOT NULL,
@@ -30,7 +28,6 @@ INSERT INTO `customers` (`customer_id`, `name`) VALUES
(5, 'Kryten'), (5, 'Kryten'),
(6, 'Kristine Kochanski'); (6, 'Kristine Kochanski');
DROP TABLE IF EXISTS `orders`;
CREATE TABLE `orders` ( CREATE TABLE `orders` (
`order_id` int(11) NOT NULL AUTO_INCREMENT, `order_id` int(11) NOT NULL AUTO_INCREMENT,
`customer_id` int(11) NOT NULL, `customer_id` int(11) NOT NULL,

View File

@@ -19,12 +19,11 @@ try {
Tester\Environment::skip($e->getMessage()); Tester\Environment::skip($e->getMessage());
} }
if ($config['system'] !== 'sqlsrv') { $tableNames = $meta->getTableNames();
Assert::same(3, count($meta->getTables())); Assert::true(in_array('customers', $tableNames, true));
$names = $meta->getTableNames(); Assert::true(in_array('orders', $tableNames, true));
sort($names); Assert::true(in_array('products', $tableNames, true));
Assert::equal(['customers', 'orders', 'products'], $names);
}
Assert::false($meta->hasTable('xxxx')); Assert::false($meta->hasTable('xxxx'));

View File

@@ -12,7 +12,6 @@ require __DIR__ . '/bootstrap.php';
$conn = new Dibi\Connection($config); $conn = new Dibi\Connection($config);
$conn->query('USE dibi_test');
$conn->query('DROP TABLE IF EXISTS timetest'); $conn->query('DROP TABLE IF EXISTS timetest');
$conn->query('CREATE TABLE timetest (col TIME NOT NULL) ENGINE=InnoDB'); $conn->query('CREATE TABLE timetest (col TIME NOT NULL) ENGINE=InnoDB');
$conn->query('INSERT INTO timetest VALUES ("12:30:40")'); $conn->query('INSERT INTO timetest VALUES ("12:30:40")');