mirror of
https://github.com/dg/dibi.git
synced 2025-09-04 03:35:26 +02:00
Compare commits
11 Commits
v5.1.0
...
translator
Author | SHA1 | Date | |
---|---|---|---|
|
09a974ed7a | ||
|
124d52139c | ||
|
78646e1790 | ||
|
cab1c5b5e6 | ||
|
94df6db03d | ||
|
0575f9ea17 | ||
|
76b8ed2108 | ||
|
4b1a2faa76 | ||
|
b931dbe13b | ||
|
a55e2a0cf8 | ||
|
6356f9f7a4 |
4
.gitattributes
vendored
4
.gitattributes
vendored
@@ -1,8 +1,8 @@
|
|||||||
.gitattributes export-ignore
|
.gitattributes export-ignore
|
||||||
.gitignore export-ignore
|
.gitignore export-ignore
|
||||||
.github export-ignore
|
.github export-ignore
|
||||||
appveyor.yml export-ignore
|
.travis.yml export-ignore
|
||||||
ncs.* export-ignore
|
ecs.php export-ignore
|
||||||
phpstan.neon export-ignore
|
phpstan.neon export-ignore
|
||||||
tests/ export-ignore
|
tests/ export-ignore
|
||||||
|
|
||||||
|
8
.github/workflows/coding-style.yml
vendored
8
.github/workflows/coding-style.yml
vendored
@@ -7,10 +7,10 @@ jobs:
|
|||||||
name: Nette Code Checker
|
name: Nette Code Checker
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
- uses: shivammathur/setup-php@v2
|
- uses: shivammathur/setup-php@v2
|
||||||
with:
|
with:
|
||||||
php-version: 8.3
|
php-version: 8.0
|
||||||
coverage: none
|
coverage: none
|
||||||
|
|
||||||
- run: composer create-project nette/code-checker temp/code-checker ^3 --no-progress
|
- run: composer create-project nette/code-checker temp/code-checker ^3 --no-progress
|
||||||
@@ -21,10 +21,10 @@ jobs:
|
|||||||
name: Nette Coding Standard
|
name: Nette Coding Standard
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
- uses: shivammathur/setup-php@v2
|
- uses: shivammathur/setup-php@v2
|
||||||
with:
|
with:
|
||||||
php-version: 8.3
|
php-version: 8.0
|
||||||
coverage: none
|
coverage: none
|
||||||
|
|
||||||
- run: composer create-project nette/coding-standard temp/coding-standard ^3 --no-progress
|
- run: composer create-project nette/coding-standard temp/coding-standard ^3 --no-progress
|
||||||
|
4
.github/workflows/static-analysis.yml
vendored
4
.github/workflows/static-analysis.yml
vendored
@@ -10,10 +10,10 @@ jobs:
|
|||||||
name: PHPStan
|
name: PHPStan
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
- uses: shivammathur/setup-php@v2
|
- uses: shivammathur/setup-php@v2
|
||||||
with:
|
with:
|
||||||
php-version: 8.2
|
php-version: 8.0
|
||||||
coverage: none
|
coverage: none
|
||||||
|
|
||||||
- run: composer install --no-progress --prefer-dist
|
- run: composer install --no-progress --prefer-dist
|
||||||
|
17
.github/workflows/tests.yml
vendored
17
.github/workflows/tests.yml
vendored
@@ -3,7 +3,7 @@ name: Tests
|
|||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
php-extensions: mbstring, intl, mysqli, pgsql, sqlsrv-5.12.0, pdo_sqlsrv-5.12.0
|
php-extensions: mbstring, intl, mysqli, pgsql, sqlsrv-5.10.0beta2, pdo_sqlsrv-5.10.0beta2
|
||||||
php-tools: "composer:v2, pecl"
|
php-tools: "composer:v2, pecl"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -11,7 +11,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
php: ['8.2', '8.3', '8.4', '8.5']
|
php: ['8.0', '8.1', '8.2']
|
||||||
|
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
|
||||||
@@ -43,6 +43,7 @@ jobs:
|
|||||||
--health-retries=5
|
--health-retries=5
|
||||||
-e MYSQL_ROOT_PASSWORD=root
|
-e MYSQL_ROOT_PASSWORD=root
|
||||||
-e MYSQL_DATABASE=dibi_test
|
-e MYSQL_DATABASE=dibi_test
|
||||||
|
--entrypoint sh mysql:8 -c "exec docker-entrypoint.sh mysqld --default-authentication-plugin=mysql_native_password"
|
||||||
|
|
||||||
postgres96:
|
postgres96:
|
||||||
image: postgres:9.6
|
image: postgres:9.6
|
||||||
@@ -82,13 +83,13 @@ jobs:
|
|||||||
- 1433:1433
|
- 1433:1433
|
||||||
options: >-
|
options: >-
|
||||||
--name=mssql
|
--name=mssql
|
||||||
--health-cmd "/opt/mssql-tools18/bin/sqlcmd -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'SELECT 1' -N -C"
|
--health-cmd "/opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'SELECT 1'"
|
||||||
--health-interval 10s
|
--health-interval 10s
|
||||||
--health-timeout 5s
|
--health-timeout 5s
|
||||||
--health-retries 5
|
--health-retries 5
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
- uses: shivammathur/setup-php@v2
|
- uses: shivammathur/setup-php@v2
|
||||||
with:
|
with:
|
||||||
php-version: ${{ matrix.php }}
|
php-version: ${{ matrix.php }}
|
||||||
@@ -100,19 +101,19 @@ jobs:
|
|||||||
run: cp ./tests/databases.github.ini ./tests/databases.ini
|
run: cp ./tests/databases.github.ini ./tests/databases.ini
|
||||||
|
|
||||||
- name: Create MS SQL Database
|
- name: Create MS SQL Database
|
||||||
run: docker exec -i mssql /opt/mssql-tools18/bin/sqlcmd -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'CREATE DATABASE dibi_test' -N -C
|
run: docker exec -i mssql /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'CREATE DATABASE dibi_test'
|
||||||
|
|
||||||
- run: composer install --no-progress --prefer-dist
|
- run: composer install --no-progress --prefer-dist
|
||||||
- run: vendor/bin/tester -p phpdbg tests -s -C --coverage ./coverage.xml --coverage-src ./src
|
- run: vendor/bin/tester -p phpdbg tests -s -C --coverage ./coverage.xml --coverage-src ./src
|
||||||
- if: failure()
|
- if: failure()
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: output-${{ matrix.php }}
|
name: output
|
||||||
path: tests/**/output
|
path: tests/**/output
|
||||||
|
|
||||||
|
|
||||||
- name: Save Code Coverage
|
- name: Save Code Coverage
|
||||||
if: ${{ matrix.php == '8.2' }}
|
if: ${{ matrix.php == '8.0' }}
|
||||||
env:
|
env:
|
||||||
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
|
@@ -11,32 +11,27 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "8.2 - 8.5"
|
"php": ">=8.0 <8.3"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"tracy/tracy": "^2.9",
|
"tracy/tracy": "^2.8",
|
||||||
"nette/tester": "^2.5",
|
"nette/tester": "^2.4",
|
||||||
"nette/di": "^3.1",
|
"nette/di": "^3.0",
|
||||||
"phpstan/phpstan-nette": "^2.0@stable",
|
"phpstan/phpstan": "^0.12"
|
||||||
"jetbrains/phpstorm-attributes": "^1.0"
|
|
||||||
},
|
},
|
||||||
"replace": {
|
"replace": {
|
||||||
"dg/dibi": "*"
|
"dg/dibi": "*"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"classmap": ["src/"],
|
"classmap": ["src/"]
|
||||||
"psr-4": {
|
|
||||||
"Dibi\\": "src/Dibi"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"minimum-stability": "dev",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"phpstan": "phpstan analyse",
|
"phpstan": "phpstan analyse",
|
||||||
"tester": "tester tests -s"
|
"tester": "tester tests -s"
|
||||||
},
|
},
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "5.1-dev"
|
"dev-master": "5.0-dev"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -27,9 +27,9 @@ $dibi = new Dibi\Connection([
|
|||||||
// using manual hints
|
// using manual hints
|
||||||
$res = $dibi->query('SELECT * FROM [customers]');
|
$res = $dibi->query('SELECT * FROM [customers]');
|
||||||
|
|
||||||
$res->setType('customer_id', Type::Integer)
|
$res->setType('customer_id', Type::INTEGER)
|
||||||
->setType('added', Type::DateTime)
|
->setType('added', Type::DATETIME)
|
||||||
->setFormat(Type::DateTime, 'Y-m-d H:i:s');
|
->setFormat(Type::DATETIME, 'Y-m-d H:i:s');
|
||||||
|
|
||||||
|
|
||||||
Tracy\Dumper::dump($res->fetch());
|
Tracy\Dumper::dump($res->fetch());
|
||||||
|
@@ -45,7 +45,7 @@ function substFallBack($expr)
|
|||||||
|
|
||||||
|
|
||||||
// define callback
|
// define callback
|
||||||
$dibi->getSubstitutes()->setCallback(substFallBack(...));
|
$dibi->getSubstitutes()->setCallback('substFallBack');
|
||||||
|
|
||||||
// define substitutes as constants
|
// define substitutes as constants
|
||||||
define('SUBST_ACCOUNT', 'eshop_');
|
define('SUBST_ACCOUNT', 'eshop_');
|
||||||
|
@@ -34,7 +34,7 @@ Install Dibi via Composer:
|
|||||||
composer require dibi/dibi
|
composer require dibi/dibi
|
||||||
```
|
```
|
||||||
|
|
||||||
The Dibi 5.1 requires PHP version 8.2 and supports PHP up to 8.5.
|
The Dibi 5.0 requires PHP version 8.0 and supports PHP up to 8.2.
|
||||||
|
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
@@ -341,7 +341,7 @@ $database->query('INSERT INTO users', [
|
|||||||
There are three methods for dealing with transactions:
|
There are three methods for dealing with transactions:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
$database->begin();
|
$database->beginTransaction();
|
||||||
|
|
||||||
$database->commit();
|
$database->commit();
|
||||||
|
|
||||||
@@ -608,7 +608,7 @@ $database->query("UPDATE [:blog:items] SET [text]='Hello World'");
|
|||||||
Dibi automatically detects the types of query columns and converts fields them to native PHP types. We can also specify the type manually. You can find the possible types in the `Dibi\Type` class.
|
Dibi automatically detects the types of query columns and converts fields them to native PHP types. We can also specify the type manually. You can find the possible types in the `Dibi\Type` class.
|
||||||
|
|
||||||
```php
|
```php
|
||||||
$result->setType('id', Dibi\Type::Integer); // id will be integer
|
$result->setType('id', Dibi\Type::INTEGER); // id will be integer
|
||||||
$row = $result->fetch();
|
$row = $result->fetch();
|
||||||
|
|
||||||
is_int($row->id) // true
|
is_int($row->id) // true
|
||||||
@@ -639,7 +639,7 @@ In the configuration file, we will register the DI extensions and add the `dibi`
|
|||||||
|
|
||||||
```neon
|
```neon
|
||||||
extensions:
|
extensions:
|
||||||
dibi: Dibi\Bridges\Nette\DibiExtension3
|
dibi: Dibi\Bridges\Nette\DibiExtension22
|
||||||
|
|
||||||
dibi:
|
dibi:
|
||||||
host: localhost
|
host: localhost
|
||||||
|
@@ -19,10 +19,15 @@ use Tracy;
|
|||||||
*/
|
*/
|
||||||
class DibiExtension22 extends Nette\DI\CompilerExtension
|
class DibiExtension22 extends Nette\DI\CompilerExtension
|
||||||
{
|
{
|
||||||
public function __construct(
|
private ?bool $debugMode;
|
||||||
private ?bool $debugMode = null,
|
|
||||||
private ?bool $cliMode = null,
|
private ?bool $cliMode;
|
||||||
) {
|
|
||||||
|
|
||||||
|
public function __construct(?bool $debugMode = null, ?bool $cliMode = null)
|
||||||
|
{
|
||||||
|
$this->debugMode = $debugMode;
|
||||||
|
$this->cliMode = $cliMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,93 +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\Bridges\Nette;
|
|
||||||
|
|
||||||
use Dibi;
|
|
||||||
use Nette;
|
|
||||||
use Nette\Schema\Expect;
|
|
||||||
use Tracy;
|
|
||||||
use function is_array;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dibi extension for Nette Framework 3. Creates 'connection' & 'panel' services.
|
|
||||||
*/
|
|
||||||
class DibiExtension3 extends Nette\DI\CompilerExtension
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private ?bool $debugMode = null,
|
|
||||||
private ?bool $cliMode = null,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,8 +1,8 @@
|
|||||||
# This will create service named 'dibi.connection'.
|
# This will create service named 'dibi.connection'.
|
||||||
# Requires Nette Framework 3 or later
|
# Requires Nette Framework 2.2 or later
|
||||||
|
|
||||||
extensions:
|
extensions:
|
||||||
dibi: Dibi\Bridges\Nette\DibiExtension3
|
dibi: Dibi\Bridges\Nette\DibiExtension22
|
||||||
|
|
||||||
dibi:
|
dibi:
|
||||||
host: localhost
|
host: localhost
|
||||||
|
@@ -13,7 +13,6 @@ use Dibi;
|
|||||||
use Dibi\Event;
|
use Dibi\Event;
|
||||||
use Dibi\Helpers;
|
use Dibi\Helpers;
|
||||||
use Tracy;
|
use Tracy;
|
||||||
use function count, is_string, strlen;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -21,23 +20,30 @@ use function count, is_string, strlen;
|
|||||||
*/
|
*/
|
||||||
class Panel implements Tracy\IBarPanel
|
class Panel implements Tracy\IBarPanel
|
||||||
{
|
{
|
||||||
|
use Dibi\Strict;
|
||||||
|
|
||||||
|
/** maximum SQL length */
|
||||||
public static int $maxLength = 1000;
|
public static int $maxLength = 1000;
|
||||||
|
|
||||||
|
public bool|string $explain;
|
||||||
|
|
||||||
|
public int $filter;
|
||||||
|
|
||||||
private array $events = [];
|
private array $events = [];
|
||||||
|
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(bool $explain = true, ?int $filter = null)
|
||||||
public bool|string $explain = true,
|
{
|
||||||
public int $filter = Event::QUERY,
|
$this->filter = $filter ?: Event::QUERY;
|
||||||
) {
|
$this->explain = $explain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function register(Dibi\Connection $connection): void
|
public function register(Dibi\Connection $connection): void
|
||||||
{
|
{
|
||||||
Tracy\Debugger::getBar()->addPanel($this);
|
Tracy\Debugger::getBar()->addPanel($this);
|
||||||
Tracy\Debugger::getBlueScreen()->addPanel(self::renderException(...));
|
Tracy\Debugger::getBlueScreen()->addPanel([self::class, 'renderException']);
|
||||||
$connection->onEvent[] = $this->logEvent(...);
|
$connection->onEvent[] = [$this, 'logEvent'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -62,7 +68,7 @@ class Panel implements Tracy\IBarPanel
|
|||||||
if ($e instanceof Dibi\Exception && $e->getSql()) {
|
if ($e instanceof Dibi\Exception && $e->getSql()) {
|
||||||
return [
|
return [
|
||||||
'tab' => 'SQL',
|
'tab' => 'SQL',
|
||||||
'panel' => Helpers::dump($e->getSql(), return: true),
|
'panel' => Helpers::dump($e->getSql(), true),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +124,7 @@ class Panel implements Tracy\IBarPanel
|
|||||||
? $this->explain
|
? $this->explain
|
||||||
: ($connection->getConfig('driver') === 'oracle' ? 'EXPLAIN PLAN FOR' : 'EXPLAIN');
|
: ($connection->getConfig('driver') === 'oracle' ? 'EXPLAIN PLAN FOR' : 'EXPLAIN');
|
||||||
try {
|
try {
|
||||||
$explain = @Helpers::dump($connection->nativeQuery("$cmd $event->sql"), return: true);
|
$explain = @Helpers::dump($connection->nativeQuery("$cmd $event->sql"), true);
|
||||||
} catch (Dibi\Exception $e) {
|
} catch (Dibi\Exception $e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +138,7 @@ class Panel implements Tracy\IBarPanel
|
|||||||
$s .= "<br /><a href='#tracy-debug-DibiProfiler-row-$counter' class='tracy-toggle tracy-collapsed' rel='#tracy-debug-DibiProfiler-row-$counter'>explain</a>";
|
$s .= "<br /><a href='#tracy-debug-DibiProfiler-row-$counter' class='tracy-toggle tracy-collapsed' rel='#tracy-debug-DibiProfiler-row-$counter'>explain</a>";
|
||||||
}
|
}
|
||||||
|
|
||||||
$s .= '</td><td class="tracy-DibiProfiler-sql">' . Helpers::dump(strlen($event->sql) > self::$maxLength ? substr($event->sql, 0, self::$maxLength) . '...' : $event->sql, return: true);
|
$s .= '</td><td class="tracy-DibiProfiler-sql">' . Helpers::dump(strlen($event->sql) > self::$maxLength ? substr($event->sql, 0, self::$maxLength) . '...' : $event->sql, true);
|
||||||
if ($explain) {
|
if ($explain) {
|
||||||
$s .= "<div id='tracy-debug-DibiProfiler-row-$counter' class='tracy-collapsed'>{$explain}</div>";
|
$s .= "<div id='tracy-debug-DibiProfiler-row-$counter' class='tracy-collapsed'>{$explain}</div>";
|
||||||
}
|
}
|
||||||
@@ -151,8 +157,8 @@ class Panel implements Tracy\IBarPanel
|
|||||||
#tracy-debug .tracy-DibiProfiler-source { color: #999 !important }
|
#tracy-debug .tracy-DibiProfiler-source { color: #999 !important }
|
||||||
#tracy-debug tracy-DibiProfiler tr table { margin: 8px 0; max-height: 150px; overflow:auto } </style>
|
#tracy-debug tracy-DibiProfiler tr table { margin: 8px 0; max-height: 150px; overflow:auto } </style>
|
||||||
<h1>Queries:' . "\u{a0}" . count($this->events)
|
<h1>Queries:' . "\u{a0}" . count($this->events)
|
||||||
. ($totalTime === null ? '' : ", time:\u{a0}" . number_format($totalTime * 1000, 1, '.', "\u{202f}") . "\u{202f}ms")
|
. ($totalTime === null ? '' : ", time:\u{a0}" . number_format($totalTime * 1000, 1, '.', "\u{202f}") . "\u{202f}ms") . ', '
|
||||||
. ($singleConnection === null ? '' : ', ' . htmlspecialchars($this->getConnectionName($singleConnection))) . '</h1>
|
. htmlspecialchars($this->getConnectionName($singleConnection)) . '</h1>
|
||||||
<div class="tracy-inner tracy-DibiProfiler">
|
<div class="tracy-inner tracy-DibiProfiler">
|
||||||
<table class="tracy-sortable">
|
<table class="tracy-sortable">
|
||||||
<tr><th>Time ms</th><th>SQL Statement</th><th>Rows</th>' . (!$singleConnection ? '<th>Connection</th>' : '') . '</tr>
|
<tr><th>Time ms</th><th>SQL Statement</th><th>Rows</th>' . (!$singleConnection ? '<th>Connection</th>' : '') . '</tr>
|
||||||
@@ -165,7 +171,7 @@ class Panel implements Tracy\IBarPanel
|
|||||||
private function getConnectionName(Dibi\Connection $connection): string
|
private function getConnectionName(Dibi\Connection $connection): string
|
||||||
{
|
{
|
||||||
$driver = $connection->getConfig('driver');
|
$driver = $connection->getConfig('driver');
|
||||||
return get_debug_type($driver)
|
return (is_object($driver) ? $driver::class : $driver)
|
||||||
. ($connection->getConfig('name') ? '/' . $connection->getConfig('name') : '')
|
. ($connection->getConfig('name') ? '/' . $connection->getConfig('name') : '')
|
||||||
. ($connection->getConfig('host') ? "\u{202f}@\u{202f}" . $connection->getConfig('host') : '');
|
. ($connection->getConfig('host') ? "\u{202f}@\u{202f}" . $connection->getConfig('host') : '');
|
||||||
}
|
}
|
||||||
|
@@ -9,10 +9,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Dibi;
|
namespace Dibi;
|
||||||
|
|
||||||
use JetBrains\PhpStorm\Language;
|
|
||||||
use Traversable;
|
use Traversable;
|
||||||
use function array_key_exists, is_array, sprintf;
|
|
||||||
use const PHP_SAPI;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -23,19 +20,26 @@ use const PHP_SAPI;
|
|||||||
*/
|
*/
|
||||||
class Connection implements IConnection
|
class Connection implements IConnection
|
||||||
{
|
{
|
||||||
|
use Strict;
|
||||||
|
|
||||||
/** function (Event $event); Occurs after query is executed */
|
/** function (Event $event); Occurs after query is executed */
|
||||||
public ?array $onEvent = [];
|
public ?array $onEvent = [];
|
||||||
|
|
||||||
|
/** Current connection configuration */
|
||||||
private array $config;
|
private array $config;
|
||||||
|
|
||||||
/** @var string[] resultset formats */
|
/** @var string[] resultset formats */
|
||||||
private array $formats;
|
private array $formats;
|
||||||
|
|
||||||
private ?Driver $driver = null;
|
private ?Driver $driver = null;
|
||||||
|
|
||||||
private ?Translator $translator = null;
|
private ?Translator $translator = null;
|
||||||
|
|
||||||
/** @var array<string, callable(object): Expression | null> */
|
/** @var array<string, callable(object): Expression> */
|
||||||
private array $translators = [];
|
private array $translators = [];
|
||||||
private bool $sortTranslators = false;
|
|
||||||
private HashMap $substitutes;
|
private HashMap $substitutes;
|
||||||
|
|
||||||
private int $transactionDepth = 0;
|
private int $transactionDepth = 0;
|
||||||
|
|
||||||
|
|
||||||
@@ -76,10 +80,10 @@ class Connection implements IConnection
|
|||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
|
|
||||||
$this->formats = [
|
$this->formats = [
|
||||||
Type::Date => $this->config['result']['formatDate'],
|
Type::DATE => $this->config['result']['formatDate'],
|
||||||
Type::DateTime => $this->config['result']['formatDateTime'],
|
Type::DATETIME => $this->config['result']['formatDateTime'],
|
||||||
Type::JSON => $this->config['result']['formatJson'] ?? 'array',
|
Type::JSON => $this->config['result']['formatJson'] ?? 'array',
|
||||||
Type::TimeInterval => $this->config['result']['formatTimeInterval'] ?? null,
|
Type::TIME_INTERVAL => $this->config['result']['formatTimeInterval'] ?? null,
|
||||||
];
|
];
|
||||||
|
|
||||||
// profiler
|
// profiler
|
||||||
@@ -212,7 +216,7 @@ class Connection implements IConnection
|
|||||||
* Generates (translates) and executes SQL query.
|
* Generates (translates) and executes SQL query.
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
final public function query(#[Language('GenericSQL')] mixed ...$args): Result
|
final public function query(mixed ...$args): Result
|
||||||
{
|
{
|
||||||
return $this->nativeQuery($this->translate(...$args));
|
return $this->nativeQuery($this->translate(...$args));
|
||||||
}
|
}
|
||||||
@@ -222,7 +226,7 @@ class Connection implements IConnection
|
|||||||
* Generates SQL query.
|
* Generates SQL query.
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
final public function translate(#[Language('GenericSQL')] mixed ...$args): string
|
final public function translate(mixed ...$args): string
|
||||||
{
|
{
|
||||||
if (!$this->driver) {
|
if (!$this->driver) {
|
||||||
$this->connect();
|
$this->connect();
|
||||||
@@ -235,7 +239,7 @@ class Connection implements IConnection
|
|||||||
/**
|
/**
|
||||||
* Generates and prints SQL query.
|
* Generates and prints SQL query.
|
||||||
*/
|
*/
|
||||||
final public function test(#[Language('GenericSQL')] mixed ...$args): bool
|
final public function test(mixed ...$args): bool
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
Helpers::dump($this->translate(...$args));
|
Helpers::dump($this->translate(...$args));
|
||||||
@@ -257,7 +261,7 @@ class Connection implements IConnection
|
|||||||
* Generates (translates) and returns SQL query as DataSource.
|
* Generates (translates) and returns SQL query as DataSource.
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
final public function dataSource(#[Language('GenericSQL')] mixed ...$args): DataSource
|
final public function dataSource(mixed ...$args): DataSource
|
||||||
{
|
{
|
||||||
return new DataSource($this->translate(...$args), $this);
|
return new DataSource($this->translate(...$args), $this);
|
||||||
}
|
}
|
||||||
@@ -267,7 +271,7 @@ class Connection implements IConnection
|
|||||||
* Executes the SQL query.
|
* Executes the SQL query.
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
final public function nativeQuery(#[Language('SQL')] string $sql): Result
|
final public function nativeQuery(string $sql): Result
|
||||||
{
|
{
|
||||||
if (!$this->driver) {
|
if (!$this->driver) {
|
||||||
$this->connect();
|
$this->connect();
|
||||||
@@ -524,68 +528,19 @@ class Connection implements IConnection
|
|||||||
/********************* value objects translation ****************d*g**/
|
/********************* value objects translation ****************d*g**/
|
||||||
|
|
||||||
|
|
||||||
/**
|
/** @param callable(object): Expression $translator */
|
||||||
* @param callable(object): Expression $translator
|
public function addObjectTranslator(string $class, callable $translator): self
|
||||||
*/
|
|
||||||
public function setObjectTranslator(callable $translator): void
|
|
||||||
{
|
{
|
||||||
if (!$translator instanceof \Closure) {
|
$this->translators[$class] = $translator;
|
||||||
$translator = \Closure::fromCallable($translator);
|
uksort($this->translators, fn($a, $b) => class_exists($a, false) && is_subclass_of($a, $b) ? -1 : 1);
|
||||||
}
|
return $this;
|
||||||
|
|
||||||
$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
|
/** @return array<string, callable(object): Expression> */
|
||||||
|
public function getObjectTranslators(): array
|
||||||
{
|
{
|
||||||
if ($this->sortTranslators) {
|
return $this->translators;
|
||||||
$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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -596,7 +551,7 @@ class Connection implements IConnection
|
|||||||
* Executes SQL query and fetch result - shortcut for query() & fetch().
|
* Executes SQL query and fetch result - shortcut for query() & fetch().
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public function fetch(#[Language('GenericSQL')] mixed ...$args): ?Row
|
public function fetch(mixed ...$args): ?Row
|
||||||
{
|
{
|
||||||
return $this->query($args)->fetch();
|
return $this->query($args)->fetch();
|
||||||
}
|
}
|
||||||
@@ -607,7 +562,7 @@ class Connection implements IConnection
|
|||||||
* @return Row[]|array[]
|
* @return Row[]|array[]
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public function fetchAll(#[Language('GenericSQL')] mixed ...$args): array
|
public function fetchAll(mixed ...$args): array
|
||||||
{
|
{
|
||||||
return $this->query($args)->fetchAll();
|
return $this->query($args)->fetchAll();
|
||||||
}
|
}
|
||||||
@@ -617,7 +572,7 @@ class Connection implements IConnection
|
|||||||
* Executes SQL query and fetch first column - shortcut for query() & fetchSingle().
|
* Executes SQL query and fetch first column - shortcut for query() & fetchSingle().
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public function fetchSingle(#[Language('GenericSQL')] mixed ...$args): mixed
|
public function fetchSingle(mixed ...$args): mixed
|
||||||
{
|
{
|
||||||
return $this->query($args)->fetchSingle();
|
return $this->query($args)->fetchSingle();
|
||||||
}
|
}
|
||||||
@@ -627,7 +582,7 @@ class Connection implements IConnection
|
|||||||
* Executes SQL query and fetch pairs - shortcut for query() & fetchPairs().
|
* Executes SQL query and fetch pairs - shortcut for query() & fetchPairs().
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public function fetchPairs(#[Language('GenericSQL')] mixed ...$args): array
|
public function fetchPairs(mixed ...$args): array
|
||||||
{
|
{
|
||||||
return $this->query($args)->fetchPairs();
|
return $this->query($args)->fetchPairs();
|
||||||
}
|
}
|
||||||
@@ -675,7 +630,7 @@ class Connection implements IConnection
|
|||||||
/**
|
/**
|
||||||
* Prevents unserialization.
|
* Prevents unserialization.
|
||||||
*/
|
*/
|
||||||
public function __unserialize($_)
|
public function __wakeup()
|
||||||
{
|
{
|
||||||
throw new NotSupportedException('You cannot serialize or unserialize ' . static::class . ' instances.');
|
throw new NotSupportedException('You cannot serialize or unserialize ' . static::class . ' instances.');
|
||||||
}
|
}
|
||||||
@@ -684,7 +639,7 @@ class Connection implements IConnection
|
|||||||
/**
|
/**
|
||||||
* Prevents serialization.
|
* Prevents serialization.
|
||||||
*/
|
*/
|
||||||
public function __serialize()
|
public function __sleep()
|
||||||
{
|
{
|
||||||
throw new NotSupportedException('You cannot serialize or unserialize ' . static::class . ' instances.');
|
throw new NotSupportedException('You cannot serialize or unserialize ' . static::class . ' instances.');
|
||||||
}
|
}
|
||||||
|
@@ -9,23 +9,32 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Dibi;
|
namespace Dibi;
|
||||||
|
|
||||||
use function func_get_args, is_array, strpbrk;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default implementation of IDataSource.
|
* Default implementation of IDataSource.
|
||||||
*/
|
*/
|
||||||
class DataSource implements IDataSource
|
class DataSource implements IDataSource
|
||||||
{
|
{
|
||||||
private readonly Connection $connection;
|
use Strict;
|
||||||
private readonly string $sql;
|
|
||||||
|
private Connection $connection;
|
||||||
|
|
||||||
|
private string $sql;
|
||||||
|
|
||||||
private ?Result $result = null;
|
private ?Result $result = null;
|
||||||
|
|
||||||
private ?int $count = null;
|
private ?int $count = null;
|
||||||
|
|
||||||
private ?int $totalCount = null;
|
private ?int $totalCount = null;
|
||||||
|
|
||||||
private array $cols = [];
|
private array $cols = [];
|
||||||
|
|
||||||
private array $sorting = [];
|
private array $sorting = [];
|
||||||
|
|
||||||
private array $conds = [];
|
private array $conds = [];
|
||||||
|
|
||||||
private ?int $offset = null;
|
private ?int $offset = null;
|
||||||
|
|
||||||
private ?int $limit = null;
|
private ?int $limit = null;
|
||||||
|
|
||||||
|
|
||||||
|
@@ -15,6 +15,8 @@ namespace Dibi;
|
|||||||
*/
|
*/
|
||||||
class DateTime extends \DateTimeImmutable
|
class DateTime extends \DateTimeImmutable
|
||||||
{
|
{
|
||||||
|
use Strict;
|
||||||
|
|
||||||
public function __construct(string|int $time = 'now', ?\DateTimeZone $timezone = null)
|
public function __construct(string|int $time = 'now', ?\DateTimeZone $timezone = null)
|
||||||
{
|
{
|
||||||
$timezone = $timezone ?: new \DateTimeZone(date_default_timezone_get());
|
$timezone = $timezone ?: new \DateTimeZone(date_default_timezone_get());
|
||||||
|
@@ -17,6 +17,8 @@ use Dibi;
|
|||||||
*/
|
*/
|
||||||
class DummyDriver implements Dibi\Driver, Dibi\ResultDriver, Dibi\Reflector
|
class DummyDriver implements Dibi\Driver, Dibi\ResultDriver, Dibi\Reflector
|
||||||
{
|
{
|
||||||
|
use Dibi\Strict;
|
||||||
|
|
||||||
public function disconnect(): void
|
public function disconnect(): void
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@@ -11,7 +11,6 @@ namespace Dibi\Drivers;
|
|||||||
|
|
||||||
use Dibi;
|
use Dibi;
|
||||||
use Dibi\Helpers;
|
use Dibi\Helpers;
|
||||||
use function is_resource;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -27,16 +26,16 @@ use function is_resource;
|
|||||||
*/
|
*/
|
||||||
class FirebirdDriver implements Dibi\Driver
|
class FirebirdDriver implements Dibi\Driver
|
||||||
{
|
{
|
||||||
public const ErrorExceptionThrown = -836;
|
use Dibi\Strict;
|
||||||
|
|
||||||
/** @deprecated use FirebirdDriver::ErrorExceptionThrown */
|
public const ERROR_EXCEPTION_THROWN = -836;
|
||||||
public const ERROR_EXCEPTION_THROWN = self::ErrorExceptionThrown;
|
|
||||||
|
|
||||||
/** @var resource */
|
/** @var resource */
|
||||||
private $connection;
|
private $connection;
|
||||||
|
|
||||||
/** @var ?resource */
|
/** @var ?resource */
|
||||||
private $transaction;
|
private $transaction;
|
||||||
|
|
||||||
private bool $inTransaction = false;
|
private bool $inTransaction = false;
|
||||||
|
|
||||||
|
|
||||||
|
@@ -17,9 +17,14 @@ use Dibi;
|
|||||||
*/
|
*/
|
||||||
class FirebirdReflector implements Dibi\Reflector
|
class FirebirdReflector implements Dibi\Reflector
|
||||||
{
|
{
|
||||||
public function __construct(
|
use Dibi\Strict;
|
||||||
private readonly Dibi\Driver $driver,
|
|
||||||
) {
|
private Dibi\Driver $driver;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct(Dibi\Driver $driver)
|
||||||
|
{
|
||||||
|
$this->driver = $driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -11,7 +11,6 @@ namespace Dibi\Drivers;
|
|||||||
|
|
||||||
use Dibi;
|
use Dibi;
|
||||||
use Dibi\Helpers;
|
use Dibi\Helpers;
|
||||||
use function is_resource;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,10 +18,31 @@ use function is_resource;
|
|||||||
*/
|
*/
|
||||||
class FirebirdResult implements Dibi\ResultDriver
|
class FirebirdResult implements Dibi\ResultDriver
|
||||||
{
|
{
|
||||||
public function __construct(
|
use Dibi\Strict;
|
||||||
/** @var resource */
|
|
||||||
private $resultSet,
|
/** @var resource */
|
||||||
) {
|
private $resultSet;
|
||||||
|
|
||||||
|
private bool $autoFree = true;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param resource $resultSet
|
||||||
|
*/
|
||||||
|
public function __construct($resultSet)
|
||||||
|
{
|
||||||
|
$this->resultSet = $resultSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatically frees the resources allocated for this result set.
|
||||||
|
*/
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
if ($this->autoFree && $this->getResultResource()) {
|
||||||
|
$this->free();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -84,6 +104,7 @@ class FirebirdResult implements Dibi\ResultDriver
|
|||||||
*/
|
*/
|
||||||
public function getResultResource(): mixed
|
public function getResultResource(): mixed
|
||||||
{
|
{
|
||||||
|
$this->autoFree = false;
|
||||||
return is_resource($this->resultSet) ? $this->resultSet : null;
|
return is_resource($this->resultSet) ? $this->resultSet : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -18,9 +18,14 @@ use Dibi;
|
|||||||
*/
|
*/
|
||||||
class MySqlReflector implements Dibi\Reflector
|
class MySqlReflector implements Dibi\Reflector
|
||||||
{
|
{
|
||||||
public function __construct(
|
use Dibi\Strict;
|
||||||
private readonly Dibi\Driver $driver,
|
|
||||||
) {
|
private Dibi\Driver $driver;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct(Dibi\Driver $driver)
|
||||||
|
{
|
||||||
|
$this->driver = $driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -10,8 +10,6 @@ declare(strict_types=1);
|
|||||||
namespace Dibi\Drivers;
|
namespace Dibi\Drivers;
|
||||||
|
|
||||||
use Dibi;
|
use Dibi;
|
||||||
use function in_array;
|
|
||||||
use const MYSQLI_REPORT_OFF, MYSQLI_STORE_RESULT, MYSQLI_USE_RESULT, PREG_SET_ORDER;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,20 +32,17 @@ use const MYSQLI_REPORT_OFF, MYSQLI_STORE_RESULT, MYSQLI_USE_RESULT, PREG_SET_OR
|
|||||||
*/
|
*/
|
||||||
class MySqliDriver implements Dibi\Driver
|
class MySqliDriver implements Dibi\Driver
|
||||||
{
|
{
|
||||||
public const ErrorAccessDenied = 1045;
|
use Dibi\Strict;
|
||||||
public const ErrorDuplicateEntry = 1062;
|
|
||||||
public const ErrorDataTruncated = 1265;
|
|
||||||
|
|
||||||
/** @deprecated use MySqliDriver::ErrorAccessDenied */
|
public const ERROR_ACCESS_DENIED = 1045;
|
||||||
public const ERROR_ACCESS_DENIED = self::ErrorAccessDenied;
|
|
||||||
|
|
||||||
/** @deprecated use MySqliDriver::ErrorDuplicateEntry */
|
public const ERROR_DUPLICATE_ENTRY = 1062;
|
||||||
public const ERROR_DUPLICATE_ENTRY = self::ErrorDuplicateEntry;
|
|
||||||
|
|
||||||
/** @deprecated use MySqliDriver::ErrorDataTruncated */
|
public const ERROR_DATA_TRUNCATED = 1265;
|
||||||
public const ERROR_DATA_TRUNCATED = self::ErrorDataTruncated;
|
|
||||||
|
|
||||||
private \mysqli $connection;
|
private \mysqli $connection;
|
||||||
|
|
||||||
|
/** Is buffered (seekable and countable)? */
|
||||||
private bool $buffered = false;
|
private bool $buffered = false;
|
||||||
|
|
||||||
|
|
||||||
|
@@ -10,7 +10,6 @@ declare(strict_types=1);
|
|||||||
namespace Dibi\Drivers;
|
namespace Dibi\Drivers;
|
||||||
|
|
||||||
use Dibi;
|
use Dibi;
|
||||||
use const MYSQLI_TYPE_LONG, MYSQLI_TYPE_SHORT, MYSQLI_TYPE_TIME, MYSQLI_TYPE_TINY;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,10 +17,31 @@ use const MYSQLI_TYPE_LONG, MYSQLI_TYPE_SHORT, MYSQLI_TYPE_TIME, MYSQLI_TYPE_TIN
|
|||||||
*/
|
*/
|
||||||
class MySqliResult implements Dibi\ResultDriver
|
class MySqliResult implements Dibi\ResultDriver
|
||||||
{
|
{
|
||||||
public function __construct(
|
use Dibi\Strict;
|
||||||
private readonly \mysqli_result $resultSet,
|
|
||||||
private readonly bool $buffered,
|
private \mysqli_result $resultSet;
|
||||||
) {
|
|
||||||
|
private bool $autoFree = true;
|
||||||
|
|
||||||
|
/** Is buffered (seekable and countable)? */
|
||||||
|
private bool $buffered;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct(\mysqli_result $resultSet, bool $buffered)
|
||||||
|
{
|
||||||
|
$this->resultSet = $resultSet;
|
||||||
|
$this->buffered = $buffered;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatically frees the resources allocated for this result set.
|
||||||
|
*/
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
if ($this->autoFree && $this->getResultResource()) {
|
||||||
|
@$this->free();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -100,7 +120,7 @@ class MySqliResult implements Dibi\ResultDriver
|
|||||||
'table' => $row['orgtable'],
|
'table' => $row['orgtable'],
|
||||||
'fullname' => $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name'],
|
'fullname' => $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name'],
|
||||||
'nativetype' => $types[$row['type']] ?? $row['type'],
|
'nativetype' => $types[$row['type']] ?? $row['type'],
|
||||||
'type' => $row['type'] === MYSQLI_TYPE_TIME ? Dibi\Type::TimeInterval : null,
|
'type' => $row['type'] === MYSQLI_TYPE_TIME ? Dibi\Type::TIME_INTERVAL : null,
|
||||||
'vendor' => $row,
|
'vendor' => $row,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -114,6 +134,7 @@ class MySqliResult implements Dibi\ResultDriver
|
|||||||
*/
|
*/
|
||||||
public function getResultResource(): \mysqli_result
|
public function getResultResource(): \mysqli_result
|
||||||
{
|
{
|
||||||
|
$this->autoFree = false;
|
||||||
return $this->resultSet;
|
return $this->resultSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -17,9 +17,14 @@ use Dibi;
|
|||||||
*/
|
*/
|
||||||
class NoDataResult implements Dibi\ResultDriver
|
class NoDataResult implements Dibi\ResultDriver
|
||||||
{
|
{
|
||||||
public function __construct(
|
use Dibi\Strict;
|
||||||
private readonly int $rows,
|
|
||||||
) {
|
private int $rows;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct(int $rows)
|
||||||
|
{
|
||||||
|
$this->rows = $rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -10,7 +10,6 @@ declare(strict_types=1);
|
|||||||
namespace Dibi\Drivers;
|
namespace Dibi\Drivers;
|
||||||
|
|
||||||
use Dibi;
|
use Dibi;
|
||||||
use function is_resource;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -26,9 +25,13 @@ use function is_resource;
|
|||||||
*/
|
*/
|
||||||
class OdbcDriver implements Dibi\Driver
|
class OdbcDriver implements Dibi\Driver
|
||||||
{
|
{
|
||||||
|
use Dibi\Strict;
|
||||||
|
|
||||||
/** @var resource */
|
/** @var resource */
|
||||||
private $connection;
|
private $connection;
|
||||||
|
|
||||||
private ?int $affectedRows;
|
private ?int $affectedRows;
|
||||||
|
|
||||||
private bool $microseconds = true;
|
private bool $microseconds = true;
|
||||||
|
|
||||||
|
|
||||||
@@ -120,7 +123,7 @@ class OdbcDriver implements Dibi\Driver
|
|||||||
*/
|
*/
|
||||||
public function begin(?string $savepoint = null): void
|
public function begin(?string $savepoint = null): void
|
||||||
{
|
{
|
||||||
if (!odbc_autocommit($this->connection)) {
|
if (!odbc_autocommit($this->connection, false)) {
|
||||||
throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection));
|
throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -17,9 +17,14 @@ use Dibi;
|
|||||||
*/
|
*/
|
||||||
class OdbcReflector implements Dibi\Reflector
|
class OdbcReflector implements Dibi\Reflector
|
||||||
{
|
{
|
||||||
public function __construct(
|
use Dibi\Strict;
|
||||||
private readonly Dibi\Driver $driver,
|
|
||||||
) {
|
private Dibi\Driver $driver;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct(Dibi\Driver $driver)
|
||||||
|
{
|
||||||
|
$this->driver = $driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -10,7 +10,6 @@ declare(strict_types=1);
|
|||||||
namespace Dibi\Drivers;
|
namespace Dibi\Drivers;
|
||||||
|
|
||||||
use Dibi;
|
use Dibi;
|
||||||
use function is_resource;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,13 +17,33 @@ use function is_resource;
|
|||||||
*/
|
*/
|
||||||
class OdbcResult implements Dibi\ResultDriver
|
class OdbcResult implements Dibi\ResultDriver
|
||||||
{
|
{
|
||||||
|
use Dibi\Strict;
|
||||||
|
|
||||||
|
/** @var resource */
|
||||||
|
private $resultSet;
|
||||||
|
|
||||||
|
private bool $autoFree = true;
|
||||||
|
|
||||||
private int $row = 0;
|
private int $row = 0;
|
||||||
|
|
||||||
|
|
||||||
public function __construct(
|
/**
|
||||||
/** @var resource */
|
* @param resource $resultSet
|
||||||
private $resultSet,
|
*/
|
||||||
) {
|
public function __construct($resultSet)
|
||||||
|
{
|
||||||
|
$this->resultSet = $resultSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatically frees the resources allocated for this result set.
|
||||||
|
*/
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
if ($this->autoFree && $this->getResultResource()) {
|
||||||
|
$this->free();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -108,6 +127,7 @@ class OdbcResult implements Dibi\ResultDriver
|
|||||||
*/
|
*/
|
||||||
public function getResultResource(): mixed
|
public function getResultResource(): mixed
|
||||||
{
|
{
|
||||||
|
$this->autoFree = false;
|
||||||
return is_resource($this->resultSet) ? $this->resultSet : null;
|
return is_resource($this->resultSet) ? $this->resultSet : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -10,7 +10,6 @@ declare(strict_types=1);
|
|||||||
namespace Dibi\Drivers;
|
namespace Dibi\Drivers;
|
||||||
|
|
||||||
use Dibi;
|
use Dibi;
|
||||||
use function in_array, is_resource;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -28,10 +27,16 @@ use function in_array, is_resource;
|
|||||||
*/
|
*/
|
||||||
class OracleDriver implements Dibi\Driver
|
class OracleDriver implements Dibi\Driver
|
||||||
{
|
{
|
||||||
|
use Dibi\Strict;
|
||||||
|
|
||||||
/** @var resource */
|
/** @var resource */
|
||||||
private $connection;
|
private $connection;
|
||||||
|
|
||||||
private bool $autocommit = true;
|
private bool $autocommit = true;
|
||||||
|
|
||||||
|
/** use native datetime format */
|
||||||
private bool $nativeDate;
|
private bool $nativeDate;
|
||||||
|
|
||||||
private ?int $affectedRows;
|
private ?int $affectedRows;
|
||||||
|
|
||||||
|
|
||||||
|
@@ -17,9 +17,14 @@ use Dibi;
|
|||||||
*/
|
*/
|
||||||
class OracleReflector implements Dibi\Reflector
|
class OracleReflector implements Dibi\Reflector
|
||||||
{
|
{
|
||||||
public function __construct(
|
use Dibi\Strict;
|
||||||
private readonly Dibi\Driver $driver,
|
|
||||||
) {
|
private Dibi\Driver $driver;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct(Dibi\Driver $driver)
|
||||||
|
{
|
||||||
|
$this->driver = $driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -10,7 +10,6 @@ declare(strict_types=1);
|
|||||||
namespace Dibi\Drivers;
|
namespace Dibi\Drivers;
|
||||||
|
|
||||||
use Dibi;
|
use Dibi;
|
||||||
use function is_resource;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,10 +17,31 @@ use function is_resource;
|
|||||||
*/
|
*/
|
||||||
class OracleResult implements Dibi\ResultDriver
|
class OracleResult implements Dibi\ResultDriver
|
||||||
{
|
{
|
||||||
public function __construct(
|
use Dibi\Strict;
|
||||||
/** @var resource */
|
|
||||||
private $resultSet,
|
/** @var resource */
|
||||||
) {
|
private $resultSet;
|
||||||
|
|
||||||
|
private bool $autoFree = true;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param resource $resultSet
|
||||||
|
*/
|
||||||
|
public function __construct($resultSet)
|
||||||
|
{
|
||||||
|
$this->resultSet = $resultSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatically frees the resources allocated for this result set.
|
||||||
|
*/
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
if ($this->autoFree && $this->getResultResource()) {
|
||||||
|
$this->free();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -75,7 +95,7 @@ class OracleResult implements Dibi\ResultDriver
|
|||||||
'name' => oci_field_name($this->resultSet, $i),
|
'name' => oci_field_name($this->resultSet, $i),
|
||||||
'table' => null,
|
'table' => null,
|
||||||
'fullname' => oci_field_name($this->resultSet, $i),
|
'fullname' => oci_field_name($this->resultSet, $i),
|
||||||
'type' => $type === 'LONG' ? Dibi\Type::Text : null,
|
'type' => $type === 'LONG' ? Dibi\Type::TEXT : null,
|
||||||
'nativetype' => $type === 'NUMBER' && oci_field_scale($this->resultSet, $i) === 0 ? 'INTEGER' : $type,
|
'nativetype' => $type === 'NUMBER' && oci_field_scale($this->resultSet, $i) === 0 ? 'INTEGER' : $type,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -90,6 +110,7 @@ class OracleResult implements Dibi\ResultDriver
|
|||||||
*/
|
*/
|
||||||
public function getResultResource(): mixed
|
public function getResultResource(): mixed
|
||||||
{
|
{
|
||||||
|
$this->autoFree = false;
|
||||||
return is_resource($this->resultSet) ? $this->resultSet : null;
|
return is_resource($this->resultSet) ? $this->resultSet : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -12,7 +12,6 @@ namespace Dibi\Drivers;
|
|||||||
use Dibi;
|
use Dibi;
|
||||||
use Dibi\Helpers;
|
use Dibi\Helpers;
|
||||||
use PDO;
|
use PDO;
|
||||||
use function sprintf;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,13 +23,20 @@ use function sprintf;
|
|||||||
* - password (or pass)
|
* - password (or pass)
|
||||||
* - options (array) => driver specific options {@see PDO::__construct}
|
* - options (array) => driver specific options {@see PDO::__construct}
|
||||||
* - resource (PDO) => existing connection
|
* - resource (PDO) => existing connection
|
||||||
|
* - version
|
||||||
*/
|
*/
|
||||||
class PdoDriver implements Dibi\Driver
|
class PdoDriver implements Dibi\Driver
|
||||||
{
|
{
|
||||||
|
use Dibi\Strict;
|
||||||
|
|
||||||
private ?PDO $connection;
|
private ?PDO $connection;
|
||||||
|
|
||||||
private ?int $affectedRows;
|
private ?int $affectedRows;
|
||||||
|
|
||||||
private string $driverName;
|
private string $driverName;
|
||||||
|
|
||||||
|
private string $serverVersion = '';
|
||||||
|
|
||||||
|
|
||||||
/** @throws Dibi\NotSupportedException */
|
/** @throws Dibi\NotSupportedException */
|
||||||
public function __construct(array $config)
|
public function __construct(array $config)
|
||||||
@@ -64,6 +70,7 @@ class PdoDriver implements Dibi\Driver
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->driverName = $this->connection->getAttribute(PDO::ATTR_DRIVER_NAME);
|
$this->driverName = $this->connection->getAttribute(PDO::ATTR_DRIVER_NAME);
|
||||||
|
$this->serverVersion = (string) ($config['version'] ?? @$this->connection->getAttribute(PDO::ATTR_SERVER_VERSION)); // @ - may be not supported
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -91,15 +98,23 @@ class PdoDriver implements Dibi\Driver
|
|||||||
$this->affectedRows = null;
|
$this->affectedRows = null;
|
||||||
|
|
||||||
[$sqlState, $code, $message] = $this->connection->errorInfo();
|
[$sqlState, $code, $message] = $this->connection->errorInfo();
|
||||||
$code ??= 0;
|
|
||||||
$message = "SQLSTATE[$sqlState]: $message";
|
$message = "SQLSTATE[$sqlState]: $message";
|
||||||
throw match ($this->driverName) {
|
switch ($this->driverName) {
|
||||||
'mysql' => MySqliDriver::createException($message, $code, $sql),
|
case 'mysql':
|
||||||
'oci' => OracleDriver::createException($message, $code, $sql),
|
throw MySqliDriver::createException($message, $code, $sql);
|
||||||
'pgsql' => PostgreDriver::createException($message, $sqlState, $sql),
|
|
||||||
'sqlite' => SqliteDriver::createException($message, $code, $sql),
|
case 'oci':
|
||||||
default => new Dibi\DriverException($message, $code, $sql),
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -129,7 +144,7 @@ class PdoDriver implements Dibi\Driver
|
|||||||
{
|
{
|
||||||
if (!$this->connection->beginTransaction()) {
|
if (!$this->connection->beginTransaction()) {
|
||||||
$err = $this->connection->errorInfo();
|
$err = $this->connection->errorInfo();
|
||||||
throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1] ?? 0);
|
throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +157,7 @@ class PdoDriver implements Dibi\Driver
|
|||||||
{
|
{
|
||||||
if (!$this->connection->commit()) {
|
if (!$this->connection->commit()) {
|
||||||
$err = $this->connection->errorInfo();
|
$err = $this->connection->errorInfo();
|
||||||
throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1] ?? 0);
|
throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +170,7 @@ class PdoDriver implements Dibi\Driver
|
|||||||
{
|
{
|
||||||
if (!$this->connection->rollBack()) {
|
if (!$this->connection->rollBack()) {
|
||||||
$err = $this->connection->errorInfo();
|
$err = $this->connection->errorInfo();
|
||||||
throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1] ?? 0);
|
throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,14 +189,27 @@ class PdoDriver implements Dibi\Driver
|
|||||||
*/
|
*/
|
||||||
public function getReflector(): Dibi\Reflector
|
public function getReflector(): Dibi\Reflector
|
||||||
{
|
{
|
||||||
return match ($this->driverName) {
|
switch ($this->driverName) {
|
||||||
'mysql' => new MySqlReflector($this),
|
case 'mysql':
|
||||||
'oci' => new OracleReflector($this),
|
return new MySqlReflector($this);
|
||||||
'pgsql' => new PostgreReflector($this),
|
|
||||||
'sqlite' => new SqliteReflector($this),
|
case 'oci':
|
||||||
'mssql', 'dblib', 'sqlsrv' => new SqlsrvReflector($this),
|
return new OracleReflector($this);
|
||||||
default => throw new Dibi\NotSupportedException,
|
|
||||||
};
|
case 'pgsql':
|
||||||
|
return new PostgreReflector($this, $this->connection->getAttribute(PDO::ATTR_SERVER_VERSION));
|
||||||
|
|
||||||
|
case 'sqlite':
|
||||||
|
return new SqliteReflector($this);
|
||||||
|
|
||||||
|
case 'mssql':
|
||||||
|
case 'dblib':
|
||||||
|
case 'sqlsrv':
|
||||||
|
return new SqlsrvReflector($this);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Dibi\NotSupportedException;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -202,34 +230,44 @@ class PdoDriver implements Dibi\Driver
|
|||||||
*/
|
*/
|
||||||
public function escapeText(string $value): string
|
public function escapeText(string $value): string
|
||||||
{
|
{
|
||||||
return match ($this->driverName) {
|
return $this->driverName === 'odbc'
|
||||||
'odbc' => "'" . str_replace("'", "''", $value) . "'",
|
? "'" . str_replace("'", "''", $value) . "'"
|
||||||
'sqlsrv' => "N'" . str_replace("'", "''", $value) . "'",
|
: $this->connection->quote($value, PDO::PARAM_STR);
|
||||||
default => $this->connection->quote($value, PDO::PARAM_STR),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function escapeBinary(string $value): string
|
public function escapeBinary(string $value): string
|
||||||
{
|
{
|
||||||
return match ($this->driverName) {
|
return $this->driverName === 'odbc'
|
||||||
'odbc' => "'" . str_replace("'", "''", $value) . "'",
|
? "'" . str_replace("'", "''", $value) . "'"
|
||||||
'sqlsrv' => '0x' . bin2hex($value),
|
: $this->connection->quote($value, PDO::PARAM_LOB);
|
||||||
default => $this->connection->quote($value, PDO::PARAM_LOB),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function escapeIdentifier(string $value): string
|
public function escapeIdentifier(string $value): string
|
||||||
{
|
{
|
||||||
return match ($this->driverName) {
|
switch ($this->driverName) {
|
||||||
'mysql' => '`' . str_replace('`', '``', $value) . '`',
|
case 'mysql':
|
||||||
'oci', 'pgsql' => '"' . str_replace('"', '""', $value) . '"',
|
return '`' . str_replace('`', '``', $value) . '`';
|
||||||
'sqlite' => '[' . strtr($value, '[]', ' ') . ']',
|
|
||||||
'odbc', 'mssql' => '[' . str_replace(['[', ']'], ['[[', ']]'], $value) . ']',
|
case 'oci':
|
||||||
'dblib', 'sqlsrv' => '[' . str_replace(']', ']]', $value) . ']',
|
case 'pgsql':
|
||||||
default => $value,
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -251,11 +289,16 @@ class PdoDriver implements Dibi\Driver
|
|||||||
|
|
||||||
public function escapeDateTime(\DateTimeInterface $value): string
|
public function escapeDateTime(\DateTimeInterface $value): string
|
||||||
{
|
{
|
||||||
return match ($this->driverName) {
|
switch ($this->driverName) {
|
||||||
'odbc' => $value->format('#m/d/Y H:i:s.u#'),
|
case 'odbc':
|
||||||
'mssql', 'dblib', 'sqlsrv' => 'CONVERT(DATETIME2(7), ' . $value->format("'Y-m-d H:i:s.u'") . ')',
|
return $value->format('#m/d/Y H:i:s.u#');
|
||||||
default => $value->format("'Y-m-d 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'");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -357,15 +400,17 @@ class PdoDriver implements Dibi\Driver
|
|||||||
case 'mssql':
|
case 'mssql':
|
||||||
case 'sqlsrv':
|
case 'sqlsrv':
|
||||||
case 'dblib':
|
case 'dblib':
|
||||||
// requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx
|
if (version_compare($this->serverVersion, '11.0') >= 0) { // 11 == SQL Server 2012
|
||||||
if ($limit !== null) {
|
// requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx
|
||||||
$sql = sprintf('%s OFFSET %d ROWS FETCH NEXT %d ROWS ONLY', rtrim($sql), $offset, $limit);
|
if ($limit !== null) {
|
||||||
} elseif ($offset) {
|
$sql = sprintf('%s OFFSET %d ROWS FETCH NEXT %d ROWS ONLY', rtrim($sql), $offset, $limit);
|
||||||
$sql = sprintf('%s OFFSET %d ROWS', rtrim($sql), $offset);
|
} elseif ($offset) {
|
||||||
|
$sql = sprintf('%s OFFSET %d ROWS', rtrim($sql), $offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
// break omitted
|
||||||
break;
|
|
||||||
|
|
||||||
case 'odbc':
|
case 'odbc':
|
||||||
if ($offset) {
|
if ($offset) {
|
||||||
throw new Dibi\NotSupportedException('Offset is not supported by this database.');
|
throw new Dibi\NotSupportedException('Offset is not supported by this database.');
|
||||||
|
@@ -19,10 +19,17 @@ use PDO;
|
|||||||
*/
|
*/
|
||||||
class PdoResult implements Dibi\ResultDriver
|
class PdoResult implements Dibi\ResultDriver
|
||||||
{
|
{
|
||||||
public function __construct(
|
use Dibi\Strict;
|
||||||
private ?\PDOStatement $resultSet,
|
|
||||||
private readonly string $driverName,
|
private ?\PDOStatement $resultSet;
|
||||||
) {
|
|
||||||
|
private string $driverName;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct(\PDOStatement $resultSet, string $driverName)
|
||||||
|
{
|
||||||
|
$this->resultSet = $resultSet;
|
||||||
|
$this->driverName = $driverName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -86,7 +93,7 @@ class PdoResult implements Dibi\ResultDriver
|
|||||||
'name' => $row['name'],
|
'name' => $row['name'],
|
||||||
'table' => $row['table'],
|
'table' => $row['table'],
|
||||||
'nativetype' => $row['native_type'],
|
'nativetype' => $row['native_type'],
|
||||||
'type' => $row['native_type'] === 'TIME' && $this->driverName === 'mysql' ? Dibi\Type::TimeInterval : null,
|
'type' => $row['native_type'] === 'TIME' && $this->driverName === 'mysql' ? Dibi\Type::TIME_INTERVAL : null,
|
||||||
'fullname' => $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name'],
|
'fullname' => $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name'],
|
||||||
'vendor' => $row,
|
'vendor' => $row,
|
||||||
];
|
];
|
||||||
|
@@ -12,7 +12,6 @@ namespace Dibi\Drivers;
|
|||||||
use Dibi;
|
use Dibi;
|
||||||
use Dibi\Helpers;
|
use Dibi\Helpers;
|
||||||
use PgSql;
|
use PgSql;
|
||||||
use function in_array, is_array, is_resource, strlen;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,12 +23,16 @@ use function in_array, is_array, is_resource, strlen;
|
|||||||
* - schema => the schema search path
|
* - schema => the schema search path
|
||||||
* - charset => character encoding to set (default is utf8)
|
* - charset => character encoding to set (default is utf8)
|
||||||
* - persistent (bool) => try to find a persistent link?
|
* - persistent (bool) => try to find a persistent link?
|
||||||
* - resource (PgSql\Connection) => existing connection resource
|
* - resource (resource) => existing connection resource
|
||||||
* - connect_type (int) => see pg_connect()
|
* - connect_type (int) => see pg_connect()
|
||||||
*/
|
*/
|
||||||
class PostgreDriver implements Dibi\Driver
|
class PostgreDriver implements Dibi\Driver
|
||||||
{
|
{
|
||||||
private PgSql\Connection $connection;
|
use Dibi\Strict;
|
||||||
|
|
||||||
|
/** @var resource|PgSql\Connection */
|
||||||
|
private $connection;
|
||||||
|
|
||||||
private ?int $affectedRows;
|
private ?int $affectedRows;
|
||||||
|
|
||||||
|
|
||||||
@@ -72,7 +75,7 @@ class PostgreDriver implements Dibi\Driver
|
|||||||
restore_error_handler();
|
restore_error_handler();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->connection instanceof PgSql\Connection) {
|
if (!is_resource($this->connection) && !$this->connection instanceof PgSql\Connection) {
|
||||||
throw new Dibi\DriverException($error ?: 'Connecting error.');
|
throw new Dibi\DriverException($error ?: 'Connecting error.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +121,7 @@ class PostgreDriver implements Dibi\Driver
|
|||||||
if ($res === false) {
|
if ($res === false) {
|
||||||
throw static::createException(pg_last_error($this->connection), null, $sql);
|
throw static::createException(pg_last_error($this->connection), null, $sql);
|
||||||
|
|
||||||
} elseif ($res instanceof PgSql\Result) {
|
} elseif (is_resource($res) || $res instanceof PgSql\Result) {
|
||||||
$this->affectedRows = Helpers::false2Null(pg_affected_rows($res));
|
$this->affectedRows = Helpers::false2Null(pg_affected_rows($res));
|
||||||
if (pg_num_fields($res)) {
|
if (pg_num_fields($res)) {
|
||||||
return $this->createResultDriver($res);
|
return $this->createResultDriver($res);
|
||||||
@@ -222,10 +225,13 @@ class PostgreDriver implements Dibi\Driver
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the connection resource.
|
* Returns the connection resource.
|
||||||
|
* @return resource|null
|
||||||
*/
|
*/
|
||||||
public function getResource(): PgSql\Connection
|
public function getResource(): mixed
|
||||||
{
|
{
|
||||||
return $this->connection;
|
return is_resource($this->connection) || $this->connection instanceof PgSql\Connection
|
||||||
|
? $this->connection
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -234,14 +240,15 @@ class PostgreDriver implements Dibi\Driver
|
|||||||
*/
|
*/
|
||||||
public function getReflector(): Dibi\Reflector
|
public function getReflector(): Dibi\Reflector
|
||||||
{
|
{
|
||||||
return new PostgreReflector($this);
|
return new PostgreReflector($this, pg_parameter_status($this->connection, 'server_version'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Result set driver factory.
|
* Result set driver factory.
|
||||||
|
* @param resource $resource
|
||||||
*/
|
*/
|
||||||
public function createResultDriver(PgSql\Result $resource): PostgreResult
|
public function createResultDriver($resource): PostgreResult
|
||||||
{
|
{
|
||||||
return new PostgreResult($resource);
|
return new PostgreResult($resource);
|
||||||
}
|
}
|
||||||
|
@@ -17,9 +17,17 @@ use Dibi;
|
|||||||
*/
|
*/
|
||||||
class PostgreReflector implements Dibi\Reflector
|
class PostgreReflector implements Dibi\Reflector
|
||||||
{
|
{
|
||||||
public function __construct(
|
use Dibi\Strict;
|
||||||
private readonly Dibi\Driver $driver,
|
|
||||||
) {
|
private Dibi\Driver $driver;
|
||||||
|
|
||||||
|
private string $version;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct(Dibi\Driver $driver, string $version)
|
||||||
|
{
|
||||||
|
$this->driver = $driver;
|
||||||
|
$this->version = $version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -38,15 +46,18 @@ class PostgreReflector implements Dibi\Reflector
|
|||||||
FROM
|
FROM
|
||||||
information_schema.tables
|
information_schema.tables
|
||||||
WHERE
|
WHERE
|
||||||
table_schema = ANY (current_schemas(false))
|
table_schema = ANY (current_schemas(false))";
|
||||||
|
|
||||||
UNION ALL
|
if ($this->version >= 9.3) {
|
||||||
SELECT
|
$query .= '
|
||||||
matviewname, 1
|
UNION ALL
|
||||||
FROM
|
SELECT
|
||||||
pg_matviews
|
matviewname, 1
|
||||||
WHERE
|
FROM
|
||||||
schemaname = ANY (current_schemas(false))";
|
pg_matviews
|
||||||
|
WHERE
|
||||||
|
schemaname = ANY (current_schemas(false))';
|
||||||
|
}
|
||||||
|
|
||||||
$res = $this->driver->query($query);
|
$res = $this->driver->query($query);
|
||||||
$tables = [];
|
$tables = [];
|
||||||
@@ -64,6 +75,13 @@ class PostgreReflector implements Dibi\Reflector
|
|||||||
public function getColumns(string $table): array
|
public function getColumns(string $table): array
|
||||||
{
|
{
|
||||||
$_table = $this->driver->escapeText($this->driver->escapeIdentifier($table));
|
$_table = $this->driver->escapeText($this->driver->escapeIdentifier($table));
|
||||||
|
$res = $this->driver->query("
|
||||||
|
SELECT indkey
|
||||||
|
FROM pg_class
|
||||||
|
LEFT JOIN pg_index on pg_class.oid = pg_index.indrelid AND pg_index.indisprimary
|
||||||
|
WHERE pg_class.oid = $_table::regclass
|
||||||
|
");
|
||||||
|
$primary = (int) $res->fetch(true)['indkey'];
|
||||||
|
|
||||||
$res = $this->driver->query("
|
$res = $this->driver->query("
|
||||||
SELECT *
|
SELECT *
|
||||||
@@ -83,8 +101,7 @@ class PostgreReflector implements Dibi\Reflector
|
|||||||
a.atttypmod-4 AS character_maximum_length,
|
a.atttypmod-4 AS character_maximum_length,
|
||||||
NOT a.attnotnull AS is_nullable,
|
NOT a.attnotnull AS is_nullable,
|
||||||
a.attnum AS ordinal_position,
|
a.attnum AS ordinal_position,
|
||||||
pg_get_expr(adef.adbin, adef.adrelid) AS column_default,
|
pg_get_expr(adef.adbin, adef.adrelid) AS column_default
|
||||||
CASE WHEN a.attidentity IN ('a', 'd') THEN 'YES' ELSE 'NO' END AS is_identity
|
|
||||||
FROM
|
FROM
|
||||||
pg_attribute a
|
pg_attribute a
|
||||||
JOIN pg_type ON a.atttypid = pg_type.oid
|
JOIN pg_type ON a.atttypid = pg_type.oid
|
||||||
@@ -109,7 +126,7 @@ class PostgreReflector implements Dibi\Reflector
|
|||||||
'size' => $size > 0 ? $size : null,
|
'size' => $size > 0 ? $size : null,
|
||||||
'nullable' => $row['is_nullable'] === 'YES' || $row['is_nullable'] === 't' || $row['is_nullable'] === true,
|
'nullable' => $row['is_nullable'] === 'YES' || $row['is_nullable'] === 't' || $row['is_nullable'] === true,
|
||||||
'default' => $row['column_default'],
|
'default' => $row['column_default'],
|
||||||
'autoincrement' => $row['is_identity'] === 'YES' || str_starts_with($row['column_default'] ?? '', 'nextval('),
|
'autoincrement' => (int) $row['ordinal_position'] === $primary && str_starts_with($row['column_default'] ?? '', 'nextval'),
|
||||||
'vendor' => $row,
|
'vendor' => $row,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -19,9 +19,31 @@ use PgSql;
|
|||||||
*/
|
*/
|
||||||
class PostgreResult implements Dibi\ResultDriver
|
class PostgreResult implements Dibi\ResultDriver
|
||||||
{
|
{
|
||||||
public function __construct(
|
use Dibi\Strict;
|
||||||
private readonly PgSql\Result $resultSet,
|
|
||||||
) {
|
/** @var resource|PgSql\Result */
|
||||||
|
private $resultSet;
|
||||||
|
|
||||||
|
private bool $autoFree = true;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param resource|PgSql\Result $resultSet
|
||||||
|
*/
|
||||||
|
public function __construct($resultSet)
|
||||||
|
{
|
||||||
|
$this->resultSet = $resultSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatically frees the resources allocated for this result set.
|
||||||
|
*/
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
if ($this->autoFree && $this->getResultResource()) {
|
||||||
|
$this->free();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -87,10 +109,14 @@ class PostgreResult implements Dibi\ResultDriver
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the result set resource.
|
* Returns the result set resource.
|
||||||
|
* @return resource|PgSql\Result|null
|
||||||
*/
|
*/
|
||||||
public function getResultResource(): PgSql\Result
|
public function getResultResource(): mixed
|
||||||
{
|
{
|
||||||
return $this->resultSet;
|
$this->autoFree = false;
|
||||||
|
return is_resource($this->resultSet) || $this->resultSet instanceof PgSql\Result
|
||||||
|
? $this->resultSet
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -25,8 +25,12 @@ use SQLite3;
|
|||||||
*/
|
*/
|
||||||
class SqliteDriver implements Dibi\Driver
|
class SqliteDriver implements Dibi\Driver
|
||||||
{
|
{
|
||||||
|
use Dibi\Strict;
|
||||||
|
|
||||||
private SQLite3 $connection;
|
private SQLite3 $connection;
|
||||||
|
|
||||||
private string $fmtDate;
|
private string $fmtDate;
|
||||||
|
|
||||||
private string $fmtDateTime;
|
private string $fmtDateTime;
|
||||||
|
|
||||||
|
|
||||||
@@ -56,7 +60,10 @@ class SqliteDriver implements Dibi\Driver
|
|||||||
}
|
}
|
||||||
|
|
||||||
// enable foreign keys support (defaultly disabled; if disabled then foreign key constraints are not enforced)
|
// enable foreign keys support (defaultly disabled; if disabled then foreign key constraints are not enforced)
|
||||||
$this->query('PRAGMA foreign_keys = ON');
|
$version = SQLite3::version();
|
||||||
|
if ($version['versionNumber'] >= '3006019') {
|
||||||
|
$this->query('PRAGMA foreign_keys = ON');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -17,9 +17,14 @@ use Dibi;
|
|||||||
*/
|
*/
|
||||||
class SqliteReflector implements Dibi\Reflector
|
class SqliteReflector implements Dibi\Reflector
|
||||||
{
|
{
|
||||||
public function __construct(
|
use Dibi\Strict;
|
||||||
private readonly Dibi\Driver $driver,
|
|
||||||
) {
|
private Dibi\Driver $driver;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct(Dibi\Driver $driver)
|
||||||
|
{
|
||||||
|
$this->driver = $driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -11,7 +11,6 @@ namespace Dibi\Drivers;
|
|||||||
|
|
||||||
use Dibi;
|
use Dibi;
|
||||||
use Dibi\Helpers;
|
use Dibi\Helpers;
|
||||||
use const SQLITE3_ASSOC, SQLITE3_BLOB, SQLITE3_FLOAT, SQLITE3_INTEGER, SQLITE3_NULL, SQLITE3_NUM, SQLITE3_TEXT;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,9 +18,27 @@ use const SQLITE3_ASSOC, SQLITE3_BLOB, SQLITE3_FLOAT, SQLITE3_INTEGER, SQLITE3_N
|
|||||||
*/
|
*/
|
||||||
class SqliteResult implements Dibi\ResultDriver
|
class SqliteResult implements Dibi\ResultDriver
|
||||||
{
|
{
|
||||||
public function __construct(
|
use Dibi\Strict;
|
||||||
private readonly \SQLite3Result $resultSet,
|
|
||||||
) {
|
private \SQLite3Result $resultSet;
|
||||||
|
|
||||||
|
private bool $autoFree = true;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct(\SQLite3Result $resultSet)
|
||||||
|
{
|
||||||
|
$this->resultSet = $resultSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatically frees the resources allocated for this result set.
|
||||||
|
*/
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
if ($this->autoFree && $this->getResultResource()) {
|
||||||
|
@$this->free();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -90,6 +107,7 @@ class SqliteResult implements Dibi\ResultDriver
|
|||||||
*/
|
*/
|
||||||
public function getResultResource(): \SQLite3Result
|
public function getResultResource(): \SQLite3Result
|
||||||
{
|
{
|
||||||
|
$this->autoFree = false;
|
||||||
return $this->resultSet;
|
return $this->resultSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -11,7 +11,6 @@ namespace Dibi\Drivers;
|
|||||||
|
|
||||||
use Dibi;
|
use Dibi;
|
||||||
use Dibi\Helpers;
|
use Dibi\Helpers;
|
||||||
use function is_resource, sprintf;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -28,10 +27,15 @@ use function is_resource, sprintf;
|
|||||||
*/
|
*/
|
||||||
class SqlsrvDriver implements Dibi\Driver
|
class SqlsrvDriver implements Dibi\Driver
|
||||||
{
|
{
|
||||||
|
use Dibi\Strict;
|
||||||
|
|
||||||
/** @var resource */
|
/** @var resource */
|
||||||
private $connection;
|
private $connection;
|
||||||
|
|
||||||
private ?int $affectedRows;
|
private ?int $affectedRows;
|
||||||
|
|
||||||
|
private string $version = '';
|
||||||
|
|
||||||
|
|
||||||
/** @throws Dibi\NotSupportedException */
|
/** @throws Dibi\NotSupportedException */
|
||||||
public function __construct(array $config)
|
public function __construct(array $config)
|
||||||
@@ -68,6 +72,8 @@ class SqlsrvDriver implements Dibi\Driver
|
|||||||
|
|
||||||
sqlsrv_configure('WarningsReturnAsErrors', 1);
|
sqlsrv_configure('WarningsReturnAsErrors', 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->version = sqlsrv_server_info($this->connection)['SQLServerVersion'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -254,6 +260,13 @@ class SqlsrvDriver implements Dibi\Driver
|
|||||||
if ($limit < 0 || $offset < 0) {
|
if ($limit < 0 || $offset < 0) {
|
||||||
throw new Dibi\NotSupportedException('Negative offset or limit.');
|
throw new Dibi\NotSupportedException('Negative offset or limit.');
|
||||||
|
|
||||||
|
} elseif (version_compare($this->version, '11', '<')) { // 11 == SQL Server 2012
|
||||||
|
if ($offset) {
|
||||||
|
throw new Dibi\NotSupportedException('Offset is not supported by this database.');
|
||||||
|
|
||||||
|
} elseif ($limit !== null) {
|
||||||
|
$sql = sprintf('SELECT TOP (%d) * FROM (%s) t', $limit, $sql);
|
||||||
|
}
|
||||||
} elseif ($limit !== null) {
|
} elseif ($limit !== null) {
|
||||||
// requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx
|
// requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx
|
||||||
$sql = sprintf('%s OFFSET %d ROWS FETCH NEXT %d ROWS ONLY', rtrim($sql), $offset, $limit);
|
$sql = sprintf('%s OFFSET %d ROWS FETCH NEXT %d ROWS ONLY', rtrim($sql), $offset, $limit);
|
||||||
|
@@ -10,7 +10,6 @@ declare(strict_types=1);
|
|||||||
namespace Dibi\Drivers;
|
namespace Dibi\Drivers;
|
||||||
|
|
||||||
use Dibi;
|
use Dibi;
|
||||||
use function sprintf;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,9 +17,14 @@ use function sprintf;
|
|||||||
*/
|
*/
|
||||||
class SqlsrvReflector implements Dibi\Reflector
|
class SqlsrvReflector implements Dibi\Reflector
|
||||||
{
|
{
|
||||||
public function __construct(
|
use Dibi\Strict;
|
||||||
private readonly Dibi\Driver $driver,
|
|
||||||
) {
|
private Dibi\Driver $driver;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct(Dibi\Driver $driver)
|
||||||
|
{
|
||||||
|
$this->driver = $driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -10,7 +10,6 @@ declare(strict_types=1);
|
|||||||
namespace Dibi\Drivers;
|
namespace Dibi\Drivers;
|
||||||
|
|
||||||
use Dibi;
|
use Dibi;
|
||||||
use function is_resource;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,10 +17,31 @@ use function is_resource;
|
|||||||
*/
|
*/
|
||||||
class SqlsrvResult implements Dibi\ResultDriver
|
class SqlsrvResult implements Dibi\ResultDriver
|
||||||
{
|
{
|
||||||
public function __construct(
|
use Dibi\Strict;
|
||||||
/** @var resource */
|
|
||||||
private $resultSet,
|
/** @var resource */
|
||||||
) {
|
private $resultSet;
|
||||||
|
|
||||||
|
private bool $autoFree = true;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param resource $resultSet
|
||||||
|
*/
|
||||||
|
public function __construct($resultSet)
|
||||||
|
{
|
||||||
|
$this->resultSet = $resultSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatically frees the resources allocated for this result set.
|
||||||
|
*/
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
if ($this->autoFree && $this->getResultResource()) {
|
||||||
|
$this->free();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -86,6 +106,7 @@ class SqlsrvResult implements Dibi\ResultDriver
|
|||||||
*/
|
*/
|
||||||
public function getResultResource(): mixed
|
public function getResultResource(): mixed
|
||||||
{
|
{
|
||||||
|
$this->autoFree = false;
|
||||||
return is_resource($this->resultSet) ? $this->resultSet : null;
|
return is_resource($this->resultSet) ? $this->resultSet : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -9,15 +9,14 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Dibi;
|
namespace Dibi;
|
||||||
|
|
||||||
use function count, dirname, microtime, preg_match, str_starts_with, strtoupper, trim;
|
|
||||||
use const DIRECTORY_SEPARATOR;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Profiler & logger event.
|
* Profiler & logger event.
|
||||||
*/
|
*/
|
||||||
class Event
|
class Event
|
||||||
{
|
{
|
||||||
|
use Strict;
|
||||||
|
|
||||||
/** event type */
|
/** event type */
|
||||||
public const
|
public const
|
||||||
CONNECT = 1,
|
CONNECT = 1,
|
||||||
@@ -32,12 +31,18 @@ class Event
|
|||||||
TRANSACTION = 448, // BEGIN | COMMIT | ROLLBACK
|
TRANSACTION = 448, // BEGIN | COMMIT | ROLLBACK
|
||||||
ALL = 1023;
|
ALL = 1023;
|
||||||
|
|
||||||
public readonly Connection $connection;
|
public Connection $connection;
|
||||||
|
|
||||||
public int $type;
|
public int $type;
|
||||||
public readonly string $sql;
|
|
||||||
public readonly Result|DriverException|null $result;
|
public string $sql;
|
||||||
|
|
||||||
|
public Result|DriverException|null $result;
|
||||||
|
|
||||||
public float $time;
|
public float $time;
|
||||||
|
|
||||||
public ?int $count = null;
|
public ?int $count = null;
|
||||||
|
|
||||||
public ?array $source = null;
|
public ?array $source = null;
|
||||||
|
|
||||||
|
|
||||||
|
@@ -15,7 +15,9 @@ namespace Dibi;
|
|||||||
*/
|
*/
|
||||||
class Expression
|
class Expression
|
||||||
{
|
{
|
||||||
private readonly array $values;
|
use Strict;
|
||||||
|
|
||||||
|
private array $values;
|
||||||
|
|
||||||
|
|
||||||
public function __construct(...$values)
|
public function __construct(...$values)
|
||||||
|
@@ -9,8 +9,6 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Dibi;
|
namespace Dibi;
|
||||||
|
|
||||||
use function array_key_exists, count, func_get_args, is_array, is_string;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SQL builder via fluent interfaces.
|
* SQL builder via fluent interfaces.
|
||||||
@@ -29,8 +27,6 @@ use function array_key_exists, count, func_get_args, is_array, is_string;
|
|||||||
* @method Fluent innerJoin(...$table)
|
* @method Fluent innerJoin(...$table)
|
||||||
* @method Fluent rightJoin(...$table)
|
* @method Fluent rightJoin(...$table)
|
||||||
* @method Fluent outerJoin(...$table)
|
* @method Fluent outerJoin(...$table)
|
||||||
* @method Fluent union(Fluent $fluent)
|
|
||||||
* @method Fluent unionAll(Fluent $fluent)
|
|
||||||
* @method Fluent as(...$field)
|
* @method Fluent as(...$field)
|
||||||
* @method Fluent on(...$cond)
|
* @method Fluent on(...$cond)
|
||||||
* @method Fluent and(...$cond)
|
* @method Fluent and(...$cond)
|
||||||
@@ -47,13 +43,9 @@ use function array_key_exists, count, func_get_args, is_array, is_string;
|
|||||||
*/
|
*/
|
||||||
class Fluent implements IDataSource
|
class Fluent implements IDataSource
|
||||||
{
|
{
|
||||||
public const
|
use Strict;
|
||||||
AffectedRows = 'a',
|
|
||||||
Identifier = 'n',
|
|
||||||
Remove = false;
|
|
||||||
|
|
||||||
/** @deprecated use Fluent::Remove */
|
public const REMOVE = false;
|
||||||
public const REMOVE = self::Remove;
|
|
||||||
|
|
||||||
public static array $masks = [
|
public static array $masks = [
|
||||||
'SELECT' => ['SELECT', 'DISTINCT', 'FROM', 'WHERE', 'GROUP BY',
|
'SELECT' => ['SELECT', 'DISTINCT', 'FROM', 'WHERE', 'GROUP BY',
|
||||||
@@ -99,11 +91,16 @@ class Fluent implements IDataSource
|
|||||||
'RIGHT JOIN' => 'FROM',
|
'RIGHT JOIN' => 'FROM',
|
||||||
];
|
];
|
||||||
|
|
||||||
private readonly Connection $connection;
|
private Connection $connection;
|
||||||
|
|
||||||
private array $setups = [];
|
private array $setups = [];
|
||||||
|
|
||||||
private ?string $command = null;
|
private ?string $command = null;
|
||||||
|
|
||||||
private array $clauses = [];
|
private array $clauses = [];
|
||||||
|
|
||||||
private array $flags = [];
|
private array $flags = [];
|
||||||
|
|
||||||
private $cursor;
|
private $cursor;
|
||||||
|
|
||||||
/** normalized clauses */
|
/** normalized clauses */
|
||||||
@@ -115,7 +112,7 @@ class Fluent implements IDataSource
|
|||||||
$this->connection = $connection;
|
$this->connection = $connection;
|
||||||
|
|
||||||
if (!isset(self::$normalizer)) {
|
if (!isset(self::$normalizer)) {
|
||||||
self::$normalizer = new HashMap(self::_formatClause(...));
|
self::$normalizer = new HashMap([self::class, '_formatClause']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,7 +145,7 @@ class Fluent implements IDataSource
|
|||||||
$this->cursor = &$this->clauses[$clause];
|
$this->cursor = &$this->clauses[$clause];
|
||||||
|
|
||||||
// TODO: really delete?
|
// TODO: really delete?
|
||||||
if ($args === [self::Remove]) {
|
if ($args === [self::REMOVE]) {
|
||||||
$this->cursor = null;
|
$this->cursor = null;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@@ -164,7 +161,7 @@ class Fluent implements IDataSource
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// append to currect flow
|
// append to currect flow
|
||||||
if ($args === [self::Remove]) {
|
if ($args === [self::REMOVE]) {
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,17 +284,19 @@ class Fluent implements IDataSource
|
|||||||
/**
|
/**
|
||||||
* Generates and executes SQL query.
|
* Generates and executes SQL query.
|
||||||
* Returns result set or number of affected rows
|
* Returns result set or number of affected rows
|
||||||
* @return ($return is self::Identifier|self::AffectedRows ? int : Result)
|
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public function execute(?string $return = null): Result|int|null
|
public function execute(?string $return = null): Result|int|null
|
||||||
{
|
{
|
||||||
$res = $this->query($this->_export());
|
$res = $this->query($this->_export());
|
||||||
return match ($return) {
|
switch ($return) {
|
||||||
self::Identifier => $this->connection->getInsertId(),
|
case \dibi::IDENTIFIER:
|
||||||
self::AffectedRows => $this->connection->getAffectedRows(),
|
return $this->connection->getInsertId();
|
||||||
default => $res,
|
case \dibi::AFFECTED_ROWS:
|
||||||
};
|
return $this->connection->getAffectedRows();
|
||||||
|
default:
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -46,7 +46,7 @@ abstract class HashMapBase
|
|||||||
*/
|
*/
|
||||||
final class HashMap extends HashMapBase
|
final class HashMap extends HashMapBase
|
||||||
{
|
{
|
||||||
public function __set(string $nm, mixed $val): void
|
public function __set(string $nm, $val)
|
||||||
{
|
{
|
||||||
if ($nm === '') {
|
if ($nm === '') {
|
||||||
$nm = "\xFF";
|
$nm = "\xFF";
|
||||||
@@ -56,7 +56,7 @@ final class HashMap extends HashMapBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function __get(string $nm): mixed
|
public function __get(string $nm)
|
||||||
{
|
{
|
||||||
if ($nm === '') {
|
if ($nm === '') {
|
||||||
$nm = "\xFF";
|
$nm = "\xFF";
|
||||||
|
@@ -9,12 +9,11 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Dibi;
|
namespace Dibi;
|
||||||
|
|
||||||
use function array_map, array_unique, explode, fclose, fgets, fopen, fstat, getenv, htmlspecialchars, is_float, is_int, is_string, levenshtein, max, mb_strlen, ob_end_flush, ob_get_clean, ob_start, preg_match, preg_replace, preg_replace_callback, rtrim, set_time_limit, str_ends_with, str_repeat, str_starts_with, strlen, strtoupper, substr, trim, wordwrap;
|
|
||||||
use const PHP_SAPI;
|
|
||||||
|
|
||||||
|
|
||||||
class Helpers
|
class Helpers
|
||||||
{
|
{
|
||||||
|
use Strict;
|
||||||
|
|
||||||
private static HashMap $types;
|
private static HashMap $types;
|
||||||
|
|
||||||
|
|
||||||
@@ -162,12 +161,12 @@ class Helpers
|
|||||||
public static function escape(Driver $driver, $value, string $type): string
|
public static function escape(Driver $driver, $value, string $type): string
|
||||||
{
|
{
|
||||||
$types = [
|
$types = [
|
||||||
Type::Text => 'text',
|
Type::TEXT => 'text',
|
||||||
Type::Binary => 'binary',
|
Type::BINARY => 'binary',
|
||||||
Type::Bool => 'bool',
|
Type::BOOL => 'bool',
|
||||||
Type::Date => 'date',
|
Type::DATE => 'date',
|
||||||
Type::DateTime => 'datetime',
|
Type::DATETIME => 'datetime',
|
||||||
Fluent::Identifier => 'identifier',
|
\dibi::IDENTIFIER => 'identifier',
|
||||||
];
|
];
|
||||||
if (isset($types[$type])) {
|
if (isset($types[$type])) {
|
||||||
return $driver->{'escape' . $types[$type]}($value);
|
return $driver->{'escape' . $types[$type]}($value);
|
||||||
@@ -184,16 +183,16 @@ class Helpers
|
|||||||
public static function detectType(string $type): ?string
|
public static function detectType(string $type): ?string
|
||||||
{
|
{
|
||||||
$patterns = [
|
$patterns = [
|
||||||
'^_' => Type::Text, // PostgreSQL arrays
|
'^_' => Type::TEXT, // PostgreSQL arrays
|
||||||
'RANGE$' => Type::Text, // PostgreSQL range types
|
'RANGE$' => Type::TEXT, // PostgreSQL range types
|
||||||
'BYTEA|BLOB|BIN' => Type::Binary,
|
'BYTEA|BLOB|BIN' => Type::BINARY,
|
||||||
'TEXT|CHAR|POINT|INTERVAL|STRING' => Type::Text,
|
'TEXT|CHAR|POINT|INTERVAL|STRING' => Type::TEXT,
|
||||||
'YEAR|BYTE|COUNTER|SERIAL|INT|LONG|SHORT|^TINY$' => Type::Integer,
|
'YEAR|BYTE|COUNTER|SERIAL|INT|LONG|SHORT|^TINY$' => Type::INTEGER,
|
||||||
'CURRENCY|REAL|MONEY|FLOAT|DOUBLE|DECIMAL|NUMERIC|NUMBER' => Type::Float,
|
'CURRENCY|REAL|MONEY|FLOAT|DOUBLE|DECIMAL|NUMERIC|NUMBER' => Type::FLOAT,
|
||||||
'^TIME$' => Type::Time,
|
'^TIME$' => Type::TIME,
|
||||||
'TIME' => Type::DateTime, // DATETIME, TIMESTAMP
|
'TIME' => Type::DATETIME, // DATETIME, TIMESTAMP
|
||||||
'DATE' => Type::Date,
|
'DATE' => Type::DATE,
|
||||||
'BOOL' => Type::Bool,
|
'BOOL' => Type::BOOL,
|
||||||
'JSON' => Type::JSON,
|
'JSON' => Type::JSON,
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -211,7 +210,7 @@ class Helpers
|
|||||||
public static function getTypeCache(): HashMap
|
public static function getTypeCache(): HashMap
|
||||||
{
|
{
|
||||||
if (!isset(self::$types)) {
|
if (!isset(self::$types)) {
|
||||||
self::$types = new HashMap(self::detectType(...));
|
self::$types = new HashMap([self::class, 'detectType']);
|
||||||
}
|
}
|
||||||
|
|
||||||
return self::$types;
|
return self::$types;
|
||||||
|
@@ -15,7 +15,9 @@ namespace Dibi;
|
|||||||
*/
|
*/
|
||||||
class Literal
|
class Literal
|
||||||
{
|
{
|
||||||
private readonly string $value;
|
use Strict;
|
||||||
|
|
||||||
|
private string $value;
|
||||||
|
|
||||||
|
|
||||||
public function __construct($value)
|
public function __construct($value)
|
||||||
|
@@ -10,8 +10,6 @@ declare(strict_types=1);
|
|||||||
namespace Dibi\Loggers;
|
namespace Dibi\Loggers;
|
||||||
|
|
||||||
use Dibi;
|
use Dibi;
|
||||||
use function sprintf;
|
|
||||||
use const FILE_APPEND, LOCK_EX;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,11 +17,21 @@ use const FILE_APPEND, LOCK_EX;
|
|||||||
*/
|
*/
|
||||||
class FileLogger
|
class FileLogger
|
||||||
{
|
{
|
||||||
public function __construct(
|
use Dibi\Strict;
|
||||||
public string $file,
|
|
||||||
public int $filter = Dibi\Event::QUERY,
|
/** Name of the file where SQL errors should be logged */
|
||||||
private bool $errorsOnly = false,
|
public string $file;
|
||||||
) {
|
|
||||||
|
public int $filter;
|
||||||
|
|
||||||
|
private bool $errorsOnly;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct(string $file, ?int $filter = null, bool $errorsOnly = false)
|
||||||
|
{
|
||||||
|
$this->file = $file;
|
||||||
|
$this->filter = $filter ?: Dibi\Event::QUERY;
|
||||||
|
$this->errorsOnly = $errorsOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -66,7 +74,7 @@ class FileLogger
|
|||||||
{
|
{
|
||||||
$driver = $event->connection->getConfig('driver');
|
$driver = $event->connection->getConfig('driver');
|
||||||
$message .=
|
$message .=
|
||||||
"\n-- driver: " . get_debug_type($driver) . '/' . $event->connection->getConfig('name')
|
"\n-- driver: " . (is_object($driver) ? $driver::class : $driver) . '/' . $event->connection->getConfig('name')
|
||||||
. "\n-- " . date('Y-m-d H:i:s')
|
. "\n-- " . date('Y-m-d H:i:s')
|
||||||
. "\n\n";
|
. "\n\n";
|
||||||
file_put_contents($this->file, $message, FILE_APPEND | LOCK_EX);
|
file_put_contents($this->file, $message, FILE_APPEND | LOCK_EX);
|
||||||
|
@@ -27,10 +27,19 @@ use Dibi;
|
|||||||
*/
|
*/
|
||||||
class Column
|
class Column
|
||||||
{
|
{
|
||||||
public function __construct(
|
use Dibi\Strict;
|
||||||
private readonly ?Dibi\Reflector $reflector,
|
|
||||||
private array $info,
|
/** when created by Result */
|
||||||
) {
|
private ?Dibi\Reflector $reflector;
|
||||||
|
|
||||||
|
/** @var array (name, nativetype, [table], [fullname], [size], [nullable], [default], [autoincrement], [vendor]) */
|
||||||
|
private array $info;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct(?Dibi\Reflector $reflector, array $info)
|
||||||
|
{
|
||||||
|
$this->reflector = $reflector;
|
||||||
|
$this->info = $info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -10,7 +10,6 @@ declare(strict_types=1);
|
|||||||
namespace Dibi\Reflection;
|
namespace Dibi\Reflection;
|
||||||
|
|
||||||
use Dibi;
|
use Dibi;
|
||||||
use function array_values, strtolower;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -22,14 +21,20 @@ use function array_values, strtolower;
|
|||||||
*/
|
*/
|
||||||
class Database
|
class Database
|
||||||
{
|
{
|
||||||
|
use Dibi\Strict;
|
||||||
|
|
||||||
|
private Dibi\Reflector $reflector;
|
||||||
|
|
||||||
|
private ?string $name;
|
||||||
|
|
||||||
/** @var Table[] */
|
/** @var Table[] */
|
||||||
private array $tables;
|
private array $tables;
|
||||||
|
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(Dibi\Reflector $reflector, ?string $name = null)
|
||||||
private readonly Dibi\Reflector $reflector,
|
{
|
||||||
private ?string $name = null,
|
$this->reflector = $reflector;
|
||||||
) {
|
$this->name = $name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -9,6 +9,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Dibi\Reflection;
|
namespace Dibi\Reflection;
|
||||||
|
|
||||||
|
use Dibi;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,10 +20,18 @@ namespace Dibi\Reflection;
|
|||||||
*/
|
*/
|
||||||
class ForeignKey
|
class ForeignKey
|
||||||
{
|
{
|
||||||
public function __construct(
|
use Dibi\Strict;
|
||||||
private readonly string $name,
|
|
||||||
private readonly array $references,
|
private string $name;
|
||||||
) {
|
|
||||||
|
/** @var array of [local, foreign, onDelete, onUpdate] */
|
||||||
|
private array $references;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct(string $name, array $references)
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
$this->references = $references;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -9,6 +9,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Dibi\Reflection;
|
namespace Dibi\Reflection;
|
||||||
|
|
||||||
|
use Dibi;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -21,9 +22,15 @@ namespace Dibi\Reflection;
|
|||||||
*/
|
*/
|
||||||
class Index
|
class Index
|
||||||
{
|
{
|
||||||
public function __construct(
|
use Dibi\Strict;
|
||||||
private readonly array $info,
|
|
||||||
) {
|
/** @var array (name, columns, [unique], [primary]) */
|
||||||
|
private array $info;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct(array $info)
|
||||||
|
{
|
||||||
|
$this->info = $info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -10,7 +10,6 @@ declare(strict_types=1);
|
|||||||
namespace Dibi\Reflection;
|
namespace Dibi\Reflection;
|
||||||
|
|
||||||
use Dibi;
|
use Dibi;
|
||||||
use function array_values, strtolower;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -21,6 +20,10 @@ use function array_values, strtolower;
|
|||||||
*/
|
*/
|
||||||
class Result
|
class Result
|
||||||
{
|
{
|
||||||
|
use Dibi\Strict;
|
||||||
|
|
||||||
|
private Dibi\ResultDriver $driver;
|
||||||
|
|
||||||
/** @var Column[]|null */
|
/** @var Column[]|null */
|
||||||
private ?array $columns;
|
private ?array $columns;
|
||||||
|
|
||||||
@@ -28,9 +31,9 @@ class Result
|
|||||||
private ?array $names;
|
private ?array $names;
|
||||||
|
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(Dibi\ResultDriver $driver)
|
||||||
private readonly Dibi\ResultDriver $driver,
|
{
|
||||||
) {
|
$this->driver = $driver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -10,7 +10,6 @@ declare(strict_types=1);
|
|||||||
namespace Dibi\Reflection;
|
namespace Dibi\Reflection;
|
||||||
|
|
||||||
use Dibi;
|
use Dibi;
|
||||||
use function array_values, strtolower;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -26,8 +25,12 @@ use function array_values, strtolower;
|
|||||||
*/
|
*/
|
||||||
class Table
|
class Table
|
||||||
{
|
{
|
||||||
private readonly Dibi\Reflector $reflector;
|
use Dibi\Strict;
|
||||||
|
|
||||||
|
private Dibi\Reflector $reflector;
|
||||||
|
|
||||||
private string $name;
|
private string $name;
|
||||||
|
|
||||||
private bool $view;
|
private bool $view;
|
||||||
|
|
||||||
/** @var Column[] */
|
/** @var Column[] */
|
||||||
@@ -38,6 +41,7 @@ class Table
|
|||||||
|
|
||||||
/** @var Index[] */
|
/** @var Index[] */
|
||||||
private array $indexes;
|
private array $indexes;
|
||||||
|
|
||||||
private ?Index $primaryKey;
|
private ?Index $primaryKey;
|
||||||
|
|
||||||
|
|
||||||
|
@@ -9,9 +9,6 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Dibi;
|
namespace Dibi;
|
||||||
|
|
||||||
use function array_keys, array_pop, count, explode, is_float, is_string, json_decode, ltrim, preg_match, preg_split, property_exists, reset, rtrim, str_contains, str_replace, str_starts_with, strpos;
|
|
||||||
use const PREG_SPLIT_DELIM_CAPTURE, PREG_SPLIT_NO_EMPTY;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query result.
|
* Query result.
|
||||||
@@ -20,10 +17,13 @@ use const PREG_SPLIT_DELIM_CAPTURE, PREG_SPLIT_NO_EMPTY;
|
|||||||
*/
|
*/
|
||||||
class Result implements IDataSource
|
class Result implements IDataSource
|
||||||
{
|
{
|
||||||
|
use Strict;
|
||||||
|
|
||||||
private ?ResultDriver $driver;
|
private ?ResultDriver $driver;
|
||||||
|
|
||||||
/** Translate table */
|
/** Translate table */
|
||||||
private array $types = [];
|
private array $types = [];
|
||||||
|
|
||||||
private ?Reflection\Result $meta;
|
private ?Reflection\Result $meta;
|
||||||
|
|
||||||
/** Already fetched? Used for allowance for first seek(0) */
|
/** Already fetched? Used for allowance for first seek(0) */
|
||||||
@@ -34,6 +34,7 @@ class Result implements IDataSource
|
|||||||
|
|
||||||
/** @var callable|null returned object factory */
|
/** @var callable|null returned object factory */
|
||||||
private $rowFactory;
|
private $rowFactory;
|
||||||
|
|
||||||
private array $formats = [];
|
private array $formats = [];
|
||||||
|
|
||||||
|
|
||||||
@@ -460,22 +461,16 @@ class Result implements IDataSource
|
|||||||
if ($type === null || $format === 'native') {
|
if ($type === null || $format === 'native') {
|
||||||
$row[$key] = $value;
|
$row[$key] = $value;
|
||||||
|
|
||||||
} elseif ($type === Type::Text) {
|
} elseif ($type === Type::TEXT) {
|
||||||
$row[$key] = (string) $value;
|
$row[$key] = (string) $value;
|
||||||
|
|
||||||
} elseif ($type === Type::Integer) {
|
} elseif ($type === Type::INTEGER) {
|
||||||
$row[$key] = is_float($tmp = $value * 1)
|
$row[$key] = is_float($tmp = $value * 1)
|
||||||
? (is_string($value) ? $value : (int) $value)
|
? (is_string($value) ? $value : (int) $value)
|
||||||
: $tmp;
|
: $tmp;
|
||||||
|
|
||||||
} elseif ($type === Type::Float) {
|
} elseif ($type === Type::FLOAT) {
|
||||||
if (!is_string($value)) {
|
$value = ltrim((string) $value, '0');
|
||||||
$row[$key] = (float) $value;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$negative = ($value[0] ?? null) === '-';
|
|
||||||
$value = ltrim($value, '0-');
|
|
||||||
$p = strpos($value, '.');
|
$p = strpos($value, '.');
|
||||||
$e = strpos($value, 'e');
|
$e = strpos($value, 'e');
|
||||||
if ($p !== false && $e === false) {
|
if ($p !== false && $e === false) {
|
||||||
@@ -488,31 +483,27 @@ class Result implements IDataSource
|
|||||||
$value = '0' . $value;
|
$value = '0' . $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($negative) {
|
|
||||||
$value = '-' . $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
$row[$key] = $value === str_replace(',', '.', (string) ($float = (float) $value))
|
$row[$key] = $value === str_replace(',', '.', (string) ($float = (float) $value))
|
||||||
? $float
|
? $float
|
||||||
: $value;
|
: $value;
|
||||||
|
|
||||||
} elseif ($type === Type::Bool) {
|
} elseif ($type === Type::BOOL) {
|
||||||
$row[$key] = ((bool) $value) && $value !== 'f' && $value !== 'F';
|
$row[$key] = ((bool) $value) && $value !== 'f' && $value !== 'F';
|
||||||
|
|
||||||
} elseif ($type === Type::DateTime || $type === Type::Date || $type === Type::Time) {
|
} elseif ($type === Type::DATETIME || $type === Type::DATE || $type === Type::TIME) {
|
||||||
if ($value && !str_starts_with((string) $value, '0000-00')) { // '', null, false, '0000-00-00', ...
|
if ($value && !str_starts_with((string) $value, '0000-00')) { // '', null, false, '0000-00-00', ...
|
||||||
$value = new DateTime($value);
|
$value = new DateTime($value);
|
||||||
$row[$key] = $format ? $value->format($format) : $value;
|
$row[$key] = $format ? $value->format($format) : $value;
|
||||||
} else {
|
} else {
|
||||||
$row[$key] = null;
|
$row[$key] = null;
|
||||||
}
|
}
|
||||||
} elseif ($type === Type::TimeInterval) {
|
} elseif ($type === Type::TIME_INTERVAL) {
|
||||||
preg_match('#^(-?)(\d+)\D(\d+)\D(\d+)\z#', $value, $m);
|
preg_match('#^(-?)(\d+)\D(\d+)\D(\d+)\z#', $value, $m);
|
||||||
$value = new \DateInterval("PT$m[2]H$m[3]M$m[4]S");
|
$value = new \DateInterval("PT$m[2]H$m[3]M$m[4]S");
|
||||||
$value->invert = (int) (bool) $m[1];
|
$value->invert = (int) (bool) $m[1];
|
||||||
$row[$key] = $format ? $value->format($format) : $value;
|
$row[$key] = $format ? $value->format($format) : $value;
|
||||||
|
|
||||||
} elseif ($type === Type::Binary) {
|
} elseif ($type === Type::BINARY) {
|
||||||
$row[$key] = is_string($value)
|
$row[$key] = is_string($value)
|
||||||
? $this->getResultDriver()->unescapeBinary($value)
|
? $this->getResultDriver()->unescapeBinary($value)
|
||||||
: $value;
|
: $value;
|
||||||
|
@@ -15,13 +15,18 @@ namespace Dibi;
|
|||||||
*/
|
*/
|
||||||
class ResultIterator implements \Iterator, \Countable
|
class ResultIterator implements \Iterator, \Countable
|
||||||
{
|
{
|
||||||
|
use Strict;
|
||||||
|
|
||||||
|
private Result $result;
|
||||||
|
|
||||||
private mixed $row;
|
private mixed $row;
|
||||||
|
|
||||||
private int $pointer = 0;
|
private int $pointer = 0;
|
||||||
|
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(Result $result)
|
||||||
private readonly Result $result,
|
{
|
||||||
) {
|
$this->result = $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -39,7 +44,6 @@ class ResultIterator implements \Iterator, \Countable
|
|||||||
/**
|
/**
|
||||||
* Returns the key of the current element.
|
* Returns the key of the current element.
|
||||||
*/
|
*/
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function key(): mixed
|
public function key(): mixed
|
||||||
{
|
{
|
||||||
return $this->pointer;
|
return $this->pointer;
|
||||||
@@ -49,7 +53,6 @@ class ResultIterator implements \Iterator, \Countable
|
|||||||
/**
|
/**
|
||||||
* Returns the current element.
|
* Returns the current element.
|
||||||
*/
|
*/
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function current(): mixed
|
public function current(): mixed
|
||||||
{
|
{
|
||||||
return $this->row;
|
return $this->row;
|
||||||
|
@@ -9,8 +9,6 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Dibi;
|
namespace Dibi;
|
||||||
|
|
||||||
use function array_keys, count, str_starts_with;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Result set single row.
|
* Result set single row.
|
||||||
@@ -50,11 +48,10 @@ class Row implements \ArrayAccess, \IteratorAggregate, \Countable
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function __get(string $key): mixed
|
public function __get(string $key)
|
||||||
{
|
{
|
||||||
$hint = Helpers::getSuggestion(array_keys((array) $this), $key);
|
$hint = Helpers::getSuggestion(array_keys((array) $this), $key);
|
||||||
trigger_error("Attempt to read missing column '$key'" . ($hint ? ", did you mean '$hint'?" : '.'), E_USER_NOTICE);
|
trigger_error("Attempt to read missing column '$key'" . ($hint ? ", did you mean '$hint'?" : '.'), E_USER_NOTICE);
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
112
src/Dibi/Strict.php
Normal file
112
src/Dibi/Strict.php
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
<?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 array $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(fn($item) => $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), fn($m) => $m->isPublic());
|
||||||
|
$items = array_map(fn($item) => $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), fn($p) => !$p->isStatic());
|
||||||
|
$items = array_map(fn($item) => $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), fn($p) => !$p->isStatic());
|
||||||
|
$items = array_map(fn($item) => $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.");
|
||||||
|
}
|
||||||
|
}
|
@@ -9,26 +9,35 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Dibi;
|
namespace Dibi;
|
||||||
|
|
||||||
use function array_filter, array_keys, array_splice, array_values, count, explode, get_debug_type, gettype, implode, is_array, is_bool, is_float, is_int, is_numeric, is_object, is_scalar, is_string, iterator_to_array, key, ltrim, number_format, preg_last_error, preg_match, preg_replace_callback, reset, rtrim, str_contains, str_replace, strcspn, strlen, strncasecmp, strtoupper, substr, trim;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SQL translator.
|
* SQL translator.
|
||||||
*/
|
*/
|
||||||
final class Translator
|
final class Translator
|
||||||
{
|
{
|
||||||
private readonly Connection $connection;
|
use Strict;
|
||||||
private readonly Driver $driver;
|
|
||||||
|
private Connection $connection;
|
||||||
|
|
||||||
|
private Driver $driver;
|
||||||
|
|
||||||
private int $cursor = 0;
|
private int $cursor = 0;
|
||||||
|
|
||||||
private array $args;
|
private array $args;
|
||||||
|
|
||||||
/** @var string[] */
|
/** @var string[] */
|
||||||
private array $errors;
|
private array $errors;
|
||||||
|
|
||||||
private bool $comment = false;
|
private bool $comment = false;
|
||||||
|
|
||||||
private int $ifLevel = 0;
|
private int $ifLevel = 0;
|
||||||
|
|
||||||
private int $ifLevelStart = 0;
|
private int $ifLevelStart = 0;
|
||||||
|
|
||||||
private ?int $limit = null;
|
private ?int $limit = null;
|
||||||
|
|
||||||
private ?int $offset = null;
|
private ?int $offset = null;
|
||||||
|
|
||||||
private HashMap $identifiers;
|
private HashMap $identifiers;
|
||||||
|
|
||||||
|
|
||||||
@@ -36,7 +45,7 @@ final class Translator
|
|||||||
{
|
{
|
||||||
$this->connection = $connection;
|
$this->connection = $connection;
|
||||||
$this->driver = $connection->getDriver();
|
$this->driver = $connection->getDriver();
|
||||||
$this->identifiers = new HashMap($this->delimite(...));
|
$this->identifiers = new HashMap([$this, 'delimite']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -90,7 +99,7 @@ final class Translator
|
|||||||
(\?) ## 11) placeholder
|
(\?) ## 11) placeholder
|
||||||
)/xs
|
)/xs
|
||||||
XX,
|
XX,
|
||||||
$this->cb(...),
|
[$this, 'cb'],
|
||||||
substr($arg, $toSkip),
|
substr($arg, $toSkip),
|
||||||
);
|
);
|
||||||
if (preg_last_error()) {
|
if (preg_last_error()) {
|
||||||
@@ -221,7 +230,7 @@ final class Translator
|
|||||||
|
|
||||||
case 'a': // key=val, key=val, ...
|
case 'a': // key=val, key=val, ...
|
||||||
foreach ($value as $k => $v) {
|
foreach ($value as $k => $v) {
|
||||||
$pair = explode('%', (string) $k, 2); // split into identifier & modifier
|
$pair = explode('%', $k, 2); // split into identifier & modifier
|
||||||
$vx[] = $this->identifiers->{$pair[0]} . '='
|
$vx[] = $this->identifiers->{$pair[0]} . '='
|
||||||
. $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null));
|
. $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null));
|
||||||
}
|
}
|
||||||
@@ -259,7 +268,7 @@ final class Translator
|
|||||||
$proto = array_keys($v);
|
$proto = array_keys($v);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return $this->errors[] = '**Unexpected type ' . get_debug_type($v) . '**';
|
return $this->errors[] = '**Unexpected type ' . (is_object($v) ? $v::class : gettype($v)) . '**';
|
||||||
}
|
}
|
||||||
|
|
||||||
$pair = explode('%', $k, 2); // split into identifier & modifier
|
$pair = explode('%', $k, 2); // split into identifier & modifier
|
||||||
@@ -309,9 +318,13 @@ final class Translator
|
|||||||
&& $modifier === null
|
&& $modifier === null
|
||||||
&& !$value instanceof Literal
|
&& !$value instanceof Literal
|
||||||
&& !$value instanceof Expression
|
&& !$value instanceof Expression
|
||||||
&& $result = $this->connection->translateObject($value)
|
|
||||||
) {
|
) {
|
||||||
return $this->connection->translate(...$result->getValues());
|
foreach ($this->connection->getObjectTranslators() as $class => $translator) {
|
||||||
|
if ($value instanceof $class) {
|
||||||
|
$value = $translator($value);
|
||||||
|
return $this->connection->translate(...$value->getValues());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// object-to-scalar procession
|
// object-to-scalar procession
|
||||||
@@ -337,7 +350,7 @@ final class Translator
|
|||||||
) {
|
) {
|
||||||
// continue
|
// continue
|
||||||
} else {
|
} else {
|
||||||
$type = get_debug_type($value);
|
$type = is_object($value) ? $value::class : gettype($value);
|
||||||
return $this->errors[] = "**Invalid combination of type $type and modifier %$modifier**";
|
return $this->errors[] = "**Invalid combination of type $type and modifier %$modifier**";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -425,18 +438,19 @@ final class Translator
|
|||||||
$value = substr($value, 0, $toSkip)
|
$value = substr($value, 0, $toSkip)
|
||||||
. preg_replace_callback(
|
. preg_replace_callback(
|
||||||
<<<'XX'
|
<<<'XX'
|
||||||
/
|
/
|
||||||
(?=[`['":])
|
(?=[`['":])
|
||||||
(?:
|
(?:
|
||||||
`(.+?)`|
|
`(.+?)`|
|
||||||
\[(.+?)]|
|
\[(.+?)\]|
|
||||||
(')((?:''|[^'])*)'|
|
(')((?:''|[^'])*)'|
|
||||||
(")((?:""|[^"])*)"|
|
(")((?:""|[^"])*)"|
|
||||||
(['"])|
|
('|")|
|
||||||
:(\S*?:)([a-zA-Z0-9._]?)
|
:(\S*?:)([a-zA-Z0-9._]?)
|
||||||
)/sx
|
)/sx
|
||||||
XX,
|
XX
|
||||||
$this->cb(...),
|
,
|
||||||
|
[$this, 'cb'],
|
||||||
substr($value, $toSkip),
|
substr($value, $toSkip),
|
||||||
);
|
);
|
||||||
if (preg_last_error()) {
|
if (preg_last_error()) {
|
||||||
@@ -503,7 +517,7 @@ final class Translator
|
|||||||
return $this->connection->translate(...$value->getValues());
|
return $this->connection->translate(...$value->getValues());
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$type = get_debug_type($value);
|
$type = is_object($value) ? $value::class : gettype($value);
|
||||||
return $this->errors[] = "**Unexpected $type**";
|
return $this->errors[] = "**Unexpected $type**";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -16,43 +16,16 @@ namespace Dibi;
|
|||||||
class Type
|
class Type
|
||||||
{
|
{
|
||||||
public const
|
public const
|
||||||
Text = 's', // as 'string'
|
TEXT = 's', // as 'string'
|
||||||
Binary = 'bin',
|
BINARY = 'bin',
|
||||||
JSON = 'json',
|
JSON = 'json',
|
||||||
Bool = 'b',
|
BOOL = 'b',
|
||||||
Integer = 'i',
|
INTEGER = 'i',
|
||||||
Float = 'f',
|
FLOAT = 'f',
|
||||||
Date = 'd',
|
DATE = 'd',
|
||||||
DateTime = 'dt',
|
DATETIME = 'dt',
|
||||||
Time = 't',
|
TIME = 't',
|
||||||
TimeInterval = 'ti';
|
TIME_INTERVAL = 'ti';
|
||||||
|
|
||||||
/** @deprecated use Type::Text */
|
|
||||||
public const TEXT = self::Text;
|
|
||||||
|
|
||||||
/** @deprecated use Type::Binary */
|
|
||||||
public const BINARY = self::Binary;
|
|
||||||
|
|
||||||
/** @deprecated use Type::Bool */
|
|
||||||
public const BOOL = self::Bool;
|
|
||||||
|
|
||||||
/** @deprecated use Type::Integer */
|
|
||||||
public const INTEGER = self::Integer;
|
|
||||||
|
|
||||||
/** @deprecated use Type::Float */
|
|
||||||
public const FLOAT = self::Float;
|
|
||||||
|
|
||||||
/** @deprecated use Type::Date */
|
|
||||||
public const DATE = self::Date;
|
|
||||||
|
|
||||||
/** @deprecated use Type::DateTime */
|
|
||||||
public const DATETIME = self::DateTime;
|
|
||||||
|
|
||||||
/** @deprecated use Type::Time */
|
|
||||||
public const TIME = self::Time;
|
|
||||||
|
|
||||||
/** @deprecated use Type::TimeInterval */
|
|
||||||
public const TIME_INTERVAL = self::TimeInterval;
|
|
||||||
|
|
||||||
|
|
||||||
final public function __construct()
|
final public function __construct()
|
||||||
|
@@ -37,16 +37,14 @@ declare(strict_types=1);
|
|||||||
*/
|
*/
|
||||||
class dibi
|
class dibi
|
||||||
{
|
{
|
||||||
public const Version = '5.1-dev';
|
use Dibi\Strict;
|
||||||
|
|
||||||
/** @deprecated use dibi::Version */
|
public const
|
||||||
public const VERSION = self::Version;
|
AFFECTED_ROWS = 'a',
|
||||||
|
IDENTIFIER = 'n';
|
||||||
|
|
||||||
/** @deprecated use Dibi\Fluent::AffectedRows */
|
/** version */
|
||||||
public const AFFECTED_ROWS = Dibi\Fluent::AffectedRows;
|
public const VERSION = '5.0-dev';
|
||||||
|
|
||||||
/** @deprecated use Dibi\Fluent::Identifier */
|
|
||||||
public const IDENTIFIER = Dibi\Fluent::Identifier;
|
|
||||||
|
|
||||||
/** sorting order */
|
/** sorting order */
|
||||||
public const
|
public const
|
||||||
|
@@ -11,7 +11,7 @@ namespace Dibi;
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A database operation failed.
|
* Dibi common exception.
|
||||||
*/
|
*/
|
||||||
class Exception extends \Exception
|
class Exception extends \Exception
|
||||||
{
|
{
|
||||||
@@ -44,7 +44,7 @@ class Exception extends \Exception
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The database server reported an error.
|
* database server exception.
|
||||||
*/
|
*/
|
||||||
class DriverException extends Exception
|
class DriverException extends Exception
|
||||||
{
|
{
|
||||||
@@ -52,7 +52,7 @@ class DriverException extends Exception
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Regular expression pattern or execution failed.
|
* PCRE exception.
|
||||||
*/
|
*/
|
||||||
class PcreException extends Exception
|
class PcreException extends Exception
|
||||||
{
|
{
|
||||||
@@ -63,24 +63,18 @@ class PcreException extends Exception
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The requested feature is not implemented.
|
|
||||||
*/
|
|
||||||
class NotImplementedException extends Exception
|
class NotImplementedException extends Exception
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The requested operation is not supported.
|
|
||||||
*/
|
|
||||||
class NotSupportedException extends Exception
|
class NotSupportedException extends Exception
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A database stored procedure failed.
|
* Database procedure exception.
|
||||||
*/
|
*/
|
||||||
class ProcedureException extends Exception
|
class ProcedureException extends Exception
|
||||||
{
|
{
|
||||||
@@ -108,7 +102,7 @@ class ProcedureException extends Exception
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A database constraint was violated.
|
* Base class for all constraint violation related exceptions.
|
||||||
*/
|
*/
|
||||||
class ConstraintViolationException extends DriverException
|
class ConstraintViolationException extends DriverException
|
||||||
{
|
{
|
||||||
@@ -116,7 +110,7 @@ class ConstraintViolationException extends DriverException
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The foreign key constraint check failed.
|
* Exception for a foreign key constraint violation.
|
||||||
*/
|
*/
|
||||||
class ForeignKeyConstraintViolationException extends ConstraintViolationException
|
class ForeignKeyConstraintViolationException extends ConstraintViolationException
|
||||||
{
|
{
|
||||||
@@ -124,7 +118,7 @@ class ForeignKeyConstraintViolationException extends ConstraintViolationExceptio
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The NOT NULL constraint check failed.
|
* Exception for a NOT NULL constraint violation.
|
||||||
*/
|
*/
|
||||||
class NotNullConstraintViolationException extends ConstraintViolationException
|
class NotNullConstraintViolationException extends ConstraintViolationException
|
||||||
{
|
{
|
||||||
@@ -132,7 +126,7 @@ class NotNullConstraintViolationException extends ConstraintViolationException
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The unique constraint check failed.
|
* Exception for a unique constraint violation.
|
||||||
*/
|
*/
|
||||||
class UniqueConstraintViolationException extends ConstraintViolationException
|
class UniqueConstraintViolationException extends ConstraintViolationException
|
||||||
{
|
{
|
||||||
|
@@ -12,7 +12,7 @@ use Tester\Assert;
|
|||||||
require __DIR__ . '/bootstrap.php';
|
require __DIR__ . '/bootstrap.php';
|
||||||
|
|
||||||
|
|
||||||
test('immediate connection and disconnection state', function () use ($config) {
|
test('', function () use ($config) {
|
||||||
$conn = new Connection($config);
|
$conn = new Connection($config);
|
||||||
Assert::true($conn->isConnected());
|
Assert::true($conn->isConnected());
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ test('immediate connection and disconnection state', function () use ($config) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test('lazy connection initiated on first query', function () use ($config) {
|
test('lazy', function () use ($config) {
|
||||||
$conn = new Connection($config + ['lazy' => true]);
|
$conn = new Connection($config + ['lazy' => true]);
|
||||||
Assert::false($conn->isConnected());
|
Assert::false($conn->isConnected());
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ test('lazy connection initiated on first query', function () use ($config) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test('config retrieval and driver instance access', function () use ($config) {
|
test('', function () use ($config) {
|
||||||
$conn = new Connection($config);
|
$conn = new Connection($config);
|
||||||
Assert::true($conn->isConnected());
|
Assert::true($conn->isConnected());
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ test('config retrieval and driver instance access', function () use ($config) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test('idempotent disconnect calls', function () use ($config) {
|
test('', function () use ($config) {
|
||||||
$conn = new Connection($config);
|
$conn = new Connection($config);
|
||||||
Assert::true($conn->isConnected());
|
Assert::true($conn->isConnected());
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ test('idempotent disconnect calls', function () use ($config) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test('reconnect after disconnection', function () use ($config) {
|
test('', function () use ($config) {
|
||||||
$conn = new Connection($config);
|
$conn = new Connection($config);
|
||||||
Assert::equal('hello', $conn->query('SELECT %s', 'hello')->fetchSingle());
|
Assert::equal('hello', $conn->query('SELECT %s', 'hello')->fetchSingle());
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ test('reconnect after disconnection', function () use ($config) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test('destructor disconnects active connection', function () use ($config) {
|
test('', function () use ($config) {
|
||||||
$conn = new Connection($config);
|
$conn = new Connection($config);
|
||||||
Assert::true($conn->isConnected());
|
Assert::true($conn->isConnected());
|
||||||
|
|
||||||
@@ -72,30 +72,25 @@ test('destructor disconnects active connection', function () use ($config) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test('invalid onConnect option triggers exceptions', function () use ($config) {
|
test('', function () use ($config) {
|
||||||
Assert::exception(
|
Assert::exception(function () use ($config) {
|
||||||
fn() => new Connection($config + ['onConnect' => '']),
|
new Connection($config + ['onConnect' => '']);
|
||||||
InvalidArgumentException::class,
|
}, InvalidArgumentException::class, "Configuration option 'onConnect' must be array.");
|
||||||
"Configuration option 'onConnect' must be array.",
|
|
||||||
);
|
|
||||||
|
|
||||||
$e = Assert::exception(
|
$e = Assert::exception(function () use ($config) {
|
||||||
fn() => new Connection($config + ['onConnect' => ['STOP']]),
|
new Connection($config + ['onConnect' => ['STOP']]);
|
||||||
Dibi\DriverException::class,
|
}, Dibi\DriverException::class);
|
||||||
);
|
|
||||||
Assert::same('STOP', $e->getSql());
|
Assert::same('STOP', $e->getSql());
|
||||||
|
|
||||||
$e = Assert::exception(
|
$e = Assert::exception(function () use ($config) {
|
||||||
fn() => new Connection($config + ['onConnect' => [['STOP %i', 123]]]),
|
new Connection($config + ['onConnect' => [['STOP %i', 123]]]);
|
||||||
Dibi\DriverException::class,
|
}, Dibi\DriverException::class);
|
||||||
);
|
|
||||||
Assert::same('STOP 123', $e->getSql());
|
Assert::same('STOP 123', $e->getSql());
|
||||||
|
|
||||||
// lazy
|
// lazy
|
||||||
$conn = new Connection($config + ['lazy' => true, 'onConnect' => ['STOP']]);
|
$conn = new Connection($config + ['lazy' => true, 'onConnect' => ['STOP']]);
|
||||||
$e = Assert::exception(
|
$e = Assert::exception(function () use ($conn) {
|
||||||
fn() => $conn->query('SELECT 1'),
|
$conn->query('SELECT 1');
|
||||||
Dibi\DriverException::class,
|
}, Dibi\DriverException::class);
|
||||||
);
|
|
||||||
Assert::same('STOP', $e->getSql());
|
Assert::same('STOP', $e->getSql());
|
||||||
});
|
});
|
||||||
|
@@ -24,16 +24,14 @@ class Time extends DateTimeImmutable
|
|||||||
|
|
||||||
|
|
||||||
test('Without object translator', function () use ($conn) {
|
test('Without object translator', function () use ($conn) {
|
||||||
Assert::exception(
|
Assert::exception(function () use ($conn) {
|
||||||
fn() => $conn->translate('?', new Email),
|
$conn->translate('?', new Email);
|
||||||
Dibi\Exception::class,
|
}, Dibi\Exception::class, 'SQL translate error: Unexpected Email');
|
||||||
'SQL translate error: Unexpected Email',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test('Basics', function () use ($conn) {
|
test('Basics', function () use ($conn) {
|
||||||
$conn->setObjectTranslator(fn(Email $email) => new Dibi\Expression('?', $email->address));
|
$conn->addObjectTranslator(Email::class, fn($object) => new Dibi\Expression('?', $object->address));
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat([
|
reformat([
|
||||||
'sqlsrv' => "N'address@example.com'",
|
'sqlsrv' => "N'address@example.com'",
|
||||||
@@ -55,7 +53,7 @@ test('DateTime', function () use ($conn) {
|
|||||||
|
|
||||||
|
|
||||||
// With object translator
|
// With object translator
|
||||||
$conn->setObjectTranslator(fn(Time $time) => new Dibi\Expression('OwnTime(?)', $time->format('H:i:s')));
|
$conn->addObjectTranslator(Time::class, fn($object) => new Dibi\Expression('OwnTime(?)', $object->format('H:i:s')));
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat([
|
reformat([
|
||||||
'sqlsrv' => "OwnTime(N'12:13:14')",
|
'sqlsrv' => "OwnTime(N'12:13:14')",
|
||||||
@@ -88,7 +86,7 @@ test('DateTime', function () use ($conn) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// But DateTime translation can be overloaded
|
// But DateTime translation can be overloaded
|
||||||
$conn->setObjectTranslator(fn(DateTimeInterface $dt) => new Dibi\Expression('OwnDateTime'));
|
$conn->addObjectTranslator(DateTimeInterface::class, fn() => new Dibi\Expression('OwnDateTime'));
|
||||||
Assert::same(
|
Assert::same(
|
||||||
'OwnDateTime',
|
'OwnDateTime',
|
||||||
$conn->translate('?', $dt),
|
$conn->translate('?', $dt),
|
||||||
@@ -97,9 +95,9 @@ test('DateTime', function () use ($conn) {
|
|||||||
|
|
||||||
|
|
||||||
test('Complex structures', function () use ($conn) {
|
test('Complex structures', function () use ($conn) {
|
||||||
$conn->setObjectTranslator(fn(Email $email) => new Dibi\Expression('?', $email->address));
|
$conn->addObjectTranslator(Email::class, fn($object) => new Dibi\Expression('?', $object->address));
|
||||||
$conn->setObjectTranslator(fn(Time $time) => new Dibi\Expression('OwnTime(?)', $time->format('H:i:s')));
|
$conn->addObjectTranslator(Time::class, fn($object) => new Dibi\Expression('OwnTime(?)', $object->format('H:i:s')));
|
||||||
$conn->setObjectTranslator(fn(DateTimeInterface $dt) => new Dibi\Expression('OwnDateTime'));
|
$conn->addObjectTranslator(Time::class, fn($object) => new Dibi\Expression('OwnTime(?)', $object->format('H:i:s')));
|
||||||
|
|
||||||
$time = Time::createFromFormat('Y-m-d H:i:s', '2022-11-22 12:13:14');
|
$time = Time::createFromFormat('Y-m-d H:i:s', '2022-11-22 12:13:14');
|
||||||
Assert::same(
|
Assert::same(
|
||||||
@@ -119,37 +117,3 @@ test('Complex structures', function () use ($conn) {
|
|||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
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.",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
@@ -15,22 +15,18 @@ $conn = new Dibi\Connection($config);
|
|||||||
$conn->loadFile(__DIR__ . "/data/$config[system].sql");
|
$conn->loadFile(__DIR__ . "/data/$config[system].sql");
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*Assert::exception(function () use ($conn) {
|
||||||
Assert::exception(
|
$conn->rollback();
|
||||||
fn() => $conn->rollback(),
|
}, Dibi\Exception::class);
|
||||||
Dibi\Exception::class,
|
|
||||||
);
|
|
||||||
|
|
||||||
Assert::exception(
|
Assert::exception(function () use ($conn) {
|
||||||
fn() => $conn->commit(),
|
$conn->commit();
|
||||||
Dibi\Exception::class,
|
}, Dibi\Exception::class);
|
||||||
);
|
|
||||||
|
|
||||||
$conn->begin();
|
$conn->begin();
|
||||||
Assert::exception(
|
Assert::exception(function () use ($conn) {
|
||||||
fn() => $conn->begin(),
|
$conn->begin();
|
||||||
Dibi\Exception::class,
|
}, Dibi\Exception::class);
|
||||||
);
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
@@ -57,16 +53,14 @@ test('begin() & commit()', function () use ($conn) {
|
|||||||
|
|
||||||
|
|
||||||
test('transaction() fail', function () use ($conn) {
|
test('transaction() fail', function () use ($conn) {
|
||||||
Assert::exception(
|
Assert::exception(function () use ($conn) {
|
||||||
fn() => $conn->transaction(function (Dibi\Connection $connection) {
|
$conn->transaction(function (Dibi\Connection $connection) {
|
||||||
$connection->query('INSERT INTO [products]', [
|
$connection->query('INSERT INTO [products]', [
|
||||||
'title' => 'Test product',
|
'title' => 'Test product',
|
||||||
]);
|
]);
|
||||||
throw new Exception('my exception');
|
throw new Exception('my exception');
|
||||||
}),
|
});
|
||||||
Throwable::class,
|
}, Throwable::class, 'my exception');
|
||||||
'my exception',
|
|
||||||
);
|
|
||||||
Assert::same(4, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
|
Assert::same(4, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -82,8 +76,8 @@ test('transaction() success', function () use ($conn) {
|
|||||||
|
|
||||||
|
|
||||||
test('nested transaction() call fail', function () use ($conn) {
|
test('nested transaction() call fail', function () use ($conn) {
|
||||||
Assert::exception(
|
Assert::exception(function () use ($conn) {
|
||||||
fn() => $conn->transaction(function (Dibi\Connection $connection) {
|
$conn->transaction(function (Dibi\Connection $connection) {
|
||||||
$connection->query('INSERT INTO [products]', [
|
$connection->query('INSERT INTO [products]', [
|
||||||
'title' => 'Test product',
|
'title' => 'Test product',
|
||||||
]);
|
]);
|
||||||
@@ -94,10 +88,8 @@ test('nested transaction() call fail', function () use ($conn) {
|
|||||||
]);
|
]);
|
||||||
throw new Exception('my exception');
|
throw new Exception('my exception');
|
||||||
});
|
});
|
||||||
}),
|
});
|
||||||
Throwable::class,
|
}, Throwable::class, 'my exception');
|
||||||
'my exception',
|
|
||||||
);
|
|
||||||
Assert::same(5, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
|
Assert::same(5, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -119,27 +111,21 @@ test('nested transaction() call success', function () use ($conn) {
|
|||||||
|
|
||||||
|
|
||||||
test('begin(), commit() & rollback() calls are forbidden in transaction()', function () use ($conn) {
|
test('begin(), commit() & rollback() calls are forbidden in transaction()', function () use ($conn) {
|
||||||
Assert::exception(
|
Assert::exception(function () use ($conn) {
|
||||||
fn() => $conn->transaction(function (Dibi\Connection $connection) {
|
$conn->transaction(function (Dibi\Connection $connection) {
|
||||||
$connection->begin();
|
$connection->begin();
|
||||||
}),
|
});
|
||||||
LogicException::class,
|
}, LogicException::class, Dibi\Connection::class . '::begin() call is forbidden inside a transaction() callback');
|
||||||
Dibi\Connection::class . '::begin() call is forbidden inside a transaction() callback',
|
|
||||||
);
|
|
||||||
|
|
||||||
Assert::exception(
|
Assert::exception(function () use ($conn) {
|
||||||
fn() => $conn->transaction(function (Dibi\Connection $connection) {
|
$conn->transaction(function (Dibi\Connection $connection) {
|
||||||
$connection->commit();
|
$connection->commit();
|
||||||
}),
|
});
|
||||||
LogicException::class,
|
}, LogicException::class, Dibi\Connection::class . '::commit() call is forbidden inside a transaction() callback');
|
||||||
Dibi\Connection::class . '::commit() call is forbidden inside a transaction() callback',
|
|
||||||
);
|
|
||||||
|
|
||||||
Assert::exception(
|
Assert::exception(function () use ($conn) {
|
||||||
fn() => $conn->transaction(function (Dibi\Connection $connection) {
|
$conn->transaction(function (Dibi\Connection $connection) {
|
||||||
$connection->rollback();
|
$connection->rollback();
|
||||||
}),
|
});
|
||||||
LogicException::class,
|
}, LogicException::class, Dibi\Connection::class . '::rollback() call is forbidden inside a transaction() callback');
|
||||||
Dibi\Connection::class . '::rollback() call is forbidden inside a transaction() callback',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
@@ -57,28 +57,28 @@ $fluent = $conn->select('*')
|
|||||||
->orderBy('customer_id');
|
->orderBy('customer_id');
|
||||||
|
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat('SELECT * FROM [customers] ORDER BY [customer_id] OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY'),
|
reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||||
(string) $fluent,
|
(string) $fluent,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
$fluent->fetch();
|
$fluent->fetch();
|
||||||
Assert::same(
|
Assert::same(
|
||||||
'SELECT * FROM [customers] ORDER BY [customer_id] OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY',
|
'SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t',
|
||||||
dibi::$sql,
|
dibi::$sql,
|
||||||
);
|
);
|
||||||
$fluent->fetchSingle();
|
$fluent->fetchSingle();
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat('SELECT * FROM [customers] ORDER BY [customer_id] OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY'),
|
reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||||
dibi::$sql,
|
dibi::$sql,
|
||||||
);
|
);
|
||||||
$fluent->fetchAll(0, 3);
|
$fluent->fetchAll(0, 3);
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat('SELECT * FROM [customers] ORDER BY [customer_id] OFFSET 0 ROWS FETCH NEXT 3 ROWS ONLY'),
|
reformat('SELECT TOP (3) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||||
dibi::$sql,
|
dibi::$sql,
|
||||||
);
|
);
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat('SELECT * FROM [customers] ORDER BY [customer_id] OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY'),
|
reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||||
(string) $fluent,
|
(string) $fluent,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -86,16 +86,16 @@ Assert::same(
|
|||||||
$fluent->limit(0);
|
$fluent->limit(0);
|
||||||
$fluent->fetch();
|
$fluent->fetch();
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat('SELECT * FROM [customers] ORDER BY [customer_id] OFFSET 0 ROWS FETCH NEXT 0 ROWS ONLY'),
|
reformat('SELECT TOP (0) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||||
dibi::$sql,
|
dibi::$sql,
|
||||||
);
|
);
|
||||||
$fluent->fetchSingle();
|
$fluent->fetchSingle();
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat('SELECT * FROM [customers] ORDER BY [customer_id] OFFSET 0 ROWS FETCH NEXT 0 ROWS ONLY'),
|
reformat('SELECT TOP (0) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||||
dibi::$sql,
|
dibi::$sql,
|
||||||
);
|
);
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat('SELECT * FROM [customers] ORDER BY [customer_id] OFFSET 0 ROWS FETCH NEXT 0 ROWS ONLY'),
|
reformat('SELECT TOP (0) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||||
(string) $fluent,
|
(string) $fluent,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -104,12 +104,12 @@ $fluent->removeClause('limit');
|
|||||||
$fluent->removeClause('offset');
|
$fluent->removeClause('offset');
|
||||||
$fluent->fetch();
|
$fluent->fetch();
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat('SELECT * FROM [customers] ORDER BY [customer_id] OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY'),
|
reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||||
dibi::$sql,
|
dibi::$sql,
|
||||||
);
|
);
|
||||||
$fluent->fetchSingle();
|
$fluent->fetchSingle();
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat('SELECT * FROM [customers] ORDER BY [customer_id] OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY'),
|
reformat('SELECT TOP (1) * FROM (SELECT * FROM [customers] ORDER BY [customer_id]) t'),
|
||||||
dibi::$sql,
|
dibi::$sql,
|
||||||
);
|
);
|
||||||
Assert::same(
|
Assert::same(
|
||||||
|
@@ -74,7 +74,7 @@ Assert::same(
|
|||||||
(string) $fluent,
|
(string) $fluent,
|
||||||
);
|
);
|
||||||
|
|
||||||
$fluent->orderBy(Dibi\Fluent::Remove);
|
$fluent->orderBy(Dibi\Fluent::REMOVE);
|
||||||
|
|
||||||
Assert::same(
|
Assert::same(
|
||||||
reformat('SELECT * , [a] , [b] AS [bAlias] , [c], [d], [e] , [d] FROM [anotherTable] AS [anotherAlias] INNER JOIN [table3] ON table.col = table3.col WHERE col > 10 OR col < 5 AND active = 1 AND [col] IN (1, 2, 3)'),
|
reformat('SELECT * , [a] , [b] AS [bAlias] , [c], [d], [e] , [d] FROM [anotherTable] AS [anotherAlias] INNER JOIN [table3] ON table.col = table3.col WHERE col > 10 OR col < 5 AND active = 1 AND [col] IN (1, 2, 3)'),
|
||||||
@@ -147,11 +147,9 @@ if ($config['system'] === 'mysql') {
|
|||||||
->limit(' 1; DROP TABLE users')
|
->limit(' 1; DROP TABLE users')
|
||||||
->offset(' 1; DROP TABLE users');
|
->offset(' 1; DROP TABLE users');
|
||||||
|
|
||||||
Assert::exception(
|
Assert::exception(function () use ($fluent) {
|
||||||
fn() => (string) $fluent,
|
(string) $fluent;
|
||||||
Dibi\Exception::class,
|
}, Dibi\Exception::class, "Expected number, ' 1; DROP TABLE users' given.");
|
||||||
"Expected number, ' 1; DROP TABLE users' given.",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -12,32 +12,22 @@ Assert::same(0, Helpers::intVal(0));
|
|||||||
Assert::same(0, Helpers::intVal('0'));
|
Assert::same(0, Helpers::intVal('0'));
|
||||||
Assert::same(-10, Helpers::intVal('-10'));
|
Assert::same(-10, Helpers::intVal('-10'));
|
||||||
|
|
||||||
Assert::exception(
|
Assert::exception(function () {
|
||||||
fn() => Helpers::intVal('12345678901234567890123456879'),
|
Helpers::intVal('12345678901234567890123456879');
|
||||||
Dibi\Exception::class,
|
}, Dibi\Exception::class, 'Number 12345678901234567890123456879 is greater than integer.');
|
||||||
'Number 12345678901234567890123456879 is greater than integer.',
|
|
||||||
);
|
|
||||||
|
|
||||||
Assert::exception(
|
Assert::exception(function () {
|
||||||
fn() => Helpers::intVal('-12345678901234567890123456879'),
|
Helpers::intVal('-12345678901234567890123456879');
|
||||||
Dibi\Exception::class,
|
}, Dibi\Exception::class, 'Number -12345678901234567890123456879 is greater than integer.');
|
||||||
'Number -12345678901234567890123456879 is greater than integer.',
|
|
||||||
);
|
|
||||||
|
|
||||||
Assert::exception(
|
Assert::exception(function () {
|
||||||
fn() => Helpers::intVal(''),
|
Helpers::intVal('');
|
||||||
Dibi\Exception::class,
|
}, Dibi\Exception::class, "Expected number, '' given.");
|
||||||
"Expected number, '' given.",
|
|
||||||
);
|
|
||||||
|
|
||||||
Assert::exception(
|
Assert::exception(function () {
|
||||||
fn() => Helpers::intVal('not number'),
|
Helpers::intVal('not number');
|
||||||
Dibi\Exception::class,
|
}, Dibi\Exception::class, "Expected number, 'not number' given.");
|
||||||
"Expected number, 'not number' given.",
|
|
||||||
);
|
|
||||||
|
|
||||||
Assert::exception(
|
Assert::exception(function () {
|
||||||
fn() => Helpers::intVal(null),
|
Helpers::intVal(null);
|
||||||
Dibi\Exception::class,
|
}, Dibi\Exception::class, "Expected number, '' given.");
|
||||||
"Expected number, '' given.",
|
|
||||||
);
|
|
||||||
|
@@ -19,22 +19,17 @@ function buildPdoDriver(?int $errorMode)
|
|||||||
|
|
||||||
|
|
||||||
// PDO error mode: exception
|
// PDO error mode: exception
|
||||||
Assert::exception(
|
Assert::exception(function () {
|
||||||
fn() => buildPdoDriver(PDO::ERRMODE_EXCEPTION),
|
buildPdoDriver(PDO::ERRMODE_EXCEPTION);
|
||||||
Dibi\DriverException::class,
|
}, Dibi\DriverException::class, 'PDO connection in exception or warning error mode is not supported.');
|
||||||
'PDO connection in exception or warning error mode is not supported.',
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
// PDO error mode: warning
|
// PDO error mode: warning
|
||||||
Assert::exception(
|
Assert::exception(function () {
|
||||||
fn() => buildPdoDriver(PDO::ERRMODE_WARNING),
|
buildPdoDriver(PDO::ERRMODE_WARNING);
|
||||||
Dibi\DriverException::class,
|
}, Dibi\DriverException::class, 'PDO connection in exception or warning error mode is not supported.');
|
||||||
'PDO connection in exception or warning error mode is not supported.',
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
test(
|
test('PDO error mode: explicitly set silent', function () {
|
||||||
'PDO error mode: explicitly set silent',
|
buildPdoDriver(PDO::ERRMODE_SILENT);
|
||||||
fn() => buildPdoDriver(PDO::ERRMODE_SILENT),
|
});
|
||||||
);
|
|
||||||
|
@@ -18,9 +18,9 @@ $tests = function ($conn) {
|
|||||||
Assert::false($conn->query("SELECT 'AAxBB' LIKE %~like~", 'A%B')->fetchSingle());
|
Assert::false($conn->query("SELECT 'AAxBB' LIKE %~like~", 'A%B')->fetchSingle());
|
||||||
Assert::true($conn->query("SELECT 'AA%BB' LIKE %~like~", 'A%B')->fetchSingle());
|
Assert::true($conn->query("SELECT 'AA%BB' LIKE %~like~", 'A%B')->fetchSingle());
|
||||||
|
|
||||||
Assert::same('AA\BB', $conn->query("SELECT 'AA\\BB'")->fetchSingle());
|
Assert::same('AA\\BB', $conn->query("SELECT 'AA\\BB'")->fetchSingle());
|
||||||
Assert::false($conn->query("SELECT 'AAxBB' LIKE %~like~", 'A\B')->fetchSingle());
|
Assert::false($conn->query("SELECT 'AAxBB' LIKE %~like~", 'A\\B')->fetchSingle());
|
||||||
Assert::true($conn->query("SELECT 'AA\\BB' LIKE %~like~", 'A\B')->fetchSingle());
|
Assert::true($conn->query("SELECT 'AA\\BB' LIKE %~like~", 'A\\B')->fetchSingle());
|
||||||
};
|
};
|
||||||
|
|
||||||
$conn = new Dibi\Connection($config);
|
$conn = new Dibi\Connection($config);
|
||||||
|
@@ -25,10 +25,10 @@ class MockResult extends Dibi\Result
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
test('native text conversion preserves boolean values', function () {
|
test('', function () {
|
||||||
$result = new MockResult;
|
$result = new MockResult;
|
||||||
$result->setType('col', Type::Text);
|
$result->setType('col', Type::TEXT);
|
||||||
$result->setFormat(Type::Text, 'native');
|
$result->setFormat(Type::TEXT, 'native');
|
||||||
|
|
||||||
Assert::same(['col' => null], $result->test(['col' => null]));
|
Assert::same(['col' => null], $result->test(['col' => null]));
|
||||||
Assert::same(['col' => true], $result->test(['col' => true]));
|
Assert::same(['col' => true], $result->test(['col' => true]));
|
||||||
@@ -36,9 +36,9 @@ test('native text conversion preserves boolean values', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test('boolean conversion from diverse representations', function () {
|
test('', function () {
|
||||||
$result = new MockResult;
|
$result = new MockResult;
|
||||||
$result->setType('col', Type::Bool);
|
$result->setType('col', Type::BOOL);
|
||||||
|
|
||||||
Assert::same(['col' => null], $result->test(['col' => null]));
|
Assert::same(['col' => null], $result->test(['col' => null]));
|
||||||
Assert::same(['col' => true], $result->test(['col' => true]));
|
Assert::same(['col' => true], $result->test(['col' => true]));
|
||||||
@@ -58,9 +58,9 @@ test('boolean conversion from diverse representations', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test('text conversion of booleans and numerics', function () {
|
test('', function () {
|
||||||
$result = new MockResult;
|
$result = new MockResult;
|
||||||
$result->setType('col', Type::Text);
|
$result->setType('col', Type::TEXT);
|
||||||
|
|
||||||
Assert::same(['col' => null], $result->test(['col' => null]));
|
Assert::same(['col' => null], $result->test(['col' => null]));
|
||||||
Assert::same(['col' => '1'], $result->test(['col' => true]));
|
Assert::same(['col' => '1'], $result->test(['col' => true]));
|
||||||
@@ -74,9 +74,9 @@ test('text conversion of booleans and numerics', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test('float conversion with various numeric formats', function () {
|
test('', function () {
|
||||||
$result = new MockResult;
|
$result = new MockResult;
|
||||||
$result->setType('col', Type::Float);
|
$result->setType('col', Type::FLOAT);
|
||||||
|
|
||||||
Assert::same(['col' => null], $result->test(['col' => null]));
|
Assert::same(['col' => null], $result->test(['col' => null]));
|
||||||
Assert::same(['col' => 1.0], $result->test(['col' => true]));
|
Assert::same(['col' => 1.0], $result->test(['col' => true]));
|
||||||
@@ -117,37 +117,6 @@ test('float conversion with various numeric formats', function () {
|
|||||||
Assert::same(['col' => '1.1e+10'], $result->test(['col' => '001.1e+10']));
|
Assert::same(['col' => '1.1e+10'], $result->test(['col' => '001.1e+10']));
|
||||||
Assert::notSame(['col' => '1.1e+1'], $result->test(['col' => '1.1e+10']));
|
Assert::notSame(['col' => '1.1e+1'], $result->test(['col' => '1.1e+10']));
|
||||||
|
|
||||||
// negative
|
|
||||||
Assert::same(['col' => -0.0], $result->test(['col' => '-']));
|
|
||||||
Assert::same(['col' => -0.0], $result->test(['col' => '-0']));
|
|
||||||
Assert::same(['col' => -1.0], $result->test(['col' => '-1']));
|
|
||||||
Assert::same(['col' => -0.0], $result->test(['col' => '-.0']));
|
|
||||||
Assert::same(['col' => -0.1], $result->test(['col' => '-.1']));
|
|
||||||
Assert::same(['col' => -0.0], $result->test(['col' => '-0.0']));
|
|
||||||
Assert::same(['col' => -0.1], $result->test(['col' => '-0.1']));
|
|
||||||
Assert::same(['col' => -0.0], $result->test(['col' => '-0.000']));
|
|
||||||
Assert::same(['col' => -0.1], $result->test(['col' => '-0.100']));
|
|
||||||
Assert::same(['col' => -1.0], $result->test(['col' => '-1.0']));
|
|
||||||
Assert::same(['col' => -1.1], $result->test(['col' => '-1.1']));
|
|
||||||
Assert::same(['col' => -1.0], $result->test(['col' => '-1.000']));
|
|
||||||
Assert::same(['col' => -1.1], $result->test(['col' => '-1.100']));
|
|
||||||
Assert::same(['col' => -1.0], $result->test(['col' => '-001.000']));
|
|
||||||
Assert::same(['col' => -1.1], $result->test(['col' => '-001.100']));
|
|
||||||
Assert::same(['col' => -10.0], $result->test(['col' => '-10']));
|
|
||||||
Assert::same(['col' => -11.0], $result->test(['col' => '-11']));
|
|
||||||
Assert::same(['col' => -10.0], $result->test(['col' => '-0010']));
|
|
||||||
Assert::same(['col' => -11.0], $result->test(['col' => '-0011']));
|
|
||||||
Assert::same(['col' => '-0.00000000000000000001'], $result->test(['col' => '-0.00000000000000000001']));
|
|
||||||
Assert::same(['col' => '-12345678901234567890'], $result->test(['col' => '-12345678901234567890']));
|
|
||||||
Assert::same(['col' => '-12345678901234567890'], $result->test(['col' => '-012345678901234567890']));
|
|
||||||
Assert::same(['col' => '-12345678901234567890'], $result->test(['col' => '-12345678901234567890.000']));
|
|
||||||
Assert::same(['col' => '-12345678901234567890.1'], $result->test(['col' => '-012345678901234567890.100']));
|
|
||||||
|
|
||||||
Assert::same(['col' => '-1.1e+10'], $result->test(['col' => '-1.1e+10']));
|
|
||||||
Assert::same(['col' => '-1.1e-10'], $result->test(['col' => '-1.1e-10']));
|
|
||||||
Assert::same(['col' => '-1.1e+10'], $result->test(['col' => '-001.1e+10']));
|
|
||||||
Assert::notSame(['col' => '-1.1e+1'], $result->test(['col' => '-1.1e+10']));
|
|
||||||
|
|
||||||
setlocale(LC_ALL, 'de_DE@euro', 'de_DE', 'deu_deu');
|
setlocale(LC_ALL, 'de_DE@euro', 'de_DE', 'deu_deu');
|
||||||
Assert::same(['col' => 0.0], $result->test(['col' => '']));
|
Assert::same(['col' => 0.0], $result->test(['col' => '']));
|
||||||
Assert::same(['col' => 0.0], $result->test(['col' => '0']));
|
Assert::same(['col' => 0.0], $result->test(['col' => '0']));
|
||||||
@@ -178,54 +147,25 @@ test('float conversion with various numeric formats', function () {
|
|||||||
Assert::same(['col' => 0.0], $result->test(['col' => 0.0]));
|
Assert::same(['col' => 0.0], $result->test(['col' => 0.0]));
|
||||||
Assert::same(['col' => 1.0], $result->test(['col' => 1]));
|
Assert::same(['col' => 1.0], $result->test(['col' => 1]));
|
||||||
Assert::same(['col' => 1.0], $result->test(['col' => 1.0]));
|
Assert::same(['col' => 1.0], $result->test(['col' => 1.0]));
|
||||||
|
|
||||||
// Same but negative
|
|
||||||
Assert::same(['col' => -0.0], $result->test(['col' => '-']));
|
|
||||||
Assert::same(['col' => -0.0], $result->test(['col' => '-0']));
|
|
||||||
Assert::same(['col' => -1.0], $result->test(['col' => '-1']));
|
|
||||||
Assert::same(['col' => -0.0], $result->test(['col' => '-.0']));
|
|
||||||
Assert::same(['col' => -0.1], $result->test(['col' => '-.1']));
|
|
||||||
Assert::same(['col' => -0.0], $result->test(['col' => '-0.0']));
|
|
||||||
Assert::same(['col' => -0.1], $result->test(['col' => '-0.1']));
|
|
||||||
Assert::same(['col' => -0.0], $result->test(['col' => '-0.000']));
|
|
||||||
Assert::same(['col' => -0.1], $result->test(['col' => '-0.100']));
|
|
||||||
Assert::same(['col' => -1.0], $result->test(['col' => '-1.0']));
|
|
||||||
Assert::same(['col' => -1.1], $result->test(['col' => '-1.1']));
|
|
||||||
Assert::same(['col' => -1.0], $result->test(['col' => '-1.000']));
|
|
||||||
Assert::same(['col' => -1.1], $result->test(['col' => '-1.100']));
|
|
||||||
Assert::same(['col' => -1.0], $result->test(['col' => '-001.000']));
|
|
||||||
Assert::same(['col' => -1.1], $result->test(['col' => '-001.100']));
|
|
||||||
Assert::same(['col' => -10.0], $result->test(['col' => '-10']));
|
|
||||||
Assert::same(['col' => -11.0], $result->test(['col' => '-11']));
|
|
||||||
Assert::same(['col' => -10.0], $result->test(['col' => '-0010']));
|
|
||||||
Assert::same(['col' => -11.0], $result->test(['col' => '-0011']));
|
|
||||||
Assert::same(['col' => '-0.00000000000000000001'], $result->test(['col' => '-0.00000000000000000001']));
|
|
||||||
Assert::same(['col' => '-12345678901234567890'], $result->test(['col' => '-12345678901234567890']));
|
|
||||||
Assert::same(['col' => '-12345678901234567890'], $result->test(['col' => '-012345678901234567890']));
|
|
||||||
Assert::same(['col' => '-12345678901234567890'], $result->test(['col' => '-12345678901234567890.000']));
|
|
||||||
Assert::same(['col' => '-12345678901234567890.1'], $result->test(['col' => '-012345678901234567890.100']));
|
|
||||||
|
|
||||||
Assert::same(['col' => -0.0], $result->test(['col' => -0]));
|
|
||||||
Assert::same(['col' => -0.0], $result->test(['col' => -0.0]));
|
|
||||||
Assert::same(['col' => -1.0], $result->test(['col' => -1]));
|
|
||||||
Assert::same(['col' => -1.0], $result->test(['col' => -1.0]));
|
|
||||||
|
|
||||||
setlocale(LC_NUMERIC, 'C');
|
setlocale(LC_NUMERIC, 'C');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test('strict integer conversion with error on empty string', function () {
|
test('', function () {
|
||||||
$result = new MockResult;
|
$result = new MockResult;
|
||||||
$result->setType('col', Type::Integer);
|
$result->setType('col', Type::INTEGER);
|
||||||
|
|
||||||
Assert::same(['col' => null], $result->test(['col' => null]));
|
Assert::same(['col' => null], $result->test(['col' => null]));
|
||||||
Assert::same(['col' => 1], $result->test(['col' => true]));
|
Assert::same(['col' => 1], $result->test(['col' => true]));
|
||||||
Assert::same(['col' => 0], $result->test(['col' => false]));
|
Assert::same(['col' => 0], $result->test(['col' => false]));
|
||||||
|
|
||||||
Assert::exception(
|
if (PHP_VERSION_ID < 80000) {
|
||||||
fn() => Assert::same(['col' => 0], $result->test(['col' => ''])),
|
Assert::same(['col' => 0], @$result->test(['col' => ''])); // triggers warning since PHP 7.1
|
||||||
TypeError::class,
|
} else {
|
||||||
);
|
Assert::exception(function () use ($result) {
|
||||||
|
Assert::same(['col' => 0], $result->test(['col' => '']));
|
||||||
|
}, TypeError::class);
|
||||||
|
}
|
||||||
|
|
||||||
Assert::same(['col' => 0], $result->test(['col' => '0']));
|
Assert::same(['col' => 0], $result->test(['col' => '0']));
|
||||||
Assert::same(['col' => 1], $result->test(['col' => '1']));
|
Assert::same(['col' => 1], $result->test(['col' => '1']));
|
||||||
@@ -244,15 +184,14 @@ test('strict integer conversion with error on empty string', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test('dateTime conversion with object instantiation', function () {
|
test('', function () {
|
||||||
$result = new MockResult;
|
$result = new MockResult;
|
||||||
$result->setType('col', Type::DateTime);
|
$result->setType('col', Type::DATETIME);
|
||||||
|
|
||||||
Assert::same(['col' => null], $result->test(['col' => null]));
|
Assert::same(['col' => null], $result->test(['col' => null]));
|
||||||
Assert::exception(
|
Assert::exception(function () use ($result) {
|
||||||
fn() => $result->test(['col' => true]),
|
$result->test(['col' => true]);
|
||||||
TypeError::class,
|
}, TypeError::class);
|
||||||
);
|
|
||||||
Assert::same(['col' => null], $result->test(['col' => false]));
|
Assert::same(['col' => null], $result->test(['col' => false]));
|
||||||
|
|
||||||
Assert::same(['col' => null], $result->test(['col' => '']));
|
Assert::same(['col' => null], $result->test(['col' => '']));
|
||||||
@@ -263,16 +202,15 @@ test('dateTime conversion with object instantiation', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test('dateTime conversion using custom format', function () {
|
test('', function () {
|
||||||
$result = new MockResult;
|
$result = new MockResult;
|
||||||
$result->setType('col', Type::DateTime);
|
$result->setType('col', Type::DATETIME);
|
||||||
$result->setFormat(Type::DateTime, 'Y-m-d H:i:s');
|
$result->setFormat(Type::DATETIME, 'Y-m-d H:i:s');
|
||||||
|
|
||||||
Assert::same(['col' => null], $result->test(['col' => null]));
|
Assert::same(['col' => null], $result->test(['col' => null]));
|
||||||
Assert::exception(
|
Assert::exception(function () use ($result) {
|
||||||
fn() => $result->test(['col' => true]),
|
$result->test(['col' => true]);
|
||||||
TypeError::class,
|
}, TypeError::class);
|
||||||
);
|
|
||||||
Assert::same(['col' => null], $result->test(['col' => false]));
|
Assert::same(['col' => null], $result->test(['col' => false]));
|
||||||
|
|
||||||
Assert::same(['col' => null], $result->test(['col' => '']));
|
Assert::same(['col' => null], $result->test(['col' => '']));
|
||||||
@@ -283,15 +221,14 @@ test('dateTime conversion using custom format', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test('date conversion to DateTime instance', function () {
|
test('', function () {
|
||||||
$result = new MockResult;
|
$result = new MockResult;
|
||||||
$result->setType('col', Type::Date);
|
$result->setType('col', Type::DATE);
|
||||||
|
|
||||||
Assert::same(['col' => null], $result->test(['col' => null]));
|
Assert::same(['col' => null], $result->test(['col' => null]));
|
||||||
Assert::exception(
|
Assert::exception(function () use ($result) {
|
||||||
fn() => $result->test(['col' => true]),
|
$result->test(['col' => true]);
|
||||||
TypeError::class,
|
}, TypeError::class);
|
||||||
);
|
|
||||||
Assert::same(['col' => null], $result->test(['col' => false]));
|
Assert::same(['col' => null], $result->test(['col' => false]));
|
||||||
|
|
||||||
Assert::same(['col' => null], $result->test(['col' => '']));
|
Assert::same(['col' => null], $result->test(['col' => '']));
|
||||||
@@ -300,15 +237,14 @@ test('date conversion to DateTime instance', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test('time conversion to DateTime instance', function () {
|
test('', function () {
|
||||||
$result = new MockResult;
|
$result = new MockResult;
|
||||||
$result->setType('col', Type::Time);
|
$result->setType('col', Type::TIME);
|
||||||
|
|
||||||
Assert::same(['col' => null], $result->test(['col' => null]));
|
Assert::same(['col' => null], $result->test(['col' => null]));
|
||||||
Assert::exception(
|
Assert::exception(function () use ($result) {
|
||||||
fn() => $result->test(['col' => true]),
|
$result->test(['col' => true]);
|
||||||
TypeError::class,
|
}, TypeError::class);
|
||||||
);
|
|
||||||
Assert::same(['col' => null], $result->test(['col' => false]));
|
Assert::same(['col' => null], $result->test(['col' => false]));
|
||||||
|
|
||||||
Assert::same(['col' => null], $result->test(['col' => '']));
|
Assert::same(['col' => null], $result->test(['col' => '']));
|
||||||
|
@@ -12,7 +12,7 @@ $conn->loadFile(__DIR__ . "/data/$config[system].sql");
|
|||||||
$res = $conn->query('SELECT * FROM [customers]');
|
$res = $conn->query('SELECT * FROM [customers]');
|
||||||
|
|
||||||
// auto-converts this column to integer
|
// auto-converts this column to integer
|
||||||
$res->setType('customer_id', Dibi\Type::DateTime);
|
$res->setType('customer_id', Dibi\Type::DATETIME);
|
||||||
|
|
||||||
Assert::equal(new Dibi\Row([
|
Assert::equal(new Dibi\Row([
|
||||||
'customer_id' => new Dibi\DateTime('1970-01-01 01:00:01'),
|
'customer_id' => new Dibi\DateTime('1970-01-01 01:00:01'),
|
||||||
|
@@ -24,17 +24,13 @@ Assert::true(isset($row['title']));
|
|||||||
|
|
||||||
|
|
||||||
// missing
|
// missing
|
||||||
Assert::error(
|
Assert::error(function () use ($row) {
|
||||||
fn() => $x = $row->missing,
|
$x = $row->missing;
|
||||||
E_USER_NOTICE,
|
}, E_USER_NOTICE, "Attempt to read missing column 'missing'.");
|
||||||
"Attempt to read missing column 'missing'.",
|
|
||||||
);
|
|
||||||
|
|
||||||
Assert::error(
|
Assert::error(function () use ($row) {
|
||||||
fn() => $x = $row['missing'],
|
$x = $row['missing'];
|
||||||
E_USER_NOTICE,
|
}, E_USER_NOTICE, "Attempt to read missing column 'missing'.");
|
||||||
"Attempt to read missing column 'missing'.",
|
|
||||||
);
|
|
||||||
|
|
||||||
Assert::false(isset($row->missing));
|
Assert::false(isset($row->missing));
|
||||||
Assert::false(isset($row['missing']));
|
Assert::false(isset($row['missing']));
|
||||||
@@ -45,17 +41,13 @@ Assert::same(123, $row['missing'] ?? 123);
|
|||||||
|
|
||||||
|
|
||||||
// suggestions
|
// suggestions
|
||||||
Assert::error(
|
Assert::error(function () use ($row) {
|
||||||
fn() => $x = $row->tilte,
|
$x = $row->tilte;
|
||||||
E_USER_NOTICE,
|
}, E_USER_NOTICE, "Attempt to read missing column 'tilte', did you mean 'title'?");
|
||||||
"Attempt to read missing column 'tilte', did you mean 'title'?",
|
|
||||||
);
|
|
||||||
|
|
||||||
Assert::error(
|
Assert::error(function () use ($row) {
|
||||||
fn() => $x = $row['tilte'],
|
$x = $row['tilte'];
|
||||||
E_USER_NOTICE,
|
}, E_USER_NOTICE, "Attempt to read missing column 'tilte', did you mean 'title'?");
|
||||||
"Attempt to read missing column 'tilte', did you mean 'title'?",
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
// to array
|
// to array
|
||||||
|
@@ -11,59 +11,82 @@ use Tester\Assert;
|
|||||||
require __DIR__ . '/bootstrap.php';
|
require __DIR__ . '/bootstrap.php';
|
||||||
|
|
||||||
$tests = function ($conn) {
|
$tests = function ($conn) {
|
||||||
// Limit and offset
|
$resource = $conn->getDriver()->getResource();
|
||||||
Assert::same(
|
$version = is_resource($resource)
|
||||||
'SELECT 1 OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY',
|
? sqlsrv_server_info($resource)['SQLServerVersion']
|
||||||
$conn->translate('SELECT 1 %ofs %lmt', 10, 10),
|
: $resource->getAttribute(PDO::ATTR_SERVER_VERSION);
|
||||||
);
|
|
||||||
|
|
||||||
// Limit only
|
// MsSQL2012+
|
||||||
Assert::same(
|
if (version_compare($version, '11.0') >= 0) {
|
||||||
'SELECT 1 OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY',
|
// Limit and offset
|
||||||
$conn->translate('SELECT 1 %lmt', 10),
|
Assert::same(
|
||||||
);
|
'SELECT 1 OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY',
|
||||||
|
$conn->translate('SELECT 1 %ofs %lmt', 10, 10),
|
||||||
|
);
|
||||||
|
|
||||||
// Offset only
|
// Limit only
|
||||||
Assert::same(
|
Assert::same(
|
||||||
'SELECT 1 OFFSET 10 ROWS',
|
'SELECT 1 OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY',
|
||||||
$conn->translate('SELECT 1 %ofs', 10),
|
$conn->translate('SELECT 1 %lmt', 10),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Offset invalid
|
// Offset only
|
||||||
Assert::error(
|
Assert::same(
|
||||||
function () use ($conn) {
|
'SELECT 1 OFFSET 10 ROWS',
|
||||||
$conn->translate('SELECT 1 %ofs', -10);
|
$conn->translate('SELECT 1 %ofs', 10),
|
||||||
},
|
);
|
||||||
Dibi\NotSupportedException::class,
|
|
||||||
'Negative offset or limit.',
|
|
||||||
);
|
|
||||||
|
|
||||||
// Limit invalid
|
// Offset invalid
|
||||||
Assert::error(
|
Assert::error(
|
||||||
function () use ($conn) {
|
function () use ($conn) {
|
||||||
$conn->translate('SELECT 1 %lmt', -10);
|
$conn->translate('SELECT 1 %ofs', -10);
|
||||||
},
|
},
|
||||||
Dibi\NotSupportedException::class,
|
Dibi\NotSupportedException::class,
|
||||||
'Negative offset or limit.',
|
'Negative offset or limit.',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Limit invalid, offset valid
|
// Limit invalid
|
||||||
Assert::error(
|
Assert::error(
|
||||||
function () use ($conn) {
|
function () use ($conn) {
|
||||||
$conn->translate('SELECT 1 %ofs %lmt', 10, -10);
|
$conn->translate('SELECT 1 %lmt', -10);
|
||||||
},
|
},
|
||||||
Dibi\NotSupportedException::class,
|
Dibi\NotSupportedException::class,
|
||||||
'Negative offset or limit.',
|
'Negative offset or limit.',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Limit valid, offset invalid
|
// Limit invalid, offset valid
|
||||||
Assert::error(
|
Assert::error(
|
||||||
function () use ($conn) {
|
function () use ($conn) {
|
||||||
$conn->translate('SELECT 1 %ofs %lmt', -10, 10);
|
$conn->translate('SELECT 1 %ofs %lmt', 10, -10);
|
||||||
},
|
},
|
||||||
Dibi\NotSupportedException::class,
|
Dibi\NotSupportedException::class,
|
||||||
'Negative offset or limit.',
|
'Negative offset or limit.',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Limit valid, offset invalid
|
||||||
|
Assert::error(
|
||||||
|
function () use ($conn) {
|
||||||
|
$conn->translate('SELECT 1 %ofs %lmt', -10, 10);
|
||||||
|
},
|
||||||
|
Dibi\NotSupportedException::class,
|
||||||
|
'Negative offset or limit.',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Assert::same(
|
||||||
|
'SELECT TOP (1) * FROM (SELECT 1) t',
|
||||||
|
$conn->translate('SELECT 1 %lmt', 1),
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert::same(
|
||||||
|
'SELECT 1',
|
||||||
|
$conn->translate('SELECT 1 %lmt', -10),
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert::exception(
|
||||||
|
$conn->translate('SELECT 1 %ofs %lmt', 10, 10),
|
||||||
|
Dibi\NotSupportedException::class,
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$conn = new Dibi\Connection($config);
|
$conn = new Dibi\Connection($config);
|
||||||
|
151
tests/dibi/Strict.phpt
Normal file
151
tests/dibi/Strict.phpt
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
<?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));
|
@@ -13,16 +13,13 @@ switch ($config['system']) {
|
|||||||
case 'mysql':
|
case 'mysql':
|
||||||
Assert::equal('10:20:30.0', $translator->formatValue(new DateInterval('PT10H20M30S'), null));
|
Assert::equal('10:20:30.0', $translator->formatValue(new DateInterval('PT10H20M30S'), null));
|
||||||
Assert::equal('-1:00:00.0', $translator->formatValue(DateInterval::createFromDateString('-1 hour'), null));
|
Assert::equal('-1:00:00.0', $translator->formatValue(DateInterval::createFromDateString('-1 hour'), null));
|
||||||
Assert::exception(
|
Assert::exception(function () use ($translator) {
|
||||||
fn() => $translator->formatValue(new DateInterval('P2Y4DT6H8M'), null),
|
$translator->formatValue(new DateInterval('P2Y4DT6H8M'), null);
|
||||||
Dibi\NotSupportedException::class,
|
}, Dibi\NotSupportedException::class, 'Only time interval is supported.');
|
||||||
'Only time interval is supported.',
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Assert::exception(
|
Assert::exception(function () use ($translator) {
|
||||||
fn() => $translator->formatValue(new DateInterval('PT10H20M30S'), null),
|
$translator->formatValue(new DateInterval('PT10H20M30S'), null);
|
||||||
Dibi\Exception::class,
|
}, Dibi\Exception::class);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @phpVersion 8.1
|
||||||
* @dataProvider ../databases.ini
|
* @dataProvider ../databases.ini
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -32,7 +33,7 @@ enum PureEnum
|
|||||||
|
|
||||||
Assert::equal('1', $translator->formatValue(EnumInt::One, null));
|
Assert::equal('1', $translator->formatValue(EnumInt::One, null));
|
||||||
|
|
||||||
Assert::equal(match ($config['system']) {
|
Assert::equal(match ($config['driver']) {
|
||||||
'sqlsrv' => "N'one'",
|
'sqlsrv' => "N'one'",
|
||||||
default => "'one'",
|
default => "'one'",
|
||||||
}, $translator->formatValue(EnumString::One, null));
|
}, $translator->formatValue(EnumString::One, null));
|
||||||
|
@@ -32,7 +32,7 @@ Assert::truthy($conn->fetchSingle('SELECT ? LIKE %like~', "a'a", "a'"));
|
|||||||
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %like~', "b'", "%'"));
|
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %like~', "b'", "%'"));
|
||||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %like~', "%'", "%'"));
|
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %like~', "%'", "%'"));
|
||||||
|
|
||||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %like~', 'a\a', 'a\\'));
|
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %like~', 'a\\a', 'a\\'));
|
||||||
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %like~', 'b\\', '%\\'));
|
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %like~', 'b\\', '%\\'));
|
||||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %like~', '%\\', '%\\'));
|
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %like~', '%\\', '%\\'));
|
||||||
|
|
||||||
@@ -60,9 +60,9 @@ Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like', "a'a", "'a"));
|
|||||||
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %~like', "'b", "'%"));
|
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %~like', "'b", "'%"));
|
||||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like', "'%", "'%"));
|
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like', "'%", "'%"));
|
||||||
|
|
||||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like', 'a\a', '\a'));
|
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like', 'a\\a', '\\a'));
|
||||||
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %~like', '\b', '\%'));
|
Assert::falsey($conn->fetchSingle('SELECT ? LIKE %~like', '\\b', '\\%'));
|
||||||
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like', '\%', '\%'));
|
Assert::truthy($conn->fetchSingle('SELECT ? LIKE %~like', '\\%', '\\%'));
|
||||||
|
|
||||||
|
|
||||||
// contains
|
// contains
|
||||||
|
@@ -91,11 +91,9 @@ Assert::same(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// invalid input
|
// invalid input
|
||||||
$e = Assert::exception(
|
$e = Assert::exception(function () use ($conn) {
|
||||||
fn() => $conn->translate('SELECT %s', (object) [123], ', %m', 123),
|
$conn->translate('SELECT %s', (object) [123], ', %m', 123);
|
||||||
Dibi\Exception::class,
|
}, Dibi\Exception::class, 'SQL translate error: Invalid combination of type stdClass and modifier %s');
|
||||||
'SQL translate error: Invalid combination of type stdClass and modifier %s',
|
|
||||||
);
|
|
||||||
Assert::same('SELECT **Invalid combination of type stdClass and modifier %s** , **Unknown or unexpected modifier %m**', $e->getSql());
|
Assert::same('SELECT **Invalid combination of type stdClass and modifier %s** , **Unknown or unexpected modifier %m**', $e->getSql());
|
||||||
|
|
||||||
Assert::same(
|
Assert::same(
|
||||||
@@ -178,10 +176,9 @@ Assert::same(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if ($config['system'] === 'odbc') {
|
if ($config['system'] === 'odbc') {
|
||||||
Assert::exception(
|
Assert::exception(function () use ($conn) {
|
||||||
fn() => $conn->translate('SELECT * FROM [products] %lmt %ofs', 2, 1),
|
$conn->translate('SELECT * FROM [products] %lmt %ofs', 2, 1);
|
||||||
Dibi\Exception::class,
|
}, Dibi\Exception::class);
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
// with limit = 2, offset = 1
|
// with limit = 2, offset = 1
|
||||||
Assert::same(
|
Assert::same(
|
||||||
@@ -229,11 +226,9 @@ Assert::same(
|
|||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
Assert::exception(
|
Assert::exception(function () use ($conn) {
|
||||||
fn() => $conn->translate('SELECT %s', new DateTime('1212-09-26')),
|
$conn->translate('SELECT %s', new DateTime('1212-09-26'));
|
||||||
Dibi\Exception::class,
|
}, Dibi\Exception::class, 'SQL translate error: Invalid combination of type Dibi\DateTime and modifier %s');
|
||||||
'SQL translate error: Invalid combination of type Dibi\DateTime and modifier %s',
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -273,11 +268,9 @@ if ($config['system'] === 'postgre') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$e = Assert::exception(
|
$e = Assert::exception(function () use ($conn) {
|
||||||
fn() => $conn->translate("SELECT '"),
|
$conn->translate("SELECT '");
|
||||||
Dibi\Exception::class,
|
}, Dibi\Exception::class, 'SQL translate error: Alone quote');
|
||||||
'SQL translate error: Alone quote',
|
|
||||||
);
|
|
||||||
Assert::same('SELECT **Alone quote**', $e->getSql());
|
Assert::same('SELECT **Alone quote**', $e->getSql());
|
||||||
|
|
||||||
Assert::match(
|
Assert::match(
|
||||||
@@ -654,17 +647,13 @@ Assert::same(
|
|||||||
$conn->translate('INSERT INTO [test.*]'),
|
$conn->translate('INSERT INTO [test.*]'),
|
||||||
);
|
);
|
||||||
|
|
||||||
Assert::exception(
|
Assert::exception(function () use ($conn) {
|
||||||
fn() => $conn->translate('INSERT INTO %i', 'ahoj'),
|
$conn->translate('INSERT INTO %i', 'ahoj');
|
||||||
Dibi\Exception::class,
|
}, Dibi\Exception::class, "Expected number, 'ahoj' given.");
|
||||||
"Expected number, 'ahoj' given.",
|
|
||||||
);
|
|
||||||
|
|
||||||
Assert::exception(
|
Assert::exception(function () use ($conn) {
|
||||||
fn() => $conn->translate('INSERT INTO %f', 'ahoj'),
|
$conn->translate('INSERT INTO %f', 'ahoj');
|
||||||
Dibi\Exception::class,
|
}, Dibi\Exception::class, "Expected number, 'ahoj' given.");
|
||||||
"Expected number, 'ahoj' given.",
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
Assert::same(
|
Assert::same(
|
||||||
|
@@ -20,7 +20,7 @@ date_default_timezone_set('Europe/Prague');
|
|||||||
try {
|
try {
|
||||||
$config = Tester\Environment::loadData();
|
$config = Tester\Environment::loadData();
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
$config = parse_ini_file(__DIR__ . '/../databases.ini', process_sections: true);
|
$config = parse_ini_file(__DIR__ . '/../databases.ini', true);
|
||||||
$config = reset($config);
|
$config = reset($config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -15,54 +15,41 @@ $conn = new Dibi\Connection($config);
|
|||||||
$conn->loadFile(__DIR__ . "/data/$config[system].sql");
|
$conn->loadFile(__DIR__ . "/data/$config[system].sql");
|
||||||
|
|
||||||
|
|
||||||
$e = Assert::exception(
|
$e = Assert::exception(function () use ($conn) {
|
||||||
fn() => new Dibi\Connection([
|
new Dibi\Connection([
|
||||||
'driver' => 'mysqli',
|
'driver' => 'mysqli',
|
||||||
'host' => 'localhost',
|
'host' => 'localhost',
|
||||||
'username' => 'unknown',
|
'username' => 'unknown',
|
||||||
'password' => 'unknown',
|
'password' => 'unknown',
|
||||||
]),
|
]);
|
||||||
Dibi\DriverException::class,
|
}, Dibi\DriverException::class);
|
||||||
);
|
|
||||||
|
|
||||||
Assert::null($e->getSql());
|
Assert::null($e->getSql());
|
||||||
|
|
||||||
|
|
||||||
$e = Assert::exception(
|
$e = Assert::exception(function () use ($conn) {
|
||||||
fn() => $conn->query('SELECT'),
|
$conn->query('SELECT');
|
||||||
Dibi\DriverException::class,
|
}, Dibi\DriverException::class, '%a% error in your SQL syntax;%a%', 1064);
|
||||||
'%a% error in your SQL syntax;%a%',
|
|
||||||
1064,
|
|
||||||
);
|
|
||||||
|
|
||||||
Assert::same('SELECT', $e->getSql());
|
Assert::same('SELECT', $e->getSql());
|
||||||
|
|
||||||
|
|
||||||
$e = Assert::exception(
|
$e = Assert::exception(function () use ($conn) {
|
||||||
fn() => $conn->query('INSERT INTO products (product_id, title) VALUES (1, "New")'),
|
$conn->query('INSERT INTO products (product_id, title) VALUES (1, "New")');
|
||||||
Dibi\UniqueConstraintViolationException::class,
|
}, Dibi\UniqueConstraintViolationException::class, "%a?%Duplicate entry '1' for key '%a?%PRIMARY'", 1062);
|
||||||
"%a?%Duplicate entry '1' for key '%a?%PRIMARY'",
|
|
||||||
1062,
|
|
||||||
);
|
|
||||||
|
|
||||||
Assert::same("INSERT INTO products (product_id, title) VALUES (1, 'New')", $e->getSql());
|
Assert::same("INSERT INTO products (product_id, title) VALUES (1, 'New')", $e->getSql());
|
||||||
|
|
||||||
|
|
||||||
$e = Assert::exception(
|
$e = Assert::exception(function () use ($conn) {
|
||||||
fn() => $conn->query('INSERT INTO products (title) VALUES (NULL)'),
|
$conn->query('INSERT INTO products (title) VALUES (NULL)');
|
||||||
Dibi\NotNullConstraintViolationException::class,
|
}, Dibi\NotNullConstraintViolationException::class, "%a?%Column 'title' cannot be null", 1048);
|
||||||
"%a?%Column 'title' cannot be null",
|
|
||||||
1048,
|
|
||||||
);
|
|
||||||
|
|
||||||
Assert::same('INSERT INTO products (title) VALUES (NULL)', $e->getSql());
|
Assert::same('INSERT INTO products (title) VALUES (NULL)', $e->getSql());
|
||||||
|
|
||||||
|
|
||||||
$e = Assert::exception(
|
$e = Assert::exception(function () use ($conn) {
|
||||||
fn() => $conn->query('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)'),
|
$conn->query('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)');
|
||||||
Dibi\ForeignKeyConstraintViolationException::class,
|
}, Dibi\ForeignKeyConstraintViolationException::class, '%a% a foreign key constraint fails %a%', 1452);
|
||||||
'%a% a foreign key constraint fails %a%',
|
|
||||||
1452,
|
|
||||||
);
|
|
||||||
|
|
||||||
Assert::same('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)', $e->getSql());
|
Assert::same('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)', $e->getSql());
|
||||||
|
@@ -15,40 +15,29 @@ $conn = new Dibi\Connection($config);
|
|||||||
$conn->loadFile(__DIR__ . "/data/$config[system].sql");
|
$conn->loadFile(__DIR__ . "/data/$config[system].sql");
|
||||||
|
|
||||||
|
|
||||||
$e = Assert::exception(
|
$e = Assert::exception(function () use ($conn) {
|
||||||
fn() => $conn->query('SELECT INTO'),
|
$conn->query('SELECT INTO');
|
||||||
Dibi\DriverException::class,
|
}, Dibi\DriverException::class, '%a?%syntax error %A%');
|
||||||
'%a?%syntax error %A%',
|
|
||||||
);
|
|
||||||
|
|
||||||
Assert::same('SELECT INTO', $e->getSql());
|
Assert::same('SELECT INTO', $e->getSql());
|
||||||
|
|
||||||
|
|
||||||
$e = Assert::exception(
|
$e = Assert::exception(function () use ($conn) {
|
||||||
fn() => $conn->query('INSERT INTO products (product_id, title) VALUES (1, "New")'),
|
$conn->query('INSERT INTO products (product_id, title) VALUES (1, "New")');
|
||||||
Dibi\UniqueConstraintViolationException::class,
|
}, Dibi\UniqueConstraintViolationException::class, '%a% violates unique constraint %A%', '23505');
|
||||||
'%a% violates unique constraint %A%',
|
|
||||||
'23505',
|
|
||||||
);
|
|
||||||
|
|
||||||
Assert::same("INSERT INTO products (product_id, title) VALUES (1, 'New')", $e->getSql());
|
Assert::same("INSERT INTO products (product_id, title) VALUES (1, 'New')", $e->getSql());
|
||||||
|
|
||||||
|
|
||||||
$e = Assert::exception(
|
$e = Assert::exception(function () use ($conn) {
|
||||||
fn() => $conn->query('INSERT INTO products (title) VALUES (NULL)'),
|
$conn->query('INSERT INTO products (title) VALUES (NULL)');
|
||||||
Dibi\NotNullConstraintViolationException::class,
|
}, Dibi\NotNullConstraintViolationException::class, '%a?%null value in column "title"%a%violates not-null constraint%A?%', '23502');
|
||||||
'%a?%null value in column "title"%a%violates not-null constraint%A?%',
|
|
||||||
'23502',
|
|
||||||
);
|
|
||||||
|
|
||||||
Assert::same('INSERT INTO products (title) VALUES (NULL)', $e->getSql());
|
Assert::same('INSERT INTO products (title) VALUES (NULL)', $e->getSql());
|
||||||
|
|
||||||
|
|
||||||
$e = Assert::exception(
|
$e = Assert::exception(function () use ($conn) {
|
||||||
fn() => $conn->query('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)'),
|
$conn->query('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)');
|
||||||
Dibi\ForeignKeyConstraintViolationException::class,
|
}, Dibi\ForeignKeyConstraintViolationException::class, '%a% violates foreign key constraint %A%', '23503');
|
||||||
'%a% violates foreign key constraint %A%',
|
|
||||||
'23503',
|
|
||||||
);
|
|
||||||
|
|
||||||
Assert::same('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)', $e->getSql());
|
Assert::same('INSERT INTO orders (customer_id, product_id, amount) VALUES (100, 1, 1)', $e->getSql());
|
||||||
|
@@ -15,32 +15,23 @@ $conn = new Dibi\Connection($config);
|
|||||||
$conn->loadFile(__DIR__ . "/data/$config[system].sql");
|
$conn->loadFile(__DIR__ . "/data/$config[system].sql");
|
||||||
|
|
||||||
|
|
||||||
$e = Assert::exception(
|
$e = Assert::exception(function () use ($conn) {
|
||||||
fn() => $conn->query('SELECT'),
|
$conn->query('SELECT');
|
||||||
Dibi\DriverException::class,
|
}, Dibi\DriverException::class, '%a%', 1);
|
||||||
'%a%',
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
|
|
||||||
Assert::same('SELECT', $e->getSql());
|
Assert::same('SELECT', $e->getSql());
|
||||||
|
|
||||||
|
|
||||||
$e = Assert::exception(
|
$e = Assert::exception(function () use ($conn) {
|
||||||
fn() => $conn->query('INSERT INTO products (product_id, title) VALUES (1, "New")'),
|
$conn->query('INSERT INTO products (product_id, title) VALUES (1, "New")');
|
||||||
Dibi\UniqueConstraintViolationException::class,
|
}, Dibi\UniqueConstraintViolationException::class, null, 19);
|
||||||
null,
|
|
||||||
19,
|
|
||||||
);
|
|
||||||
|
|
||||||
Assert::same("INSERT INTO products (product_id, title) VALUES (1, 'New')", $e->getSql());
|
Assert::same("INSERT INTO products (product_id, title) VALUES (1, 'New')", $e->getSql());
|
||||||
|
|
||||||
|
|
||||||
$e = Assert::exception(
|
$e = Assert::exception(function () use ($conn) {
|
||||||
fn() => $conn->query('INSERT INTO products (title) VALUES (NULL)'),
|
$conn->query('INSERT INTO products (title) VALUES (NULL)');
|
||||||
Dibi\NotNullConstraintViolationException::class,
|
}, Dibi\NotNullConstraintViolationException::class, null, 19);
|
||||||
null,
|
|
||||||
19,
|
|
||||||
);
|
|
||||||
|
|
||||||
Assert::same('INSERT INTO products (title) VALUES (NULL)', $e->getSql());
|
Assert::same('INSERT INTO products (title) VALUES (NULL)', $e->getSql());
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user