1
0
mirror of https://github.com/dg/dibi.git synced 2025-08-30 09:19:48 +02:00

Compare commits

...

32 Commits
v4.2 ... v5.0.2

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
102 changed files with 1499 additions and 1550 deletions

View File

@@ -7,7 +7,7 @@ jobs:
name: Nette Code Checker
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: 8.0
@@ -21,7 +21,7 @@ jobs:
name: Nette Coding Standard
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: 8.0

View File

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

View File

@@ -3,7 +3,7 @@ name: Tests
on: [push, pull_request]
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"
jobs:
@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3']
php: ['8.0', '8.1', '8.2', '8.3', '8.4']
fail-fast: false
@@ -43,7 +43,6 @@ jobs:
--health-retries=5
-e MYSQL_ROOT_PASSWORD=root
-e MYSQL_DATABASE=dibi_test
--entrypoint sh mysql:8 -c "exec docker-entrypoint.sh mysqld --default-authentication-plugin=mysql_native_password"
postgres96:
image: postgres:9.6
@@ -83,13 +82,13 @@ jobs:
- 1433:1433
options: >-
--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-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
@@ -101,7 +100,7 @@ jobs:
run: cp ./tests/databases.github.ini ./tests/databases.ini
- 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: vendor/bin/tester -p phpdbg tests -s -C --coverage ./coverage.xml --coverage-src ./src

View File

@@ -1,31 +1,34 @@
build: off
cache:
- c:\php7 -> appveyor.yml
- c:\php -> appveyor.yml
- '%LOCALAPPDATA%\Composer\files -> appveyor.yml'
clone_folder: c:\projects\dibi
environment:
MYSQL_PWD: Password12!
services:
- mssql2012sp1
# - mssql2014
- mysql
init:
- SET PATH=c:\php7;%PATH%
- SET PATH=c:\php;c:\Program Files\MySQL\MySQL Server 5.7\bin;%PATH%
- SET ANSICON=121x90 (121x90)
install:
# Install PHP 7.2
- IF EXIST c:\php7 (SET PHP=0) ELSE (SET PHP=1)
- IF %PHP%==1 mkdir c:\php7
- IF %PHP%==1 cd c:\php7
- IF %PHP%==1 curl https://windows.php.net/downloads/releases/archives/php-7.2.18-Win32-VC15-x64.zip --output php.zip
# Install PHP 8.0
- IF EXIST c:\php (SET PHP=0) ELSE (SET PHP=1)
- IF %PHP%==1 mkdir c:\php
- IF %PHP%==1 cd c:\php
- IF %PHP%==1 curl https://windows.php.net/downloads/releases/archives/php-8.0.1-Win32-vs16-x64.zip --output php.zip
- IF %PHP%==1 7z x php.zip >nul
- IF %PHP%==1 echo extension_dir=ext >> php.ini
- IF %PHP%==1 echo extension=php_openssl.dll >> php.ini
- 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 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
# Install Microsoft Access Database Engine x64
@@ -40,8 +43,12 @@ install:
# Create 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:
- 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:
# Print *.actual content

View File

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

View File

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

View File

@@ -28,7 +28,7 @@ $dibi->test('
SELECT COUNT(*) as [count]
FROM [comments]
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
@@ -69,7 +69,7 @@ $array = [1, 2, 3];
$dibi->test('
SELECT *
FROM people
WHERE id IN (?)', $array
WHERE id IN (?)', $array,
);
// -> SELECT * FROM people WHERE id IN ( 1, 2, 3 )

View File

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

View File

@@ -29,6 +29,6 @@ $dibi->test('
'id' => 123,
'date' => new DateTime('12.3.2007'),
'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')

View File

@@ -54,6 +54,6 @@ define('SUBST_ACTIVE', 7);
$dibi->test("
UPDATE :account:user
SET name='John Doe', status=:active:
WHERE id=", 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
```
The Dibi 4.2 requires PHP version 7.2 and supports PHP up to 8.3.
The Dibi 5.0 requires PHP version 8.0 and supports PHP up to 8.4.
Usage
@@ -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.
```php
$result->setType('id', Dibi\Type::INTEGER); // id will be integer
$result->setType('id', Dibi\Type::Integer); // id will be integer
$row = $result->fetch();
is_int($row->id) // true
@@ -639,7 +639,7 @@ In the configuration file, we will register the DI extensions and add the `dibi`
```neon
extensions:
dibi: Dibi\Bridges\Nette\DibiExtension22
dibi: Dibi\Bridges\Nette\DibiExtension3
dibi:
host: localhost

View File

@@ -19,11 +19,8 @@ use Tracy;
*/
class DibiExtension22 extends Nette\DI\CompilerExtension
{
/** @var bool|null */
private $debugMode;
/** @var bool|null */
private $cliMode;
private ?bool $debugMode;
private ?bool $cliMode;
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)) {
$connection->addSetup(
[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'.
# Requires Nette Framework 2.2 or later
# Requires Nette Framework 3 or later
extensions:
dibi: Dibi\Bridges\Nette\DibiExtension22
dibi: Dibi\Bridges\Nette\DibiExtension3
dibi:
host: localhost

View File

@@ -20,22 +20,13 @@ use Tracy;
*/
class Panel implements Tracy\IBarPanel
{
use Dibi\Strict;
/** @var int maximum SQL length */
public static $maxLength = 1000;
/** @var bool|string explain queries? */
public $explain;
/** @var int */
public $filter;
/** @var array */
private $events = [];
public static int $maxLength = 1000;
public bool|string $explain;
public int $filter;
private array $events = [];
public function __construct($explain = true, ?int $filter = null)
public function __construct(bool $explain = true, ?int $filter = null)
{
$this->filter = $filter ?: Event::QUERY;
$this->explain = $explain;
@@ -71,7 +62,7 @@ class Panel implements Tracy\IBarPanel
if ($e instanceof Dibi\Exception && $e->getSql()) {
return [
'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
: ($connection->getConfig('driver') === 'oracle' ? 'EXPLAIN PLAN FOR' : 'EXPLAIN');
try {
$explain = @Helpers::dump($connection->nativeQuery("$cmd $event->sql"), true);
$explain = @Helpers::dump($connection->nativeQuery("$cmd $event->sql"), return: true);
} 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 .= '</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) {
$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 tr table { margin: 8px 0; max-height: 150px; overflow:auto } </style>
<h1>Queries:' . "\u{a0}" . count($this->events)
. ($totalTime === null ? '' : ", time:\u{a0}" . number_format($totalTime * 1000, 1, '.', "\u{202f}") . "\u{202f}ms") . ', '
. htmlspecialchars($this->getConnectionName($singleConnection)) . '</h1>
. ($totalTime === null ? '' : ", time:\u{a0}" . number_format($totalTime * 1000, 1, '.', "\u{202f}") . "\u{202f}ms")
. ($singleConnection === null ? '' : ', ' . htmlspecialchars($this->getConnectionName($singleConnection))) . '</h1>
<div class="tracy-inner tracy-DibiProfiler">
<table class="tracy-sortable">
<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
{
$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('host') ? "\u{202f}@\u{202f}" . $connection->getConfig('host') : '');
}

View File

@@ -9,6 +9,7 @@ declare(strict_types=1);
namespace Dibi;
use JetBrains\PhpStorm\Language;
use Traversable;
@@ -20,27 +21,20 @@ use Traversable;
*/
class Connection implements IConnection
{
use Strict;
/** @var array of function (Event $event); Occurs after query is executed */
public $onEvent = [];
/** @var array Current connection configuration */
private $config;
/** function (Event $event); Occurs after query is executed */
public ?array $onEvent = [];
private array $config;
/** @var string[] resultset formats */
private $formats;
private array $formats;
private ?Driver $driver = null;
private ?Translator $translator = null;
/** @var Driver|null */
private $driver;
/** @var Translator|null */
private $translator;
/** @var HashMap Substitutes for identifiers */
private $substitutes;
private $transactionDepth = 0;
/** @var array<string, callable(object): Expression | null> */
private array $translators = [];
private bool $sortTranslators = false;
private HashMap $substitutes;
private int $transactionDepth = 0;
/**
@@ -75,15 +69,15 @@ class Connection implements IConnection
Helpers::alias($config, 'host', 'hostname');
Helpers::alias($config, 'result|formatDate', 'resultDate');
Helpers::alias($config, 'result|formatDateTime', 'resultDateTime');
$config['driver'] = $config['driver'] ?? 'mysqli';
$config['driver'] ??= 'mysqli';
$config['name'] = $name;
$this->config = $config;
$this->formats = [
Type::DATE => $this->config['result']['formatDate'],
Type::DATETIME => $this->config['result']['formatDateTime'],
Type::Date => $this->config['result']['formatDate'],
Type::DateTime => $this->config['result']['formatDateTime'],
Type::JSON => $this->config['result']['formatJson'] ?? 'array',
Type::TIME_INTERVAL => $this->config['result']['formatTimeInterval'] ?? null,
Type::TimeInterval => $this->config['result']['formatTimeInterval'] ?? null,
];
// profiler
@@ -93,7 +87,7 @@ class Connection implements IConnection
$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'])) {
foreach ($config['substitutes'] as $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.
* @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
? $this->config
@@ -215,10 +208,9 @@ class Connection implements IConnection
/**
* Generates (translates) and executes SQL query.
* @param mixed ...$args
* @throws Exception
*/
final public function query(...$args): Result
final public function query(#[Language('GenericSQL')] mixed ...$args): Result
{
return $this->nativeQuery($this->translate(...$args));
}
@@ -226,10 +218,9 @@ class Connection implements IConnection
/**
* Generates SQL query.
* @param mixed ...$args
* @throws Exception
*/
final public function translate(...$args): string
final public function translate(#[Language('GenericSQL')] mixed ...$args): string
{
if (!$this->driver) {
$this->connect();
@@ -241,9 +232,8 @@ class Connection implements IConnection
/**
* Generates and prints SQL query.
* @param mixed ...$args
*/
final public function test(...$args): bool
final public function test(#[Language('GenericSQL')] mixed ...$args): bool
{
try {
Helpers::dump($this->translate(...$args));
@@ -253,7 +243,7 @@ class Connection implements IConnection
if ($e->getSql()) {
Helpers::dump($e->getSql());
} else {
echo get_class($e) . ': ' . $e->getMessage() . (PHP_SAPI === 'cli' ? "\n" : '<br>');
echo $e::class . ': ' . $e->getMessage() . (PHP_SAPI === 'cli' ? "\n" : '<br>');
}
return false;
@@ -263,10 +253,9 @@ class Connection implements IConnection
/**
* Generates (translates) and returns SQL query as DataSource.
* @param mixed ...$args
* @throws Exception
*/
final public function dataSource(...$args): DataSource
final public function dataSource(#[Language('GenericSQL')] mixed ...$args): DataSource
{
return new DataSource($this->translate(...$args), $this);
}
@@ -276,7 +265,7 @@ class Connection implements IConnection
* Executes the SQL query.
* @throws Exception
*/
final public function nativeQuery(string $sql): Result
final public function nativeQuery(#[Language('SQL')] string $sql): Result
{
if (!$this->driver) {
$this->connect();
@@ -429,10 +418,7 @@ class Connection implements IConnection
}
/**
* @return mixed
*/
public function transaction(callable $callback)
public function transaction(callable $callback): mixed
{
if ($this->transactionDepth === 0) {
$this->begin();
@@ -527,9 +513,77 @@ class Connection implements IConnection
*/
public function substitute(string $value): string
{
return strpos($value, ':') === false
? $value
: preg_replace_callback('#:([^:\s]*):#', function (array $m) { return $this->substitutes->{$m[1]}; }, $value);
return str_contains($value, ':')
? preg_replace_callback('#:([^:\s]*):#', fn(array $m) => $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().
* @param mixed ...$args
* @throws Exception
*/
public function fetch(...$args): ?Row
public function fetch(#[Language('GenericSQL')] mixed ...$args): ?Row
{
return $this->query($args)->fetch();
}
@@ -549,11 +602,10 @@ class Connection implements IConnection
/**
* Executes SQL query and fetch results - shortcut for query() & fetchAll().
* @param mixed ...$args
* @return Row[]|array[]
* @throws Exception
*/
public function fetchAll(...$args): array
public function fetchAll(#[Language('GenericSQL')] mixed ...$args): array
{
return $this->query($args)->fetchAll();
}
@@ -561,11 +613,9 @@ class Connection implements IConnection
/**
* Executes SQL query and fetch first column - shortcut for query() & fetchSingle().
* @param mixed ...$args
* @return mixed
* @throws Exception
*/
public function fetchSingle(...$args)
public function fetchSingle(#[Language('GenericSQL')] mixed ...$args): mixed
{
return $this->query($args)->fetchSingle();
}
@@ -573,10 +623,9 @@ class Connection implements IConnection
/**
* Executes SQL query and fetch pairs - shortcut for query() & fetchPairs().
* @param mixed ...$args
* @throws Exception
*/
public function fetchPairs(...$args): array
public function fetchPairs(#[Language('GenericSQL')] mixed ...$args): array
{
return $this->query($args)->fetchPairs();
}

View File

@@ -15,37 +15,16 @@ namespace Dibi;
*/
class DataSource implements IDataSource
{
use Strict;
/** @var Connection */
private $connection;
/** @var string */
private $sql;
/** @var Result|null */
private $result;
/** @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;
private Connection $connection;
private string $sql;
private ?Result $result = null;
private ?int $count = null;
private ?int $totalCount = null;
private array $cols = [];
private array $sorting = [];
private array $conds = [];
private ?int $offset = null;
private ?int $limit = null;
/**
@@ -65,7 +44,7 @@ class DataSource implements IDataSource
* @param string|array $col column name or array of column names
* @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)) {
$this->cols = $col;
@@ -81,7 +60,7 @@ class DataSource implements IDataSource
/**
* Adds conditions to query.
*/
public function where($cond): self
public function where($cond): static
{
$this->conds[] = is_array($cond)
? $cond // TODO: not consistent with select and orderBy
@@ -95,7 +74,7 @@ class DataSource implements IDataSource
* Selects columns to order by.
* @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)) {
$this->sorting = $row;
@@ -111,7 +90,7 @@ class DataSource implements IDataSource
/**
* 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->offset = $offset;
@@ -161,7 +140,7 @@ class DataSource implements IDataSource
* Like fetch(), but returns only first field.
* @return mixed value on success, null if no next record
*/
public function fetchSingle()
public function fetchSingle(): mixed
{
return $this->getResult()->fetchSingle();
}
@@ -229,24 +208,19 @@ class DataSource implements IDataSource
*/
public function __toString(): string
{
try {
return $this->connection->translate(
"\nSELECT %n",
(empty($this->cols) ? '*' : $this->cols),
"\nFROM %SQL",
$this->sql,
"\n%ex",
$this->conds ? ['WHERE %and', $this->conds] : null,
"\n%ex",
$this->sorting ? ['ORDER BY %by', $this->sorting] : null,
"\n%ofs %lmt",
$this->offset,
$this->limit
);
} catch (\Throwable $e) {
trigger_error($e->getMessage(), E_USER_ERROR);
return '';
}
return $this->connection->translate(
"\nSELECT %n",
(empty($this->cols) ? '*' : $this->cols),
"\nFROM %SQL",
$this->sql,
"\n%ex",
$this->conds ? ['WHERE %and', $this->conds] : null,
"\n%ex",
$this->sorting ? ['ORDER BY %by', $this->sorting] : null,
"\n%ofs %lmt",
$this->offset,
$this->limit,
);
}
@@ -261,7 +235,7 @@ class DataSource implements IDataSource
if ($this->count === null) {
$this->count = $this->conds || $this->offset || $this->limit
? Helpers::intVal($this->connection->nativeQuery(
'SELECT COUNT(*) FROM (' . $this->__toString() . ') t'
'SELECT COUNT(*) FROM (' . $this->__toString() . ') t',
)->fetchSingle())
: $this->getTotalCount();
}
@@ -277,7 +251,7 @@ class DataSource implements IDataSource
{
if ($this->totalCount === null) {
$this->totalCount = Helpers::intVal($this->connection->nativeQuery(
'SELECT COUNT(*) FROM ' . $this->sql
'SELECT COUNT(*) FROM ' . $this->sql,
)->fetchSingle());
}

View File

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

View File

@@ -17,8 +17,6 @@ use Dibi;
*/
class DummyDriver implements Dibi\Driver, Dibi\ResultDriver, Dibi\Reflector
{
use Dibi\Strict;
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;
}
@@ -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
{
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 */
private $connection;
/** @var resource|null */
/** @var ?resource */
private $transaction;
/** @var bool */
private $inTransaction = false;
private bool $inTransaction = false;
/** @throws Dibi\NotSupportedException */
@@ -191,7 +190,7 @@ class FirebirdDriver implements Dibi\Driver
* Returns the connection resource.
* @return resource|null
*/
public function getResource()
public function getResource(): mixed
{
return is_resource($this->connection) ? $this->connection : null;
}

View File

@@ -17,10 +17,7 @@ use Dibi;
*/
class FirebirdReflector implements Dibi\Reflector
{
use Dibi\Strict;
/** @var Dibi\Driver */
private $driver;
private Dibi\Driver $driver;
public function __construct(Dibi\Driver $driver)
@@ -242,7 +239,7 @@ class FirebirdReflector implements Dibi\Reflector
END AS TRIGGER_ENABLED
FROM RDB\$TRIGGERS
WHERE RDB\$SYSTEM_FLAG = 0"
. ($table === null ? ';' : " AND RDB\$RELATION_NAME = UPPER('$table');")
. ($table === null ? ';' : " AND RDB\$RELATION_NAME = UPPER('$table');"),
);
$triggers = [];
while ($row = $res->fetch(true)) {

View File

@@ -18,8 +18,6 @@ use Dibi\Helpers;
*/
class FirebirdResult implements Dibi\ResultDriver
{
use Dibi\Strict;
/** @var resource */
private $resultSet;
@@ -89,7 +87,7 @@ class FirebirdResult implements Dibi\ResultDriver
* Returns the result set resource.
* @return resource|null
*/
public function getResultResource()
public function getResultResource(): mixed
{
return is_resource($this->resultSet) ? $this->resultSet : null;
}

View File

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

View File

@@ -32,19 +32,21 @@ use Dibi;
*/
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 $connection;
/** @var bool Is buffered (seekable and countable)? */
private $buffered;
private \mysqli $connection;
private bool $buffered = false;
/** @throws Dibi\NotSupportedException */
@@ -96,7 +98,7 @@ class MySqliDriver implements Dibi\Driver
$config['database'] ?? '',
$config['port'] ?? 0,
$config['socket'],
$config['flags'] ?? 0
$config['flags'] ?? 0,
);
if ($this->connection->connect_errno) {
@@ -159,10 +161,7 @@ class MySqliDriver implements Dibi\Driver
}
/**
* @param int|string $code
*/
public static function createException(string $message, $code, string $sql): Dibi\DriverException
public static function createException(string $message, int|string $code, string $sql): Dibi\DriverException
{
if (in_array($code, [1216, 1217, 1451, 1452, 1701], true)) {
return new Dibi\ForeignKeyConstraintViolationException($message, $code, $sql);

View File

@@ -17,13 +17,8 @@ use Dibi;
*/
class MySqliResult implements Dibi\ResultDriver
{
use Dibi\Strict;
/** @var \mysqli_result */
private $resultSet;
/** @var bool Is buffered (seekable and countable)? */
private $buffered;
private \mysqli_result $resultSet;
private bool $buffered;
public function __construct(\mysqli_result $resultSet, bool $buffered)
@@ -108,7 +103,7 @@ class MySqliResult implements Dibi\ResultDriver
'table' => $row['orgtable'],
'fullname' => $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name'],
'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,
];
}

View File

@@ -17,10 +17,7 @@ use Dibi;
*/
class NoDataResult implements Dibi\ResultDriver
{
use Dibi\Strict;
/** @var int */
private $rows;
private 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;
}

View File

@@ -25,16 +25,10 @@ use Dibi;
*/
class OdbcDriver implements Dibi\Driver
{
use Dibi\Strict;
/** @var resource */
private $connection;
/** @var int|null Affected rows */
private $affectedRows;
/** @var bool */
private $microseconds = true;
private ?int $affectedRows;
private bool $microseconds = true;
/** @throws Dibi\NotSupportedException */
@@ -125,7 +119,7 @@ class OdbcDriver implements Dibi\Driver
*/
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));
}
}
@@ -141,7 +135,7 @@ class OdbcDriver implements Dibi\Driver
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));
}
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.
* @return resource|null
*/
public function getResource()
public function getResource(): mixed
{
return is_resource($this->connection) ? $this->connection : null;
}

View File

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

View File

@@ -17,13 +17,9 @@ use Dibi;
*/
class OdbcResult implements Dibi\ResultDriver
{
use Dibi\Strict;
/** @var resource */
private $resultSet;
/** @var int Cursor */
private $row = 0;
private int $row = 0;
/**
@@ -113,7 +109,7 @@ class OdbcResult implements Dibi\ResultDriver
* Returns the result set resource.
* @return resource|null
*/
public function getResultResource()
public function getResultResource(): mixed
{
return is_resource($this->resultSet) ? $this->resultSet : null;
}

View File

@@ -27,19 +27,11 @@ use Dibi;
*/
class OracleDriver implements Dibi\Driver
{
use Dibi\Strict;
/** @var resource */
private $connection;
/** @var bool */
private $autocommit = true;
/** @var bool use native datetime format */
private $nativeDate;
/** @var int|null Number of affected rows */
private $affectedRows;
private bool $autocommit = true;
private bool $nativeDate;
private ?int $affectedRows;
/** @throws Dibi\NotSupportedException */
@@ -188,7 +180,7 @@ class OracleDriver implements Dibi\Driver
* Returns the connection resource.
* @return resource|null
*/
public function getResource()
public function getResource(): mixed
{
return is_resource($this->connection) ? $this->connection : null;
}

View File

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

View File

@@ -17,8 +17,6 @@ use Dibi;
*/
class OracleResult implements Dibi\ResultDriver
{
use Dibi\Strict;
/** @var resource */
private $resultSet;
@@ -82,7 +80,7 @@ class OracleResult implements Dibi\ResultDriver
'name' => oci_field_name($this->resultSet, $i),
'table' => null,
'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,
];
}
@@ -95,7 +93,7 @@ class OracleResult implements Dibi\ResultDriver
* Returns the result set resource.
* @return resource|null
*/
public function getResultResource()
public function getResultResource(): mixed
{
return is_resource($this->resultSet) ? $this->resultSet : null;
}

View File

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

View File

@@ -19,13 +19,8 @@ use PDO;
*/
class PdoResult implements Dibi\ResultDriver
{
use Dibi\Strict;
/** @var \PDOStatement|null */
private $resultSet;
/** @var string */
private $driverName;
private ?\PDOStatement $resultSet;
private string $driverName;
public function __construct(\PDOStatement $resultSet, string $driverName)
@@ -95,7 +90,7 @@ class PdoResult implements Dibi\ResultDriver
'name' => $row['name'],
'table' => $row['table'],
'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'],
'vendor' => $row,
];

View File

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

View File

@@ -17,13 +17,8 @@ use Dibi;
*/
class PostgreReflector implements Dibi\Reflector
{
use Dibi\Strict;
/** @var Dibi\Driver */
private $driver;
/** @var string */
private $version;
private Dibi\Driver $driver;
private 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
{
$_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("
SELECT *
@@ -103,7 +91,8 @@ class PostgreReflector implements Dibi\Reflector
a.atttypmod-4 AS character_maximum_length,
NOT a.attnotnull AS is_nullable,
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
pg_attribute a
JOIN pg_type ON a.atttypid = pg_type.oid
@@ -128,7 +117,7 @@ class PostgreReflector implements Dibi\Reflector
'size' => $size > 0 ? $size : null,
'nullable' => $row['is_nullable'] === 'YES' || $row['is_nullable'] === 't' || $row['is_nullable'] === true,
'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,
];
}

View File

@@ -19,8 +19,6 @@ use PgSql;
*/
class PostgreResult implements Dibi\ResultDriver
{
use Dibi\Strict;
/** @var resource|PgSql\Result */
private $resultSet;
@@ -98,7 +96,7 @@ class PostgreResult implements Dibi\ResultDriver
* Returns the result set resource.
* @return resource|PgSql\Result|null
*/
public function getResultResource()
public function getResultResource(): mixed
{
return is_resource($this->resultSet) || $this->resultSet instanceof PgSql\Result
? $this->resultSet

View File

@@ -25,16 +25,9 @@ use SQLite3;
*/
class SqliteDriver implements Dibi\Driver
{
use Dibi\Strict;
/** @var SQLite3 */
private $connection;
/** @var string Date format */
private $fmtDate;
/** @var string Datetime format */
private $fmtDateTime;
private SQLite3 $connection;
private string $fmtDate;
private string $fmtDateTime;
/** @throws Dibi\NotSupportedException */
@@ -102,19 +95,19 @@ class SqliteDriver implements Dibi\Driver
if ($code !== 19) {
return new Dibi\DriverException($message, $code, $sql);
} elseif (strpos($message, 'must be unique') !== false
|| strpos($message, 'is not unique') !== false
|| strpos($message, 'UNIQUE constraint failed') !== false
} elseif (str_contains($message, 'must be unique')
|| str_contains($message, 'is not unique')
|| str_contains($message, 'UNIQUE constraint failed')
) {
return new Dibi\UniqueConstraintViolationException($message, $code, $sql);
} elseif (strpos($message, 'may not be null') !== false
|| strpos($message, 'NOT NULL constraint failed') !== false
} elseif (str_contains($message, 'may not be null')
|| str_contains($message, 'NOT NULL constraint failed')
) {
return new Dibi\NotNullConstraintViolationException($message, $code, $sql);
} elseif (strpos($message, 'foreign key constraint failed') !== false
|| strpos($message, 'FOREIGN KEY constraint failed') !== false
} elseif (str_contains($message, 'foreign key constraint failed')
|| str_contains($message, 'FOREIGN KEY constraint failed')
) {
return new Dibi\ForeignKeyConstraintViolationException($message, $code, $sql);
@@ -291,7 +284,7 @@ class SqliteDriver implements Dibi\Driver
string $name,
callable $rowCallback,
callable $agrCallback,
int $numArgs = -1
int $numArgs = -1,
): void
{
$this->connection->createAggregate($name, $rowCallback, $agrCallback, $numArgs);

View File

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

View File

@@ -18,10 +18,7 @@ use Dibi\Helpers;
*/
class SqliteResult implements Dibi\ResultDriver
{
use Dibi\Strict;
/** @var \SQLite3Result */
private $resultSet;
private \SQLite3Result $resultSet;
public function __construct(\SQLite3Result $resultSet)
@@ -76,7 +73,7 @@ class SqliteResult implements Dibi\ResultDriver
{
$count = $this->resultSet->numColumns();
$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++) {
$columns[] = [
'name' => $this->resultSet->columnName($i),

View File

@@ -27,16 +27,10 @@ use Dibi\Helpers;
*/
class SqlsrvDriver implements Dibi\Driver
{
use Dibi\Strict;
/** @var resource */
private $connection;
/** @var int|null Affected rows */
private $affectedRows;
/** @var string */
private $version = '';
private ?int $affectedRows;
private string $version = '';
/** @throws Dibi\NotSupportedException */
@@ -60,7 +54,7 @@ class SqlsrvDriver implements Dibi\Driver
$options = $config['options'];
// Default values
$options['CharacterSet'] = $options['CharacterSet'] ?? 'UTF-8';
$options['CharacterSet'] ??= 'UTF-8';
$options['PWD'] = (string) $options['PWD'];
$options['UID'] = (string) $options['UID'];
$options['Database'] = (string) $options['Database'];
@@ -170,7 +164,7 @@ class SqlsrvDriver implements Dibi\Driver
* Returns the connection resource.
* @return resource|null
*/
public function getResource()
public function getResource(): mixed
{
return is_resource($this->connection) ? $this->connection : null;
}

View File

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

View File

@@ -17,8 +17,6 @@ use Dibi;
*/
class SqlsrvResult implements Dibi\ResultDriver
{
use Dibi\Strict;
/** @var resource */
private $resultSet;
@@ -91,7 +89,7 @@ class SqlsrvResult implements Dibi\ResultDriver
* Returns the result set resource.
* @return resource|null
*/
public function getResultResource()
public function getResultResource(): mixed
{
return is_resource($this->resultSet) ? $this->resultSet : null;
}

View File

@@ -15,8 +15,6 @@ namespace Dibi;
*/
class Event
{
use Strict;
/** event type */
public const
CONNECT = 1,
@@ -31,26 +29,13 @@ class Event
TRANSACTION = 448, // BEGIN | COMMIT | ROLLBACK
ALL = 1023;
/** @var Connection */
public $connection;
/** @var int */
public $type;
/** @var string */
public $sql;
/** @var Result|DriverException|null */
public $result;
/** @var float */
public $time;
/** @var int|null */
public $count;
/** @var array|null */
public $source;
public Connection $connection;
public int $type;
public string $sql;
public Result|DriverException|null $result;
public float $time;
public ?int $count = null;
public ?array $source = null;
public function __construct(Connection $connection, int $type, ?string $sql = null)
@@ -61,7 +46,7 @@ class Event
$this->time = -microtime(true);
if ($type === self::QUERY && preg_match('#\(?\s*(SELECT|UPDATE|INSERT|DELETE)#iA', $this->sql, $matches)) {
static $types = [
$types = [
'SELECT' => self::SELECT, 'UPDATE' => self::UPDATE,
'INSERT' => self::INSERT, 'DELETE' => self::DELETE,
];
@@ -73,7 +58,7 @@ class Event
if (
isset($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']];
break;
@@ -86,10 +71,7 @@ class Event
}
/**
* @param Result|DriverException|null $result
*/
public function done($result = null): self
public function done(Result|DriverException|null $result = null): static
{
$this->result = $result;
try {

View File

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

View File

@@ -45,12 +45,15 @@ namespace Dibi;
*/
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 $masks = [
public static array $masks = [
'SELECT' => ['SELECT', 'DISTINCT', 'FROM', 'WHERE', 'GROUP BY',
'HAVING', 'ORDER BY', 'LIMIT', 'OFFSET', ],
'UPDATE' => ['UPDATE', 'SET', 'WHERE', 'ORDER BY', 'LIMIT'],
@@ -58,8 +61,8 @@ class Fluent implements IDataSource
'DELETE' => ['DELETE', 'FROM', 'USING', 'WHERE', 'ORDER BY', 'LIMIT'],
];
/** @var array default modifiers for arrays */
public static $modifiers = [
/** default modifiers for arrays */
public static array $modifiers = [
'SELECT' => '%n',
'FROM' => '%n',
'IN' => '%in',
@@ -71,8 +74,8 @@ class Fluent implements IDataSource
'GROUP BY' => '%by',
];
/** @var array clauses separators */
public static $separators = [
/** clauses separators */
public static array $separators = [
'SELECT' => ',',
'FROM' => ',',
'WHERE' => 'AND',
@@ -86,41 +89,30 @@ class Fluent implements IDataSource
'INTO' => false,
];
/** @var array clauses */
public static $clauseSwitches = [
/** clauses */
public static array $clauseSwitches = [
'JOIN' => 'FROM',
'INNER JOIN' => 'FROM',
'LEFT JOIN' => 'FROM',
'RIGHT JOIN' => 'FROM',
];
/** @var Connection */
private $connection;
/** @var array */
private $setups = [];
/** @var string|null */
private $command;
/** @var array */
private $clauses = [];
/** @var array */
private $flags = [];
/** @var array|null */
private Connection $connection;
private array $setups = [];
private ?string $command = null;
private array $clauses = [];
private array $flags = [];
private $cursor;
/** @var HashMap normalized clauses */
private static $normalizer;
/** normalized clauses */
private static HashMap $normalizer;
public function __construct(Connection $connection)
{
$this->connection = $connection;
if (self::$normalizer === null) {
if (!isset(self::$normalizer)) {
self::$normalizer = new HashMap([self::class, '_formatClause']);
}
}
@@ -129,7 +121,7 @@ class Fluent implements IDataSource
/**
* 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;
@@ -154,7 +146,7 @@ class Fluent implements IDataSource
$this->cursor = &$this->clauses[$clause];
// TODO: really delete?
if ($args === [self::REMOVE]) {
if ($args === [self::Remove]) {
$this->cursor = null;
return $this;
}
@@ -170,7 +162,7 @@ class Fluent implements IDataSource
}
} else {
// append to currect flow
if ($args === [self::REMOVE]) {
if ($args === [self::Remove]) {
return $this;
}
@@ -216,7 +208,7 @@ class Fluent implements IDataSource
/**
* Switch to a clause.
*/
public function clause(string $clause): self
public function clause(string $clause): static
{
$this->cursor = &$this->clauses[self::$normalizer->$clause];
if ($this->cursor === null) {
@@ -230,7 +222,7 @@ class Fluent implements IDataSource
/**
* Removes a clause.
*/
public function removeClause(string $clause): self
public function removeClause(string $clause): static
{
$this->clauses[self::$normalizer->$clause] = null;
return $this;
@@ -240,7 +232,7 @@ class Fluent implements IDataSource
/**
* 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);
if ($value) {
@@ -280,7 +272,7 @@ class Fluent implements IDataSource
/**
* Adds Result setup.
*/
public function setupResult(string $method): self
public function setupResult(string $method): static
{
$this->setups[] = func_get_args();
return $this;
@@ -292,28 +284,25 @@ class Fluent implements IDataSource
/**
* 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
*/
public function execute(?string $return = null)
public function execute(?string $return = null): Result|int|null
{
$res = $this->query($this->_export());
switch ($return) {
case \dibi::IDENTIFIER:
return $this->connection->getInsertId();
case \dibi::AFFECTED_ROWS:
return $this->connection->getAffectedRows();
default:
return $res;
}
return match ($return) {
self::Identifier => $this->connection->getInsertId(),
self::AffectedRows => $this->connection->getAffectedRows(),
default => $res,
};
}
/**
* 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']
? $this->query($this->_export(null, ['%lmt', 1]))->fetch()
@@ -323,9 +312,9 @@ class Fluent implements IDataSource
/**
* 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']
? $this->query($this->_export(null, ['%lmt', 1]))->fetchSingle()
@@ -413,12 +402,7 @@ class Fluent implements IDataSource
*/
final public function __toString(): string
{
try {
return $this->connection->translate($this->_export());
} catch (\Throwable $e) {
trigger_error($e->getMessage(), E_USER_ERROR);
return '';
}
return $this->connection->translate($this->_export());
}

View File

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

View File

@@ -12,21 +12,17 @@ namespace Dibi;
class Helpers
{
use Strict;
/** @var HashMap */
private static $types;
private static HashMap $types;
/**
* 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();
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;
foreach ($sql as $i => $row) {
if ($i === 0) {
@@ -75,8 +71,8 @@ class Helpers
$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';
static $keywords2 = 'ALL|DISTINCT|DISTINCTROW|IGNORE|AS|USING|ON|AND|OR|IN|IS|NOT|NULL|LIKE|RLIKE|REGEXP|TRUE|FALSE';
$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';
$keywords2 = 'ALL|DISTINCT|DISTINCTROW|IGNORE|AS|USING|ON|AND|OR|IN|IS|NOT|NULL|LIKE|RLIKE|REGEXP|TRUE|FALSE';
// insert new lines
$sql = " $sql ";
@@ -91,7 +87,7 @@ class Helpers
// syntax highlight
$highlighter = "#(/\\*.+?\\*/)|(\\*\\*.+?\\*\\*)|(?<=[\\s,(])($keywords1)(?=[\\s,)])|(?<=[\\s,(=])($keywords2)(?=[\\s,)=])#is";
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) {
if (!empty($m[1])) { // comment
return "\033[1;30m" . $m[1] . "\033[0m";
@@ -162,13 +158,13 @@ class Helpers
/** @internal */
public static function escape(Driver $driver, $value, string $type): string
{
static $types = [
Type::TEXT => 'text',
Type::BINARY => 'binary',
Type::BOOL => 'bool',
Type::DATE => 'date',
Type::DATETIME => 'datetime',
\dibi::IDENTIFIER => 'identifier',
$types = [
Type::Text => 'text',
Type::Binary => 'binary',
Type::Bool => 'bool',
Type::Date => 'date',
Type::DateTime => 'datetime',
Fluent::Identifier => 'identifier',
];
if (isset($types[$type])) {
return $driver->{'escape' . $types[$type]}($value);
@@ -184,17 +180,17 @@ class Helpers
*/
public static function detectType(string $type): ?string
{
static $patterns = [
'^_' => Type::TEXT, // PostgreSQL arrays
'RANGE$' => Type::TEXT, // PostgreSQL range types
'BYTEA|BLOB|BIN' => Type::BINARY,
'TEXT|CHAR|POINT|INTERVAL|STRING' => Type::TEXT,
'YEAR|BYTE|COUNTER|SERIAL|INT|LONG|SHORT|^TINY$' => Type::INTEGER,
'CURRENCY|REAL|MONEY|FLOAT|DOUBLE|DECIMAL|NUMERIC|NUMBER' => Type::FLOAT,
'^TIME$' => Type::TIME,
'TIME' => Type::DATETIME, // DATETIME, TIMESTAMP
'DATE' => Type::DATE,
'BOOL' => Type::BOOL,
$patterns = [
'^_' => Type::Text, // PostgreSQL arrays
'RANGE$' => Type::Text, // PostgreSQL range types
'BYTEA|BLOB|BIN' => Type::Binary,
'TEXT|CHAR|POINT|INTERVAL|STRING' => Type::Text,
'YEAR|BYTE|COUNTER|SERIAL|INT|LONG|SHORT|^TINY$' => Type::Integer,
'CURRENCY|REAL|MONEY|FLOAT|DOUBLE|DECIMAL|NUMERIC|NUMBER' => Type::Float,
'^TIME$' => Type::Time,
'TIME' => Type::DateTime, // DATETIME, TIMESTAMP
'DATE' => Type::Date,
'BOOL' => Type::Bool,
'JSON' => Type::JSON,
];
@@ -211,7 +207,7 @@ class Helpers
/** @internal */
public static function getTypeCache(): HashMap
{
if (self::$types === null) {
if (!isset(self::$types)) {
self::$types = new HashMap([self::class, 'detectType']);
}
@@ -238,7 +234,7 @@ class Helpers
/**
* 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
{
@@ -259,7 +255,7 @@ class Helpers
if (strtoupper(substr($s, 0, 10)) === 'DELIMITER ') {
$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));
$driver->query($sql);
$sql = '';
@@ -286,14 +282,14 @@ class Helpers
/** @internal */
public static function false2Null($val)
public static function false2Null(mixed $val): mixed
{
return $val === false ? null : $val;
}
/** @internal */
public static function intVal($value): int
public static function intVal(mixed $value): int
{
if (is_int($value)) {
return $value;

View File

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

View File

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

View File

@@ -27,13 +27,11 @@ use Dibi;
*/
class Column
{
use Dibi\Strict;
/** @var Dibi\Reflector|null when created by Result */
private $reflector;
/** when created by Result */
private ?Dibi\Reflector $reflector;
/** @var array (name, nativetype, [table], [fullname], [size], [nullable], [default], [autoincrement], [vendor]) */
private $info;
private array $info;
public function __construct(?Dibi\Reflector $reflector, array $info)
@@ -109,15 +107,13 @@ class Column
}
/** @return mixed */
public function getDefault()
public function getDefault(): mixed
{
return $this->info['default'] ?? null;
}
/** @return mixed */
public function getVendorInfo(string $key)
public function getVendorInfo(string $key): mixed
{
return $this->info['vendor'][$key] ?? null;
}

View File

@@ -21,16 +21,11 @@ use Dibi;
*/
class Database
{
use Dibi\Strict;
private Dibi\Reflector $reflector;
private ?string $name;
/** @var Dibi\Reflector */
private $reflector;
/** @var string|null */
private $name;
/** @var Table[]|null */
private $tables;
/** @var Table[] */
private array $tables;
public function __construct(Dibi\Reflector $reflector, ?string $name = null)
@@ -89,7 +84,7 @@ class Database
protected function init(): void
{
if ($this->tables === null) {
if (!isset($this->tables)) {
$this->tables = [];
foreach ($this->reflector->getTables() as $info) {
$this->tables[strtolower($info['name'])] = new Table($this->reflector, $info);

View File

@@ -9,7 +9,6 @@ declare(strict_types=1);
namespace Dibi\Reflection;
use Dibi;
/**
@@ -20,13 +19,10 @@ use Dibi;
*/
class ForeignKey
{
use Dibi\Strict;
/** @var string */
private $name;
private string $name;
/** @var array of [local, foreign, onDelete, onUpdate] */
private $references;
private array $references;
public function __construct(string $name, array $references)

View File

@@ -9,7 +9,6 @@ declare(strict_types=1);
namespace Dibi\Reflection;
use Dibi;
/**
@@ -22,10 +21,8 @@ use Dibi;
*/
class Index
{
use Dibi\Strict;
/** @var array (name, columns, [unique], [primary]) */
private $info;
private array $info;
public function __construct(array $info)

View File

@@ -20,16 +20,13 @@ use Dibi;
*/
class Result
{
use Dibi\Strict;
/** @var Dibi\ResultDriver */
private $driver;
private Dibi\ResultDriver $driver;
/** @var Column[]|null */
private $columns;
private ?array $columns;
/** @var Column[]|null */
private $names;
private ?array $names;
public function __construct(Dibi\ResultDriver $driver)
@@ -81,7 +78,7 @@ class Result
protected function initColumns(): void
{
if ($this->columns === null) {
if (!isset($this->columns)) {
$this->columns = [];
$reflector = $this->driver instanceof Dibi\Reflector
? $this->driver

View File

@@ -25,28 +25,19 @@ use Dibi;
*/
class Table
{
use Dibi\Strict;
private Dibi\Reflector $reflector;
private string $name;
private bool $view;
/** @var Dibi\Reflector */
private $reflector;
/** @var Column[] */
private array $columns;
/** @var string */
private $name;
/** @var ForeignKey[] */
private array $foreignKeys;
/** @var bool */
private $view;
/** @var Column[]|null */
private $columns;
/** @var ForeignKey[]|null */
private $foreignKeys;
/** @var Index[]|null */
private $indexes;
/** @var Index|null */
private $primaryKey;
/** @var Index[] */
private array $indexes;
private ?Index $primaryKey;
public function __construct(Dibi\Reflector $reflector, array $info)
@@ -135,7 +126,7 @@ class Table
protected function initColumns(): void
{
if ($this->columns === null) {
if (!isset($this->columns)) {
$this->columns = [];
foreach ($this->reflector->getColumns($this->name) as $info) {
$this->columns[strtolower($info['name'])] = new Column($this->reflector, $info);
@@ -146,7 +137,7 @@ class Table
protected function initIndexes(): void
{
if ($this->indexes === null) {
if (!isset($this->indexes)) {
$this->initColumns();
$this->indexes = [];
foreach ($this->reflector->getIndexes($this->name) as $info) {

View File

@@ -17,28 +17,21 @@ namespace Dibi;
*/
class Result implements IDataSource
{
use Strict;
private ?ResultDriver $driver;
/** @var ResultDriver|null */
private $driver;
/** Translate table */
private array $types = [];
private ?Reflection\Result $meta;
/** @var array Translate table */
private $types = [];
/** Already fetched? Used for allowance for first seek(0) */
private bool $fetched = false;
/** @var Reflection\Result|null */
private $meta;
/** @var bool Already fetched? Used for allowance for first seek(0) */
private $fetched = false;
/** @var string|null returned object class */
private $rowClass = Row::class;
/** returned object class */
private ?string $rowClass = Row::class;
/** @var callable|null returned object factory */
private $rowFactory;
/** @var array format */
private $formats = [];
private array $formats = [];
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.
*/
public function setRowClass(?string $class): self
public function setRowClass(?string $class): static
{
$this->rowClass = $class;
return $this;
@@ -152,7 +145,7 @@ class Result implements IDataSource
/**
* 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;
return $this;
@@ -162,9 +155,8 @@ class Result implements IDataSource
/**
* Fetches the row at current position, process optional type conversion.
* 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);
if ($row === null) {
@@ -185,9 +177,9 @@ class Result implements IDataSource
/**
* 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);
if ($row === null) {
@@ -206,7 +198,7 @@ class Result implements IDataSource
*/
final public function fetchAll(?int $offset = null, ?int $limit = null): array
{
$limit = $limit ?? -1;
$limit ??= -1;
$this->seek($offset ?: 0);
$row = $this->fetch();
if (!$row) {
@@ -238,7 +230,7 @@ class Result implements IDataSource
*/
final public function fetchAssoc(string $assoc): array
{
if (strpos($assoc, ',') !== false) {
if (str_contains($assoc, ',')) {
return $this->oldFetchAssoc($assoc);
}
@@ -465,16 +457,22 @@ class Result implements IDataSource
if ($type === null || $format === 'native') {
$row[$key] = $value;
} elseif ($type === Type::TEXT) {
} elseif ($type === Type::Text) {
$row[$key] = (string) $value;
} elseif ($type === Type::INTEGER) {
} elseif ($type === Type::Integer) {
$row[$key] = is_float($tmp = $value * 1)
? (is_string($value) ? $value : (int) $value)
: $tmp;
} elseif ($type === Type::FLOAT) {
$value = ltrim((string) $value, '0');
} elseif ($type === Type::Float) {
if (!is_string($value)) {
$row[$key] = (float) $value;
continue;
}
$negative = ($value[0] ?? null) === '-';
$value = ltrim($value, '0-');
$p = strpos($value, '.');
$e = strpos($value, 'e');
if ($p !== false && $e === false) {
@@ -487,27 +485,31 @@ class Result implements IDataSource
$value = '0' . $value;
}
if ($negative) {
$value = '-' . $value;
}
$row[$key] = $value === str_replace(',', '.', (string) ($float = (float) $value))
? $float
: $value;
} elseif ($type === Type::BOOL) {
} elseif ($type === Type::Bool) {
$row[$key] = ((bool) $value) && $value !== 'f' && $value !== 'F';
} elseif ($type === Type::DATETIME || $type === Type::DATE || $type === Type::TIME) {
if ($value && substr((string) $value, 0, 7) !== '0000-00') { // '', null, false, '0000-00-00', ...
} elseif ($type === Type::DateTime || $type === Type::Date || $type === Type::Time) {
if ($value && !str_starts_with((string) $value, '0000-00')) { // '', null, false, '0000-00-00', ...
$value = new DateTime($value);
$row[$key] = $format ? $value->format($format) : $value;
} else {
$row[$key] = null;
}
} elseif ($type === Type::TIME_INTERVAL) {
} elseif ($type === Type::TimeInterval) {
preg_match('#^(-?)(\d+)\D(\d+)\D(\d+)\z#', $value, $m);
$value = new \DateInterval("PT$m[2]H$m[3]M$m[4]S");
$value->invert = (int) (bool) $m[1];
$row[$key] = $format ? $value->format($format) : $value;
} elseif ($type === Type::BINARY) {
} elseif ($type === Type::Binary) {
$row[$key] = is_string($value)
? $this->getResultDriver()->unescapeBinary($value)
: $value;
@@ -529,7 +531,7 @@ class Result implements IDataSource
* Define column 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;
return $this;
@@ -557,7 +559,7 @@ class Result implements IDataSource
/**
* 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;
return $this;
@@ -567,7 +569,7 @@ class Result implements IDataSource
/**
* Sets type formats.
*/
final public function setFormats(array $formats): self
final public function setFormats(array $formats): static
{
$this->formats = $formats;
return $this;
@@ -591,7 +593,7 @@ class Result implements IDataSource
*/
public function getInfo(): Reflection\Result
{
if ($this->meta === null) {
if (!isset($this->meta)) {
$this->meta = new Reflection\Result($this->getResultDriver());
}

View File

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

View File

@@ -32,13 +32,12 @@ class Row implements \ArrayAccess, \IteratorAggregate, \Countable
/**
* 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];
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;
}
@@ -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);
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)
final public function offsetGet($nm): mixed
{
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
{
use Strict;
/** @var Connection */
private $connection;
/** @var Driver */
private $driver;
/** @var int */
private $cursor = 0;
/** @var array */
private $args;
private Connection $connection;
private Driver $driver;
private int $cursor = 0;
private array $args;
/** @var string[] */
private $errors;
/** @var bool */
private $comment = false;
/** @var int */
private $ifLevel = 0;
/** @var int */
private $ifLevelStart = 0;
/** @var int|null */
private $limit;
/** @var int|null */
private $offset;
/** @var HashMap */
private $identifiers;
private array $errors;
private bool $comment = false;
private int $ifLevel = 0;
private int $ifLevelStart = 0;
private ?int $limit = null;
private ?int $offset = null;
private HashMap $identifiers;
public function __construct(Connection $connection)
@@ -96,22 +75,21 @@ final class Translator
// note: this can change $this->args & $this->cursor & ...
. preg_replace_callback(
<<<'XX'
/
(?=[`['":%?]) ## speed-up
(?:
`(.+?)`| ## 1) `identifier`
\[(.+?)\]| ## 2) [identifier]
(')((?:''|[^'])*)'| ## 3,4) string
(")((?:""|[^"])*)"| ## 5,6) "string"
('|")| ## 7) lone quote
:(\S*?:)([a-zA-Z0-9._]?)| ## 8,9) :substitution:
%([a-zA-Z~][a-zA-Z0-9~]{0,5})| ## 10) modifier
(\?) ## 11) placeholder
)/xs
XX
,
/
(?=[`['":%?]) ## speed-up
(?:
`(.+?)`| ## 1) `identifier`
\[(.+?)\]| ## 2) [identifier]
(')((?:''|[^'])*)'| ## 3,4) string
(")((?:""|[^"])*)"| ## 5,6) "string"
('|")| ## 7) lone quote
:(\S*?:)([a-zA-Z0-9._]?)| ## 8,9) :substitution:
%([a-zA-Z~][a-zA-Z0-9~]{0,5})| ## 10) modifier
(\?) ## 11) placeholder
)/xs
XX,
[$this, 'cb'],
substr($arg, $toSkip)
substr($arg, $toSkip),
);
if (preg_last_error()) {
throw new PcreException;
@@ -173,9 +151,8 @@ XX
/**
* 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) {
return '...';
@@ -210,7 +187,7 @@ XX
$v = $this->formatValue($v, $pair[1]);
if ($pair[1] === 'l' || $pair[1] === 'in') {
$op = 'IN ';
} elseif (strpos($pair[1], 'like') !== false) {
} elseif (str_contains($pair[1], 'like')) {
$op = 'LIKE ';
} elseif ($v === 'NULL') {
$op = 'IS ';
@@ -242,7 +219,7 @@ XX
case 'a': // key=val, key=val, ...
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]} . '='
. $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null));
}
@@ -280,7 +257,7 @@ XX
$proto = array_keys($v);
}
} 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
@@ -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
if ($value instanceof \BackedEnum && is_scalar($value->value)) {
$value = $value->value;
@@ -349,7 +335,7 @@ XX
) {
// continue
} 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**";
}
}
@@ -437,20 +423,19 @@ XX
$value = substr($value, 0, $toSkip)
. preg_replace_callback(
<<<'XX'
/
(?=[`['":])
(?:
`(.+?)`|
\[(.+?)\]|
(')((?:''|[^'])*)'|
(")((?:""|[^"])*)"|
('|")|
:(\S*?:)([a-zA-Z0-9._]?)
)/sx
XX
,
/
(?=[`['":])
(?:
`(.+?)`|
\[(.+?)]|
(')((?:''|[^'])*)'|
(")((?:""|[^"])*)"|
(['"])|
:(\S*?:)([a-zA-Z0-9._]?)
)/sx
XX,
[$this, 'cb'],
substr($value, $toSkip)
substr($value, $toSkip),
);
if (preg_last_error()) {
throw new PcreException;
@@ -516,7 +501,7 @@ XX
return $this->connection->translate(...$value->getValues());
} else {
$type = is_object($value) ? get_class($value) : gettype($value);
$type = get_debug_type($value);
return $this->errors[] = "**Unexpected $type**";
}
}

View File

@@ -16,16 +16,43 @@ namespace Dibi;
class Type
{
public const
TEXT = 's', // as 'string'
BINARY = 'bin',
Text = 's', // as 'string'
Binary = 'bin',
JSON = 'json',
BOOL = 'b',
INTEGER = 'i',
FLOAT = 'f',
DATE = 'd',
DATETIME = 'dt',
TIME = 't',
TIME_INTERVAL = 'ti';
Bool = 'b',
Integer = 'i',
Float = 'f',
Date = 'd',
DateTime = 'dt',
Time = 't',
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()

View File

@@ -37,37 +37,39 @@ declare(strict_types=1);
*/
class dibi
{
use Dibi\Strict;
public const Version = '5.0.2';
public const
AFFECTED_ROWS = 'a',
IDENTIFIER = 'n';
/** @deprecated use dibi::Version */
public const VERSION = self::Version;
/** version */
public const VERSION = '4.2.8';
/** @deprecated use Dibi\Fluent::AffectedRows */
public const AFFECTED_ROWS = Dibi\Fluent::AffectedRows;
/** @deprecated use Dibi\Fluent::Identifier */
public const IDENTIFIER = Dibi\Fluent::Identifier;
/** sorting order */
public const
ASC = 'ASC',
DESC = 'DESC';
/** @var string|null Last SQL command @see dibi::query() */
public static $sql;
/** Last SQL command @see dibi::query() */
public static ?string $sql = null;
/** @var float|null Elapsed time for last query */
public static $elapsedTime;
/** Elapsed time for last query */
public static ?float $elapsedTime = null;
/** @var float Elapsed time for all queries */
public static $totalTime;
/** Elapsed time for all queries */
public static float $totalTime = 0;
/** @var int Number or queries */
public static $numOfQueries = 0;
/** Number or queries */
public static int $numOfQueries = 0;
/** @var Dibi\Connection[] Connection registry storage for Dibi\Connection objects */
private static $registry = [];
private static array $registry = [];
/** @var Dibi\Connection Current connection */
private static $connection;
/** Current connection */
private static Dibi\Connection $connection;
/**
@@ -87,7 +89,7 @@ class dibi
* @param array $config connection parameters
* @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);
}
@@ -150,10 +152,9 @@ class dibi
/**
* 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?
*/
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);
}
@@ -164,7 +165,7 @@ class dibi
*/
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());
}
}

View File

@@ -15,15 +15,15 @@ namespace Dibi;
*/
class Exception extends \Exception
{
/** @var string|null */
private $sql;
private ?string $sql;
/**
* @param int|string $code
*/
public function __construct(string $message = '', $code = 0, ?string $sql = null, ?\Throwable $previous = null)
{
public function __construct(
string $message = '',
int|string $code = 0,
?string $sql = null,
?\Throwable $previous = null,
) {
parent::__construct($message, 0, $previous);
$this->code = $code;
$this->sql = $sql;
@@ -56,17 +56,9 @@ class DriverException extends Exception
*/
class PcreException extends Exception
{
public function __construct(string $message = '%msg.')
public function __construct()
{
static $messages = [
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);
parent::__construct(preg_last_error_msg(), preg_last_error());
}
}
@@ -86,8 +78,7 @@ class NotSupportedException extends Exception
*/
class ProcedureException extends Exception
{
/** @var string */
protected $severity;
protected string $severity;
/**

View File

@@ -67,9 +67,8 @@ interface Driver
/**
* Returns the connection resource.
* @return mixed
*/
function getResource();
function getResource(): mixed;
/**
* Returns the connection reflector.
@@ -117,7 +116,6 @@ interface ResultDriver
/**
* Moves cursor position without fetching row.
* @return bool true on success, false if unable to seek to specified record
* @throws Exception
*/
function seek(int $row): bool;
@@ -142,9 +140,8 @@ interface ResultDriver
/**
* Returns the result set resource.
* @return mixed
*/
function getResultResource();
function getResultResource(): mixed;
/**
* Decodes data from result set.

View File

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

View File

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

View File

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

View File

@@ -33,13 +33,13 @@ Assert::equal([
$res = $conn->query('SELECT * FROM [products] ORDER BY product_id');
Assert::same(
[1 => 'Chair', 'Table', 'Computer'],
$res->fetchPairs('product_id', 'title')
$res->fetchPairs('product_id', 'title'),
);
$res = $conn->query('SELECT * FROM [products] ORDER BY product_id');
Assert::same(
[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(
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(
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(
reformat("UPDATE 'wp_' SET [val]=1"),
$conn->translate('UPDATE :blog: SET [val]=1')
$conn->translate('UPDATE :blog: SET [val]=1'),
);
Assert::same(
reformat("UPDATE ':blg:' SET [val]=1"),
$conn->translate('UPDATE :blg: SET [val]=1')
$conn->translate('UPDATE :blg: SET [val]=1'),
);
Assert::same(
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(
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
$conn->getSubstitutes()->setCallback(function ($expr) {
return '_' . $expr . '_';
});
$conn->getSubstitutes()->setCallback(fn($expr) => '_' . $expr . '_');
Assert::same(
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");
/*Assert::exception(function () use ($conn) {
$conn->rollback();
}, Dibi\Exception::class);
/*
Assert::exception(
fn() => $conn->rollback(),
Dibi\Exception::class,
);
Assert::exception(function () use ($conn) {
$conn->commit();
}, Dibi\Exception::class);
Assert::exception(
fn() => $conn->commit(),
Dibi\Exception::class,
);
$conn->begin();
Assert::exception(function () use ($conn) {
$conn->begin();
}, Dibi\Exception::class);
Assert::exception(
fn() => $conn->begin(),
Dibi\Exception::class,
);
*/
@@ -53,14 +57,16 @@ test('begin() & commit()', function () use ($conn) {
test('transaction() fail', function () use ($conn) {
Assert::exception(function () use ($conn) {
$conn->transaction(function (Dibi\Connection $connection) {
Assert::exception(
fn() => $conn->transaction(function (Dibi\Connection $connection) {
$connection->query('INSERT INTO [products]', [
'title' => 'Test product',
]);
throw new Exception('my exception');
});
}, Throwable::class, 'my exception');
}),
Throwable::class,
'my exception',
);
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) {
Assert::exception(function () use ($conn) {
$conn->transaction(function (Dibi\Connection $connection) {
Assert::exception(
fn() => $conn->transaction(function (Dibi\Connection $connection) {
$connection->query('INSERT INTO [products]', [
'title' => 'Test product',
]);
@@ -88,8 +94,10 @@ test('nested transaction() call fail', function () use ($conn) {
]);
throw new Exception('my exception');
});
});
}, Throwable::class, 'my exception');
}),
Throwable::class,
'my exception',
);
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) {
Assert::exception(function () use ($conn) {
$conn->transaction(function (Dibi\Connection $connection) {
Assert::exception(
fn() => $conn->transaction(function (Dibi\Connection $connection) {
$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) {
$conn->transaction(function (Dibi\Connection $connection) {
Assert::exception(
fn() => $conn->transaction(function (Dibi\Connection $connection) {
$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) {
$conn->transaction(function (Dibi\Connection $connection) {
Assert::exception(
fn() => $conn->transaction(function (Dibi\Connection $connection) {
$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('
SELECT *
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(
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%')
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)
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)
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)
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)
ORDER BY [product_id] ASC
) t"),
(string) $fluent
(string) $fluent,
);
@@ -117,7 +117,7 @@ Assert::match(
reformat('
SELECT *
FROM (SELECT [title] FROM [products]) t'),
(string) $ds
(string) $ds,
);
Assert::equal(new Row([
@@ -129,7 +129,7 @@ Assert::same(1, $conn->dataSource('SELECT * FROM products ORDER BY product_id')-
Assert::same(
[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([
@@ -154,7 +154,7 @@ Assert::match(
reformat('
SELECT *
FROM [products]'),
(string) $ds
(string) $ds,
);
Assert::same(3, $ds->count());

View File

@@ -10,10 +10,10 @@ require __DIR__ . '/bootstrap.php';
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)->setTimestamp(254400000));
Assert::same(254400000, (new DateTime(254400000))->getTimestamp());
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(254_400_000));
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'));

View File

@@ -15,33 +15,33 @@ $fluent = $conn->delete('table')->as('bAlias')
Assert::same(
reformat('DELETE IGNORE FROM [table] AS [bAlias]'),
(string) $fluent
(string) $fluent,
);
$fluent->removeClause('from')->from('anotherTable');
Assert::same(
reformat('DELETE IGNORE FROM [anotherTable]'),
(string) $fluent
(string) $fluent,
);
$fluent->using('thirdTable');
Assert::same(
reformat('DELETE IGNORE FROM [anotherTable] USING [thirdTable]'),
(string) $fluent
(string) $fluent,
);
$fluent->setFlag('IGNORE', false);
Assert::same(
reformat('DELETE FROM [anotherTable] USING [thirdTable]'),
(string) $fluent
(string) $fluent,
);
$fluent->limit(10);
Assert::same(
reformat('DELETE FROM [anotherTable] USING [thirdTable] LIMIT 10'),
(string) $fluent
(string) $fluent,
);

View File

@@ -58,28 +58,28 @@ $fluent = $conn->select('*')
Assert::same(
reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
(string) $fluent
(string) $fluent,
);
$fluent->fetch();
Assert::same(
'SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t',
dibi::$sql
dibi::$sql,
);
$fluent->fetchSingle();
Assert::same(
reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
dibi::$sql
dibi::$sql,
);
$fluent->fetchAll(0, 3);
Assert::same(
reformat('SELECT TOP (3) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
dibi::$sql
dibi::$sql,
);
Assert::same(
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();
Assert::same(
reformat('SELECT TOP (0) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
dibi::$sql
dibi::$sql,
);
$fluent->fetchSingle();
Assert::same(
reformat('SELECT TOP (0) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
dibi::$sql
dibi::$sql,
);
Assert::same(
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();
Assert::same(
reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
dibi::$sql
dibi::$sql,
);
$fluent->fetchSingle();
Assert::same(
reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
dibi::$sql
dibi::$sql,
);
Assert::same(
reformat('SELECT * FROM [customers] ORDER BY [customer_id]'),
(string) $fluent
(string) $fluent,
);

View File

@@ -23,28 +23,28 @@ $fluent = $conn->select('*')
Assert::same(
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
(string) $fluent
(string) $fluent,
);
$fluent->fetch();
Assert::same(
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
dibi::$sql
dibi::$sql,
);
$fluent->fetchSingle();
Assert::same(
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
dibi::$sql
dibi::$sql,
);
$fluent->fetchAll(2, 3);
Assert::same(
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 3 OFFSET 2'),
dibi::$sql
dibi::$sql,
);
Assert::same(
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();
Assert::same(
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 0 OFFSET 3'),
dibi::$sql
dibi::$sql,
);
$fluent->fetchSingle();
Assert::same(
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 0 OFFSET 3'),
dibi::$sql
dibi::$sql,
);
Assert::same(
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();
Assert::same(
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
dibi::$sql
dibi::$sql,
);
$fluent->fetchSingle();
Assert::same(
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1 OFFSET 3'),
dibi::$sql
dibi::$sql,
);
Assert::same(
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();
Assert::same(
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1'),
dibi::$sql
dibi::$sql,
);
$fluent->fetchSingle();
Assert::same(
reformat('SELECT * FROM [customers] ORDER BY [customer_id] LIMIT 1'),
dibi::$sql
dibi::$sql,
);
Assert::same(
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(
reformat('INSERT IGNORE DELAYED INTO [table] ([title], [price], [brand]) VALUES (\'Super Product\', 12, NULL)'),
(string) $fluent
(string) $fluent,
);
$fluent->setFlag('IGNORE', false);
Assert::same(
reformat('INSERT DELAYED INTO [table] ([title], [price], [brand]) VALUES (\'Super Product\', 12, NULL)'),
(string) $fluent
(string) $fluent,
);
$fluent->setFlag('HIGH_priority');
Assert::same(
reformat('INSERT DELAYED HIGH_PRIORITY INTO [table] ([title], [price], [brand]) VALUES (\'Super Product\', 12, NULL)'),
(string) $fluent
(string) $fluent,
);
$fluent->into('anotherTable');
Assert::same(
reformat('INSERT DELAYED HIGH_PRIORITY INTO [anotherTable] VALUES (\'Super Product\', 12, NULL)'),
(string) $fluent
(string) $fluent,
);
$fluent->values('%l', $arr);
Assert::same(
reformat('INSERT DELAYED HIGH_PRIORITY INTO [anotherTable] VALUES (\'Super Product\', 12, NULL) , (\'Super Product\', 12, NULL)'),
(string) $fluent
(string) $fluent,
);
$fluent->values($arr);
Assert::same(
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(
reformat('SELECT * , [a] , [b] AS [bAlias] , [c], [d], [e] , [d]'),
(string) $fluent
(string) $fluent,
);
$fluent->from('table')->as('table.Alias')
@@ -34,21 +34,21 @@ $fluent->from('table')->as('table.Alias')
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'),
(string) $fluent
(string) $fluent,
);
$fluent->from('anotherTable');
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]'),
(string) $fluent
(string) $fluent,
);
$fluent->removeClause('from')->from('anotherTable');
Assert::same(
reformat('SELECT * , [a] , [b] AS [bAlias] , [c], [d], [e] , [d] FROM [anotherTable]'),
(string) $fluent
(string) $fluent,
);
$fluent->as('anotherAlias')
@@ -58,7 +58,7 @@ $fluent->as('anotherAlias')
Assert::same(
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)
@@ -71,14 +71,14 @@ $fluent->where('col > %i', $max)
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'),
(string) $fluent
(string) $fluent,
);
$fluent->orderBy(Dibi\Fluent::REMOVE);
$fluent->orderBy(Dibi\Fluent::Remove);
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)'),
(string) $fluent
(string) $fluent,
);
@@ -86,7 +86,7 @@ $fluent = $conn->select('*')
->select(
$conn->select('count(*)')
->from('precteni')->as('P')
->where('P.id_clanku', '=', 'C.id_clanku')
->where('P.id_clanku', '=', 'C.id_clanku'),
)
->from('clanky')->as('C')
->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',
'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(
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(
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)",
"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')
->offset(' 1; DROP TABLE users');
Assert::error(function () use ($fluent) {
(string) $fluent;
}, E_USER_ERROR, "Expected number, ' 1; DROP TABLE users' given.");
Assert::exception(
fn() => (string) $fluent,
Dibi\Exception::class,
"Expected number, ' 1; DROP TABLE users' given.",
);
}
@@ -158,5 +160,5 @@ $fluent = $conn->select('*')->from('abc')
Assert::same(
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(
reformat('UPDATE IGNORE DELAYED [table] SET [title]=\'Super Product\', [price]=12, [brand]=NULL'),
(string) $fluent
(string) $fluent,
);
$fluent->set(['another' => 123]);
Assert::same(
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);
Assert::same(
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';
$hash = new Dibi\HashMap(function ($v) {
return "b-$v-e";
});
$hash = new Dibi\HashMap(fn($v) => "b-$v-e");
Assert::same('b-X-e', $hash->{'X'});
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(-10, Helpers::intVal('-10'));
Assert::exception(function () {
Helpers::intVal('12345678901234567890123456879');
}, Dibi\Exception::class, 'Number 12345678901234567890123456879 is greater than integer.');
Assert::exception(
fn() => Helpers::intVal('12345678901234567890123456879'),
Dibi\Exception::class,
'Number 12345678901234567890123456879 is greater than integer.',
);
Assert::exception(function () {
Helpers::intVal('-12345678901234567890123456879');
}, Dibi\Exception::class, 'Number -12345678901234567890123456879 is greater than integer.');
Assert::exception(
fn() => Helpers::intVal('-12345678901234567890123456879'),
Dibi\Exception::class,
'Number -12345678901234567890123456879 is greater than integer.',
);
Assert::exception(function () {
Helpers::intVal('');
}, Dibi\Exception::class, "Expected number, '' given.");
Assert::exception(
fn() => Helpers::intVal(''),
Dibi\Exception::class,
"Expected number, '' given.",
);
Assert::exception(function () {
Helpers::intVal('not number');
}, Dibi\Exception::class, "Expected number, 'not number' given.");
Assert::exception(
fn() => Helpers::intVal('not number'),
Dibi\Exception::class,
"Expected number, 'not number' given.",
);
Assert::exception(function () {
Helpers::intVal(null);
}, Dibi\Exception::class, "Expected number, '' given.");
Assert::exception(
fn() => Helpers::intVal(null),
Dibi\Exception::class,
"Expected number, '' given.",
);

View File

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

View File

@@ -28,14 +28,14 @@ Assert::same(4, $res->getColumnCount());
Assert::same(
['product_id', 'order_id', 'name', 'xXx'],
$info->getColumnNames()
$info->getColumnNames(),
);
if (!in_array($config['driver'], ['sqlite', 'sqlite3', 'pdo', 'sqlsrv'], true)) {
Assert::same(
['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 () {
$result = new MockResult;
$result->setType('col', Type::TEXT);
$result->setFormat(Type::TEXT, 'native');
$result->setType('col', Type::Text);
$result->setFormat(Type::Text, 'native');
Assert::same(['col' => null], $result->test(['col' => null]));
Assert::same(['col' => true], $result->test(['col' => true]));
@@ -38,7 +38,7 @@ test('', function () {
test('', function () {
$result = new MockResult;
$result->setType('col', Type::BOOL);
$result->setType('col', Type::Bool);
Assert::same(['col' => null], $result->test(['col' => null]));
Assert::same(['col' => true], $result->test(['col' => true]));
@@ -60,7 +60,7 @@ test('', function () {
test('', function () {
$result = new MockResult;
$result->setType('col', Type::TEXT);
$result->setType('col', Type::Text);
Assert::same(['col' => null], $result->test(['col' => null]));
Assert::same(['col' => '1'], $result->test(['col' => true]));
@@ -76,7 +76,7 @@ test('', function () {
test('', function () {
$result = new MockResult;
$result->setType('col', Type::FLOAT);
$result->setType('col', Type::Float);
Assert::same(['col' => null], $result->test(['col' => null]));
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::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');
Assert::same(['col' => 0.0], $result->test(['col' => '']));
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' => 1.0], $result->test(['col' => 1]));
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');
});
test('', function () {
$result = new MockResult;
$result->setType('col', Type::INTEGER);
$result->setType('col', Type::Integer);
Assert::same(['col' => null], $result->test(['col' => null]));
Assert::same(['col' => 1], $result->test(['col' => true]));
@@ -162,9 +225,10 @@ test('', function () {
if (PHP_VERSION_ID < 80000) {
Assert::same(['col' => 0], @$result->test(['col' => ''])); // triggers warning since PHP 7.1
} else {
Assert::exception(function () use ($result) {
Assert::same(['col' => 0], $result->test(['col' => '']));
}, TypeError::class);
Assert::exception(
fn() => Assert::same(['col' => 0], $result->test(['col' => ''])),
TypeError::class,
);
}
Assert::same(['col' => 0], $result->test(['col' => '0']));
@@ -186,12 +250,13 @@ test('', function () {
test('', function () {
$result = new MockResult;
$result->setType('col', Type::DATETIME);
$result->setType('col', Type::DateTime);
Assert::same(['col' => null], $result->test(['col' => null]));
Assert::exception(function () use ($result) {
$result->test(['col' => true]);
}, TypeError::class);
Assert::exception(
fn() => $result->test(['col' => true]),
TypeError::class,
);
Assert::same(['col' => null], $result->test(['col' => false]));
Assert::same(['col' => null], $result->test(['col' => '']));
@@ -204,13 +269,14 @@ test('', function () {
test('', function () {
$result = new MockResult;
$result->setType('col', Type::DATETIME);
$result->setFormat(Type::DATETIME, 'Y-m-d H:i:s');
$result->setType('col', Type::DateTime);
$result->setFormat(Type::DateTime, 'Y-m-d H:i:s');
Assert::same(['col' => null], $result->test(['col' => null]));
Assert::exception(function () use ($result) {
$result->test(['col' => true]);
}, TypeError::class);
Assert::exception(
fn() => $result->test(['col' => true]),
TypeError::class,
);
Assert::same(['col' => null], $result->test(['col' => false]));
Assert::same(['col' => null], $result->test(['col' => '']));
@@ -223,12 +289,13 @@ test('', function () {
test('', function () {
$result = new MockResult;
$result->setType('col', Type::DATE);
$result->setType('col', Type::Date);
Assert::same(['col' => null], $result->test(['col' => null]));
Assert::exception(function () use ($result) {
$result->test(['col' => true]);
}, TypeError::class);
Assert::exception(
fn() => $result->test(['col' => true]),
TypeError::class,
);
Assert::same(['col' => null], $result->test(['col' => false]));
Assert::same(['col' => null], $result->test(['col' => '']));
@@ -239,12 +306,13 @@ test('', function () {
test('', function () {
$result = new MockResult;
$result->setType('col', Type::TIME);
$result->setType('col', Type::Time);
Assert::same(['col' => null], $result->test(['col' => null]));
Assert::exception(function () use ($result) {
$result->test(['col' => true]);
}, TypeError::class);
Assert::exception(
fn() => $result->test(['col' => true]),
TypeError::class,
);
Assert::same(['col' => null], $result->test(['col' => false]));
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]');
// 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([
'customer_id' => new Dibi\DateTime('1970-01-01 01:00:01'),

View File

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

View File

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

View File

@@ -21,19 +21,19 @@ $tests = function ($conn) {
// Limit and offset
Assert::same(
'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
Assert::same(
'SELECT 1 OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY',
$conn->translate('SELECT 1 %lmt', 10)
$conn->translate('SELECT 1 %lmt', 10),
);
// Offset only
Assert::same(
'SELECT 1 OFFSET 10 ROWS',
$conn->translate('SELECT 1 %ofs', 10)
$conn->translate('SELECT 1 %ofs', 10),
);
// Offset invalid
@@ -42,7 +42,7 @@ $tests = function ($conn) {
$conn->translate('SELECT 1 %ofs', -10);
},
Dibi\NotSupportedException::class,
'Negative offset or limit.'
'Negative offset or limit.',
);
// Limit invalid
@@ -51,7 +51,7 @@ $tests = function ($conn) {
$conn->translate('SELECT 1 %lmt', -10);
},
Dibi\NotSupportedException::class,
'Negative offset or limit.'
'Negative offset or limit.',
);
// Limit invalid, offset valid
@@ -60,7 +60,7 @@ $tests = function ($conn) {
$conn->translate('SELECT 1 %ofs %lmt', 10, -10);
},
Dibi\NotSupportedException::class,
'Negative offset or limit.'
'Negative offset or limit.',
);
// Limit valid, offset invalid
@@ -69,22 +69,23 @@ $tests = function ($conn) {
$conn->translate('SELECT 1 %ofs %lmt', -10, 10);
},
Dibi\NotSupportedException::class,
'Negative offset or limit.'
'Negative offset or limit.',
);
} else {
Assert::same(
'SELECT TOP (1) * FROM (SELECT 1) t',
$conn->translate('SELECT 1 %lmt', 1)
$conn->translate('SELECT 1 %lmt', 1),
);
Assert::same(
'SELECT 1',
$conn->translate('SELECT 1 %lmt', -10)
$conn->translate('SELECT 1 %lmt', -10),
);
Assert::exception(function () {
$conn->translate('SELECT 1 %ofs %lmt', 10, 10);
}, Dibi\NotSupportedException::class);
Assert::exception(
fn() => $conn->translate('SELECT 1 %ofs %lmt', 10, 10),
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':
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::exception(function () use ($translator) {
$translator->formatValue(new DateInterval('P2Y4DT6H8M'), null);
}, Dibi\NotSupportedException::class, 'Only time interval is supported.');
Assert::exception(
fn() => $translator->formatValue(new DateInterval('P2Y4DT6H8M'), null),
Dibi\NotSupportedException::class,
'Only time interval is supported.',
);
break;
default:
Assert::exception(function () use ($translator) {
$translator->formatValue(new DateInterval('PT10H20M30S'), null);
}, Dibi\Exception::class);
Assert::exception(
fn() => $translator->formatValue(new DateInterval('PT10H20M30S'), null),
Dibi\Exception::class,
);
}

View File

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

View File

@@ -33,7 +33,7 @@ enum PureEnum
Assert::equal('1', $translator->formatValue(EnumInt::One, null));
Assert::equal(match ($config['driver']) {
Assert::equal(match ($config['system']) {
'sqlsrv' => "N'one'",
default => "'one'",
}, $translator->formatValue(EnumString::One, null));

View File

@@ -15,35 +15,35 @@ $conn = new Dibi\Connection($config);
Assert::same(
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(
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(
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(
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(
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(
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]', [
'title' => 'Drticka',
'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)",
"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)",
"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', [
'color' => 'blue',
'order' => 12,
], 'WHERE [id]=%i', 123)
], 'WHERE [id]=%i', 123),
);
@@ -74,26 +74,28 @@ Assert::same(
$array = [1, 2, 3];
Assert::same(
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
Assert::same(
reformat('SELECT -123456789123456789123456789'),
$conn->translate('SELECT %i', '-123456789123456789123456789')
$conn->translate('SELECT %i', '-123456789123456789123456789'),
);
// long float numbers
Assert::same(
reformat('SELECT -.12345678912345678912345678e10'),
$conn->translate('SELECT %f', '-.12345678912345678912345678e10')
$conn->translate('SELECT %f', '-.12345678912345678912345678e10'),
);
// invalid input
$e = Assert::exception(function () use ($conn) {
$conn->translate('SELECT %s', (object) [123], ', %m', 123);
}, Dibi\Exception::class, 'SQL translate error: Invalid combination of type stdClass and modifier %s');
$e = Assert::exception(
fn() => $conn->translate('SELECT %s', (object) [123], ', %m', 123),
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(
@@ -101,7 +103,7 @@ Assert::same(
'sqlsrv' => "SELECT * FROM [table] WHERE id=10 AND name=N'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(
@@ -109,7 +111,7 @@ Assert::same(
'sqlsrv' => "TEST ([cond] > 2) OR ([cond2] = N'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(
@@ -117,7 +119,7 @@ Assert::same(
'sqlsrv' => "TEST ([cond] > 2) AND ([cond2] = N'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';
Assert::same(
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))",
"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 = [];
Assert::same(
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(
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',
'SELECT * FROM [products] LIMIT 2',
]),
$conn->translate('SELECT * FROM [products] %lmt', 2)
$conn->translate('SELECT * FROM [products] %lmt', 2),
);
if ($config['system'] === 'odbc') {
Assert::exception(function () use ($conn) {
$conn->translate('SELECT * FROM [products] %lmt %ofs', 2, 1);
}, Dibi\Exception::class);
Assert::exception(
fn() => $conn->translate('SELECT * FROM [products] %lmt %ofs', 2, 1),
Dibi\Exception::class,
);
} else {
// with limit = 2, offset = 1
Assert::same(
@@ -186,7 +189,7 @@ if ($config['system'] === 'odbc') {
'sqlsrv' => 'SELECT * FROM [products] OFFSET 1 ROWS FETCH NEXT 2 ROWS ONLY',
'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
@@ -197,7 +200,7 @@ if ($config['system'] === 'odbc') {
'sqlsrv' => 'SELECT * FROM [products] OFFSET 50 ROWS',
'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,
'b9%t' => null,
'c1%t' => new DateTime('1212-09-26 16:51:34.0124'),
])
]),
);
Assert::exception(function () use ($conn) {
$conn->translate('SELECT %s', new DateTime('1212-09-26'));
}, Dibi\Exception::class, 'SQL translate error: Invalid combination of type Dibi\DateTime and modifier %s');
Assert::exception(
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',
);
@@ -247,13 +252,13 @@ if ($config['system'] === 'postgre') {
$conn->query('SET standard_conforming_strings = off');
Assert::same(
"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');
Assert::same(
"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
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[%][_]\\''\"%'",
"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) {
$conn->translate("SELECT '");
}, Dibi\Exception::class, 'SQL translate error: Alone quote');
$e = Assert::exception(
fn() => $conn->translate("SELECT '"),
Dibi\Exception::class,
'SQL translate error: Alone quote',
);
Assert::same('SELECT **Alone quote**', $e->getSql());
Assert::match(
reformat([
'mysql' => "SELECT DISTINCT HIGH_PRIORITY SQL_BUFFER_RESULT
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'
",
'sqlsrv' => "SELECT DISTINCT HIGH_PRIORITY SQL_BUFFER_RESULT
CONCAT(last_name, N', ', first_name) AS full_name
GROUP BY [user]
HAVING MAX(salary) > %i 123
INTO OUTFILE N'/tmp/result''.txt'
FIELDS TERMINATED BY N',' OPTIONALLY ENCLOSED BY N'\"'
LINES TERMINATED BY N'\\n'", "SELECT DISTINCT HIGH_PRIORITY SQL_BUFFER_RESULT
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'
",
pattern: reformat([
'mysql' => <<<'XX'
SELECT DISTINCT HIGH_PRIORITY SQL_BUFFER_RESULT
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,
'sqlsrv' => <<<'XX'
SELECT DISTINCT HIGH_PRIORITY SQL_BUFFER_RESULT
CONCAT(last_name, N', ', first_name) AS full_name
GROUP BY [user]
HAVING MAX(salary) > %i 123
INTO OUTFILE N'/tmp/result''.txt'
FIELDS TERMINATED BY N',' OPTIONALLY ENCLOSED BY N'"'
LINES TERMINATED BY N'\n'
XX,
<<<'XX'
SELECT DISTINCT HIGH_PRIORITY SQL_BUFFER_RESULT
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
GROUP BY [user]
HAVING MAX(salary) > %i', 123, "
INTO OUTFILE '/tmp/result''.txt'
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"'
LINES TERMINATED BY '\\n'
")
"),
);
@@ -330,114 +342,124 @@ $array5 = ['RAND()', '[col1] > [col2]'];
Assert::match(
reformat([
'mysql' => "SELECT *
FROM `db`.`table`
WHERE (`test`.`a` LIKE '1995-03-01'
OR `b1` IN ( 1, 2, 3 )
OR `b2` IN ('1', '2', '3' )
OR `b3` IN ( )
OR `b4` IN ( 'one', 'two', 'three' )
OR `b5` IN (`col1` AS `one`, `col2` AS `two`, `col3` AS `thr.ee` )
OR `b6` IN ('one', 'two', 'thr.ee')
OR `b7` IN (NULL)
OR `b8` IN (RAND() `col1` > `col2` )
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",
'sqlsrv' => "SELECT *
FROM [db].[table]
WHERE ([test].[a] LIKE '1995-03-01'
OR [b1] IN ( 1, 2, 3 )
OR [b2] IN (N'1', N'2', N'3' )
OR [b3] IN ( )
OR [b4] IN ( N'one', N'two', N'three' )
OR [b5] IN ([col1] AS [one], [col2] AS [two], [col3] AS [thr.ee] )
OR [b6] IN (N'one', N'two', N'thr.ee')
OR [b7] IN (NULL)
OR [b8] IN (RAND() [col1] > [col2] )
OR [b9] IN (RAND(), [col1] > [col2] )
OR [b10] IN ( )
AND [c] = N'embedded '' string'
OR [d]=10
OR [e]=NULL
OR [true]= 1
OR [false]= 0
OR [str_null]=NULL
OR [str_not_null]=N'hello'
LIMIT 10",
'postgre' => 'SELECT *
FROM "db"."table"
WHERE ("test"."a" LIKE \'1995-03-01\'
OR "b1" IN ( 1, 2, 3 )
OR "b2" IN (\'1\', \'2\', \'3\' )
OR "b3" IN ( )
OR "b4" IN ( \'one\', \'two\', \'three\' )
OR "b5" IN ("col1" AS "one", "col2" AS "two", "col3" AS "thr.ee" )
OR "b6" IN (\'one\', \'two\', \'thr.ee\')
OR "b7" IN (NULL)
OR "b8" IN (RAND() "col1" > "col2" )
OR "b9" IN (RAND(), [col1] > [col2] )
OR "b10" IN ( )
AND "c" = \'embedded \'\' string\'
OR "d"=10
OR "e"=NULL
OR "true"= TRUE
OR "false"= FALSE
OR "str_null"=NULL
OR "str_not_null"=\'hello\'
LIMIT 10',
'odbc' => "SELECT *
FROM [db].[table]
WHERE ([test].[a] LIKE #03/01/1995#
OR [b1] IN ( 1, 2, 3 )
OR [b2] IN ('1', '2', '3' )
OR [b3] IN ( )
OR [b4] IN ( 'one', 'two', 'three' )
OR [b5] IN ([col1] AS [one], [col2] AS [two], [col3] AS [thr.ee] )
OR [b6] IN ('one', 'two', 'thr.ee')
OR [b7] IN (NULL)
OR [b8] IN (RAND() [col1] > [col2] )
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",
"SELECT *
FROM [db].[table]
WHERE ([test].[a] LIKE '1995-03-01'
OR [b1] IN ( 1, 2, 3 )
OR [b2] IN ('1', '2', '3' )
OR [b3] IN ( )
OR [b4] IN ( 'one', 'two', 'three' )
OR [b5] IN ([col1] AS [one], [col2] AS [two], [col3] AS [thr.ee] )
OR [b6] IN ('one', 'two', 'thr.ee')
OR [b7] IN (NULL)
OR [b8] IN (RAND() [col1] > [col2] )
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",
pattern: reformat([
'mysql' => <<<'XX'
SELECT *
FROM `db`.`table`
WHERE (`test`.`a` LIKE '1995-03-01'
OR `b1` IN ( 1, 2, 3 )
OR `b2` IN ('1', '2', '3' )
OR `b3` IN ( )
OR `b4` IN ( 'one', 'two', 'three' )
OR `b5` IN (`col1` AS `one`, `col2` AS `two`, `col3` AS `thr.ee` )
OR `b6` IN ('one', 'two', 'thr.ee')
OR `b7` IN (NULL)
OR `b8` IN (RAND() `col1` > `col2` )
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,
'sqlsrv' => <<<'XX'
SELECT *
FROM [db].[table]
WHERE ([test].[a] LIKE '1995-03-01'
OR [b1] IN ( 1, 2, 3 )
OR [b2] IN (N'1', N'2', N'3' )
OR [b3] IN ( )
OR [b4] IN ( N'one', N'two', N'three' )
OR [b5] IN ([col1] AS [one], [col2] AS [two], [col3] AS [thr.ee] )
OR [b6] IN (N'one', N'two', N'thr.ee')
OR [b7] IN (NULL)
OR [b8] IN (RAND() [col1] > [col2] )
OR [b9] IN (RAND(), [col1] > [col2] )
OR [b10] IN ( )
AND [c] = N'embedded '' string'
OR [d]=10
OR [e]=NULL
OR [true]= 1
OR [false]= 0
OR [str_null]=NULL
OR [str_not_null]=N'hello'
LIMIT 10
XX,
'postgre' => <<<'XX'
SELECT *
FROM "db"."table"
WHERE ("test"."a" LIKE '1995-03-01'
OR "b1" IN ( 1, 2, 3 )
OR "b2" IN ('1', '2', '3' )
OR "b3" IN ( )
OR "b4" IN ( 'one', 'two', 'three' )
OR "b5" IN ("col1" AS "one", "col2" AS "two", "col3" AS "thr.ee" )
OR "b6" IN ('one', 'two', 'thr.ee')
OR "b7" IN (NULL)
OR "b8" IN (RAND() "col1" > "col2" )
OR "b9" IN (RAND(), [col1] > [col2] )
OR "b10" IN ( )
AND "c" = 'embedded '' string'
OR "d"=10
OR "e"=NULL
OR "true"= TRUE
OR "false"= FALSE
OR "str_null"=NULL
OR "str_not_null"='hello'
LIMIT 10
XX,
'odbc' => <<<'XX'
SELECT *
FROM [db].[table]
WHERE ([test].[a] LIKE #03/01/1995#
OR [b1] IN ( 1, 2, 3 )
OR [b2] IN ('1', '2', '3' )
OR [b3] IN ( )
OR [b4] IN ( 'one', 'two', 'three' )
OR [b5] IN ([col1] AS [one], [col2] AS [two], [col3] AS [thr.ee] )
OR [b6] IN ('one', 'two', 'thr.ee')
OR [b7] IN (NULL)
OR [b8] IN (RAND() [col1] > [col2] )
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,
<<<'XX'
SELECT *
FROM [db].[table]
WHERE ([test].[a] LIKE '1995-03-01'
OR [b1] IN ( 1, 2, 3 )
OR [b2] IN ('1', '2', '3' )
OR [b3] IN ( )
OR [b4] IN ( 'one', 'two', 'three' )
OR [b5] IN ([col1] AS [one], [col2] AS [two], [col3] AS [thr.ee] )
OR [b6] IN ('one', 'two', 'thr.ee')
OR [b7] IN (NULL)
OR [b8] IN (RAND() [col1] > [col2] )
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]
WHERE ([test.a] LIKE %d', '1995-03-01', '
OR [b1] IN (', $array1, ')
@@ -457,7 +479,7 @@ WHERE ([test.a] LIKE %d', '1995-03-01', '
OR [false]=', false, '
OR [str_null]=%sn', '', '
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",
"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(
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(
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')",
"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',
'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(
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(
reformat('SELECT FROM ...'),
$conn->translate('SELECT FROM ... %lmt', null)
$conn->translate('SELECT FROM ... %lmt', null),
);
Assert::same(
@@ -520,7 +542,7 @@ Assert::same(
'sqlsrv' => "SELECT N'%i'",
"SELECT '%i'",
]),
$conn->translate("SELECT '%i'")
$conn->translate("SELECT '%i'"),
);
Assert::same(
@@ -528,7 +550,7 @@ Assert::same(
'sqlsrv' => "SELECT N'%i'",
"SELECT '%i'",
]),
$conn->translate('SELECT "%i"')
$conn->translate('SELECT "%i"'),
);
@@ -543,7 +565,7 @@ Assert::same(
], [
'product_id' => 1,
'title' => new Dibi\Expression('SHA1(%s)', 'Test product'),
])
]),
);
Assert::same(
@@ -554,7 +576,7 @@ Assert::same(
$conn->translate('UPDATE [products]', [
'product_id' => 1,
'title' => new Dibi\Expression('SHA1(%s)', 'Test product'),
])
]),
);
Assert::same(
@@ -565,7 +587,7 @@ Assert::same(
$conn->translate('UPDATE [products]', [
'product_id' => 1,
'title' => new Dibi\Expression('SHA1(%s)', 'Test product'),
])
]),
);
Assert::same(
@@ -576,7 +598,7 @@ Assert::same(
$conn->translate('SELECT * FROM [products] WHERE', [
'product_id' => 1,
'title' => new Dibi\Expression('SHA1(%s)', 'Test product'),
])
]),
);
@@ -588,7 +610,7 @@ Assert::same(
'top' => 2,
]),
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)",
"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(
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(
reformat('INSERT INTO [test].*'),
$conn->translate('INSERT INTO [test.*]')
$conn->translate('INSERT INTO [test.*]'),
);
Assert::exception(function () use ($conn) {
$conn->translate('INSERT INTO %i', 'ahoj');
}, Dibi\Exception::class, "Expected number, 'ahoj' given.");
Assert::exception(
fn() => $conn->translate('INSERT INTO %i', 'ahoj'),
Dibi\Exception::class,
"Expected number, 'ahoj' given.",
);
Assert::exception(function () use ($conn) {
$conn->translate('INSERT INTO %f', 'ahoj');
}, Dibi\Exception::class, "Expected number, 'ahoj' given.");
Assert::exception(
fn() => $conn->translate('INSERT INTO %f', 'ahoj'),
Dibi\Exception::class,
"Expected number, 'ahoj' given.",
);
Assert::same(
reformat('SELECT * FROM table'),
$conn->translate('SELECT', new Dibi\Literal('* FROM table'))
$conn->translate('SELECT', new Dibi\Literal('* FROM table')),
);
Assert::same(
reformat('SELECT * FROM table'),
$conn->translate('SELECT %SQL', new Dibi\Literal('* FROM table'))
$conn->translate('SELECT %SQL', new Dibi\Literal('* FROM table')),
);
Assert::same(
reformat('SELECT * FROM table'),
$conn->translate(new Dibi\Literal('SELECT * FROM table'))
$conn->translate(new Dibi\Literal('SELECT * FROM table')),
);
Assert::same(
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,
'spec3%i' => 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 {
$config = Tester\Environment::loadData();
} catch (Throwable $e) {
$config = parse_ini_file(__DIR__ . '/../databases.ini', true);
$config = parse_ini_file(__DIR__ . '/../databases.ini', process_sections: true);
$config = reset($config);
}

View File

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

View File

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

View File

@@ -15,23 +15,32 @@ $conn = new Dibi\Connection($config);
$conn->loadFile(__DIR__ . "/data/$config[system].sql");
$e = Assert::exception(function () use ($conn) {
$conn->query('SELECT');
}, Dibi\DriverException::class, '%a%', 1);
$e = Assert::exception(
fn() => $conn->query('SELECT'),
Dibi\DriverException::class,
'%a%',
1,
);
Assert::same('SELECT', $e->getSql());
$e = Assert::exception(function () use ($conn) {
$conn->query('INSERT INTO products (product_id, title) VALUES (1, "New")');
}, Dibi\UniqueConstraintViolationException::class, null, 19);
$e = Assert::exception(
fn() => $conn->query('INSERT INTO products (product_id, title) VALUES (1, "New")'),
Dibi\UniqueConstraintViolationException::class,
null,
19,
);
Assert::same("INSERT INTO products (product_id, title) VALUES (1, 'New')", $e->getSql());
$e = Assert::exception(function () use ($conn) {
$conn->query('INSERT INTO products (title) VALUES (NULL)');
}, Dibi\NotNullConstraintViolationException::class, null, 19);
$e = Assert::exception(
fn() => $conn->query('INSERT INTO products (title) VALUES (NULL)'),
Dibi\NotNullConstraintViolationException::class,
null,
19,
);
Assert::same('INSERT INTO products (title) VALUES (NULL)', $e->getSql());

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