1
0
mirror of https://github.com/dg/dibi.git synced 2025-09-03 02:52:47 +02:00

Compare commits

...

39 Commits

Author SHA1 Message Date
David Grudl
97053089e0 Released version 5.0.2 2024-09-03 03:18:11 +02:00
Lukáš Kotržena
2c7b35c29d Result::normalize() Fixed normalization of "-." numbers 2024-09-03 03:16:52 +02:00
Marek Bartoš
c04d2197e3 Translator: fixed numeric column formatting 2024-09-03 03:10:05 +02:00
Matěj Koubík
d342d8d78f DibiExtension3: fixed schema 2024-09-03 03:10:05 +02:00
David Grudl
7d8c39f42a cs 2024-09-03 03:00:38 +02:00
David Grudl
29b58d64dd PdoDriver: applied #332 #287 2024-09-03 03:00:38 +02:00
David Grudl
0a32bb5bdf support for PHP 8.4 2024-09-03 03:00:38 +02:00
David Grudl
d707b4ba0e PascalCase constants 2024-09-03 03:00:38 +02:00
David Grudl
490cf143ba GitHub actions fixed
- https://learn.microsoft.com/en-us/answers/questions/1853144/error-failed-to-initialize-container-mcr-microsoft
- https://learn.microsoft.com/en-us/sql/tools/sqlcmd/sqlcmd-utility?view=sql-server-ver16&tabs=go%2Cwindows&pivots=cs1-bash
2024-09-03 03:00:38 +02:00
stanley89
12ffa0ffd1 Tracy\Panel: fixed type error 2024-09-02 23:53:15 +02:00
David Grudl
23f65ef837 Revert "SqliteDriver: disables exceptions (is enabled since PHP 8.3)"
This reverts commit bb1f7d4b93.
2024-09-02 23:53:15 +02:00
David Grudl
86a71dde28 Released version 5.0.1 2023-11-25 14:08:47 +01:00
David Grudl
bb1f7d4b93 SqliteDriver: disables exceptions (is enabled since PHP 8.3) 2023-11-25 14:08:47 +01:00
David Grudl
680026747e added DibiExtension3 2023-11-25 14:08:47 +01:00
Jan Rössler
7ca47508cb PostgreReflector: detect IDENTITY columns as autoincrement 2023-11-05 20:38:26 +01:00
Jan Rössler
beba7b3592 PostgreReflector: fix autoincrement column detection 2023-11-05 20:38:26 +01:00
David Grudl
6cc7ce8e44 used PhpStorm Language attribute 2023-11-05 20:38:25 +01:00
Petr Hubík
92b8e6077e fix: PDO::errorInfo() can return NULL as a code, but Exception does not accept NULL code 2023-09-29 15:55:05 +02:00
David Grudl
e45638eab4 cs 2023-09-29 15:55:05 +02:00
Marek Bartoš
8564217bc1 Fluent: execute() has conditional return type 2023-09-05 12:40:51 +02:00
David Grudl
82c45c3076 Released version 5.0.0 2023-08-09 16:38:02 +02:00
Miloslav Hůla
df45bd3553 added object translators (#420)
The Translator is now capable to translate objects into Expression via object translators registered by the Connection.
2023-08-09 16:35:02 +02:00
Miloslav Hůla
fe22e230ce tests: remove dependency on dibi_test database name 2023-08-09 16:35:02 +02:00
David Grudl
8257532630 used native PHP 8 functions 2023-08-09 16:35:02 +02:00
David Grudl
08dfc37492 added PHP 8 typehints 2023-08-09 16:33:28 +02:00
David Grudl
7acef0c34b added property typehints 2023-08-09 16:33:28 +02:00
David Grudl
b01d97ac86 removed Dibi\Strict 2023-08-09 16:33:28 +02:00
David Grudl
8915b0343c removed support for PHP 7 2023-08-09 16:33:28 +02:00
David Grudl
d1a3362321 coding style 2023-08-09 16:33:28 +02:00
David Grudl
b6ead80202 composer: updated dependencies 2023-08-09 16:15:31 +02:00
David Grudl
a640ac2a8f requires PHP 8.0 2023-08-09 16:15:31 +02:00
David Grudl
87e702d1fc opened 5.0-dev 2023-08-09 16:15:31 +02:00
David Grudl
cb0cf4ba2f Released version 4.2.8 2023-08-09 16:15:07 +02:00
David Grudl
8e7df8374b drivers: removed auto-free feature 2023-08-09 16:15:07 +02:00
Marek Bartoš
848ac76fed Fluent: improved phpDoc 2023-08-09 16:15:07 +02:00
David Grudl
a0f2ca2fca typo 2023-08-09 16:15:04 +02:00
David Grudl
cf14987b42 cs 2023-08-05 19:56:38 +02:00
David Grudl
01c7ab63e3 tested in PHP 8.3 2023-08-05 19:50:57 +02:00
David Grudl
520119740d updated .gitattributes 2022-12-21 02:06:10 +01:00
103 changed files with 1504 additions and 1659 deletions

4
.gitattributes vendored
View File

@@ -1,8 +1,8 @@
.gitattributes export-ignore .gitattributes export-ignore
.gitignore export-ignore .gitignore export-ignore
.github export-ignore .github export-ignore
.travis.yml export-ignore appveyor.yml export-ignore
ecs.php export-ignore ncs.* export-ignore
phpstan.neon export-ignore phpstan.neon export-ignore
tests/ export-ignore tests/ export-ignore

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@v3 - uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2 - uses: shivammathur/setup-php@v2
with: with:
php-version: 8.0 php-version: 8.0
@@ -21,7 +21,7 @@ jobs:
name: Nette Coding Standard name: Nette Coding Standard
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2 - uses: shivammathur/setup-php@v2
with: with:
php-version: 8.0 php-version: 8.0

View File

@@ -10,7 +10,7 @@ jobs:
name: PHPStan name: PHPStan
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- 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.10.0beta2, pdo_sqlsrv-5.10.0beta2 php-extensions: mbstring, intl, mysqli, pgsql, sqlsrv-5.12.0, pdo_sqlsrv-5.12.0
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', '8.1', '8.2'] php: ['8.0', '8.1', '8.2', '8.3', '8.4']
fail-fast: false fail-fast: false
@@ -43,7 +43,6 @@ jobs:
--health-retries=5 --health-retries=5
-e MYSQL_ROOT_PASSWORD=root -e MYSQL_ROOT_PASSWORD=root
-e MYSQL_DATABASE=dibi_test -e MYSQL_DATABASE=dibi_test
--entrypoint sh mysql:8 -c "exec docker-entrypoint.sh mysqld --default-authentication-plugin=mysql_native_password"
postgres96: postgres96:
image: postgres:9.6 image: postgres:9.6
@@ -83,13 +82,13 @@ jobs:
- 1433:1433 - 1433:1433
options: >- options: >-
--name=mssql --name=mssql
--health-cmd "/opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'SELECT 1'" --health-cmd "/opt/mssql-tools18/bin/sqlcmd -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'SELECT 1' -N -C"
--health-interval 10s --health-interval 10s
--health-timeout 5s --health-timeout 5s
--health-retries 5 --health-retries 5
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2 - uses: shivammathur/setup-php@v2
with: with:
php-version: ${{ matrix.php }} php-version: ${{ matrix.php }}
@@ -101,7 +100,7 @@ jobs:
run: cp ./tests/databases.github.ini ./tests/databases.ini run: cp ./tests/databases.github.ini ./tests/databases.ini
- name: Create MS SQL Database - name: Create MS SQL Database
run: docker exec -i mssql /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'CREATE DATABASE dibi_test' run: docker exec -i mssql /opt/mssql-tools18/bin/sqlcmd -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'CREATE DATABASE dibi_test' -N -C
- 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

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,13 +11,14 @@
} }
], ],
"require": { "require": {
"php": ">=7.2" "php": "8.0 - 8.4"
}, },
"require-dev": { "require-dev": {
"tracy/tracy": "~2.2", "tracy/tracy": "^2.9",
"nette/tester": "~2.0", "nette/tester": "^2.5",
"nette/di": "^3.0", "nette/di": "^3.1",
"phpstan/phpstan": "^0.12" "phpstan/phpstan": "^1.0",
"jetbrains/phpstorm-attributes": "^1.0"
}, },
"replace": { "replace": {
"dg/dibi": "*" "dg/dibi": "*"
@@ -25,13 +26,14 @@
"autoload": { "autoload": {
"classmap": ["src/"] "classmap": ["src/"]
}, },
"minimum-stability": "dev",
"scripts": { "scripts": {
"phpstan": "phpstan analyse", "phpstan": "phpstan analyse",
"tester": "tester tests -s" "tester": "tester tests -s"
}, },
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "4.2-dev" "dev-master": "5.0-dev"
} }
} }
} }

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

@@ -27,9 +27,9 @@ $dibi = new Dibi\Connection([
// using manual hints // using manual hints
$res = $dibi->query('SELECT * FROM [customers]'); $res = $dibi->query('SELECT * FROM [customers]');
$res->setType('customer_id', Type::INTEGER) $res->setType('customer_id', Type::Integer)
->setType('added', Type::DATETIME) ->setType('added', Type::DateTime)
->setFormat(Type::DATETIME, 'Y-m-d H:i:s'); ->setFormat(Type::DateTime, 'Y-m-d H:i:s');
Tracy\Dumper::dump($res->fetch()); Tracy\Dumper::dump($res->fetch());

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.2. The Dibi 5.0 requires PHP version 8.0 and supports PHP up to 8.4.
Usage Usage
@@ -341,7 +341,7 @@ $database->query('INSERT INTO users', [
There are three methods for dealing with transactions: There are three methods for dealing with transactions:
```php ```php
$database->beginTransaction(); $database->begin();
$database->commit(); $database->commit();
@@ -608,7 +608,7 @@ $database->query("UPDATE [:blog:items] SET [text]='Hello World'");
Dibi automatically detects the types of query columns and converts fields them to native PHP types. We can also specify the type manually. You can find the possible types in the `Dibi\Type` class. Dibi automatically detects the types of query columns and converts fields them to native PHP types. We can also specify the type manually. You can find the possible types in the `Dibi\Type` class.
```php ```php
$result->setType('id', Dibi\Type::INTEGER); // id will be integer $result->setType('id', Dibi\Type::Integer); // id will be integer
$row = $result->fetch(); $row = $result->fetch();
is_int($row->id) // true is_int($row->id) // true
@@ -639,7 +639,7 @@ In the configuration file, we will register the DI extensions and add the `dibi`
```neon ```neon
extensions: extensions:
dibi: Dibi\Bridges\Nette\DibiExtension22 dibi: Dibi\Bridges\Nette\DibiExtension3
dibi: dibi:
host: localhost host: localhost

View File

@@ -19,11 +19,8 @@ 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;
/** @var bool|null */
private $cliMode;
public function __construct(?bool $debugMode = null, ?bool $cliMode = null) public function __construct(?bool $debugMode = null, ?bool $cliMode = null)
@@ -66,7 +63,7 @@ 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']],
); );
} }

View File

@@ -0,0 +1,96 @@
<?php
/**
* This file is part of the Dibi, smart database abstraction layer (https://dibiphp.com)
* Copyright (c) 2005 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Dibi\Bridges\Nette;
use Dibi;
use Nette;
use Nette\Schema\Expect;
use Tracy;
/**
* Dibi extension for Nette Framework 3. Creates 'connection' & 'panel' services.
*/
class DibiExtension3 extends Nette\DI\CompilerExtension
{
private ?bool $debugMode;
private ?bool $cliMode;
public function __construct(?bool $debugMode = null, ?bool $cliMode = null)
{
$this->debugMode = $debugMode;
$this->cliMode = $cliMode;
}
public function getConfigSchema(): Nette\Schema\Schema
{
return Expect::structure([
'autowired' => Expect::bool(true),
'flags' => Expect::anyOf(Expect::arrayOf('string'), Expect::type('dynamic')),
'profiler' => Expect::bool(),
'explain' => Expect::bool(true),
'filter' => Expect::bool(true),
'driver' => Expect::string()->dynamic(),
'name' => Expect::string()->dynamic(),
'lazy' => Expect::bool(false)->dynamic(),
'onConnect' => Expect::array()->dynamic(),
'substitutes' => Expect::arrayOf('string')->dynamic(),
'result' => Expect::structure([
'normalize' => Expect::bool(true),
'formatDateTime' => Expect::string(),
'formatTimeInterval' => Expect::string(),
'formatJson' => Expect::string(),
])->castTo('array'),
])->otherItems(Expect::type('mixed'))
->castTo('array');
}
public function loadConfiguration()
{
$container = $this->getContainerBuilder();
$config = $this->getConfig();
$this->debugMode ??= $container->parameters['debugMode'];
$this->cliMode ??= $container->parameters['consoleMode'];
$useProfiler = $config['profiler'] ?? (class_exists(Tracy\Debugger::class) && $this->debugMode && !$this->cliMode);
unset($config['profiler']);
if (is_array($config['flags'])) {
$flags = 0;
foreach ((array) $config['flags'] as $flag) {
$flags |= constant($flag);
}
$config['flags'] = $flags;
}
$connection = $container->addDefinition($this->prefix('connection'))
->setCreator(Dibi\Connection::class, [$config])
->setAutowired($config['autowired']);
if (class_exists(Tracy\Debugger::class)) {
$connection->addSetup(
[new Nette\DI\Definitions\Statement('Tracy\Debugger::getBlueScreen'), 'addPanel'],
[[Dibi\Bridges\Tracy\Panel::class, 'renderException']],
);
}
if ($useProfiler) {
$panel = $container->addDefinition($this->prefix('panel'))
->setCreator(Dibi\Bridges\Tracy\Panel::class, [
$config['explain'],
$config['filter'] ? Dibi\Event::QUERY : Dibi\Event::ALL,
]);
$connection->addSetup([$panel, 'register'], [$connection]);
}
}
}

View File

@@ -1,8 +1,8 @@
# This will create service named 'dibi.connection'. # This will create service named 'dibi.connection'.
# Requires Nette Framework 2.2 or later # Requires Nette Framework 3 or later
extensions: extensions:
dibi: Dibi\Bridges\Nette\DibiExtension22 dibi: Dibi\Bridges\Nette\DibiExtension3
dibi: dibi:
host: localhost host: localhost

View File

@@ -20,22 +20,13 @@ use Tracy;
*/ */
class Panel implements Tracy\IBarPanel class Panel implements Tracy\IBarPanel
{ {
use Dibi\Strict; public static int $maxLength = 1000;
public bool|string $explain;
/** @var int maximum SQL length */ public int $filter;
public static $maxLength = 1000; private array $events = [];
/** @var bool|string explain queries? */
public $explain;
/** @var int */
public $filter;
/** @var array */
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;
@@ -71,7 +62,7 @@ class Panel implements Tracy\IBarPanel
if ($e instanceof Dibi\Exception && $e->getSql()) { if ($e instanceof Dibi\Exception && $e->getSql()) {
return [ return [
'tab' => 'SQL', 'tab' => 'SQL',
'panel' => Helpers::dump($e->getSql(), true), 'panel' => Helpers::dump($e->getSql(), return: true),
]; ];
} }
@@ -127,7 +118,7 @@ class Panel implements Tracy\IBarPanel
? $this->explain ? $this->explain
: ($connection->getConfig('driver') === 'oracle' ? 'EXPLAIN PLAN FOR' : 'EXPLAIN'); : ($connection->getConfig('driver') === 'oracle' ? 'EXPLAIN PLAN FOR' : 'EXPLAIN');
try { try {
$explain = @Helpers::dump($connection->nativeQuery("$cmd $event->sql"), true); $explain = @Helpers::dump($connection->nativeQuery("$cmd $event->sql"), return: true);
} catch (Dibi\Exception $e) { } catch (Dibi\Exception $e) {
} }
@@ -141,7 +132,7 @@ class Panel implements Tracy\IBarPanel
$s .= "<br /><a href='#tracy-debug-DibiProfiler-row-$counter' class='tracy-toggle tracy-collapsed' rel='#tracy-debug-DibiProfiler-row-$counter'>explain</a>"; $s .= "<br /><a href='#tracy-debug-DibiProfiler-row-$counter' class='tracy-toggle tracy-collapsed' rel='#tracy-debug-DibiProfiler-row-$counter'>explain</a>";
} }
$s .= '</td><td class="tracy-DibiProfiler-sql">' . Helpers::dump(strlen($event->sql) > self::$maxLength ? substr($event->sql, 0, self::$maxLength) . '...' : $event->sql, true); $s .= '</td><td class="tracy-DibiProfiler-sql">' . Helpers::dump(strlen($event->sql) > self::$maxLength ? substr($event->sql, 0, self::$maxLength) . '...' : $event->sql, return: true);
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>";
} }
@@ -160,8 +151,8 @@ class Panel implements Tracy\IBarPanel
#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:' . "\u{a0}" . count($this->events) <h1>Queries:' . "\u{a0}" . count($this->events)
. ($totalTime === null ? '' : ", time:\u{a0}" . number_format($totalTime * 1000, 1, '.', "\u{202f}") . "\u{202f}ms") . ', ' . ($totalTime === null ? '' : ", time:\u{a0}" . number_format($totalTime * 1000, 1, '.', "\u{202f}") . "\u{202f}ms")
. htmlspecialchars($this->getConnectionName($singleConnection)) . '</h1> . ($singleConnection === null ? '' : ', ' . 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">
<tr><th>Time&nbsp;ms</th><th>SQL Statement</th><th>Rows</th>' . (!$singleConnection ? '<th>Connection</th>' : '') . '</tr> <tr><th>Time&nbsp;ms</th><th>SQL Statement</th><th>Rows</th>' . (!$singleConnection ? '<th>Connection</th>' : '') . '</tr>
@@ -174,7 +165,7 @@ 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 get_debug_type($driver)
. ($connection->getConfig('name') ? '/' . $connection->getConfig('name') : '') . ($connection->getConfig('name') ? '/' . $connection->getConfig('name') : '')
. ($connection->getConfig('host') ? "\u{202f}@\u{202f}" . $connection->getConfig('host') : ''); . ($connection->getConfig('host') ? "\u{202f}@\u{202f}" . $connection->getConfig('host') : '');
} }

View File

@@ -9,6 +9,7 @@ declare(strict_types=1);
namespace Dibi; namespace Dibi;
use JetBrains\PhpStorm\Language;
use Traversable; use Traversable;
@@ -20,27 +21,20 @@ use Traversable;
*/ */
class Connection implements IConnection class Connection implements IConnection
{ {
use Strict; /** function (Event $event); Occurs after query is executed */
public ?array $onEvent = [];
/** @var array of function (Event $event); Occurs after query is executed */ private array $config;
public $onEvent = [];
/** @var array Current connection configuration */
private $config;
/** @var string[] resultset formats */ /** @var string[] resultset formats */
private $formats; private array $formats;
private ?Driver $driver = null;
private ?Translator $translator = null;
/** @var Driver|null */ /** @var array<string, callable(object): Expression | null> */
private $driver; private array $translators = [];
private bool $sortTranslators = false;
/** @var Translator|null */ private HashMap $substitutes;
private $translator; private int $transactionDepth = 0;
/** @var HashMap Substitutes for identifiers */
private $substitutes;
private $transactionDepth = 0;
/** /**
@@ -75,15 +69,15 @@ class Connection implements IConnection
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;
$this->formats = [ $this->formats = [
Type::DATE => $this->config['result']['formatDate'], Type::Date => $this->config['result']['formatDate'],
Type::DATETIME => $this->config['result']['formatDateTime'], Type::DateTime => $this->config['result']['formatDateTime'],
Type::JSON => $this->config['result']['formatJson'] ?? 'array', Type::JSON => $this->config['result']['formatJson'] ?? 'array',
Type::TIME_INTERVAL => $this->config['result']['formatTimeInterval'] ?? null, Type::TimeInterval => $this->config['result']['formatTimeInterval'] ?? null,
]; ];
// profiler // profiler
@@ -93,7 +87,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;
@@ -190,9 +184,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
@@ -215,10 +208,9 @@ class Connection implements IConnection
/** /**
* 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(#[Language('GenericSQL')] mixed ...$args): Result
{ {
return $this->nativeQuery($this->translate(...$args)); return $this->nativeQuery($this->translate(...$args));
} }
@@ -226,10 +218,9 @@ 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(#[Language('GenericSQL')] mixed ...$args): string
{ {
if (!$this->driver) { if (!$this->driver) {
$this->connect(); $this->connect();
@@ -241,9 +232,8 @@ class Connection implements IConnection
/** /**
* Generates and prints SQL query. * Generates and prints SQL query.
* @param mixed ...$args
*/ */
final public function test(...$args): bool final public function test(#[Language('GenericSQL')] mixed ...$args): bool
{ {
try { try {
Helpers::dump($this->translate(...$args)); Helpers::dump($this->translate(...$args));
@@ -253,7 +243,7 @@ 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;
@@ -263,10 +253,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(#[Language('GenericSQL')] mixed ...$args): DataSource
{ {
return new DataSource($this->translate(...$args), $this); return new DataSource($this->translate(...$args), $this);
} }
@@ -276,7 +265,7 @@ class Connection implements IConnection
* Executes the SQL query. * Executes the SQL query.
* @throws Exception * @throws Exception
*/ */
final public function nativeQuery(string $sql): Result final public function nativeQuery(#[Language('SQL')] string $sql): Result
{ {
if (!$this->driver) { if (!$this->driver) {
$this->connect(); $this->connect();
@@ -429,10 +418,7 @@ class Connection implements IConnection
} }
/** 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();
@@ -527,9 +513,77 @@ 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 setObjectTranslator(callable $translator): void
{
if (!$translator instanceof \Closure) {
$translator = \Closure::fromCallable($translator);
}
$param = (new \ReflectionFunction($translator))->getParameters()[0] ?? null;
$type = $param?->getType();
$types = match (true) {
$type instanceof \ReflectionNamedType => [$type],
$type instanceof \ReflectionUnionType => $type->getTypes(),
default => throw new Exception('Object translator must have exactly one parameter with class typehint.'),
};
foreach ($types as $type) {
if ($type->isBuiltin() || $type->allowsNull()) {
throw new Exception("Object translator must have exactly one parameter with non-nullable class typehint, got '$type'.");
}
$this->translators[$type->getName()] = $translator;
}
$this->sortTranslators = true;
}
public function translateObject(object $object): ?Expression
{
if ($this->sortTranslators) {
$this->translators = array_filter($this->translators);
uksort($this->translators, fn($a, $b) => is_subclass_of($a, $b) ? -1 : 1);
$this->sortTranslators = false;
}
if (!array_key_exists($object::class, $this->translators)) {
$translator = null;
foreach ($this->translators as $class => $t) {
if ($object instanceof $class) {
$translator = $t;
break;
}
}
$this->translators[$object::class] = $translator;
}
$translator = $this->translators[$object::class];
if ($translator === null) {
return null;
}
$result = $translator($object);
if (!$result instanceof Expression) {
throw new Exception(sprintf(
"Object translator for class '%s' returned '%s' but %s expected.",
$object::class,
get_debug_type($result),
Expression::class,
));
}
return $result;
} }
@@ -538,10 +592,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(#[Language('GenericSQL')] mixed ...$args): ?Row
{ {
return $this->query($args)->fetch(); return $this->query($args)->fetch();
} }
@@ -549,11 +602,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(#[Language('GenericSQL')] mixed ...$args): array
{ {
return $this->query($args)->fetchAll(); return $this->query($args)->fetchAll();
} }
@@ -561,11 +613,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(#[Language('GenericSQL')] mixed ...$args): mixed
{ {
return $this->query($args)->fetchSingle(); return $this->query($args)->fetchSingle();
} }
@@ -573,10 +623,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(#[Language('GenericSQL')] mixed ...$args): array
{ {
return $this->query($args)->fetchPairs(); return $this->query($args)->fetchPairs();
} }

View File

@@ -15,37 +15,16 @@ namespace Dibi;
*/ */
class DataSource implements IDataSource class DataSource implements IDataSource
{ {
use Strict; private Connection $connection;
private string $sql;
/** @var Connection */ private ?Result $result = null;
private $connection; private ?int $count = null;
private ?int $totalCount = null;
/** @var string */ private array $cols = [];
private $sql; private array $sorting = [];
private array $conds = [];
/** @var Result|null */ private ?int $offset = null;
private $result; private ?int $limit = null;
/** @var int|null */
private $count;
/** @var int|null */
private $totalCount;
/** @var array */
private $cols = [];
/** @var array */
private $sorting = [];
/** @var array */
private $conds = [];
/** @var int|null */
private $offset;
/** @var int|null */
private $limit;
/** /**
@@ -65,7 +44,7 @@ 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;
@@ -81,7 +60,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
@@ -95,7 +74,7 @@ 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;
@@ -111,7 +90,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;
@@ -161,7 +140,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();
} }
@@ -229,24 +208,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 '';
}
} }
@@ -261,7 +235,7 @@ 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();
} }
@@ -277,7 +251,7 @@ 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());
} }

View File

@@ -15,12 +15,7 @@ namespace Dibi;
*/ */
class DateTime extends \DateTimeImmutable class DateTime extends \DateTimeImmutable
{ {
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

@@ -17,8 +17,6 @@ use Dibi;
*/ */
class DummyDriver implements Dibi\Driver, Dibi\ResultDriver, Dibi\Reflector class DummyDriver implements Dibi\Driver, Dibi\ResultDriver, Dibi\Reflector
{ {
use Dibi\Strict;
public function disconnect(): void public function disconnect(): void
{ {
} }
@@ -57,7 +55,7 @@ class DummyDriver implements Dibi\Driver, Dibi\ResultDriver, Dibi\Reflector
} }
public function getResource() public function getResource(): mixed
{ {
return null; return null;
} }
@@ -171,8 +169,9 @@ class DummyDriver implements Dibi\Driver, Dibi\ResultDriver, Dibi\Reflector
} }
public function getResultResource() public function getResultResource(): mixed
{ {
return null;
} }

View File

@@ -26,18 +26,17 @@ use Dibi\Helpers;
*/ */
class FirebirdDriver implements Dibi\Driver class FirebirdDriver implements Dibi\Driver
{ {
use Dibi\Strict; public const ErrorExceptionThrown = -836;
public const ERROR_EXCEPTION_THROWN = -836; /** @deprecated use FirebirdDriver::ErrorExceptionThrown */
public const ERROR_EXCEPTION_THROWN = self::ErrorExceptionThrown;
/** @var resource */ /** @var resource */
private $connection; private $connection;
/** @var resource|null */ /** @var ?resource */
private $transaction; private $transaction;
private bool $inTransaction = false;
/** @var bool */
private $inTransaction = false;
/** @throws Dibi\NotSupportedException */ /** @throws Dibi\NotSupportedException */
@@ -191,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

@@ -17,10 +17,7 @@ use Dibi;
*/ */
class FirebirdReflector implements Dibi\Reflector class FirebirdReflector implements Dibi\Reflector
{ {
use Dibi\Strict; private Dibi\Driver $driver;
/** @var Dibi\Driver */
private $driver;
public function __construct(Dibi\Driver $driver) public function __construct(Dibi\Driver $driver)
@@ -242,7 +239,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)) {

View File

@@ -18,14 +18,9 @@ use Dibi\Helpers;
*/ */
class FirebirdResult implements Dibi\ResultDriver class FirebirdResult implements Dibi\ResultDriver
{ {
use Dibi\Strict;
/** @var resource */ /** @var resource */
private $resultSet; private $resultSet;
/** @var bool */
private $autoFree = true;
/** /**
* @param resource $resultSet * @param resource $resultSet
@@ -36,17 +31,6 @@ class FirebirdResult implements Dibi\ResultDriver
} }
/**
* Automatically frees the resources allocated for this result set.
*/
public function __destruct()
{
if ($this->autoFree && $this->getResultResource()) {
$this->free();
}
}
/** /**
* Returns the number of rows in a result set. * Returns the number of rows in a result set.
*/ */
@@ -103,9 +87,8 @@ 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;
return is_resource($this->resultSet) ? $this->resultSet : null; return is_resource($this->resultSet) ? $this->resultSet : null;
} }

View File

@@ -18,10 +18,7 @@ use Dibi;
*/ */
class MySqlReflector implements Dibi\Reflector class MySqlReflector implements Dibi\Reflector
{ {
use Dibi\Strict; private Dibi\Driver $driver;
/** @var Dibi\Driver */
private $driver;
public function __construct(Dibi\Driver $driver) public function __construct(Dibi\Driver $driver)

View File

@@ -32,19 +32,21 @@ use Dibi;
*/ */
class MySqliDriver implements Dibi\Driver class MySqliDriver implements Dibi\Driver
{ {
use Dibi\Strict; public const ErrorAccessDenied = 1045;
public const ErrorDuplicateEntry = 1062;
public const ErrorDataTruncated = 1265;
public const ERROR_ACCESS_DENIED = 1045; /** @deprecated use MySqliDriver::ErrorAccessDenied */
public const ERROR_ACCESS_DENIED = self::ErrorAccessDenied;
public const ERROR_DUPLICATE_ENTRY = 1062; /** @deprecated use MySqliDriver::ErrorDuplicateEntry */
public const ERROR_DUPLICATE_ENTRY = self::ErrorDuplicateEntry;
public const ERROR_DATA_TRUNCATED = 1265; /** @deprecated use MySqliDriver::ErrorDataTruncated */
public const ERROR_DATA_TRUNCATED = self::ErrorDataTruncated;
/** @var \mysqli */ private \mysqli $connection;
private $connection; private bool $buffered = false;
/** @var bool Is buffered (seekable and countable)? */
private $buffered;
/** @throws Dibi\NotSupportedException */ /** @throws Dibi\NotSupportedException */
@@ -96,7 +98,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) {
@@ -159,10 +161,7 @@ class MySqliDriver implements Dibi\Driver
} }
/** 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);

View File

@@ -17,16 +17,8 @@ use Dibi;
*/ */
class MySqliResult implements Dibi\ResultDriver class MySqliResult implements Dibi\ResultDriver
{ {
use Dibi\Strict; private \mysqli_result $resultSet;
private bool $buffered;
/** @var \mysqli_result */
private $resultSet;
/** @var bool */
private $autoFree = true;
/** @var bool Is buffered (seekable and countable)? */
private $buffered;
public function __construct(\mysqli_result $resultSet, bool $buffered) public function __construct(\mysqli_result $resultSet, bool $buffered)
@@ -36,17 +28,6 @@ class MySqliResult implements Dibi\ResultDriver
} }
/**
* Automatically frees the resources allocated for this result set.
*/
public function __destruct()
{
if ($this->autoFree && $this->getResultResource()) {
@$this->free();
}
}
/** /**
* Returns the number of rows in a result set. * Returns the number of rows in a result set.
*/ */
@@ -122,7 +103,7 @@ class MySqliResult implements Dibi\ResultDriver
'table' => $row['orgtable'], 'table' => $row['orgtable'],
'fullname' => $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name'], 'fullname' => $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name'],
'nativetype' => $types[$row['type']] ?? $row['type'], 'nativetype' => $types[$row['type']] ?? $row['type'],
'type' => $row['type'] === MYSQLI_TYPE_TIME ? Dibi\Type::TIME_INTERVAL : null, 'type' => $row['type'] === MYSQLI_TYPE_TIME ? Dibi\Type::TimeInterval : null,
'vendor' => $row, 'vendor' => $row,
]; ];
} }
@@ -136,7 +117,6 @@ class MySqliResult implements Dibi\ResultDriver
*/ */
public function getResultResource(): \mysqli_result public function getResultResource(): \mysqli_result
{ {
$this->autoFree = false;
return $this->resultSet; return $this->resultSet;
} }

View File

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

View File

@@ -25,16 +25,10 @@ use Dibi;
*/ */
class OdbcDriver implements Dibi\Driver class OdbcDriver implements Dibi\Driver
{ {
use Dibi\Strict;
/** @var resource */ /** @var resource */
private $connection; private $connection;
private ?int $affectedRows;
/** @var int|null Affected rows */ private bool $microseconds = true;
private $affectedRows;
/** @var bool */
private $microseconds = true;
/** @throws Dibi\NotSupportedException */ /** @throws Dibi\NotSupportedException */
@@ -125,7 +119,7 @@ class OdbcDriver implements Dibi\Driver
*/ */
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)) {
throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection)); throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection));
} }
} }
@@ -141,7 +135,7 @@ class OdbcDriver implements Dibi\Driver
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);
} }
@@ -155,7 +149,7 @@ class OdbcDriver implements Dibi\Driver
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);
} }
@@ -172,7 +166,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

@@ -17,10 +17,7 @@ use Dibi;
*/ */
class OdbcReflector implements Dibi\Reflector class OdbcReflector implements Dibi\Reflector
{ {
use Dibi\Strict; private Dibi\Driver $driver;
/** @var Dibi\Driver */
private $driver;
public function __construct(Dibi\Driver $driver) public function __construct(Dibi\Driver $driver)

View File

@@ -17,16 +17,9 @@ use Dibi;
*/ */
class OdbcResult implements Dibi\ResultDriver class OdbcResult implements Dibi\ResultDriver
{ {
use Dibi\Strict;
/** @var resource */ /** @var resource */
private $resultSet; private $resultSet;
private int $row = 0;
/** @var bool */
private $autoFree = true;
/** @var int Cursor */
private $row = 0;
/** /**
@@ -38,17 +31,6 @@ class OdbcResult implements Dibi\ResultDriver
} }
/**
* Automatically frees the resources allocated for this result set.
*/
public function __destruct()
{
if ($this->autoFree && $this->getResultResource()) {
$this->free();
}
}
/** /**
* Returns the number of rows in a result set. * Returns the number of rows in a result set.
*/ */
@@ -127,9 +109,8 @@ 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;
return is_resource($this->resultSet) ? $this->resultSet : null; return is_resource($this->resultSet) ? $this->resultSet : null;
} }

View File

@@ -27,19 +27,11 @@ use Dibi;
*/ */
class OracleDriver implements Dibi\Driver class OracleDriver implements Dibi\Driver
{ {
use Dibi\Strict;
/** @var resource */ /** @var resource */
private $connection; private $connection;
private bool $autocommit = true;
/** @var bool */ private bool $nativeDate;
private $autocommit = true; private ?int $affectedRows;
/** @var bool use native datetime format */
private $nativeDate;
/** @var int|null Number of affected rows */
private $affectedRows;
/** @throws Dibi\NotSupportedException */ /** @throws Dibi\NotSupportedException */
@@ -188,7 +180,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

@@ -17,10 +17,7 @@ use Dibi;
*/ */
class OracleReflector implements Dibi\Reflector class OracleReflector implements Dibi\Reflector
{ {
use Dibi\Strict; private Dibi\Driver $driver;
/** @var Dibi\Driver */
private $driver;
public function __construct(Dibi\Driver $driver) public function __construct(Dibi\Driver $driver)

View File

@@ -17,14 +17,9 @@ use Dibi;
*/ */
class OracleResult implements Dibi\ResultDriver class OracleResult implements Dibi\ResultDriver
{ {
use Dibi\Strict;
/** @var resource */ /** @var resource */
private $resultSet; private $resultSet;
/** @var bool */
private $autoFree = true;
/** /**
* @param resource $resultSet * @param resource $resultSet
@@ -35,17 +30,6 @@ class OracleResult implements Dibi\ResultDriver
} }
/**
* Automatically frees the resources allocated for this result set.
*/
public function __destruct()
{
if ($this->autoFree && $this->getResultResource()) {
$this->free();
}
}
/** /**
* Returns the number of rows in a result set. * Returns the number of rows in a result set.
*/ */
@@ -96,7 +80,7 @@ class OracleResult implements Dibi\ResultDriver
'name' => oci_field_name($this->resultSet, $i), 'name' => oci_field_name($this->resultSet, $i),
'table' => null, 'table' => null,
'fullname' => oci_field_name($this->resultSet, $i), 'fullname' => oci_field_name($this->resultSet, $i),
'type' => $type === 'LONG' ? Dibi\Type::TEXT : null, 'type' => $type === 'LONG' ? Dibi\Type::Text : null,
'nativetype' => $type === 'NUMBER' && oci_field_scale($this->resultSet, $i) === 0 ? 'INTEGER' : $type, 'nativetype' => $type === 'NUMBER' && oci_field_scale($this->resultSet, $i) === 0 ? 'INTEGER' : $type,
]; ];
} }
@@ -109,9 +93,8 @@ 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;
return is_resource($this->resultSet) ? $this->resultSet : null; return is_resource($this->resultSet) ? $this->resultSet : null;
} }

View File

@@ -27,19 +27,10 @@ use PDO;
*/ */
class PdoDriver implements Dibi\Driver class PdoDriver implements Dibi\Driver
{ {
use Dibi\Strict; private ?PDO $connection;
private ?int $affectedRows;
/** @var PDO|null Connection resource */ private string $driverName;
private $connection; private string $serverVersion = '';
/** @var int|null Affected rows */
private $affectedRows;
/** @var string */
private $driverName;
/** @var string */
private $serverVersion = '';
/** @throws Dibi\NotSupportedException */ /** @throws Dibi\NotSupportedException */
@@ -102,23 +93,15 @@ class PdoDriver implements Dibi\Driver
$this->affectedRows = null; $this->affectedRows = null;
[$sqlState, $code, $message] = $this->connection->errorInfo(); [$sqlState, $code, $message] = $this->connection->errorInfo();
$code ??= 0;
$message = "SQLSTATE[$sqlState]: $message"; $message = "SQLSTATE[$sqlState]: $message";
switch ($this->driverName) { throw match ($this->driverName) {
case 'mysql': 'mysql' => MySqliDriver::createException($message, $code, $sql),
throw MySqliDriver::createException($message, $code, $sql); 'oci' => OracleDriver::createException($message, $code, $sql),
'pgsql' => PostgreDriver::createException($message, $sqlState, $sql),
case 'oci': 'sqlite' => SqliteDriver::createException($message, $code, $sql),
throw OracleDriver::createException($message, $code, $sql); default => new Dibi\DriverException($message, $code, $sql),
};
case 'pgsql':
throw PostgreDriver::createException($message, $sqlState, $sql);
case 'sqlite':
throw SqliteDriver::createException($message, $code, $sql);
default:
throw new Dibi\DriverException($message, $code, $sql);
}
} }
@@ -148,7 +131,7 @@ class PdoDriver implements Dibi\Driver
{ {
if (!$this->connection->beginTransaction()) { if (!$this->connection->beginTransaction()) {
$err = $this->connection->errorInfo(); $err = $this->connection->errorInfo();
throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1]); throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1] ?? 0);
} }
} }
@@ -161,7 +144,7 @@ class PdoDriver implements Dibi\Driver
{ {
if (!$this->connection->commit()) { if (!$this->connection->commit()) {
$err = $this->connection->errorInfo(); $err = $this->connection->errorInfo();
throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1]); throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1] ?? 0);
} }
} }
@@ -174,7 +157,7 @@ class PdoDriver implements Dibi\Driver
{ {
if (!$this->connection->rollBack()) { if (!$this->connection->rollBack()) {
$err = $this->connection->errorInfo(); $err = $this->connection->errorInfo();
throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1]); throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1] ?? 0);
} }
} }
@@ -193,27 +176,14 @@ class PdoDriver implements Dibi\Driver
*/ */
public function getReflector(): Dibi\Reflector public function getReflector(): Dibi\Reflector
{ {
switch ($this->driverName) { return match ($this->driverName) {
case 'mysql': 'mysql' => new MySqlReflector($this),
return new MySqlReflector($this); 'oci' => new OracleReflector($this),
'pgsql' => new PostgreReflector($this, $this->connection->getAttribute(PDO::ATTR_SERVER_VERSION)),
case 'oci': 'sqlite' => new SqliteReflector($this),
return new OracleReflector($this); 'mssql', 'dblib', 'sqlsrv' => new SqlsrvReflector($this),
default => throw new Dibi\NotSupportedException,
case 'pgsql': };
return new PostgreReflector($this, $this->connection->getAttribute(PDO::ATTR_SERVER_VERSION));
case 'sqlite':
return new SqliteReflector($this);
case 'mssql':
case 'dblib':
case 'sqlsrv':
return new SqlsrvReflector($this);
default:
throw new Dibi\NotSupportedException;
}
} }
@@ -234,44 +204,34 @@ class PdoDriver implements Dibi\Driver
*/ */
public function escapeText(string $value): string public function escapeText(string $value): string
{ {
return $this->driverName === 'odbc' return match ($this->driverName) {
? "'" . str_replace("'", "''", $value) . "'" 'odbc' => "'" . str_replace("'", "''", $value) . "'",
: $this->connection->quote($value, PDO::PARAM_STR); 'sqlsrv' => "N'" . str_replace("'", "''", $value) . "'",
default => $this->connection->quote($value, PDO::PARAM_STR),
};
} }
public function escapeBinary(string $value): string public function escapeBinary(string $value): string
{ {
return $this->driverName === 'odbc' return match ($this->driverName) {
? "'" . str_replace("'", "''", $value) . "'" 'odbc' => "'" . str_replace("'", "''", $value) . "'",
: $this->connection->quote($value, PDO::PARAM_LOB); 'sqlsrv' => '0x' . bin2hex($value),
default => $this->connection->quote($value, PDO::PARAM_LOB),
};
} }
public function escapeIdentifier(string $value): string public function escapeIdentifier(string $value): string
{ {
switch ($this->driverName) { return match ($this->driverName) {
case 'mysql': 'mysql' => '`' . str_replace('`', '``', $value) . '`',
return '`' . str_replace('`', '``', $value) . '`'; 'oci', 'pgsql' => '"' . str_replace('"', '""', $value) . '"',
'sqlite' => '[' . strtr($value, '[]', ' ') . ']',
case 'oci': 'odbc', 'mssql' => '[' . str_replace(['[', ']'], ['[[', ']]'], $value) . ']',
case 'pgsql': 'dblib', 'sqlsrv' => '[' . str_replace(']', ']]', $value) . ']',
return '"' . str_replace('"', '""', $value) . '"'; default => $value,
};
case 'sqlite':
return '[' . strtr($value, '[]', ' ') . ']';
case 'odbc':
case 'mssql':
return '[' . str_replace(['[', ']'], ['[[', ']]'], $value) . ']';
case 'dblib':
case 'sqlsrv':
return '[' . str_replace(']', ']]', $value) . ']';
default:
return $value;
}
} }
@@ -293,16 +253,11 @@ class PdoDriver implements Dibi\Driver
public function escapeDateTime(\DateTimeInterface $value): string public function escapeDateTime(\DateTimeInterface $value): string
{ {
switch ($this->driverName) { return match ($this->driverName) {
case 'odbc': 'odbc' => $value->format('#m/d/Y H:i:s.u#'),
return $value->format('#m/d/Y H:i:s.u#'); 'mssql', 'dblib', 'sqlsrv' => 'CONVERT(DATETIME2(7), ' . $value->format("'Y-m-d H:i:s.u'") . ')',
case 'mssql': default => $value->format("'Y-m-d H:i:s.u'"),
case 'dblib': };
case 'sqlsrv':
return 'CONVERT(DATETIME2(7), ' . $value->format("'Y-m-d H:i:s.u'") . ')';
default:
return $value->format("'Y-m-d H:i:s.u'");
}
} }

View File

@@ -19,13 +19,8 @@ use PDO;
*/ */
class PdoResult implements Dibi\ResultDriver class PdoResult implements Dibi\ResultDriver
{ {
use Dibi\Strict; private ?\PDOStatement $resultSet;
private string $driverName;
/** @var \PDOStatement|null */
private $resultSet;
/** @var string */
private $driverName;
public function __construct(\PDOStatement $resultSet, string $driverName) public function __construct(\PDOStatement $resultSet, string $driverName)
@@ -95,7 +90,7 @@ class PdoResult implements Dibi\ResultDriver
'name' => $row['name'], 'name' => $row['name'],
'table' => $row['table'], 'table' => $row['table'],
'nativetype' => $row['native_type'], 'nativetype' => $row['native_type'],
'type' => $row['native_type'] === 'TIME' && $this->driverName === 'mysql' ? Dibi\Type::TIME_INTERVAL : null, 'type' => $row['native_type'] === 'TIME' && $this->driverName === 'mysql' ? Dibi\Type::TimeInterval : null,
'fullname' => $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name'], 'fullname' => $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name'],
'vendor' => $row, 'vendor' => $row,
]; ];

View File

@@ -28,13 +28,9 @@ use PgSql;
*/ */
class PostgreDriver implements Dibi\Driver class PostgreDriver implements Dibi\Driver
{ {
use Dibi\Strict;
/** @var resource|PgSql\Connection */ /** @var resource|PgSql\Connection */
private $connection; private $connection;
private ?int $affectedRows;
/** @var int|null Affected rows */
private $affectedRows;
/** @throws Dibi\NotSupportedException */ /** @throws Dibi\NotSupportedException */
@@ -140,7 +136,7 @@ class PostgreDriver implements Dibi\Driver
$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') {
@@ -228,7 +224,7 @@ 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 instanceof PgSql\Connection return is_resource($this->connection) || $this->connection instanceof PgSql\Connection
? $this->connection ? $this->connection

View File

@@ -17,13 +17,8 @@ use Dibi;
*/ */
class PostgreReflector implements Dibi\Reflector class PostgreReflector implements Dibi\Reflector
{ {
use Dibi\Strict; private Dibi\Driver $driver;
private string $version;
/** @var Dibi\Driver */
private $driver;
/** @var string */
private $version;
public function __construct(Dibi\Driver $driver, string $version) public function __construct(Dibi\Driver $driver, string $version)
@@ -77,13 +72,6 @@ class PostgreReflector implements Dibi\Reflector
public function getColumns(string $table): array public function getColumns(string $table): array
{ {
$_table = $this->driver->escapeText($this->driver->escapeIdentifier($table)); $_table = $this->driver->escapeText($this->driver->escapeIdentifier($table));
$res = $this->driver->query("
SELECT indkey
FROM pg_class
LEFT JOIN pg_index on pg_class.oid = pg_index.indrelid AND pg_index.indisprimary
WHERE pg_class.oid = $_table::regclass
");
$primary = (int) $res->fetch(true)['indkey'];
$res = $this->driver->query(" $res = $this->driver->query("
SELECT * SELECT *
@@ -103,7 +91,8 @@ 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,
pg_get_expr(adef.adbin, adef.adrelid) AS column_default pg_get_expr(adef.adbin, adef.adrelid) AS column_default,
CASE WHEN a.attidentity IN ('a', 'd') THEN 'YES' ELSE 'NO' END AS is_identity
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
@@ -128,7 +117,7 @@ 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' => $row['is_identity'] === 'YES' || str_starts_with($row['column_default'] ?? '', 'nextval('),
'vendor' => $row, 'vendor' => $row,
]; ];
} }

View File

@@ -19,14 +19,9 @@ use PgSql;
*/ */
class PostgreResult implements Dibi\ResultDriver class PostgreResult implements Dibi\ResultDriver
{ {
use Dibi\Strict;
/** @var resource|PgSql\Result */ /** @var resource|PgSql\Result */
private $resultSet; private $resultSet;
/** @var bool */
private $autoFree = true;
/** /**
* @param resource|PgSql\Result $resultSet * @param resource|PgSql\Result $resultSet
@@ -37,17 +32,6 @@ class PostgreResult implements Dibi\ResultDriver
} }
/**
* Automatically frees the resources allocated for this result set.
*/
public function __destruct()
{
if ($this->autoFree && $this->getResultResource()) {
$this->free();
}
}
/** /**
* Returns the number of rows in a result set. * Returns the number of rows in a result set.
*/ */
@@ -112,9 +96,8 @@ class PostgreResult implements Dibi\ResultDriver
* Returns the result set resource. * Returns the result set resource.
* @return resource|PgSql\Result|null * @return resource|PgSql\Result|null
*/ */
public function getResultResource() public function getResultResource(): mixed
{ {
$this->autoFree = false;
return is_resource($this->resultSet) || $this->resultSet instanceof PgSql\Result return is_resource($this->resultSet) || $this->resultSet instanceof PgSql\Result
? $this->resultSet ? $this->resultSet
: null; : null;

View File

@@ -25,16 +25,9 @@ use SQLite3;
*/ */
class SqliteDriver implements Dibi\Driver class SqliteDriver implements Dibi\Driver
{ {
use Dibi\Strict; private SQLite3 $connection;
private string $fmtDate;
/** @var SQLite3 */ private string $fmtDateTime;
private $connection;
/** @var string Date format */
private $fmtDate;
/** @var string Datetime format */
private $fmtDateTime;
/** @throws Dibi\NotSupportedException */ /** @throws Dibi\NotSupportedException */
@@ -102,19 +95,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);
@@ -291,7 +284,7 @@ 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

@@ -17,10 +17,7 @@ use Dibi;
*/ */
class SqliteReflector implements Dibi\Reflector class SqliteReflector implements Dibi\Reflector
{ {
use Dibi\Strict; private Dibi\Driver $driver;
/** @var Dibi\Driver */
private $driver;
public function __construct(Dibi\Driver $driver) public function __construct(Dibi\Driver $driver)

View File

@@ -18,13 +18,7 @@ use Dibi\Helpers;
*/ */
class SqliteResult implements Dibi\ResultDriver class SqliteResult implements Dibi\ResultDriver
{ {
use Dibi\Strict; private \SQLite3Result $resultSet;
/** @var \SQLite3Result */
private $resultSet;
/** @var bool */
private $autoFree = true;
public function __construct(\SQLite3Result $resultSet) public function __construct(\SQLite3Result $resultSet)
@@ -33,17 +27,6 @@ class SqliteResult implements Dibi\ResultDriver
} }
/**
* Automatically frees the resources allocated for this result set.
*/
public function __destruct()
{
if ($this->autoFree && $this->getResultResource()) {
@$this->free();
}
}
/** /**
* Returns the number of rows in a result set. * Returns the number of rows in a result set.
* @throws Dibi\NotSupportedException * @throws Dibi\NotSupportedException
@@ -90,7 +73,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),
@@ -109,7 +92,6 @@ class SqliteResult implements Dibi\ResultDriver
*/ */
public function getResultResource(): \SQLite3Result public function getResultResource(): \SQLite3Result
{ {
$this->autoFree = false;
return $this->resultSet; return $this->resultSet;
} }

View File

@@ -27,16 +27,10 @@ use Dibi\Helpers;
*/ */
class SqlsrvDriver implements Dibi\Driver class SqlsrvDriver implements Dibi\Driver
{ {
use Dibi\Strict;
/** @var resource */ /** @var resource */
private $connection; private $connection;
private ?int $affectedRows;
/** @var int|null Affected rows */ private string $version = '';
private $affectedRows;
/** @var string */
private $version = '';
/** @throws Dibi\NotSupportedException */ /** @throws Dibi\NotSupportedException */
@@ -60,7 +54,7 @@ class SqlsrvDriver implements Dibi\Driver
$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'];
@@ -170,7 +164,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;
} }

View File

@@ -17,10 +17,7 @@ use Dibi;
*/ */
class SqlsrvReflector implements Dibi\Reflector class SqlsrvReflector implements Dibi\Reflector
{ {
use Dibi\Strict; private Dibi\Driver $driver;
/** @var Dibi\Driver */
private $driver;
public function __construct(Dibi\Driver $driver) public function __construct(Dibi\Driver $driver)

View File

@@ -17,14 +17,9 @@ use Dibi;
*/ */
class SqlsrvResult implements Dibi\ResultDriver class SqlsrvResult implements Dibi\ResultDriver
{ {
use Dibi\Strict;
/** @var resource */ /** @var resource */
private $resultSet; private $resultSet;
/** @var bool */
private $autoFree = true;
/** /**
* @param resource $resultSet * @param resource $resultSet
@@ -35,17 +30,6 @@ class SqlsrvResult implements Dibi\ResultDriver
} }
/**
* Automatically frees the resources allocated for this result set.
*/
public function __destruct()
{
if ($this->autoFree && $this->getResultResource()) {
$this->free();
}
}
/** /**
* Returns the number of rows in a result set. * Returns the number of rows in a result set.
*/ */
@@ -105,9 +89,8 @@ 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;
return is_resource($this->resultSet) ? $this->resultSet : null; return is_resource($this->resultSet) ? $this->resultSet : null;
} }

View File

@@ -15,8 +15,6 @@ namespace Dibi;
*/ */
class Event class Event
{ {
use Strict;
/** event type */ /** event type */
public const public const
CONNECT = 1, CONNECT = 1,
@@ -31,26 +29,13 @@ class Event
TRANSACTION = 448, // BEGIN | COMMIT | ROLLBACK TRANSACTION = 448, // BEGIN | COMMIT | ROLLBACK
ALL = 1023; ALL = 1023;
/** @var Connection */ public Connection $connection;
public $connection; public int $type;
public string $sql;
/** @var int */ public Result|DriverException|null $result;
public $type; public float $time;
public ?int $count = null;
/** @var string */ public ?array $source = null;
public $sql;
/** @var Result|DriverException|null */
public $result;
/** @var float */
public $time;
/** @var int|null */
public $count;
/** @var array|null */
public $source;
public function __construct(Connection $connection, int $type, ?string $sql = null) public function __construct(Connection $connection, int $type, ?string $sql = null)
@@ -61,7 +46,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,
]; ];
@@ -73,7 +58,7 @@ class Event
if ( if (
isset($row['file']) isset($row['file'])
&& preg_match('~\.(php.?|phtml)$~', $row['file']) && preg_match('~\.(php.?|phtml)$~', $row['file'])
&& substr($row['file'], 0, strlen($dibiDir)) !== $dibiDir && !str_starts_with($row['file'], $dibiDir)
) { ) {
$this->source = [$row['file'], (int) $row['line']]; $this->source = [$row['file'], (int) $row['line']];
break; break;
@@ -86,10 +71,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

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

View File

@@ -27,6 +27,8 @@ namespace Dibi;
* @method Fluent innerJoin(...$table) * @method Fluent innerJoin(...$table)
* @method Fluent rightJoin(...$table) * @method Fluent rightJoin(...$table)
* @method Fluent outerJoin(...$table) * @method Fluent outerJoin(...$table)
* @method Fluent union(Fluent $fluent)
* @method Fluent unionAll(Fluent $fluent)
* @method Fluent as(...$field) * @method Fluent as(...$field)
* @method Fluent on(...$cond) * @method Fluent on(...$cond)
* @method Fluent and(...$cond) * @method Fluent and(...$cond)
@@ -43,12 +45,15 @@ namespace Dibi;
*/ */
class Fluent implements IDataSource class Fluent implements IDataSource
{ {
use Strict; public const
AffectedRows = 'a',
Identifier = 'n',
Remove = false;
public const REMOVE = false; /** @deprecated use Fluent::Remove */
public const REMOVE = self::Remove;
/** @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 +61,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 +74,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 +89,30 @@ 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; private array $setups = [];
private ?string $command = null;
/** @var array */ private array $clauses = [];
private $setups = []; private array $flags = [];
/** @var string|null */
private $command;
/** @var array */
private $clauses = [];
/** @var array */
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 +121,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;
@@ -152,7 +146,7 @@ class Fluent implements IDataSource
$this->cursor = &$this->clauses[$clause]; $this->cursor = &$this->clauses[$clause];
// TODO: really delete? // TODO: really delete?
if ($args === [self::REMOVE]) { if ($args === [self::Remove]) {
$this->cursor = null; $this->cursor = null;
return $this; return $this;
} }
@@ -168,7 +162,7 @@ class Fluent implements IDataSource
} }
} else { } else {
// append to currect flow // append to currect flow
if ($args === [self::REMOVE]) { if ($args === [self::Remove]) {
return $this; return $this;
} }
@@ -214,7 +208,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) {
@@ -228,7 +222,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;
@@ -238,7 +232,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) {
@@ -278,7 +272,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;
@@ -290,28 +284,25 @@ 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
* @return ($return is self::Identifier|self::AffectedRows ? int : Result)
* @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) { return match ($return) {
case \dibi::IDENTIFIER: self::Identifier => $this->connection->getInsertId(),
return $this->connection->getInsertId(); self::AffectedRows => $this->connection->getAffectedRows(),
case \dibi::AFFECTED_ROWS: default => $res,
return $this->connection->getAffectedRows(); };
default:
return $res;
}
} }
/** /**
* 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()
@@ -321,9 +312,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()
@@ -411,12 +402,7 @@ 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 '';
}
} }

View File

@@ -46,7 +46,7 @@ abstract class HashMapBase
*/ */
final class HashMap extends HashMapBase final class HashMap extends HashMapBase
{ {
public function __set(string $nm, $val) public function __set(string $nm, mixed $val): void
{ {
if ($nm === '') { if ($nm === '') {
$nm = "\xFF"; $nm = "\xFF";
@@ -56,7 +56,7 @@ final class HashMap extends HashMapBase
} }
public function __get(string $nm) public function __get(string $nm): mixed
{ {
if ($nm === '') { if ($nm === '') {
$nm = "\xFF"; $nm = "\xFF";

View File

@@ -12,21 +12,17 @@ namespace Dibi;
class Helpers class Helpers
{ {
use Strict; private static HashMap $types;
/** @var HashMap */
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) {
@@ -75,8 +71,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 ";
@@ -91,7 +87,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";
@@ -162,13 +158,13 @@ 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',
Type::DATE => 'date', Type::Date => 'date',
Type::DATETIME => 'datetime', Type::DateTime => 'datetime',
\dibi::IDENTIFIER => 'identifier', Fluent::Identifier => 'identifier',
]; ];
if (isset($types[$type])) { if (isset($types[$type])) {
return $driver->{'escape' . $types[$type]}($value); return $driver->{'escape' . $types[$type]}($value);
@@ -184,17 +180,17 @@ 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 '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,
'CURRENCY|REAL|MONEY|FLOAT|DOUBLE|DECIMAL|NUMERIC|NUMBER' => Type::FLOAT, 'CURRENCY|REAL|MONEY|FLOAT|DOUBLE|DECIMAL|NUMERIC|NUMBER' => Type::Float,
'^TIME$' => Type::TIME, '^TIME$' => Type::Time,
'TIME' => Type::DATETIME, // DATETIME, TIMESTAMP 'TIME' => Type::DateTime, // DATETIME, TIMESTAMP
'DATE' => Type::DATE, 'DATE' => Type::Date,
'BOOL' => Type::BOOL, 'BOOL' => Type::Bool,
'JSON' => Type::JSON, 'JSON' => Type::JSON,
]; ];
@@ -211,7 +207,7 @@ 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']);
} }
@@ -238,7 +234,7 @@ 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
{ {
@@ -259,7 +255,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 = '';
@@ -286,14 +282,14 @@ class Helpers
/** @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;

View File

@@ -15,10 +15,7 @@ namespace Dibi;
*/ */
class Literal class Literal
{ {
use Strict; private string $value;
/** @var string */
private $value;
public function __construct($value) public function __construct($value)

View File

@@ -17,16 +17,10 @@ use Dibi;
*/ */
class FileLogger class FileLogger
{ {
use Dibi\Strict; /** Name of the file where SQL errors should be logged */
public string $file;
/** @var string Name of the file where SQL errors should be logged */ public int $filter;
public $file; private bool $errorsOnly;
/** @var int */
public $filter;
/** @var bool */
private $errorsOnly;
public function __construct(string $file, ?int $filter = null, bool $errorsOnly = false) public function __construct(string $file, ?int $filter = null, bool $errorsOnly = false)
@@ -58,7 +52,7 @@ class FileLogger
$this->writeToFile( $this->writeToFile(
$event, $event,
"ERROR: $message" "ERROR: $message"
. "\n-- SQL: " . $event->sql . "\n-- SQL: " . $event->sql,
); );
} else { } else {
$this->writeToFile( $this->writeToFile(
@@ -66,7 +60,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),
); );
} }
} }
@@ -76,7 +70,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: " . get_debug_type($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

@@ -27,13 +27,11 @@ use Dibi;
*/ */
class Column class Column
{ {
use Dibi\Strict; /** when created by Result */
private ?Dibi\Reflector $reflector;
/** @var Dibi\Reflector|null when created by Result */
private $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, array $info) public function __construct(?Dibi\Reflector $reflector, array $info)
@@ -109,15 +107,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

@@ -21,16 +21,11 @@ use Dibi;
*/ */
class Database class Database
{ {
use Dibi\Strict; private Dibi\Reflector $reflector;
private ?string $name;
/** @var Dibi\Reflector */ /** @var Table[] */
private $reflector; private array $tables;
/** @var string|null */
private $name;
/** @var Table[]|null */
private $tables;
public function __construct(Dibi\Reflector $reflector, ?string $name = null) public function __construct(Dibi\Reflector $reflector, ?string $name = null)
@@ -89,7 +84,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

@@ -9,7 +9,6 @@ declare(strict_types=1);
namespace Dibi\Reflection; namespace Dibi\Reflection;
use Dibi;
/** /**
@@ -20,13 +19,10 @@ use Dibi;
*/ */
class ForeignKey class ForeignKey
{ {
use Dibi\Strict; private string $name;
/** @var string */
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

@@ -9,7 +9,6 @@ declare(strict_types=1);
namespace Dibi\Reflection; namespace Dibi\Reflection;
use Dibi;
/** /**
@@ -22,10 +21,8 @@ use Dibi;
*/ */
class Index class Index
{ {
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

@@ -20,16 +20,13 @@ use Dibi;
*/ */
class Result class Result
{ {
use Dibi\Strict; private Dibi\ResultDriver $driver;
/** @var Dibi\ResultDriver */
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)
@@ -81,7 +78,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

@@ -25,28 +25,19 @@ use Dibi;
*/ */
class Table class Table
{ {
use Dibi\Strict; private Dibi\Reflector $reflector;
private string $name;
private bool $view;
/** @var Dibi\Reflector */ /** @var Column[] */
private $reflector; private array $columns;
/** @var string */ /** @var ForeignKey[] */
private $name; private array $foreignKeys;
/** @var bool */ /** @var Index[] */
private $view; private array $indexes;
private ?Index $primaryKey;
/** @var Column[]|null */
private $columns;
/** @var ForeignKey[]|null */
private $foreignKeys;
/** @var Index[]|null */
private $indexes;
/** @var Index|null */
private $primaryKey;
public function __construct(Dibi\Reflector $reflector, array $info) public function __construct(Dibi\Reflector $reflector, array $info)
@@ -135,7 +126,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);
@@ -146,7 +137,7 @@ 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) {

View File

@@ -17,28 +17,21 @@ namespace Dibi;
*/ */
class Result implements IDataSource class Result implements IDataSource
{ {
use Strict; private ?ResultDriver $driver;
/** @var ResultDriver|null */ /** Translate table */
private $driver; private array $types = [];
private ?Reflection\Result $meta;
/** @var array Translate table */ /** Already fetched? Used for allowance for first seek(0) */
private $types = []; private bool $fetched = false;
/** @var Reflection\Result|null */ /** returned object class */
private $meta; private ?string $rowClass = Row::class;
/** @var bool Already fetched? Used for allowance for first seek(0) */
private $fetched = false;
/** @var string|null returned object class */
private $rowClass = Row::class;
/** @var callable|null returned object factory */ /** @var callable|null returned object factory */
private $rowFactory; private $rowFactory;
private array $formats = [];
/** @var array format */
private $formats = [];
public function __construct(ResultDriver $driver, bool $normalize = true) public function __construct(ResultDriver $driver, bool $normalize = true)
@@ -133,7 +126,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 +145,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,9 +155,8 @@ 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) {
@@ -185,9 +177,9 @@ class Result 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
*/ */
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) {
@@ -206,7 +198,7 @@ class Result implements IDataSource
*/ */
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) {
@@ -238,7 +230,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);
} }
@@ -465,16 +457,22 @@ class Result implements IDataSource
if ($type === null || $format === 'native') { if ($type === null || $format === 'native') {
$row[$key] = $value; $row[$key] = $value;
} elseif ($type === Type::TEXT) { } elseif ($type === Type::Text) {
$row[$key] = (string) $value; $row[$key] = (string) $value;
} elseif ($type === Type::INTEGER) { } elseif ($type === Type::Integer) {
$row[$key] = is_float($tmp = $value * 1) $row[$key] = is_float($tmp = $value * 1)
? (is_string($value) ? $value : (int) $value) ? (is_string($value) ? $value : (int) $value)
: $tmp; : $tmp;
} elseif ($type === Type::FLOAT) { } elseif ($type === Type::Float) {
$value = ltrim((string) $value, '0'); if (!is_string($value)) {
$row[$key] = (float) $value;
continue;
}
$negative = ($value[0] ?? null) === '-';
$value = ltrim($value, '0-');
$p = strpos($value, '.'); $p = strpos($value, '.');
$e = strpos($value, 'e'); $e = strpos($value, 'e');
if ($p !== false && $e === false) { if ($p !== false && $e === false) {
@@ -487,27 +485,31 @@ class Result implements IDataSource
$value = '0' . $value; $value = '0' . $value;
} }
if ($negative) {
$value = '-' . $value;
}
$row[$key] = $value === str_replace(',', '.', (string) ($float = (float) $value)) $row[$key] = $value === str_replace(',', '.', (string) ($float = (float) $value))
? $float ? $float
: $value; : $value;
} elseif ($type === Type::BOOL) { } elseif ($type === Type::Bool) {
$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, 7) !== '0000-00') { // '', 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::TimeInterval) {
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");
$value->invert = (int) (bool) $m[1]; $value->invert = (int) (bool) $m[1];
$row[$key] = $format ? $value->format($format) : $value; $row[$key] = $format ? $value->format($format) : $value;
} elseif ($type === Type::BINARY) { } elseif ($type === Type::Binary) {
$row[$key] = is_string($value) $row[$key] = is_string($value)
? $this->getResultDriver()->unescapeBinary($value) ? $this->getResultDriver()->unescapeBinary($value)
: $value; : $value;
@@ -529,7 +531,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;
@@ -557,7 +559,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;
@@ -567,7 +569,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;
@@ -591,7 +593,7 @@ 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());
} }

View File

@@ -15,16 +15,9 @@ namespace Dibi;
*/ */
class ResultIterator implements \Iterator, \Countable class ResultIterator implements \Iterator, \Countable
{ {
use Strict; private Result $result;
private mixed $row;
/** @var Result */ private int $pointer = 0;
private $result;
/** @var mixed */
private $row;
/** @var int */
private $pointer = 0;
public function __construct(Result $result) public function __construct(Result $result)
@@ -44,23 +37,21 @@ class ResultIterator implements \Iterator, \Countable
} }
#[\ReturnTypeWillChange]
/** /**
* Returns the key of the current element. * Returns the key of the current element.
* @return mixed
*/ */
public function key() #[\ReturnTypeWillChange]
public function key(): mixed
{ {
return $this->pointer; return $this->pointer;
} }
#[\ReturnTypeWillChange]
/** /**
* Returns the current element. * Returns the current element.
* @return mixed
*/ */
public function current() #[\ReturnTypeWillChange]
public function current(): mixed
{ {
return $this->row; return $this->row;
} }

View File

@@ -32,13 +32,12 @@ 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, 7) === '0000-00') { // '', null, false, '0000-00-00', ... if (!$time || str_starts_with((string) $time, '0000-00')) { // '', null, false, '0000-00-00', ...
return null; return null;
} }
@@ -49,10 +48,11 @@ class Row implements \ArrayAccess, \IteratorAggregate, \Countable
} }
public function __get(string $key) public function __get(string $key): mixed
{ {
$hint = Helpers::getSuggestion(array_keys((array) $this), $key); $hint = Helpers::getSuggestion(array_keys((array) $this), $key);
trigger_error("Attempt to read missing column '$key'" . ($hint ? ", did you mean '$hint'?" : '.'), E_USER_NOTICE); trigger_error("Attempt to read missing column '$key'" . ($hint ? ", did you mean '$hint'?" : '.'), E_USER_NOTICE);
return null;
} }
@@ -83,8 +83,7 @@ class Row implements \ArrayAccess, \IteratorAggregate, \Countable
} }
#[\ReturnTypeWillChange] final public function offsetGet($nm): mixed
final public function offsetGet($nm)
{ {
return $this->$nm; return $this->$nm;
} }

View File

@@ -1,112 +0,0 @@
<?php
/**
* This file is part of the Dibi, smart database abstraction layer (https://dibiphp.com)
* Copyright (c) 2005 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Dibi;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
/**
* Better OOP experience.
*/
trait Strict
{
/** @var array [method => [type => callback]] */
private static $extMethods;
/**
* Call to undefined method.
* @throws \LogicException
*/
public function __call(string $name, array $args)
{
$class = method_exists($this, $name) ? 'parent' : static::class;
$items = (new ReflectionClass($this))->getMethods(ReflectionMethod::IS_PUBLIC);
$items = array_map(function ($item) { return $item->getName(); }, $items);
$hint = ($t = Helpers::getSuggestion($items, $name))
? ", did you mean $t()?"
: '.';
throw new \LogicException("Call to undefined method $class::$name()$hint");
}
/**
* Call to undefined static method.
* @throws \LogicException
*/
public static function __callStatic(string $name, array $args)
{
$rc = new ReflectionClass(static::class);
$items = array_filter($rc->getMethods(\ReflectionMethod::IS_STATIC), function ($m) { return $m->isPublic(); });
$items = array_map(function ($item) { return $item->getName(); }, $items);
$hint = ($t = Helpers::getSuggestion($items, $name))
? ", did you mean $t()?"
: '.';
throw new \LogicException("Call to undefined static method {$rc->getName()}::$name()$hint");
}
/**
* Access to undeclared property.
* @throws \LogicException
*/
public function &__get(string $name)
{
if ((method_exists($this, $m = 'get' . $name) || method_exists($this, $m = 'is' . $name))
&& (new ReflectionMethod($this, $m))->isPublic()
) { // back compatiblity
$ret = $this->$m();
return $ret;
}
$rc = new ReflectionClass($this);
$items = array_filter($rc->getProperties(ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); });
$items = array_map(function ($item) { return $item->getName(); }, $items);
$hint = ($t = Helpers::getSuggestion($items, $name))
? ", did you mean $$t?"
: '.';
throw new \LogicException("Attempt to read undeclared property {$rc->getName()}::$$name$hint");
}
/**
* Access to undeclared property.
* @throws \LogicException
*/
public function __set(string $name, $value)
{
$rc = new ReflectionClass($this);
$items = array_filter($rc->getProperties(ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); });
$items = array_map(function ($item) { return $item->getName(); }, $items);
$hint = ($t = Helpers::getSuggestion($items, $name))
? ", did you mean $$t?"
: '.';
throw new \LogicException("Attempt to write to undeclared property {$rc->getName()}::$$name$hint");
}
public function __isset(string $name): bool
{
return false;
}
/**
* Access to undeclared property.
* @throws \LogicException
*/
public function __unset(string $name)
{
$class = static::class;
throw new \LogicException("Attempt to unset undeclared property $class::$$name.");
}
}

View File

@@ -15,40 +15,19 @@ namespace Dibi;
*/ */
final class Translator final class Translator
{ {
use Strict; private Connection $connection;
private Driver $driver;
/** @var Connection */ private int $cursor = 0;
private $connection; private array $args;
/** @var Driver */
private $driver;
/** @var int */
private $cursor = 0;
/** @var array */
private $args;
/** @var string[] */ /** @var string[] */
private $errors; private array $errors;
private bool $comment = false;
/** @var bool */ private int $ifLevel = 0;
private $comment = false; private int $ifLevelStart = 0;
private ?int $limit = null;
/** @var int */ private ?int $offset = null;
private $ifLevel = 0; private HashMap $identifiers;
/** @var int */
private $ifLevelStart = 0;
/** @var int|null */
private $limit;
/** @var int|null */
private $offset;
/** @var HashMap */
private $identifiers;
public function __construct(Connection $connection) public function __construct(Connection $connection)
@@ -96,22 +75,21 @@ 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;
@@ -173,9 +151,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 '...';
@@ -210,7 +187,7 @@ 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 ';
@@ -242,7 +219,7 @@ XX
case 'a': // key=val, key=val, ... case 'a': // key=val, key=val, ...
foreach ($value as $k => $v) { foreach ($value as $k => $v) {
$pair = explode('%', $k, 2); // split into identifier & modifier $pair = explode('%', (string) $k, 2); // split into identifier & modifier
$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));
} }
@@ -280,7 +257,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 ' . get_debug_type($v) . '**';
} }
$pair = explode('%', $k, 2); // split into identifier & modifier $pair = explode('%', $k, 2); // split into identifier & modifier
@@ -326,6 +303,15 @@ XX
} }
} }
if (is_object($value)
&& $modifier === null
&& !$value instanceof Literal
&& !$value instanceof Expression
&& $result = $this->connection->translateObject($value)
) {
return $this->connection->translate(...$result->getValues());
}
// object-to-scalar procession // object-to-scalar procession
if ($value instanceof \BackedEnum && is_scalar($value->value)) { if ($value instanceof \BackedEnum && is_scalar($value->value)) {
$value = $value->value; $value = $value->value;
@@ -349,7 +335,7 @@ XX
) { ) {
// continue // continue
} else { } else {
$type = is_object($value) ? get_class($value) : gettype($value); $type = get_debug_type($value);
return $this->errors[] = "**Invalid combination of type $type and modifier %$modifier**"; return $this->errors[] = "**Invalid combination of type $type and modifier %$modifier**";
} }
} }
@@ -437,20 +423,19 @@ 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;
@@ -516,7 +501,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 = get_debug_type($value);
return $this->errors[] = "**Unexpected $type**"; return $this->errors[] = "**Unexpected $type**";
} }
} }

View File

@@ -16,16 +16,43 @@ namespace Dibi;
class Type class Type
{ {
public const public const
TEXT = 's', // as 'string' Text = 's', // as 'string'
BINARY = 'bin', Binary = 'bin',
JSON = 'json', JSON = 'json',
BOOL = 'b', Bool = 'b',
INTEGER = 'i', Integer = 'i',
FLOAT = 'f', Float = 'f',
DATE = 'd', Date = 'd',
DATETIME = 'dt', DateTime = 'dt',
TIME = 't', Time = 't',
TIME_INTERVAL = 'ti'; TimeInterval = 'ti';
/** @deprecated use Type::Text */
public const TEXT = self::Text;
/** @deprecated use Type::Binary */
public const BINARY = self::Binary;
/** @deprecated use Type::Bool */
public const BOOL = self::Bool;
/** @deprecated use Type::Integer */
public const INTEGER = self::Integer;
/** @deprecated use Type::Float */
public const FLOAT = self::Float;
/** @deprecated use Type::Date */
public const DATE = self::Date;
/** @deprecated use Type::DateTime */
public const DATETIME = self::DateTime;
/** @deprecated use Type::Time */
public const TIME = self::Time;
/** @deprecated use Type::TimeInterval */
public const TIME_INTERVAL = self::TimeInterval;
final public function __construct() final public function __construct()

View File

@@ -37,37 +37,39 @@ declare(strict_types=1);
*/ */
class dibi class dibi
{ {
use Dibi\Strict; public const Version = '5.0.2';
public const /** @deprecated use dibi::Version */
AFFECTED_ROWS = 'a', public const VERSION = self::Version;
IDENTIFIER = 'n';
/** version */ /** @deprecated use Dibi\Fluent::AffectedRows */
public const VERSION = '4.2.6'; public const AFFECTED_ROWS = Dibi\Fluent::AffectedRows;
/** @deprecated use Dibi\Fluent::Identifier */
public const IDENTIFIER = Dibi\Fluent::Identifier;
/** 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;
/** /**
@@ -87,7 +89,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);
} }
@@ -150,10 +152,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);
} }
@@ -164,7 +165,7 @@ class dibi
*/ */
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,8 +78,7 @@ class NotSupportedException extends Exception
*/ */
class ProcedureException extends Exception class ProcedureException extends Exception
{ {
/** @var string */ protected string $severity;
protected $severity;
/** /**

View File

@@ -67,9 +67,8 @@ interface Driver
/** /**
* 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.

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

@@ -73,24 +73,29 @@ test('', function () use ($config) {
test('', function () use ($config) { test('', function () use ($config) {
Assert::exception(function () use ($config) { Assert::exception(
new Connection($config + ['onConnect' => '']); fn() => new Connection($config + ['onConnect' => '']),
}, InvalidArgumentException::class, "Configuration option 'onConnect' must be array."); InvalidArgumentException::class,
"Configuration option 'onConnect' must be array.",
);
$e = Assert::exception(function () use ($config) { $e = Assert::exception(
new Connection($config + ['onConnect' => ['STOP']]); fn() => new Connection($config + ['onConnect' => ['STOP']]),
}, Dibi\DriverException::class); Dibi\DriverException::class,
);
Assert::same('STOP', $e->getSql()); Assert::same('STOP', $e->getSql());
$e = Assert::exception(function () use ($config) { $e = Assert::exception(
new Connection($config + ['onConnect' => [['STOP %i', 123]]]); fn() => new Connection($config + ['onConnect' => [['STOP %i', 123]]]),
}, Dibi\DriverException::class); Dibi\DriverException::class,
);
Assert::same('STOP 123', $e->getSql()); Assert::same('STOP 123', $e->getSql());
// lazy // lazy
$conn = new Connection($config + ['lazy' => true, 'onConnect' => ['STOP']]); $conn = new Connection($config + ['lazy' => true, 'onConnect' => ['STOP']]);
$e = Assert::exception(function () use ($conn) { $e = Assert::exception(
$conn->query('SELECT 1'); fn() => $conn->query('SELECT 1'),
}, Dibi\DriverException::class); Dibi\DriverException::class,
);
Assert::same('STOP', $e->getSql()); Assert::same('STOP', $e->getSql());
}); });

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,155 @@
<?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(
fn() => $conn->translate('?', new Email),
Dibi\Exception::class,
'SQL translate error: Unexpected Email',
);
});
test('Basics', function () use ($conn) {
$conn->setObjectTranslator(fn(Email $email) => new Dibi\Expression('?', $email->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->setObjectTranslator(fn(Time $time) => new Dibi\Expression('OwnTime(?)', $time->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->setObjectTranslator(fn(DateTimeInterface $dt) => new Dibi\Expression('OwnDateTime'));
Assert::same(
'OwnDateTime',
$conn->translate('?', $dt),
);
});
test('Complex structures', function () use ($conn) {
$conn->setObjectTranslator(fn(Email $email) => new Dibi\Expression('?', $email->address));
$conn->setObjectTranslator(fn(Time $time) => new Dibi\Expression('OwnTime(?)', $time->format('H:i:s')));
$conn->setObjectTranslator(fn(DateTimeInterface $dt) => new Dibi\Expression('OwnDateTime'));
$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,
]),
);
});
test('Invalid translator', function () use ($conn) {
Assert::exception(
fn() => $conn->setObjectTranslator(fn($email) => 'foo'),
Dibi\Exception::class,
'Object translator must have exactly one parameter with class typehint.',
);
Assert::exception(
fn() => $conn->setObjectTranslator(fn(string $email) => 'foo'),
Dibi\Exception::class,
"Object translator must have exactly one parameter with non-nullable class typehint, got 'string'.",
);
Assert::exception(
fn() => $conn->setObjectTranslator(fn(Email|bool $email) => 'foo'),
Dibi\Exception::class,
"Object translator must have exactly one parameter with non-nullable class typehint, got 'bool'.",
);
Assert::exception(
fn() => $conn->setObjectTranslator(fn(Email|null $email) => 'foo'),
Dibi\Exception::class,
"Object translator must have exactly one parameter with non-nullable class typehint, got '?Email'.",
);
$conn->setObjectTranslator(fn(Email $email) => 'foo');
Assert::exception(
fn() => $conn->translate('?', new Email),
Dibi\Exception::class,
"Object translator for class 'Email' returned 'string' but Dibi\\Expression expected.",
);
});

View File

@@ -14,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'"),
); );
@@ -43,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

@@ -15,18 +15,22 @@ $conn = new Dibi\Connection($config);
$conn->loadFile(__DIR__ . "/data/$config[system].sql"); $conn->loadFile(__DIR__ . "/data/$config[system].sql");
/*Assert::exception(function () use ($conn) { /*
$conn->rollback(); Assert::exception(
}, Dibi\Exception::class); fn() => $conn->rollback(),
Dibi\Exception::class,
);
Assert::exception(function () use ($conn) { Assert::exception(
$conn->commit(); fn() => $conn->commit(),
}, Dibi\Exception::class); Dibi\Exception::class,
);
$conn->begin(); $conn->begin();
Assert::exception(function () use ($conn) { Assert::exception(
$conn->begin(); fn() => $conn->begin(),
}, Dibi\Exception::class); Dibi\Exception::class,
);
*/ */
@@ -53,14 +57,16 @@ test('begin() & commit()', function () use ($conn) {
test('transaction() fail', function () use ($conn) { test('transaction() fail', function () use ($conn) {
Assert::exception(function () use ($conn) { Assert::exception(
$conn->transaction(function (Dibi\Connection $connection) { fn() => $conn->transaction(function (Dibi\Connection $connection) {
$connection->query('INSERT INTO [products]', [ $connection->query('INSERT INTO [products]', [
'title' => 'Test product', 'title' => 'Test product',
]); ]);
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());
}); });
@@ -76,8 +82,8 @@ test('transaction() success', function () use ($conn) {
test('nested transaction() call fail', function () use ($conn) { test('nested transaction() call fail', function () use ($conn) {
Assert::exception(function () use ($conn) { Assert::exception(
$conn->transaction(function (Dibi\Connection $connection) { fn() => $conn->transaction(function (Dibi\Connection $connection) {
$connection->query('INSERT INTO [products]', [ $connection->query('INSERT INTO [products]', [
'title' => 'Test product', 'title' => 'Test product',
]); ]);
@@ -88,8 +94,10 @@ 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());
}); });
@@ -111,21 +119,27 @@ test('nested transaction() call success', function () use ($conn) {
test('begin(), commit() & rollback() calls are forbidden in transaction()', function () use ($conn) { test('begin(), commit() & rollback() calls are forbidden in transaction()', function () use ($conn) {
Assert::exception(function () use ($conn) { Assert::exception(
$conn->transaction(function (Dibi\Connection $connection) { fn() => $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(
$conn->transaction(function (Dibi\Connection $connection) { fn() => $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(
$conn->transaction(function (Dibi\Connection $connection) { fn() => $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

@@ -17,7 +17,7 @@ Assert::match(
reformat(' reformat('
SELECT * SELECT *
FROM (SELECT * FROM products) t'), FROM (SELECT * FROM products) t'),
(string) $ds (string) $ds,
); );
@@ -25,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,
); );
@@ -39,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,
); );
@@ -53,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,
); );
@@ -67,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,
); );
@@ -95,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,
); );
@@ -108,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,
); );
@@ -117,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([
@@ -129,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([
@@ -154,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,10 +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(is_int(2544000000) ? 2544000000 : '2544000000', (new DateTime(2544000000))->getTimestamp()); // 64 bit Assert::same(is_int(2_544_000_000) ? 2_544_000_000 : '2544000000', (new DateTime(2_544_000_000))->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

@@ -15,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

@@ -58,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,
); );
@@ -87,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,
); );
@@ -105,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

@@ -21,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,11 @@ 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(
(string) $fluent; fn() => (string) $fluent,
}, E_USER_ERROR, "Expected number, ' 1; DROP TABLE users' given."); Dibi\Exception::class,
"Expected number, ' 1; DROP TABLE users' given.",
);
} }
@@ -158,5 +160,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

@@ -21,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,
); );
@@ -40,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

@@ -12,22 +12,32 @@ Assert::same(0, Helpers::intVal(0));
Assert::same(0, Helpers::intVal('0')); Assert::same(0, Helpers::intVal('0'));
Assert::same(-10, Helpers::intVal('-10')); Assert::same(-10, Helpers::intVal('-10'));
Assert::exception(function () { Assert::exception(
Helpers::intVal('12345678901234567890123456879'); fn() => Helpers::intVal('12345678901234567890123456879'),
}, Dibi\Exception::class, 'Number 12345678901234567890123456879 is greater than integer.'); Dibi\Exception::class,
'Number 12345678901234567890123456879 is greater than integer.',
);
Assert::exception(function () { Assert::exception(
Helpers::intVal('-12345678901234567890123456879'); fn() => Helpers::intVal('-12345678901234567890123456879'),
}, Dibi\Exception::class, 'Number -12345678901234567890123456879 is greater than integer.'); Dibi\Exception::class,
'Number -12345678901234567890123456879 is greater than integer.',
);
Assert::exception(function () { Assert::exception(
Helpers::intVal(''); fn() => Helpers::intVal(''),
}, Dibi\Exception::class, "Expected number, '' given."); Dibi\Exception::class,
"Expected number, '' given.",
);
Assert::exception(function () { Assert::exception(
Helpers::intVal('not number'); fn() => Helpers::intVal('not number'),
}, Dibi\Exception::class, "Expected number, 'not number' given."); Dibi\Exception::class,
"Expected number, 'not number' given.",
);
Assert::exception(function () { Assert::exception(
Helpers::intVal(null); fn() => Helpers::intVal(null),
}, Dibi\Exception::class, "Expected number, '' given."); Dibi\Exception::class,
"Expected number, '' given.",
);

View File

@@ -19,17 +19,22 @@ function buildPdoDriver(?int $errorMode)
// PDO error mode: exception // PDO error mode: exception
Assert::exception(function () { Assert::exception(
buildPdoDriver(PDO::ERRMODE_EXCEPTION); fn() => buildPdoDriver(PDO::ERRMODE_EXCEPTION),
}, Dibi\DriverException::class, 'PDO connection in exception or warning error mode is not supported.'); Dibi\DriverException::class,
'PDO connection in exception or warning error mode is not supported.',
);
// PDO error mode: warning // PDO error mode: warning
Assert::exception(function () { Assert::exception(
buildPdoDriver(PDO::ERRMODE_WARNING); fn() => buildPdoDriver(PDO::ERRMODE_WARNING),
}, Dibi\DriverException::class, 'PDO connection in exception or warning error mode is not supported.'); Dibi\DriverException::class,
'PDO connection in exception or warning error mode is not supported.',
);
test('PDO error mode: explicitly set silent', function () { test(
buildPdoDriver(PDO::ERRMODE_SILENT); 'PDO error mode: explicitly set silent',
}); fn() => buildPdoDriver(PDO::ERRMODE_SILENT)
);

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

@@ -27,8 +27,8 @@ class MockResult extends Dibi\Result
test('', function () { test('', function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::TEXT); $result->setType('col', Type::Text);
$result->setFormat(Type::TEXT, 'native'); $result->setFormat(Type::Text, 'native');
Assert::same(['col' => null], $result->test(['col' => null])); Assert::same(['col' => null], $result->test(['col' => null]));
Assert::same(['col' => true], $result->test(['col' => true])); Assert::same(['col' => true], $result->test(['col' => true]));
@@ -38,7 +38,7 @@ test('', function () {
test('', function () { test('', function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::BOOL); $result->setType('col', Type::Bool);
Assert::same(['col' => null], $result->test(['col' => null])); Assert::same(['col' => null], $result->test(['col' => null]));
Assert::same(['col' => true], $result->test(['col' => true])); Assert::same(['col' => true], $result->test(['col' => true]));
@@ -60,7 +60,7 @@ test('', function () {
test('', function () { test('', function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::TEXT); $result->setType('col', Type::Text);
Assert::same(['col' => null], $result->test(['col' => null])); Assert::same(['col' => null], $result->test(['col' => null]));
Assert::same(['col' => '1'], $result->test(['col' => true])); Assert::same(['col' => '1'], $result->test(['col' => true]));
@@ -76,7 +76,7 @@ test('', function () {
test('', function () { test('', function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::FLOAT); $result->setType('col', Type::Float);
Assert::same(['col' => null], $result->test(['col' => null])); Assert::same(['col' => null], $result->test(['col' => null]));
Assert::same(['col' => 1.0], $result->test(['col' => true])); Assert::same(['col' => 1.0], $result->test(['col' => true]));
@@ -117,6 +117,37 @@ test('', function () {
Assert::same(['col' => '1.1e+10'], $result->test(['col' => '001.1e+10'])); Assert::same(['col' => '1.1e+10'], $result->test(['col' => '001.1e+10']));
Assert::notSame(['col' => '1.1e+1'], $result->test(['col' => '1.1e+10'])); Assert::notSame(['col' => '1.1e+1'], $result->test(['col' => '1.1e+10']));
// negative
Assert::same(['col' => -0.0], $result->test(['col' => '-']));
Assert::same(['col' => -0.0], $result->test(['col' => '-0']));
Assert::same(['col' => -1.0], $result->test(['col' => '-1']));
Assert::same(['col' => -0.0], $result->test(['col' => '-.0']));
Assert::same(['col' => -0.1], $result->test(['col' => '-.1']));
Assert::same(['col' => -0.0], $result->test(['col' => '-0.0']));
Assert::same(['col' => -0.1], $result->test(['col' => '-0.1']));
Assert::same(['col' => -0.0], $result->test(['col' => '-0.000']));
Assert::same(['col' => -0.1], $result->test(['col' => '-0.100']));
Assert::same(['col' => -1.0], $result->test(['col' => '-1.0']));
Assert::same(['col' => -1.1], $result->test(['col' => '-1.1']));
Assert::same(['col' => -1.0], $result->test(['col' => '-1.000']));
Assert::same(['col' => -1.1], $result->test(['col' => '-1.100']));
Assert::same(['col' => -1.0], $result->test(['col' => '-001.000']));
Assert::same(['col' => -1.1], $result->test(['col' => '-001.100']));
Assert::same(['col' => -10.0], $result->test(['col' => '-10']));
Assert::same(['col' => -11.0], $result->test(['col' => '-11']));
Assert::same(['col' => -10.0], $result->test(['col' => '-0010']));
Assert::same(['col' => -11.0], $result->test(['col' => '-0011']));
Assert::same(['col' => '-0.00000000000000000001'], $result->test(['col' => '-0.00000000000000000001']));
Assert::same(['col' => '-12345678901234567890'], $result->test(['col' => '-12345678901234567890']));
Assert::same(['col' => '-12345678901234567890'], $result->test(['col' => '-012345678901234567890']));
Assert::same(['col' => '-12345678901234567890'], $result->test(['col' => '-12345678901234567890.000']));
Assert::same(['col' => '-12345678901234567890.1'], $result->test(['col' => '-012345678901234567890.100']));
Assert::same(['col' => '-1.1e+10'], $result->test(['col' => '-1.1e+10']));
Assert::same(['col' => '-1.1e-10'], $result->test(['col' => '-1.1e-10']));
Assert::same(['col' => '-1.1e+10'], $result->test(['col' => '-001.1e+10']));
Assert::notSame(['col' => '-1.1e+1'], $result->test(['col' => '-1.1e+10']));
setlocale(LC_ALL, 'de_DE@euro', 'de_DE', 'deu_deu'); setlocale(LC_ALL, 'de_DE@euro', 'de_DE', 'deu_deu');
Assert::same(['col' => 0.0], $result->test(['col' => ''])); Assert::same(['col' => 0.0], $result->test(['col' => '']));
Assert::same(['col' => 0.0], $result->test(['col' => '0'])); Assert::same(['col' => 0.0], $result->test(['col' => '0']));
@@ -147,13 +178,45 @@ test('', function () {
Assert::same(['col' => 0.0], $result->test(['col' => 0.0])); Assert::same(['col' => 0.0], $result->test(['col' => 0.0]));
Assert::same(['col' => 1.0], $result->test(['col' => 1])); Assert::same(['col' => 1.0], $result->test(['col' => 1]));
Assert::same(['col' => 1.0], $result->test(['col' => 1.0])); Assert::same(['col' => 1.0], $result->test(['col' => 1.0]));
// Same but negative
Assert::same(['col' => -0.0], $result->test(['col' => '-']));
Assert::same(['col' => -0.0], $result->test(['col' => '-0']));
Assert::same(['col' => -1.0], $result->test(['col' => '-1']));
Assert::same(['col' => -0.0], $result->test(['col' => '-.0']));
Assert::same(['col' => -0.1], $result->test(['col' => '-.1']));
Assert::same(['col' => -0.0], $result->test(['col' => '-0.0']));
Assert::same(['col' => -0.1], $result->test(['col' => '-0.1']));
Assert::same(['col' => -0.0], $result->test(['col' => '-0.000']));
Assert::same(['col' => -0.1], $result->test(['col' => '-0.100']));
Assert::same(['col' => -1.0], $result->test(['col' => '-1.0']));
Assert::same(['col' => -1.1], $result->test(['col' => '-1.1']));
Assert::same(['col' => -1.0], $result->test(['col' => '-1.000']));
Assert::same(['col' => -1.1], $result->test(['col' => '-1.100']));
Assert::same(['col' => -1.0], $result->test(['col' => '-001.000']));
Assert::same(['col' => -1.1], $result->test(['col' => '-001.100']));
Assert::same(['col' => -10.0], $result->test(['col' => '-10']));
Assert::same(['col' => -11.0], $result->test(['col' => '-11']));
Assert::same(['col' => -10.0], $result->test(['col' => '-0010']));
Assert::same(['col' => -11.0], $result->test(['col' => '-0011']));
Assert::same(['col' => '-0.00000000000000000001'], $result->test(['col' => '-0.00000000000000000001']));
Assert::same(['col' => '-12345678901234567890'], $result->test(['col' => '-12345678901234567890']));
Assert::same(['col' => '-12345678901234567890'], $result->test(['col' => '-012345678901234567890']));
Assert::same(['col' => '-12345678901234567890'], $result->test(['col' => '-12345678901234567890.000']));
Assert::same(['col' => '-12345678901234567890.1'], $result->test(['col' => '-012345678901234567890.100']));
Assert::same(['col' => -0.0], $result->test(['col' => -0]));
Assert::same(['col' => -0.0], $result->test(['col' => -0.0]));
Assert::same(['col' => -1.0], $result->test(['col' => -1]));
Assert::same(['col' => -1.0], $result->test(['col' => -1.0]));
setlocale(LC_NUMERIC, 'C'); setlocale(LC_NUMERIC, 'C');
}); });
test('', function () { test('', function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::INTEGER); $result->setType('col', Type::Integer);
Assert::same(['col' => null], $result->test(['col' => null])); Assert::same(['col' => null], $result->test(['col' => null]));
Assert::same(['col' => 1], $result->test(['col' => true])); Assert::same(['col' => 1], $result->test(['col' => true]));
@@ -162,9 +225,10 @@ test('', function () {
if (PHP_VERSION_ID < 80000) { if (PHP_VERSION_ID < 80000) {
Assert::same(['col' => 0], @$result->test(['col' => ''])); // triggers warning since PHP 7.1 Assert::same(['col' => 0], @$result->test(['col' => ''])); // triggers warning since PHP 7.1
} else { } else {
Assert::exception(function () use ($result) { Assert::exception(
Assert::same(['col' => 0], $result->test(['col' => ''])); fn() => Assert::same(['col' => 0], $result->test(['col' => ''])),
}, TypeError::class); TypeError::class,
);
} }
Assert::same(['col' => 0], $result->test(['col' => '0'])); Assert::same(['col' => 0], $result->test(['col' => '0']));
@@ -186,12 +250,13 @@ test('', function () {
test('', function () { test('', function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::DATETIME); $result->setType('col', Type::DateTime);
Assert::same(['col' => null], $result->test(['col' => null])); Assert::same(['col' => null], $result->test(['col' => null]));
Assert::exception(function () use ($result) { Assert::exception(
$result->test(['col' => true]); fn() => $result->test(['col' => true]),
}, TypeError::class); TypeError::class,
);
Assert::same(['col' => null], $result->test(['col' => false])); Assert::same(['col' => null], $result->test(['col' => false]));
Assert::same(['col' => null], $result->test(['col' => ''])); Assert::same(['col' => null], $result->test(['col' => '']));
@@ -204,13 +269,14 @@ test('', function () {
test('', function () { test('', function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::DATETIME); $result->setType('col', Type::DateTime);
$result->setFormat(Type::DATETIME, 'Y-m-d H:i:s'); $result->setFormat(Type::DateTime, 'Y-m-d H:i:s');
Assert::same(['col' => null], $result->test(['col' => null])); Assert::same(['col' => null], $result->test(['col' => null]));
Assert::exception(function () use ($result) { Assert::exception(
$result->test(['col' => true]); fn() => $result->test(['col' => true]),
}, TypeError::class); TypeError::class,
);
Assert::same(['col' => null], $result->test(['col' => false])); Assert::same(['col' => null], $result->test(['col' => false]));
Assert::same(['col' => null], $result->test(['col' => ''])); Assert::same(['col' => null], $result->test(['col' => '']));
@@ -223,12 +289,13 @@ test('', function () {
test('', function () { test('', function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::DATE); $result->setType('col', Type::Date);
Assert::same(['col' => null], $result->test(['col' => null])); Assert::same(['col' => null], $result->test(['col' => null]));
Assert::exception(function () use ($result) { Assert::exception(
$result->test(['col' => true]); fn() => $result->test(['col' => true]),
}, TypeError::class); TypeError::class,
);
Assert::same(['col' => null], $result->test(['col' => false])); Assert::same(['col' => null], $result->test(['col' => false]));
Assert::same(['col' => null], $result->test(['col' => ''])); Assert::same(['col' => null], $result->test(['col' => '']));
@@ -239,12 +306,13 @@ test('', function () {
test('', function () { test('', function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::TIME); $result->setType('col', Type::Time);
Assert::same(['col' => null], $result->test(['col' => null])); Assert::same(['col' => null], $result->test(['col' => null]));
Assert::exception(function () use ($result) { Assert::exception(
$result->test(['col' => true]); fn() => $result->test(['col' => true]),
}, TypeError::class); TypeError::class,
);
Assert::same(['col' => null], $result->test(['col' => false])); Assert::same(['col' => null], $result->test(['col' => false]));
Assert::same(['col' => null], $result->test(['col' => ''])); Assert::same(['col' => null], $result->test(['col' => '']));

View File

@@ -12,7 +12,7 @@ $conn->loadFile(__DIR__ . "/data/$config[system].sql");
$res = $conn->query('SELECT * FROM [customers]'); $res = $conn->query('SELECT * FROM [customers]');
// auto-converts this column to integer // auto-converts this column to integer
$res->setType('customer_id', Dibi\Type::DATETIME); $res->setType('customer_id', Dibi\Type::DateTime);
Assert::equal(new Dibi\Row([ Assert::equal(new Dibi\Row([
'customer_id' => new Dibi\DateTime('1970-01-01 01:00:01'), 'customer_id' => new Dibi\DateTime('1970-01-01 01:00:01'),

View File

@@ -24,13 +24,17 @@ Assert::true(isset($row['title']));
// missing // missing
Assert::error(function () use ($row) { Assert::error(
$x = $row->missing; fn() => $x = $row->missing,
}, E_USER_NOTICE, "Attempt to read missing column 'missing'."); E_USER_NOTICE,
"Attempt to read missing column 'missing'.",
);
Assert::error(function () use ($row) { Assert::error(
$x = $row['missing']; fn() => $x = $row['missing'],
}, E_USER_NOTICE, "Attempt to read missing column 'missing'."); E_USER_NOTICE,
"Attempt to read missing column 'missing'.",
);
Assert::false(isset($row->missing)); Assert::false(isset($row->missing));
Assert::false(isset($row['missing'])); Assert::false(isset($row['missing']));
@@ -41,13 +45,17 @@ Assert::same(123, $row['missing'] ?? 123);
// suggestions // suggestions
Assert::error(function () use ($row) { Assert::error(
$x = $row->tilte; fn() => $x = $row->tilte,
}, E_USER_NOTICE, "Attempt to read missing column 'tilte', did you mean 'title'?"); E_USER_NOTICE,
"Attempt to read missing column 'tilte', did you mean 'title'?",
);
Assert::error(function () use ($row) { Assert::error(
$x = $row['tilte']; fn() => $x = $row['tilte'],
}, E_USER_NOTICE, "Attempt to read missing column 'tilte', did you mean 'title'?"); E_USER_NOTICE,
"Attempt to read missing column 'tilte', did you mean 'title'?",
);
// to array // to array

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), fn() => $conn->translate('SELECT 1 %ofs %lmt', 10, 10),
Dibi\NotSupportedException::class Dibi\NotSupportedException::class,
); );
} }
}; };

View File

@@ -1,151 +0,0 @@
<?php
declare(strict_types=1);
use Tester\Assert;
require __DIR__ . '/bootstrap.php';
class TestClass
{
use Dibi\Strict;
public $public;
public static $publicStatic;
protected $protected;
public function publicMethod()
{
}
public static function publicMethodStatic()
{
}
protected function protectedMethod()
{
}
protected static function protectedMethodS()
{
}
public function getBar()
{
return 123;
}
public function isFoo()
{
return 456;
}
}
class TestChild extends TestClass
{
public function callParent()
{
parent::callParent();
}
}
// calling
Assert::exception(function () {
$obj = new TestClass;
$obj->undeclared();
}, LogicException::class, 'Call to undefined method TestClass::undeclared().');
Assert::exception(function () {
TestClass::undeclared();
}, LogicException::class, 'Call to undefined static method TestClass::undeclared().');
Assert::exception(function () {
$obj = new TestChild;
$obj->callParent();
}, LogicException::class, 'Call to undefined method parent::callParent().');
Assert::exception(function () {
$obj = new TestClass;
$obj->publicMethodX();
}, LogicException::class, 'Call to undefined method TestClass::publicMethodX(), did you mean publicMethod()?');
Assert::exception(function () { // suggest static method
$obj = new TestClass;
$obj->publicMethodStaticX();
}, LogicException::class, 'Call to undefined method TestClass::publicMethodStaticX(), did you mean publicMethodStatic()?');
Assert::exception(function () { // suggest only public method
$obj = new TestClass;
$obj->protectedMethodX();
}, LogicException::class, 'Call to undefined method TestClass::protectedMethodX().');
// writing
Assert::exception(function () {
$obj = new TestClass;
$obj->undeclared = 'value';
}, LogicException::class, 'Attempt to write to undeclared property TestClass::$undeclared.');
Assert::exception(function () {
$obj = new TestClass;
$obj->publicX = 'value';
}, LogicException::class, 'Attempt to write to undeclared property TestClass::$publicX, did you mean $public?');
Assert::exception(function () { // suggest only non-static property
$obj = new TestClass;
$obj->publicStaticX = 'value';
}, LogicException::class, 'Attempt to write to undeclared property TestClass::$publicStaticX.');
Assert::exception(function () { // suggest only public property
$obj = new TestClass;
$obj->protectedX = 'value';
}, LogicException::class, 'Attempt to write to undeclared property TestClass::$protectedX.');
// property getter
$obj = new TestClass;
Assert::false(isset($obj->bar));
Assert::same(123, $obj->bar);
Assert::false(isset($obj->foo));
Assert::same(456, $obj->foo);
// reading
Assert::exception(function () {
$obj = new TestClass;
$val = $obj->undeclared;
}, LogicException::class, 'Attempt to read undeclared property TestClass::$undeclared.');
Assert::exception(function () {
$obj = new TestClass;
$val = $obj->publicX;
}, LogicException::class, 'Attempt to read undeclared property TestClass::$publicX, did you mean $public?');
Assert::exception(function () { // suggest only non-static property
$obj = new TestClass;
$val = $obj->publicStaticX;
}, LogicException::class, 'Attempt to read undeclared property TestClass::$publicStaticX.');
Assert::exception(function () { // suggest only public property
$obj = new TestClass;
$val = $obj->protectedX;
}, LogicException::class, 'Attempt to read undeclared property TestClass::$protectedX.');
// unset/isset
Assert::exception(function () {
$obj = new TestClass;
unset($obj->undeclared);
}, LogicException::class, 'Attempt to unset undeclared property TestClass::$undeclared.');
Assert::false(isset($obj->undeclared));

View File

@@ -13,13 +13,16 @@ switch ($config['system']) {
case 'mysql': case 'mysql':
Assert::equal('10:20:30.0', $translator->formatValue(new DateInterval('PT10H20M30S'), null)); Assert::equal('10:20:30.0', $translator->formatValue(new DateInterval('PT10H20M30S'), null));
Assert::equal('-1:00:00.0', $translator->formatValue(DateInterval::createFromDateString('-1 hour'), null)); Assert::equal('-1:00:00.0', $translator->formatValue(DateInterval::createFromDateString('-1 hour'), null));
Assert::exception(function () use ($translator) { Assert::exception(
$translator->formatValue(new DateInterval('P2Y4DT6H8M'), null); fn() => $translator->formatValue(new DateInterval('P2Y4DT6H8M'), null),
}, Dibi\NotSupportedException::class, 'Only time interval is supported.'); Dibi\NotSupportedException::class,
'Only time interval is supported.',
);
break; break;
default: default:
Assert::exception(function () use ($translator) { Assert::exception(
$translator->formatValue(new DateInterval('PT10H20M30S'), null); fn() => $translator->formatValue(new DateInterval('PT10H20M30S'), null),
}, Dibi\Exception::class); Dibi\Exception::class,
);
} }

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

@@ -33,7 +33,7 @@ enum PureEnum
Assert::equal('1', $translator->formatValue(EnumInt::One, null)); Assert::equal('1', $translator->formatValue(EnumInt::One, null));
Assert::equal(match ($config['driver']) { Assert::equal(match ($config['system']) {
'sqlsrv' => "N'one'", 'sqlsrv' => "N'one'",
default => "'one'", default => "'one'",
}, $translator->formatValue(EnumString::One, null)); }, $translator->formatValue(EnumString::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,26 +74,28 @@ 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
$e = Assert::exception(function () use ($conn) { $e = Assert::exception(
$conn->translate('SELECT %s', (object) [123], ', %m', 123); fn() => $conn->translate('SELECT %s', (object) [123], ', %m', 123),
}, Dibi\Exception::class, 'SQL translate error: Invalid combination of type stdClass and modifier %s'); Dibi\Exception::class,
'SQL translate error: Invalid combination of type stdClass and modifier %s',
);
Assert::same('SELECT **Invalid combination of type stdClass and modifier %s** , **Unknown or unexpected modifier %m**', $e->getSql()); Assert::same('SELECT **Invalid combination of type stdClass and modifier %s** , **Unknown or unexpected modifier %m**', $e->getSql());
Assert::same( Assert::same(
@@ -101,7 +103,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 +111,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 +119,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 +128,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 +141,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 +163,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,13 +174,14 @@ 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') {
Assert::exception(function () use ($conn) { Assert::exception(
$conn->translate('SELECT * FROM [products] %lmt %ofs', 2, 1); fn() => $conn->translate('SELECT * FROM [products] %lmt %ofs', 2, 1),
}, Dibi\Exception::class); Dibi\Exception::class,
);
} else { } else {
// with limit = 2, offset = 1 // with limit = 2, offset = 1
Assert::same( Assert::same(
@@ -186,7 +189,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 +200,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,12 +226,14 @@ 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(
$conn->translate('SELECT %s', new DateTime('1212-09-26')); fn() => $conn->translate('SELECT %s', new DateTime('1212-09-26')),
}, Dibi\Exception::class, 'SQL translate error: Invalid combination of type Dibi\DateTime and modifier %s'); Dibi\Exception::class,
'SQL translate error: Invalid combination of type Dibi\DateTime and modifier %s',
);
@@ -247,13 +252,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,49 +268,56 @@ 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]),
); );
} }
$e = Assert::exception(function () use ($conn) { $e = Assert::exception(
$conn->translate("SELECT '"); fn() => $conn->translate("SELECT '"),
}, Dibi\Exception::class, 'SQL translate error: Alone quote'); Dibi\Exception::class,
'SQL translate error: Alone quote',
);
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 +342,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 +479,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 +488,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 +509,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 +519,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 +528,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 +542,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 +550,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 +565,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 +576,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 +587,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 +598,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 +610,7 @@ Assert::same(
'top' => 2, 'top' => 2,
]), ]),
new Dibi\Expression('number < %i', 100), new Dibi\Expression('number < %i', 100),
]) ]),
); );
@@ -613,7 +635,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,42 +646,46 @@ $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(
$conn->translate('INSERT INTO %i', 'ahoj'); fn() => $conn->translate('INSERT INTO %i', 'ahoj'),
}, Dibi\Exception::class, "Expected number, 'ahoj' given."); Dibi\Exception::class,
"Expected number, 'ahoj' given.",
);
Assert::exception(function () use ($conn) { Assert::exception(
$conn->translate('INSERT INTO %f', 'ahoj'); fn() => $conn->translate('INSERT INTO %f', 'ahoj'),
}, Dibi\Exception::class, "Expected number, 'ahoj' given."); Dibi\Exception::class,
"Expected number, 'ahoj' given.",
);
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 +703,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

@@ -20,7 +20,7 @@ date_default_timezone_set('Europe/Prague');
try { try {
$config = Tester\Environment::loadData(); $config = Tester\Environment::loadData();
} catch (Throwable $e) { } catch (Throwable $e) {
$config = parse_ini_file(__DIR__ . '/../databases.ini', true); $config = parse_ini_file(__DIR__ . '/../databases.ini', process_sections: true);
$config = reset($config); $config = reset($config);
} }

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

@@ -15,41 +15,54 @@ $conn = new Dibi\Connection($config);
$conn->loadFile(__DIR__ . "/data/$config[system].sql"); $conn->loadFile(__DIR__ . "/data/$config[system].sql");
$e = Assert::exception(function () use ($conn) { $e = Assert::exception(
new Dibi\Connection([ fn() => new Dibi\Connection([
'driver' => 'mysqli', 'driver' => 'mysqli',
'host' => 'localhost', 'host' => 'localhost',
'username' => 'unknown', 'username' => 'unknown',
'password' => 'unknown', 'password' => 'unknown',
]); ]),
}, Dibi\DriverException::class); Dibi\DriverException::class,
);
Assert::null($e->getSql()); Assert::null($e->getSql());
$e = Assert::exception(function () use ($conn) { $e = Assert::exception(
$conn->query('SELECT'); fn() => $conn->query('SELECT'),
}, Dibi\DriverException::class, '%a% error in your SQL syntax;%a%', 1064); Dibi\DriverException::class,
'%a% error in your SQL syntax;%a%',
1064,
);
Assert::same('SELECT', $e->getSql()); Assert::same('SELECT', $e->getSql());
$e = Assert::exception(function () use ($conn) { $e = Assert::exception(
$conn->query('INSERT INTO products (product_id, title) VALUES (1, "New")'); fn() => $conn->query('INSERT INTO products (product_id, title) VALUES (1, "New")'),
}, Dibi\UniqueConstraintViolationException::class, "%a?%Duplicate entry '1' for key '%a?%PRIMARY'", 1062); Dibi\UniqueConstraintViolationException::class,
"%a?%Duplicate entry '1' for key '%a?%PRIMARY'",
1062,
);
Assert::same("INSERT INTO products (product_id, title) VALUES (1, 'New')", $e->getSql()); Assert::same("INSERT INTO products (product_id, title) VALUES (1, 'New')", $e->getSql());
$e = Assert::exception(function () use ($conn) { $e = Assert::exception(
$conn->query('INSERT INTO products (title) VALUES (NULL)'); fn() => $conn->query('INSERT INTO products (title) VALUES (NULL)'),
}, Dibi\NotNullConstraintViolationException::class, "%a?%Column 'title' cannot be null", 1048); Dibi\NotNullConstraintViolationException::class,
"%a?%Column 'title' cannot be null",
1048,
);
Assert::same('INSERT INTO products (title) VALUES (NULL)', $e->getSql()); Assert::same('INSERT INTO products (title) VALUES (NULL)', $e->getSql());
$e = Assert::exception(function () use ($conn) { $e = Assert::exception(
$conn->query('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)'); fn() => $conn->query('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)'),
}, Dibi\ForeignKeyConstraintViolationException::class, '%a% a foreign key constraint fails %a%', 1452); Dibi\ForeignKeyConstraintViolationException::class,
'%a% a foreign key constraint fails %a%',
1452,
);
Assert::same('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)', $e->getSql()); Assert::same('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)', $e->getSql());

View File

@@ -15,29 +15,40 @@ $conn = new Dibi\Connection($config);
$conn->loadFile(__DIR__ . "/data/$config[system].sql"); $conn->loadFile(__DIR__ . "/data/$config[system].sql");
$e = Assert::exception(function () use ($conn) { $e = Assert::exception(
$conn->query('SELECT INTO'); fn() => $conn->query('SELECT INTO'),
}, Dibi\DriverException::class, '%a?%syntax error %A%'); Dibi\DriverException::class,
'%a?%syntax error %A%',
);
Assert::same('SELECT INTO', $e->getSql()); Assert::same('SELECT INTO', $e->getSql());
$e = Assert::exception(function () use ($conn) { $e = Assert::exception(
$conn->query('INSERT INTO products (product_id, title) VALUES (1, "New")'); fn() => $conn->query('INSERT INTO products (product_id, title) VALUES (1, "New")'),
}, Dibi\UniqueConstraintViolationException::class, '%a% violates unique constraint %A%', '23505'); Dibi\UniqueConstraintViolationException::class,
'%a% violates unique constraint %A%',
'23505',
);
Assert::same("INSERT INTO products (product_id, title) VALUES (1, 'New')", $e->getSql()); Assert::same("INSERT INTO products (product_id, title) VALUES (1, 'New')", $e->getSql());
$e = Assert::exception(function () use ($conn) { $e = Assert::exception(
$conn->query('INSERT INTO products (title) VALUES (NULL)'); fn() => $conn->query('INSERT INTO products (title) VALUES (NULL)'),
}, Dibi\NotNullConstraintViolationException::class, '%a?%null value in column "title"%a%violates not-null constraint%A?%', '23502'); Dibi\NotNullConstraintViolationException::class,
'%a?%null value in column "title"%a%violates not-null constraint%A?%',
'23502',
);
Assert::same('INSERT INTO products (title) VALUES (NULL)', $e->getSql()); Assert::same('INSERT INTO products (title) VALUES (NULL)', $e->getSql());
$e = Assert::exception(function () use ($conn) { $e = Assert::exception(
$conn->query('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)'); fn() => $conn->query('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)'),
}, Dibi\ForeignKeyConstraintViolationException::class, '%a% violates foreign key constraint %A%', '23503'); Dibi\ForeignKeyConstraintViolationException::class,
'%a% violates foreign key constraint %A%',
'23503',
);
Assert::same('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)', $e->getSql()); Assert::same('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)', $e->getSql());

Some files were not shown because too many files have changed in this diff Show More