1
0
mirror of https://github.com/dg/dibi.git synced 2025-09-02 10:32:33 +02:00

Compare commits

..

73 Commits

Author SHA1 Message Date
David Grudl
97053089e0 Released version 5.0.2 2024-09-03 03:18:11 +02:00
Lukáš Kotržena
2c7b35c29d Result::normalize() Fixed normalization of "-." numbers 2024-09-03 03:16:52 +02:00
Marek Bartoš
c04d2197e3 Translator: fixed numeric column formatting 2024-09-03 03:10:05 +02:00
Matěj Koubík
d342d8d78f DibiExtension3: fixed schema 2024-09-03 03:10:05 +02:00
David Grudl
7d8c39f42a cs 2024-09-03 03:00:38 +02:00
David Grudl
29b58d64dd PdoDriver: applied #332 #287 2024-09-03 03:00:38 +02:00
David Grudl
0a32bb5bdf support for PHP 8.4 2024-09-03 03:00:38 +02:00
David Grudl
d707b4ba0e PascalCase constants 2024-09-03 03:00:38 +02:00
David Grudl
490cf143ba GitHub actions fixed
- https://learn.microsoft.com/en-us/answers/questions/1853144/error-failed-to-initialize-container-mcr-microsoft
- https://learn.microsoft.com/en-us/sql/tools/sqlcmd/sqlcmd-utility?view=sql-server-ver16&tabs=go%2Cwindows&pivots=cs1-bash
2024-09-03 03:00:38 +02:00
stanley89
12ffa0ffd1 Tracy\Panel: fixed type error 2024-09-02 23:53:15 +02:00
David Grudl
23f65ef837 Revert "SqliteDriver: disables exceptions (is enabled since PHP 8.3)"
This reverts commit bb1f7d4b93.
2024-09-02 23:53:15 +02:00
David Grudl
86a71dde28 Released version 5.0.1 2023-11-25 14:08:47 +01:00
David Grudl
bb1f7d4b93 SqliteDriver: disables exceptions (is enabled since PHP 8.3) 2023-11-25 14:08:47 +01:00
David Grudl
680026747e added DibiExtension3 2023-11-25 14:08:47 +01:00
Jan Rössler
7ca47508cb PostgreReflector: detect IDENTITY columns as autoincrement 2023-11-05 20:38:26 +01:00
Jan Rössler
beba7b3592 PostgreReflector: fix autoincrement column detection 2023-11-05 20:38:26 +01:00
David Grudl
6cc7ce8e44 used PhpStorm Language attribute 2023-11-05 20:38:25 +01:00
Petr Hubík
92b8e6077e fix: PDO::errorInfo() can return NULL as a code, but Exception does not accept NULL code 2023-09-29 15:55:05 +02:00
David Grudl
e45638eab4 cs 2023-09-29 15:55:05 +02:00
Marek Bartoš
8564217bc1 Fluent: execute() has conditional return type 2023-09-05 12:40:51 +02:00
David Grudl
82c45c3076 Released version 5.0.0 2023-08-09 16:38:02 +02:00
Miloslav Hůla
df45bd3553 added object translators (#420)
The Translator is now capable to translate objects into Expression via object translators registered by the Connection.
2023-08-09 16:35:02 +02:00
Miloslav Hůla
fe22e230ce tests: remove dependency on dibi_test database name 2023-08-09 16:35:02 +02:00
David Grudl
8257532630 used native PHP 8 functions 2023-08-09 16:35:02 +02:00
David Grudl
08dfc37492 added PHP 8 typehints 2023-08-09 16:33:28 +02:00
David Grudl
7acef0c34b added property typehints 2023-08-09 16:33:28 +02:00
David Grudl
b01d97ac86 removed Dibi\Strict 2023-08-09 16:33:28 +02:00
David Grudl
8915b0343c removed support for PHP 7 2023-08-09 16:33:28 +02:00
David Grudl
d1a3362321 coding style 2023-08-09 16:33:28 +02:00
David Grudl
b6ead80202 composer: updated dependencies 2023-08-09 16:15:31 +02:00
David Grudl
a640ac2a8f requires PHP 8.0 2023-08-09 16:15:31 +02:00
David Grudl
87e702d1fc opened 5.0-dev 2023-08-09 16:15:31 +02:00
David Grudl
cb0cf4ba2f Released version 4.2.8 2023-08-09 16:15:07 +02:00
David Grudl
8e7df8374b drivers: removed auto-free feature 2023-08-09 16:15:07 +02:00
Marek Bartoš
848ac76fed Fluent: improved phpDoc 2023-08-09 16:15:07 +02:00
David Grudl
a0f2ca2fca typo 2023-08-09 16:15:04 +02:00
David Grudl
cf14987b42 cs 2023-08-05 19:56:38 +02:00
David Grudl
01c7ab63e3 tested in PHP 8.3 2023-08-05 19:50:57 +02:00
David Grudl
520119740d updated .gitattributes 2022-12-21 02:06:10 +01:00
Marek Štípek
7fa05f381b Event: detecting source without filesystem check (#428)
Co-authored-by: Marek Stipek <stipek@shoptet.cz>
2022-11-18 04:40:51 +01:00
David Grudl
3a962de553 cs 2022-10-13 03:58:51 +02:00
David Grudl
96e370b8fe updated github workflow 2022-10-13 03:51:11 +02:00
Jirka Hrazdil
ec0455ae00 Panel: typo (#421) 2022-09-06 04:32:10 +02:00
David Grudl
b61737311e support for PHP 8.2 2022-09-06 04:26:58 +02:00
David Grudl
9d5d430d3d Released version 4.2.6 2022-01-19 18:38:15 +01:00
Miloslav Hůla
04bb5ede3d Translator: convert BackedEnum to scalar
also closes (#412)
2022-01-18 16:15:30 +01:00
Andrej Rypo
7d82ce2ff6 MySqliDriver::getResource() fixed access to resource being closed prior to the call in PHP 8 (#410) 2021-12-12 18:00:04 +01:00
David Grudl
82150d120d cs nullable typehints 2021-12-12 17:46:56 +01:00
David Grudl
0f045c0986 cs whitespace 2021-12-12 03:52:44 +01:00
David Grudl
5646884899 cs 2021-12-12 03:52:44 +01:00
David Grudl
af33a354d6 removed ecs.php 2021-12-12 03:52:44 +01:00
David Grudl
a0c86747dc GitHub Actions updates 2021-12-06 19:05:06 +01:00
David Grudl
d70e274244 Released version 4.2.5 2021-11-29 13:48:16 +01:00
David Grudl
e05eb01233 fixed PHP 7.2 compatibility 2021-11-28 15:09:41 +01:00
David Grudl
2ac618ffff Date 0000-01-01 is valid [Closes #402] 2021-11-24 18:34:20 +01:00
Milan Otáhal
1881fea0e5 Profiler is not used in CLI mode 2021-11-24 18:34:20 +01:00
Jan Rössler
cb82357cfb Helpers::detectType(): detect PostgreSQL range types as Type::TEXT 2021-11-24 18:34:20 +01:00
Jan Rössler
0a29fcb502 PostgreReflector: fix reflection of matview columns on PostgreSQL 12+ 2021-11-24 18:34:20 +01:00
David Grudl
8270b7c1c3 support for PHP 8.1 2021-11-24 18:34:20 +01:00
David Grudl
73e16eb1a3 Released version 4.2.3 2021-07-23 10:49:27 +02:00
David Grudl
0b394a993d SqlsrvDriver: fixed after 40ad77cf [Closes #391][Closes #392] 2021-06-30 12:47:45 +02:00
David Grudl
df3edee70b removed travis 2021-04-23 20:54:50 +02:00
David Grudl
245da39a9f added github workflows 2021-04-23 20:53:38 +02:00
David Grudl
3df64fc3b3 fixed tests 2021-04-23 20:48:29 +02:00
David Grudl
95c3f72a17 Released version 4.2.2 2021-04-21 13:56:00 +02:00
David Grudl
d71caf0c75 Row: fixed ?? usage 2021-04-21 13:54:59 +02:00
Miloslav Hůla
3066fea2aa Connection: begin(), commit() & rollback() calls are forbidden in transaction() 2021-04-05 16:50:15 +02:00
Miloslav Hůla
b00e556289 Connection::transtaction() call can be nested 2021-04-05 16:50:15 +02:00
Miloslav Hůla
877dffd460 tests: use test() helper 2021-04-05 13:37:16 +02:00
Miloslav Hůla
7049949b14 Connection::transaction(): pass self as a callback argument 2021-04-05 13:37:16 +02:00
Miloslav Hůla
771e846a62 tests: Sqlite3 driver fix 2021-04-05 13:37:16 +02:00
David Grudl
4e056c52dd updated appveyor.yml 2021-03-10 16:53:34 +01:00
David Grudl
40ad77cf5f SqlsrvDriver: workaround for "Driver's SQLSetConnectAttr failed on ODBC <=13" bug 2021-03-10 16:42:33 +01:00
113 changed files with 2248 additions and 1916 deletions

4
.gitattributes vendored
View File

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

31
.github/workflows/coding-style.yml vendored Normal file
View File

@@ -0,0 +1,31 @@
name: Coding Style
on: [push, pull_request]
jobs:
nette_cc:
name: Nette Code Checker
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: 8.0
coverage: none
- run: composer create-project nette/code-checker temp/code-checker ^3 --no-progress
- run: php temp/code-checker/code-checker --strict-types --no-progress --ignore "tests/*/fixtures"
nette_cs:
name: Nette Coding Standard
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: 8.0
coverage: none
- run: composer create-project nette/coding-standard temp/coding-standard ^3 --no-progress
- run: php temp/coding-standard/ecs check

21
.github/workflows/static-analysis.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: Static Analysis (only informative)
on:
push:
branches:
- master
jobs:
phpstan:
name: PHPStan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: 8.0
coverage: none
- run: composer install --no-progress --prefer-dist
- run: composer phpstan -- --no-progress
continue-on-error: true # is only informative

120
.github/workflows/tests.yml vendored Normal file
View File

@@ -0,0 +1,120 @@
name: Tests
on: [push, pull_request]
env:
php-extensions: mbstring, intl, mysqli, pgsql, sqlsrv-5.12.0, pdo_sqlsrv-5.12.0
php-tools: "composer:v2, pecl"
jobs:
tests:
runs-on: ubuntu-latest
strategy:
matrix:
php: ['8.0', '8.1', '8.2', '8.3', '8.4']
fail-fast: false
name: PHP ${{ matrix.php }} tests
services:
mysql57:
image: mysql:5.7
env:
MYSQL_DATABASE: dibi_test
MYSQL_ROOT_PASSWORD: root
ports:
- 3306:3306
options: >-
--health-cmd "mysqladmin ping -ppass"
--health-interval 10s
--health-start-period 10s
--health-timeout 5s
--health-retries 10
mysql80:
image: mysql:8.0
ports:
- 3307:3306
options: >-
--health-cmd="mysqladmin ping -ppass"
--health-interval=10s
--health-timeout=5s
--health-retries=5
-e MYSQL_ROOT_PASSWORD=root
-e MYSQL_DATABASE=dibi_test
postgres96:
image: postgres:9.6
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: dibi_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
postgres13:
image: postgres:13
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: dibi_test
ports:
- 5433:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
mssql:
image: mcr.microsoft.com/mssql/server:latest
env:
ACCEPT_EULA: Y
SA_PASSWORD: YourStrong!Passw0rd
MSSQL_PID: Developer
ports:
- 1433:1433
options: >-
--name=mssql
--health-cmd "/opt/mssql-tools18/bin/sqlcmd -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'SELECT 1' -N -C"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: ${{ env.php-extensions }}
tools: ${{ env.php-tools }}
coverage: none
- name: Create databases.ini
run: cp ./tests/databases.github.ini ./tests/databases.ini
- 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: composer install --no-progress --prefer-dist
- run: vendor/bin/tester -p phpdbg tests -s -C --coverage ./coverage.xml --coverage-src ./src
- if: failure()
uses: actions/upload-artifact@v3
with:
name: output
path: tests/**/output
- name: Save Code Coverage
if: ${{ matrix.php == '8.0' }}
env:
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.4.3/php-coveralls.phar
php php-coveralls.phar --verbose --config tests/.coveralls.yml

View File

@@ -1,76 +0,0 @@
language: php
php:
- 7.2
- 7.3
- 7.4
- 8.0snapshot
services:
- mysql
- postgresql
before_install:
# turn off XDebug
- phpenv config-rm xdebug.ini || return 0
# Create databases.ini
- cp ./tests/databases.travis.ini ./tests/databases.ini
# Create Postgre database
- psql -c 'CREATE DATABASE dibi_test' -U postgres
install:
- travis_retry composer install --no-progress --prefer-dist
script:
- vendor/bin/tester tests -s
after_failure:
# Print *.actual content
- for i in $(find tests -name \*.actual); do echo "--- $i"; cat $i; echo; echo; done
jobs:
include:
- name: Nette Code Checker
install:
- travis_retry composer create-project nette/code-checker temp/code-checker ^3 --no-progress
script:
- php temp/code-checker/code-checker --strict-types
- name: Nette Coding Standard
php: 7.4
install:
- travis_retry composer create-project nette/coding-standard temp/coding-standard ^3 --no-progress
script:
- php temp/coding-standard/ecs check src tests
- stage: Static Analysis (informative)
php: 7.4
script:
- composer run-script phpstan
- stage: Code Coverage
php: 7.4
script:
- vendor/bin/tester -p phpdbg tests -s --coverage ./coverage.xml --coverage-src ./src
after_script:
- wget https://github.com/satooshi/php-coveralls/releases/download/v1.0.1/coveralls.phar
- php coveralls.phar --verbose --config tests/.coveralls.yml
allow_failures:
- stage: Static Analysis (informative)
- stage: Code Coverage
dist: xenial
cache:
directories:
- $HOME/.composer/cache
notifications:
email: false

View File

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

View File

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

21
ecs.php
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
========================================================= =========================================================
[![Downloads this Month](https://img.shields.io/packagist/dm/dibi/dibi.svg)](https://packagist.org/packages/dibi/dibi) [![Downloads this Month](https://img.shields.io/packagist/dm/dibi/dibi.svg)](https://packagist.org/packages/dibi/dibi)
[![Build Status](https://travis-ci.org/dg/dibi.svg?branch=master)](https://travis-ci.org/dg/dibi) [![Tests](https://github.com/dg/dibi/workflows/Tests/badge.svg?branch=master)](https://github.com/dg/dibi/actions)
[![Build Status Windows](https://ci.appveyor.com/api/projects/status/github/dg/dibi?branch=master&svg=true)](https://ci.appveyor.com/project/dg/dibi/branch/master) [![Build Status Windows](https://ci.appveyor.com/api/projects/status/github/dg/dibi?branch=master&svg=true)](https://ci.appveyor.com/project/dg/dibi/branch/master)
[![Latest Stable Version](https://poser.pugx.org/dibi/dibi/v/stable)](https://github.com/dg/dibi/releases) [![Latest Stable Version](https://poser.pugx.org/dibi/dibi/v/stable)](https://github.com/dg/dibi/releases)
[![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/dg/dibi/blob/master/license.md) [![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/dg/dibi/blob/master/license.md)
@@ -34,7 +34,7 @@ Install Dibi via Composer:
composer require dibi/dibi composer require dibi/dibi
``` ```
The Dibi 4.2 requires PHP version 7.2 and supports PHP up to 8.0. The Dibi 5.0 requires PHP version 8.0 and supports PHP up to 8.4.
Usage Usage
@@ -341,7 +341,7 @@ $database->query('INSERT INTO users', [
There are three methods for dealing with transactions: There are three methods for dealing with transactions:
```php ```php
$database->beginTransaction(); $database->begin();
$database->commit(); $database->commit();
@@ -608,7 +608,7 @@ $database->query("UPDATE [:blog:items] SET [text]='Hello World'");
Dibi automatically detects the types of query columns and converts fields them to native PHP types. We can also specify the type manually. You can find the possible types in the `Dibi\Type` class. Dibi automatically detects the types of query columns and converts fields them to native PHP types. We can also specify the type manually. You can find the possible types in the `Dibi\Type` class.
```php ```php
$result->setType('id', Dibi\Type::INTEGER); // id will be integer $result->setType('id', Dibi\Type::Integer); // id will be integer
$row = $result->fetch(); $row = $result->fetch();
is_int($row->id) // true is_int($row->id) // true
@@ -639,7 +639,7 @@ In the configuration file, we will register the DI extensions and add the `dibi`
```neon ```neon
extensions: extensions:
dibi: Dibi\Bridges\Nette\DibiExtension22 dibi: Dibi\Bridges\Nette\DibiExtension3
dibi: dibi:
host: localhost host: localhost

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,6 +9,7 @@ declare(strict_types=1);
namespace Dibi; namespace Dibi;
use JetBrains\PhpStorm\Language;
use Traversable; use Traversable;
@@ -20,25 +21,20 @@ use Traversable;
*/ */
class Connection implements IConnection class Connection implements IConnection
{ {
use Strict; /** function (Event $event); Occurs after query is executed */
public ?array $onEvent = [];
/** @var array of function (Event $event); Occurs after query is executed */ private array $config;
public $onEvent = [];
/** @var array Current connection configuration */
private $config;
/** @var string[] resultset formats */ /** @var string[] resultset formats */
private $formats; private array $formats;
private ?Driver $driver = null;
private ?Translator $translator = null;
/** @var Driver|null */ /** @var array<string, callable(object): Expression | null> */
private $driver; private array $translators = [];
private bool $sortTranslators = false;
/** @var Translator|null */ private HashMap $substitutes;
private $translator; private int $transactionDepth = 0;
/** @var HashMap Substitutes for identifiers */
private $substitutes;
/** /**
@@ -66,22 +62,22 @@ class Connection implements IConnection
* - onConnect (array) => list of SQL queries to execute (by Connection::query()) after connection is established * - onConnect (array) => list of SQL queries to execute (by Connection::query()) after connection is established
* @throws Exception * @throws Exception
*/ */
public function __construct(array $config, string $name = null) public function __construct(array $config, ?string $name = null)
{ {
Helpers::alias($config, 'username', 'user'); Helpers::alias($config, 'username', 'user');
Helpers::alias($config, 'password', 'pass'); Helpers::alias($config, 'password', 'pass');
Helpers::alias($config, 'host', 'hostname'); Helpers::alias($config, 'host', 'hostname');
Helpers::alias($config, 'result|formatDate', 'resultDate'); Helpers::alias($config, 'result|formatDate', 'resultDate');
Helpers::alias($config, 'result|formatDateTime', 'resultDateTime'); Helpers::alias($config, 'result|formatDateTime', 'resultDateTime');
$config['driver'] = $config['driver'] ?? 'mysqli'; $config['driver'] ??= 'mysqli';
$config['name'] = $name; $config['name'] = $name;
$this->config = $config; $this->config = $config;
$this->formats = [ $this->formats = [
Type::DATE => $this->config['result']['formatDate'], Type::Date => $this->config['result']['formatDate'],
Type::DATETIME => $this->config['result']['formatDateTime'], Type::DateTime => $this->config['result']['formatDateTime'],
Type::JSON => $this->config['result']['formatJson'] ?? 'array', Type::JSON => $this->config['result']['formatJson'] ?? 'array',
Type::TIME_INTERVAL => $this->config['result']['formatTimeInterval'] ?? null, Type::TimeInterval => $this->config['result']['formatTimeInterval'] ?? null,
]; ];
// profiler // profiler
@@ -91,7 +87,7 @@ class Connection implements IConnection
$this->onEvent[] = [new Loggers\FileLogger($config['profiler']['file'], $filter, $errorsOnly), 'logEvent']; $this->onEvent[] = [new Loggers\FileLogger($config['profiler']['file'], $filter, $errorsOnly), 'logEvent'];
} }
$this->substitutes = new HashMap(function (string $expr) { return ":$expr:"; }); $this->substitutes = new HashMap(fn(string $expr) => ":$expr:");
if (!empty($config['substitutes'])) { if (!empty($config['substitutes'])) {
foreach ($config['substitutes'] as $key => $value) { foreach ($config['substitutes'] as $key => $value) {
$this->substitutes->$key = $value; $this->substitutes->$key = $value;
@@ -148,16 +144,17 @@ class Connection implements IConnection
if ($event) { if ($event) {
$this->onEvent($event->done()); $this->onEvent($event->done());
} }
if (isset($this->config['onConnect'])) { if (isset($this->config['onConnect'])) {
foreach ($this->config['onConnect'] as $sql) { foreach ($this->config['onConnect'] as $sql) {
$this->query($sql); $this->query($sql);
} }
} }
} catch (DriverException $e) { } catch (DriverException $e) {
if ($event) { if ($event) {
$this->onEvent($event->done($e)); $this->onEvent($event->done($e));
} }
throw $e; throw $e;
} }
} }
@@ -187,9 +184,8 @@ class Connection implements IConnection
/** /**
* Returns configuration variable. If no $key is passed, returns the entire array. * Returns configuration variable. If no $key is passed, returns the entire array.
* @see self::__construct * @see self::__construct
* @return mixed
*/ */
final public function getConfig(string $key = null, $default = null) final public function getConfig(?string $key = null, $default = null): mixed
{ {
return $key === null return $key === null
? $this->config ? $this->config
@@ -205,16 +201,16 @@ class Connection implements IConnection
if (!$this->driver) { if (!$this->driver) {
$this->connect(); $this->connect();
} }
return $this->driver; return $this->driver;
} }
/** /**
* Generates (translates) and executes SQL query. * Generates (translates) and executes SQL query.
* @param mixed ...$args
* @throws Exception * @throws Exception
*/ */
final public function query(...$args): Result final public function query(#[Language('GenericSQL')] mixed ...$args): Result
{ {
return $this->nativeQuery($this->translate(...$args)); return $this->nativeQuery($this->translate(...$args));
} }
@@ -222,23 +218,22 @@ class Connection implements IConnection
/** /**
* Generates SQL query. * Generates SQL query.
* @param mixed ...$args
* @throws Exception * @throws Exception
*/ */
final public function translate(...$args): string final public function translate(#[Language('GenericSQL')] mixed ...$args): string
{ {
if (!$this->driver) { if (!$this->driver) {
$this->connect(); $this->connect();
} }
return (clone $this->translator)->translate($args); return (clone $this->translator)->translate($args);
} }
/** /**
* Generates and prints SQL query. * Generates and prints SQL query.
* @param mixed ...$args
*/ */
final public function test(...$args): bool final public function test(#[Language('GenericSQL')] mixed ...$args): bool
{ {
try { try {
Helpers::dump($this->translate(...$args)); Helpers::dump($this->translate(...$args));
@@ -248,8 +243,9 @@ class Connection implements IConnection
if ($e->getSql()) { if ($e->getSql()) {
Helpers::dump($e->getSql()); Helpers::dump($e->getSql());
} else { } else {
echo get_class($e) . ': ' . $e->getMessage() . (PHP_SAPI === 'cli' ? "\n" : '<br>'); echo $e::class . ': ' . $e->getMessage() . (PHP_SAPI === 'cli' ? "\n" : '<br>');
} }
return false; return false;
} }
} }
@@ -257,10 +253,9 @@ class Connection implements IConnection
/** /**
* Generates (translates) and returns SQL query as DataSource. * Generates (translates) and returns SQL query as DataSource.
* @param mixed ...$args
* @throws Exception * @throws Exception
*/ */
final public function dataSource(...$args): DataSource final public function dataSource(#[Language('GenericSQL')] mixed ...$args): DataSource
{ {
return new DataSource($this->translate(...$args), $this); return new DataSource($this->translate(...$args), $this);
} }
@@ -270,7 +265,7 @@ class Connection implements IConnection
* Executes the SQL query. * Executes the SQL query.
* @throws Exception * @throws Exception
*/ */
final public function nativeQuery(string $sql): Result final public function nativeQuery(#[Language('SQL')] string $sql): Result
{ {
if (!$this->driver) { if (!$this->driver) {
$this->connect(); $this->connect();
@@ -285,6 +280,7 @@ class Connection implements IConnection
if ($event) { if ($event) {
$this->onEvent($event->done($e)); $this->onEvent($event->done($e));
} }
throw $e; throw $e;
} }
@@ -292,6 +288,7 @@ class Connection implements IConnection
if ($event) { if ($event) {
$this->onEvent($event->done($res)); $this->onEvent($event->done($res));
} }
return $res; return $res;
} }
@@ -305,10 +302,12 @@ class Connection implements IConnection
if (!$this->driver) { if (!$this->driver) {
$this->connect(); $this->connect();
} }
$rows = $this->driver->getAffectedRows(); $rows = $this->driver->getAffectedRows();
if ($rows === null || $rows < 0) { if ($rows === null || $rows < 0) {
throw new Exception('Cannot retrieve number of affected rows.'); throw new Exception('Cannot retrieve number of affected rows.');
} }
return $rows; return $rows;
} }
@@ -317,15 +316,17 @@ class Connection implements IConnection
* Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query. * Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
* @throws Exception * @throws Exception
*/ */
public function getInsertId(string $sequence = null): int public function getInsertId(?string $sequence = null): int
{ {
if (!$this->driver) { if (!$this->driver) {
$this->connect(); $this->connect();
} }
$id = $this->driver->getInsertId($sequence); $id = $this->driver->getInsertId($sequence);
if ($id === null) { if ($id === null) {
throw new Exception('Cannot retrieve last generated ID.'); throw new Exception('Cannot retrieve last generated ID.');
} }
return $id; return $id;
} }
@@ -333,22 +334,27 @@ class Connection implements IConnection
/** /**
* Begins a transaction (if supported). * Begins a transaction (if supported).
*/ */
public function begin(string $savepoint = null): void public function begin(?string $savepoint = null): void
{ {
if ($this->transactionDepth !== 0) {
throw new \LogicException(__METHOD__ . '() call is forbidden inside a transaction() callback');
}
if (!$this->driver) { if (!$this->driver) {
$this->connect(); $this->connect();
} }
$event = $this->onEvent ? new Event($this, Event::BEGIN, $savepoint) : null; $event = $this->onEvent ? new Event($this, Event::BEGIN, $savepoint) : null;
try { try {
$this->driver->begin($savepoint); $this->driver->begin($savepoint);
if ($event) { if ($event) {
$this->onEvent($event->done()); $this->onEvent($event->done());
} }
} catch (DriverException $e) { } catch (DriverException $e) {
if ($event) { if ($event) {
$this->onEvent($event->done($e)); $this->onEvent($event->done($e));
} }
throw $e; throw $e;
} }
} }
@@ -357,22 +363,27 @@ class Connection implements IConnection
/** /**
* Commits statements in a transaction. * Commits statements in a transaction.
*/ */
public function commit(string $savepoint = null): void public function commit(?string $savepoint = null): void
{ {
if ($this->transactionDepth !== 0) {
throw new \LogicException(__METHOD__ . '() call is forbidden inside a transaction() callback');
}
if (!$this->driver) { if (!$this->driver) {
$this->connect(); $this->connect();
} }
$event = $this->onEvent ? new Event($this, Event::COMMIT, $savepoint) : null; $event = $this->onEvent ? new Event($this, Event::COMMIT, $savepoint) : null;
try { try {
$this->driver->commit($savepoint); $this->driver->commit($savepoint);
if ($event) { if ($event) {
$this->onEvent($event->done()); $this->onEvent($event->done());
} }
} catch (DriverException $e) { } catch (DriverException $e) {
if ($event) { if ($event) {
$this->onEvent($event->done($e)); $this->onEvent($event->done($e));
} }
throw $e; throw $e;
} }
} }
@@ -381,40 +392,55 @@ class Connection implements IConnection
/** /**
* Rollback changes in a transaction. * Rollback changes in a transaction.
*/ */
public function rollback(string $savepoint = null): void public function rollback(?string $savepoint = null): void
{ {
if ($this->transactionDepth !== 0) {
throw new \LogicException(__METHOD__ . '() call is forbidden inside a transaction() callback');
}
if (!$this->driver) { if (!$this->driver) {
$this->connect(); $this->connect();
} }
$event = $this->onEvent ? new Event($this, Event::ROLLBACK, $savepoint) : null; $event = $this->onEvent ? new Event($this, Event::ROLLBACK, $savepoint) : null;
try { try {
$this->driver->rollback($savepoint); $this->driver->rollback($savepoint);
if ($event) { if ($event) {
$this->onEvent($event->done()); $this->onEvent($event->done());
} }
} catch (DriverException $e) { } catch (DriverException $e) {
if ($event) { if ($event) {
$this->onEvent($event->done($e)); $this->onEvent($event->done($e));
} }
throw $e; throw $e;
} }
} }
/** public function transaction(callable $callback): mixed
* @return mixed
*/
public function transaction(callable $callback)
{ {
$this->begin(); if ($this->transactionDepth === 0) {
$this->begin();
}
$this->transactionDepth++;
try { try {
$res = $callback(); $res = $callback($this);
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->rollback(); $this->transactionDepth--;
if ($this->transactionDepth === 0) {
$this->rollback();
}
throw $e; throw $e;
} }
$this->commit();
$this->transactionDepth--;
if ($this->transactionDepth === 0) {
$this->commit();
}
return $res; return $res;
} }
@@ -458,6 +484,7 @@ class Connection implements IConnection
if ($args instanceof Traversable) { if ($args instanceof Traversable) {
$args = iterator_to_array($args); $args = iterator_to_array($args);
} }
return $this->command()->insert() return $this->command()->insert()
->into('%n', $table, '(%n)', array_keys($args))->values('%l', $args); ->into('%n', $table, '(%n)', array_keys($args))->values('%l', $args);
} }
@@ -486,9 +513,77 @@ class Connection implements IConnection
*/ */
public function substitute(string $value): string public function substitute(string $value): string
{ {
return strpos($value, ':') === false return str_contains($value, ':')
? $value ? preg_replace_callback('#:([^:\s]*):#', fn(array $m) => $this->substitutes->{$m[1]}, $value)
: preg_replace_callback('#:([^:\s]*):#', function (array $m) { return $this->substitutes->{$m[1]}; }, $value); : $value;
}
/********************* value objects translation ****************d*g**/
/**
* @param callable(object): Expression $translator
*/
public function setObjectTranslator(callable $translator): void
{
if (!$translator instanceof \Closure) {
$translator = \Closure::fromCallable($translator);
}
$param = (new \ReflectionFunction($translator))->getParameters()[0] ?? null;
$type = $param?->getType();
$types = match (true) {
$type instanceof \ReflectionNamedType => [$type],
$type instanceof \ReflectionUnionType => $type->getTypes(),
default => throw new Exception('Object translator must have exactly one parameter with class typehint.'),
};
foreach ($types as $type) {
if ($type->isBuiltin() || $type->allowsNull()) {
throw new Exception("Object translator must have exactly one parameter with non-nullable class typehint, got '$type'.");
}
$this->translators[$type->getName()] = $translator;
}
$this->sortTranslators = true;
}
public function translateObject(object $object): ?Expression
{
if ($this->sortTranslators) {
$this->translators = array_filter($this->translators);
uksort($this->translators, fn($a, $b) => is_subclass_of($a, $b) ? -1 : 1);
$this->sortTranslators = false;
}
if (!array_key_exists($object::class, $this->translators)) {
$translator = null;
foreach ($this->translators as $class => $t) {
if ($object instanceof $class) {
$translator = $t;
break;
}
}
$this->translators[$object::class] = $translator;
}
$translator = $this->translators[$object::class];
if ($translator === null) {
return null;
}
$result = $translator($object);
if (!$result instanceof Expression) {
throw new Exception(sprintf(
"Object translator for class '%s' returned '%s' but %s expected.",
$object::class,
get_debug_type($result),
Expression::class,
));
}
return $result;
} }
@@ -497,10 +592,9 @@ class Connection implements IConnection
/** /**
* Executes SQL query and fetch result - shortcut for query() & fetch(). * Executes SQL query and fetch result - shortcut for query() & fetch().
* @param mixed ...$args
* @throws Exception * @throws Exception
*/ */
public function fetch(...$args): ?Row public function fetch(#[Language('GenericSQL')] mixed ...$args): ?Row
{ {
return $this->query($args)->fetch(); return $this->query($args)->fetch();
} }
@@ -508,11 +602,10 @@ class Connection implements IConnection
/** /**
* Executes SQL query and fetch results - shortcut for query() & fetchAll(). * Executes SQL query and fetch results - shortcut for query() & fetchAll().
* @param mixed ...$args
* @return Row[]|array[] * @return Row[]|array[]
* @throws Exception * @throws Exception
*/ */
public function fetchAll(...$args): array public function fetchAll(#[Language('GenericSQL')] mixed ...$args): array
{ {
return $this->query($args)->fetchAll(); return $this->query($args)->fetchAll();
} }
@@ -520,11 +613,9 @@ class Connection implements IConnection
/** /**
* Executes SQL query and fetch first column - shortcut for query() & fetchSingle(). * Executes SQL query and fetch first column - shortcut for query() & fetchSingle().
* @param mixed ...$args
* @return mixed
* @throws Exception * @throws Exception
*/ */
public function fetchSingle(...$args) public function fetchSingle(#[Language('GenericSQL')] mixed ...$args): mixed
{ {
return $this->query($args)->fetchSingle(); return $this->query($args)->fetchSingle();
} }
@@ -532,10 +623,9 @@ class Connection implements IConnection
/** /**
* Executes SQL query and fetch pairs - shortcut for query() & fetchPairs(). * Executes SQL query and fetch pairs - shortcut for query() & fetchPairs().
* @param mixed ...$args
* @throws Exception * @throws Exception
*/ */
public function fetchPairs(...$args): array public function fetchPairs(#[Language('GenericSQL')] mixed ...$args): array
{ {
return $this->query($args)->fetchPairs(); return $this->query($args)->fetchPairs();
} }
@@ -561,7 +651,7 @@ class Connection implements IConnection
* @param callable $onProgress function (int $count, ?float $percent): void * @param callable $onProgress function (int $count, ?float $percent): void
* @return int count of sql commands * @return int count of sql commands
*/ */
public function loadFile(string $file, callable $onProgress = null): int public function loadFile(string $file, ?callable $onProgress = null): int
{ {
return Helpers::loadFromFile($this, $file, $onProgress); return Helpers::loadFromFile($this, $file, $onProgress);
} }
@@ -575,6 +665,7 @@ class Connection implements IConnection
if (!$this->driver) { if (!$this->driver) {
$this->connect(); $this->connect();
} }
return new Reflection\Database($this->driver->getReflector(), $this->config['database'] ?? null); return new Reflection\Database($this->driver->getReflector(), $this->config['database'] ?? null);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,16 +17,8 @@ use Dibi;
*/ */
class MySqliResult implements Dibi\ResultDriver class MySqliResult implements Dibi\ResultDriver
{ {
use Dibi\Strict; private \mysqli_result $resultSet;
private bool $buffered;
/** @var \mysqli_result */
private $resultSet;
/** @var bool */
private $autoFree = true;
/** @var bool Is buffered (seekable and countable)? */
private $buffered;
public function __construct(\mysqli_result $resultSet, bool $buffered) public function __construct(\mysqli_result $resultSet, bool $buffered)
@@ -36,17 +28,6 @@ class MySqliResult implements Dibi\ResultDriver
} }
/**
* Automatically frees the resources allocated for this result set.
*/
public function __destruct()
{
if ($this->autoFree && $this->getResultResource()) {
@$this->free();
}
}
/** /**
* Returns the number of rows in a result set. * Returns the number of rows in a result set.
*/ */
@@ -55,6 +36,7 @@ class MySqliResult implements Dibi\ResultDriver
if (!$this->buffered) { if (!$this->buffered) {
throw new Dibi\NotSupportedException('Row count is not available for unbuffered queries.'); throw new Dibi\NotSupportedException('Row count is not available for unbuffered queries.');
} }
return $this->resultSet->num_rows; return $this->resultSet->num_rows;
} }
@@ -80,6 +62,7 @@ class MySqliResult implements Dibi\ResultDriver
if (!$this->buffered) { if (!$this->buffered) {
throw new Dibi\NotSupportedException('Cannot seek an unbuffered result set.'); throw new Dibi\NotSupportedException('Cannot seek an unbuffered result set.');
} }
return $this->resultSet->data_seek($row); return $this->resultSet->data_seek($row);
} }
@@ -107,6 +90,7 @@ class MySqliResult implements Dibi\ResultDriver
$types[$value] = substr($key, 12); $types[$value] = substr($key, 12);
} }
} }
$types[MYSQLI_TYPE_TINY] = $types[MYSQLI_TYPE_SHORT] = $types[MYSQLI_TYPE_LONG] = 'INT'; $types[MYSQLI_TYPE_TINY] = $types[MYSQLI_TYPE_SHORT] = $types[MYSQLI_TYPE_LONG] = 'INT';
} }
@@ -119,10 +103,11 @@ class MySqliResult implements Dibi\ResultDriver
'table' => $row['orgtable'], 'table' => $row['orgtable'],
'fullname' => $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name'], 'fullname' => $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name'],
'nativetype' => $types[$row['type']] ?? $row['type'], 'nativetype' => $types[$row['type']] ?? $row['type'],
'type' => $row['type'] === MYSQLI_TYPE_TIME ? Dibi\Type::TIME_INTERVAL : null, 'type' => $row['type'] === MYSQLI_TYPE_TIME ? Dibi\Type::TimeInterval : null,
'vendor' => $row, 'vendor' => $row,
]; ];
} }
return $columns; return $columns;
} }
@@ -132,7 +117,6 @@ class MySqliResult implements Dibi\ResultDriver
*/ */
public function getResultResource(): \mysqli_result public function getResultResource(): \mysqli_result
{ {
$this->autoFree = false;
return $this->resultSet; return $this->resultSet;
} }

View File

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

View File

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

View File

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

View File

@@ -17,16 +17,9 @@ use Dibi;
*/ */
class OdbcResult implements Dibi\ResultDriver class OdbcResult implements Dibi\ResultDriver
{ {
use Dibi\Strict;
/** @var resource */ /** @var resource */
private $resultSet; private $resultSet;
private int $row = 0;
/** @var bool */
private $autoFree = true;
/** @var int Cursor */
private $row = 0;
/** /**
@@ -38,17 +31,6 @@ class OdbcResult implements Dibi\ResultDriver
} }
/**
* Automatically frees the resources allocated for this result set.
*/
public function __destruct()
{
if ($this->autoFree && $this->getResultResource()) {
$this->free();
}
}
/** /**
* Returns the number of rows in a result set. * Returns the number of rows in a result set.
*/ */
@@ -72,11 +54,13 @@ class OdbcResult implements Dibi\ResultDriver
if (!odbc_fetch_row($set, ++$this->row)) { if (!odbc_fetch_row($set, ++$this->row)) {
return null; return null;
} }
$count = odbc_num_fields($set); $count = odbc_num_fields($set);
$cols = []; $cols = [];
for ($i = 1; $i <= $count; $i++) { for ($i = 1; $i <= $count; $i++) {
$cols[] = odbc_result($set, $i); $cols[] = odbc_result($set, $i);
} }
return $cols; return $cols;
} }
} }
@@ -116,6 +100,7 @@ class OdbcResult implements Dibi\ResultDriver
'nativetype' => odbc_field_type($this->resultSet, $i), 'nativetype' => odbc_field_type($this->resultSet, $i),
]; ];
} }
return $columns; return $columns;
} }
@@ -124,9 +109,8 @@ class OdbcResult implements Dibi\ResultDriver
* Returns the result set resource. * Returns the result set resource.
* @return resource|null * @return resource|null
*/ */
public function getResultResource() public function getResultResource(): mixed
{ {
$this->autoFree = false;
return is_resource($this->resultSet) ? $this->resultSet : null; return is_resource($this->resultSet) ? $this->resultSet : null;
} }

View File

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

View File

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

View File

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

View File

@@ -27,19 +27,10 @@ use PDO;
*/ */
class PdoDriver implements Dibi\Driver class PdoDriver implements Dibi\Driver
{ {
use Dibi\Strict; private ?PDO $connection;
private ?int $affectedRows;
/** @var PDO|null Connection resource */ private string $driverName;
private $connection; private string $serverVersion = '';
/** @var int|null Affected rows */
private $affectedRows;
/** @var string */
private $driverName;
/** @var string */
private $serverVersion = '';
/** @throws Dibi\NotSupportedException */ /** @throws Dibi\NotSupportedException */
@@ -60,7 +51,6 @@ class PdoDriver implements Dibi\Driver
if ($this->connection->getAttribute(PDO::ATTR_ERRMODE) !== PDO::ERRMODE_SILENT) { if ($this->connection->getAttribute(PDO::ATTR_ERRMODE) !== PDO::ERRMODE_SILENT) {
throw new Dibi\DriverException('PDO connection in exception or warning error mode is not supported.'); throw new Dibi\DriverException('PDO connection in exception or warning error mode is not supported.');
} }
} else { } else {
try { try {
$this->connection = new PDO($config['dsn'], $config['username'], $config['password'], $config['options']); $this->connection = new PDO($config['dsn'], $config['username'], $config['password'], $config['options']);
@@ -69,6 +59,7 @@ class PdoDriver implements Dibi\Driver
if ($e->getMessage() === 'could not find driver') { if ($e->getMessage() === 'could not find driver') {
throw new Dibi\NotSupportedException('PHP extension for PDO is not loaded.'); throw new Dibi\NotSupportedException('PHP extension for PDO is not loaded.');
} }
throw new Dibi\DriverException($e->getMessage(), $e->getCode()); throw new Dibi\DriverException($e->getMessage(), $e->getCode());
} }
} }
@@ -102,23 +93,15 @@ class PdoDriver implements Dibi\Driver
$this->affectedRows = null; $this->affectedRows = null;
[$sqlState, $code, $message] = $this->connection->errorInfo(); [$sqlState, $code, $message] = $this->connection->errorInfo();
$code ??= 0;
$message = "SQLSTATE[$sqlState]: $message"; $message = "SQLSTATE[$sqlState]: $message";
switch ($this->driverName) { throw match ($this->driverName) {
case 'mysql': 'mysql' => MySqliDriver::createException($message, $code, $sql),
throw MySqliDriver::createException($message, $code, $sql); 'oci' => OracleDriver::createException($message, $code, $sql),
'pgsql' => PostgreDriver::createException($message, $sqlState, $sql),
case 'oci': 'sqlite' => SqliteDriver::createException($message, $code, $sql),
throw OracleDriver::createException($message, $code, $sql); default => new Dibi\DriverException($message, $code, $sql),
};
case 'pgsql':
throw PostgreDriver::createException($message, $sqlState, $sql);
case 'sqlite':
throw SqliteDriver::createException($message, $code, $sql);
default:
throw new Dibi\DriverException($message, $code, $sql);
}
} }
@@ -144,11 +127,11 @@ class PdoDriver implements Dibi\Driver
* Begins a transaction (if supported). * Begins a transaction (if supported).
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function begin(string $savepoint = null): void public function begin(?string $savepoint = null): void
{ {
if (!$this->connection->beginTransaction()) { if (!$this->connection->beginTransaction()) {
$err = $this->connection->errorInfo(); $err = $this->connection->errorInfo();
throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1]); throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1] ?? 0);
} }
} }
@@ -157,11 +140,11 @@ class PdoDriver implements Dibi\Driver
* Commits statements in a transaction. * Commits statements in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function commit(string $savepoint = null): void public function commit(?string $savepoint = null): void
{ {
if (!$this->connection->commit()) { if (!$this->connection->commit()) {
$err = $this->connection->errorInfo(); $err = $this->connection->errorInfo();
throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1]); throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1] ?? 0);
} }
} }
@@ -170,11 +153,11 @@ class PdoDriver implements Dibi\Driver
* Rollback changes in a transaction. * Rollback changes in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function rollback(string $savepoint = null): void public function rollback(?string $savepoint = null): void
{ {
if (!$this->connection->rollBack()) { if (!$this->connection->rollBack()) {
$err = $this->connection->errorInfo(); $err = $this->connection->errorInfo();
throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1]); throw new Dibi\DriverException("SQLSTATE[$err[0]]: $err[2]", $err[1] ?? 0);
} }
} }
@@ -193,27 +176,14 @@ class PdoDriver implements Dibi\Driver
*/ */
public function getReflector(): Dibi\Reflector public function getReflector(): Dibi\Reflector
{ {
switch ($this->driverName) { return match ($this->driverName) {
case 'mysql': 'mysql' => new MySqlReflector($this),
return new MySqlReflector($this); 'oci' => new OracleReflector($this),
'pgsql' => new PostgreReflector($this, $this->connection->getAttribute(PDO::ATTR_SERVER_VERSION)),
case 'oci': 'sqlite' => new SqliteReflector($this),
return new OracleReflector($this); 'mssql', 'dblib', 'sqlsrv' => new SqlsrvReflector($this),
default => throw new Dibi\NotSupportedException,
case 'pgsql': };
return new PostgreReflector($this, $this->connection->getAttribute(PDO::ATTR_SERVER_VERSION));
case 'sqlite':
return new SqliteReflector($this);
case 'mssql':
case 'dblib':
case 'sqlsrv':
return new SqlsrvReflector($this);
default:
throw new Dibi\NotSupportedException;
}
} }
@@ -234,44 +204,34 @@ class PdoDriver implements Dibi\Driver
*/ */
public function escapeText(string $value): string public function escapeText(string $value): string
{ {
return $this->driverName === 'odbc' return match ($this->driverName) {
? "'" . str_replace("'", "''", $value) . "'" 'odbc' => "'" . str_replace("'", "''", $value) . "'",
: $this->connection->quote($value, PDO::PARAM_STR); 'sqlsrv' => "N'" . str_replace("'", "''", $value) . "'",
default => $this->connection->quote($value, PDO::PARAM_STR),
};
} }
public function escapeBinary(string $value): string public function escapeBinary(string $value): string
{ {
return $this->driverName === 'odbc' return match ($this->driverName) {
? "'" . str_replace("'", "''", $value) . "'" 'odbc' => "'" . str_replace("'", "''", $value) . "'",
: $this->connection->quote($value, PDO::PARAM_LOB); 'sqlsrv' => '0x' . bin2hex($value),
default => $this->connection->quote($value, PDO::PARAM_LOB),
};
} }
public function escapeIdentifier(string $value): string public function escapeIdentifier(string $value): string
{ {
switch ($this->driverName) { return match ($this->driverName) {
case 'mysql': 'mysql' => '`' . str_replace('`', '``', $value) . '`',
return '`' . str_replace('`', '``', $value) . '`'; 'oci', 'pgsql' => '"' . str_replace('"', '""', $value) . '"',
'sqlite' => '[' . strtr($value, '[]', ' ') . ']',
case 'oci': 'odbc', 'mssql' => '[' . str_replace(['[', ']'], ['[[', ']]'], $value) . ']',
case 'pgsql': 'dblib', 'sqlsrv' => '[' . str_replace(']', ']]', $value) . ']',
return '"' . str_replace('"', '""', $value) . '"'; default => $value,
};
case 'sqlite':
return '[' . strtr($value, '[]', ' ') . ']';
case 'odbc':
case 'mssql':
return '[' . str_replace(['[', ']'], ['[[', ']]'], $value) . ']';
case 'dblib':
case 'sqlsrv':
return '[' . str_replace(']', ']]', $value) . ']';
default:
return $value;
}
} }
@@ -293,16 +253,11 @@ class PdoDriver implements Dibi\Driver
public function escapeDateTime(\DateTimeInterface $value): string public function escapeDateTime(\DateTimeInterface $value): string
{ {
switch ($this->driverName) { return match ($this->driverName) {
case 'odbc': 'odbc' => $value->format('#m/d/Y H:i:s.u#'),
return $value->format('#m/d/Y H:i:s.u#'); 'mssql', 'dblib', 'sqlsrv' => 'CONVERT(DATETIME2(7), ' . $value->format("'Y-m-d H:i:s.u'") . ')',
case 'mssql': default => $value->format("'Y-m-d H:i:s.u'"),
case 'dblib': };
case 'sqlsrv':
return 'CONVERT(DATETIME2(7), ' . $value->format("'Y-m-d H:i:s.u'") . ')';
default:
return $value->format("'Y-m-d H:i:s.u'");
}
} }
@@ -366,15 +321,18 @@ class PdoDriver implements Dibi\Driver
$sql .= ' LIMIT ' . ($limit ?? '18446744073709551615') $sql .= ' LIMIT ' . ($limit ?? '18446744073709551615')
. ($offset ? ' OFFSET ' . $offset : ''); . ($offset ? ' OFFSET ' . $offset : '');
} }
break; break;
case 'pgsql': case 'pgsql':
if ($limit !== null) { if ($limit !== null) {
$sql .= ' LIMIT ' . $limit; $sql .= ' LIMIT ' . $limit;
} }
if ($offset) { if ($offset) {
$sql .= ' OFFSET ' . $offset; $sql .= ' OFFSET ' . $offset;
} }
break; break;
case 'sqlite': case 'sqlite':
@@ -382,6 +340,7 @@ class PdoDriver implements Dibi\Driver
$sql .= ' LIMIT ' . ($limit ?? '-1') $sql .= ' LIMIT ' . ($limit ?? '-1')
. ($offset ? ' OFFSET ' . $offset : ''); . ($offset ? ' OFFSET ' . $offset : '');
} }
break; break;
case 'oci': case 'oci':
@@ -394,6 +353,7 @@ class PdoDriver implements Dibi\Driver
} elseif ($limit !== null) { } elseif ($limit !== null) {
$sql = 'SELECT * FROM (' . $sql . ') WHERE ROWNUM <= ' . $limit; $sql = 'SELECT * FROM (' . $sql . ') WHERE ROWNUM <= ' . $limit;
} }
break; break;
case 'mssql': case 'mssql':
@@ -406,10 +366,10 @@ class PdoDriver implements Dibi\Driver
} elseif ($offset) { } elseif ($offset) {
$sql = sprintf('%s OFFSET %d ROWS', rtrim($sql), $offset); $sql = sprintf('%s OFFSET %d ROWS', rtrim($sql), $offset);
} }
break; break;
} }
// break omitted // break omitted
case 'odbc': case 'odbc':
if ($offset) { if ($offset) {
throw new Dibi\NotSupportedException('Offset is not supported by this database.'); throw new Dibi\NotSupportedException('Offset is not supported by this database.');
@@ -419,7 +379,6 @@ class PdoDriver implements Dibi\Driver
break; break;
} }
// break omitted // break omitted
default: default:
throw new Dibi\NotSupportedException('PDO or driver does not support applying limit or offset.'); throw new Dibi\NotSupportedException('PDO or driver does not support applying limit or offset.');
} }

View File

@@ -19,13 +19,8 @@ use PDO;
*/ */
class PdoResult implements Dibi\ResultDriver class PdoResult implements Dibi\ResultDriver
{ {
use Dibi\Strict; private ?\PDOStatement $resultSet;
private string $driverName;
/** @var \PDOStatement|null */
private $resultSet;
/** @var string */
private $driverName;
public function __construct(\PDOStatement $resultSet, string $driverName) public function __construct(\PDOStatement $resultSet, string $driverName)
@@ -85,6 +80,7 @@ class PdoResult implements Dibi\ResultDriver
if ($row === false) { if ($row === false) {
throw new Dibi\NotSupportedException('Driver does not support meta data.'); throw new Dibi\NotSupportedException('Driver does not support meta data.');
} }
$row += [ $row += [
'table' => null, 'table' => null,
'native_type' => 'VAR_STRING', 'native_type' => 'VAR_STRING',
@@ -94,11 +90,12 @@ class PdoResult implements Dibi\ResultDriver
'name' => $row['name'], 'name' => $row['name'],
'table' => $row['table'], 'table' => $row['table'],
'nativetype' => $row['native_type'], 'nativetype' => $row['native_type'],
'type' => $row['native_type'] === 'TIME' && $this->driverName === 'mysql' ? Dibi\Type::TIME_INTERVAL : null, 'type' => $row['native_type'] === 'TIME' && $this->driverName === 'mysql' ? Dibi\Type::TimeInterval : null,
'fullname' => $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name'], 'fullname' => $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name'],
'vendor' => $row, 'vendor' => $row,
]; ];
} }
return $columns; return $columns;
} }

View File

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

View File

@@ -17,13 +17,8 @@ use Dibi;
*/ */
class PostgreReflector implements Dibi\Reflector class PostgreReflector implements Dibi\Reflector
{ {
use Dibi\Strict; private Dibi\Driver $driver;
private string $version;
/** @var Dibi\Driver */
private $driver;
/** @var string */
private $version;
public function __construct(Dibi\Driver $driver, string $version) public function __construct(Dibi\Driver $driver, string $version)
@@ -66,6 +61,7 @@ class PostgreReflector implements Dibi\Reflector
while ($row = $res->fetch(true)) { while ($row = $res->fetch(true)) {
$tables[] = $row; $tables[] = $row;
} }
return $tables; return $tables;
} }
@@ -76,13 +72,6 @@ class PostgreReflector implements Dibi\Reflector
public function getColumns(string $table): array public function getColumns(string $table): array
{ {
$_table = $this->driver->escapeText($this->driver->escapeIdentifier($table)); $_table = $this->driver->escapeText($this->driver->escapeIdentifier($table));
$res = $this->driver->query("
SELECT indkey
FROM pg_class
LEFT JOIN pg_index on pg_class.oid = pg_index.indrelid AND pg_index.indisprimary
WHERE pg_class.oid = $_table::regclass
");
$primary = (int) $res->fetch(true)['indkey'];
$res = $this->driver->query(" $res = $this->driver->query("
SELECT * SELECT *
@@ -102,7 +91,8 @@ class PostgreReflector implements Dibi\Reflector
a.atttypmod-4 AS character_maximum_length, a.atttypmod-4 AS character_maximum_length,
NOT a.attnotnull AS is_nullable, NOT a.attnotnull AS is_nullable,
a.attnum AS ordinal_position, a.attnum AS ordinal_position,
adef.adsrc 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
@@ -127,10 +117,11 @@ class PostgreReflector implements Dibi\Reflector
'size' => $size > 0 ? $size : null, 'size' => $size > 0 ? $size : null,
'nullable' => $row['is_nullable'] === 'YES' || $row['is_nullable'] === 't' || $row['is_nullable'] === true, 'nullable' => $row['is_nullable'] === 'YES' || $row['is_nullable'] === 't' || $row['is_nullable'] === true,
'default' => $row['column_default'], 'default' => $row['column_default'],
'autoincrement' => (int) $row['ordinal_position'] === $primary && substr($row['column_default'] ?? '', 0, 7) === 'nextval', 'autoincrement' => $row['is_identity'] === 'YES' || str_starts_with($row['column_default'] ?? '', 'nextval('),
'vendor' => $row, 'vendor' => $row,
]; ];
} }
return $columns; return $columns;
} }
@@ -180,6 +171,7 @@ class PostgreReflector implements Dibi\Reflector
} }
} }
} }
return array_values($indexes); return array_values($indexes);
} }

View File

@@ -11,6 +11,7 @@ namespace Dibi\Drivers;
use Dibi; use Dibi;
use Dibi\Helpers; use Dibi\Helpers;
use PgSql;
/** /**
@@ -18,17 +19,12 @@ use Dibi\Helpers;
*/ */
class PostgreResult implements Dibi\ResultDriver class PostgreResult implements Dibi\ResultDriver
{ {
use Dibi\Strict; /** @var resource|PgSql\Result */
/** @var resource */
private $resultSet; private $resultSet;
/** @var bool */
private $autoFree = true;
/** /**
* @param resource $resultSet * @param resource|PgSql\Result $resultSet
*/ */
public function __construct($resultSet) public function __construct($resultSet)
{ {
@@ -36,17 +32,6 @@ class PostgreResult implements Dibi\ResultDriver
} }
/**
* Automatically frees the resources allocated for this result set.
*/
public function __destruct()
{
if ($this->autoFree && $this->getResultResource()) {
$this->free();
}
}
/** /**
* Returns the number of rows in a result set. * Returns the number of rows in a result set.
*/ */
@@ -102,18 +87,20 @@ class PostgreResult implements Dibi\ResultDriver
: $row['name']; : $row['name'];
$columns[] = $row; $columns[] = $row;
} }
return $columns; return $columns;
} }
/** /**
* Returns the result set resource. * Returns the result set resource.
* @return resource|null * @return resource|PgSql\Result|null
*/ */
public function getResultResource() public function getResultResource(): mixed
{ {
$this->autoFree = false; return is_resource($this->resultSet) || $this->resultSet instanceof PgSql\Result
return is_resource($this->resultSet) ? $this->resultSet : null; ? $this->resultSet
: null;
} }

View File

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

View File

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

View File

@@ -18,13 +18,7 @@ use Dibi\Helpers;
*/ */
class SqliteResult implements Dibi\ResultDriver class SqliteResult implements Dibi\ResultDriver
{ {
use Dibi\Strict; private \SQLite3Result $resultSet;
/** @var \SQLite3Result */
private $resultSet;
/** @var bool */
private $autoFree = true;
public function __construct(\SQLite3Result $resultSet) public function __construct(\SQLite3Result $resultSet)
@@ -33,17 +27,6 @@ class SqliteResult implements Dibi\ResultDriver
} }
/**
* Automatically frees the resources allocated for this result set.
*/
public function __destruct()
{
if ($this->autoFree && $this->getResultResource()) {
@$this->free();
}
}
/** /**
* Returns the number of rows in a result set. * Returns the number of rows in a result set.
* @throws Dibi\NotSupportedException * @throws Dibi\NotSupportedException
@@ -90,7 +73,7 @@ class SqliteResult implements Dibi\ResultDriver
{ {
$count = $this->resultSet->numColumns(); $count = $this->resultSet->numColumns();
$columns = []; $columns = [];
static $types = [SQLITE3_INTEGER => 'int', SQLITE3_FLOAT => 'float', SQLITE3_TEXT => 'text', SQLITE3_BLOB => 'blob', SQLITE3_NULL => 'null']; $types = [SQLITE3_INTEGER => 'int', SQLITE3_FLOAT => 'float', SQLITE3_TEXT => 'text', SQLITE3_BLOB => 'blob', SQLITE3_NULL => 'null'];
for ($i = 0; $i < $count; $i++) { for ($i = 0; $i < $count; $i++) {
$columns[] = [ $columns[] = [
'name' => $this->resultSet->columnName($i), 'name' => $this->resultSet->columnName($i),
@@ -99,6 +82,7 @@ class SqliteResult implements Dibi\ResultDriver
'nativetype' => $types[$this->resultSet->columnType($i)] ?? null, // buggy in PHP 7.4.4 & 7.3.16, bug 79414 'nativetype' => $types[$this->resultSet->columnType($i)] ?? null, // buggy in PHP 7.4.4 & 7.3.16, bug 79414
]; ];
} }
return $columns; return $columns;
} }
@@ -108,7 +92,6 @@ class SqliteResult implements Dibi\ResultDriver
*/ */
public function getResultResource(): \SQLite3Result public function getResultResource(): \SQLite3Result
{ {
$this->autoFree = false;
return $this->resultSet; return $this->resultSet;
} }

View File

@@ -27,16 +27,10 @@ use Dibi\Helpers;
*/ */
class SqlsrvDriver implements Dibi\Driver class SqlsrvDriver implements Dibi\Driver
{ {
use Dibi\Strict;
/** @var resource */ /** @var resource */
private $connection; private $connection;
private ?int $affectedRows;
/** @var int|null Affected rows */ private string $version = '';
private $affectedRows;
/** @var string */
private $version = '';
/** @throws Dibi\NotSupportedException */ /** @throws Dibi\NotSupportedException */
@@ -53,23 +47,28 @@ class SqlsrvDriver implements Dibi\Driver
if (isset($config['resource'])) { if (isset($config['resource'])) {
$this->connection = $config['resource']; $this->connection = $config['resource'];
if (!is_resource($this->connection)) {
throw new \InvalidArgumentException("Configuration option 'resource' is not resource.");
}
} else { } else {
$options = $config['options']; $options = $config['options'];
// Default values // Default values
$options['CharacterSet'] = $options['CharacterSet'] ?? 'UTF-8'; $options['CharacterSet'] ??= 'UTF-8';
$options['PWD'] = (string) $options['PWD']; $options['PWD'] = (string) $options['PWD'];
$options['UID'] = (string) $options['UID']; $options['UID'] = (string) $options['UID'];
$options['Database'] = (string) $options['Database']; $options['Database'] = (string) $options['Database'];
sqlsrv_configure('WarningsReturnAsErrors', 0);
$this->connection = sqlsrv_connect($config['host'], $options); $this->connection = sqlsrv_connect($config['host'], $options);
if (!is_resource($this->connection)) {
$info = sqlsrv_errors(SQLSRV_ERR_ERRORS);
throw new Dibi\DriverException($info[0]['message'], $info[0]['code']);
}
sqlsrv_configure('WarningsReturnAsErrors', 1);
} }
if (!is_resource($this->connection)) {
$info = sqlsrv_errors();
throw new Dibi\DriverException($info[0]['message'], $info[0]['code']);
}
$this->version = sqlsrv_server_info($this->connection)['SQLServerVersion']; $this->version = sqlsrv_server_info($this->connection)['SQLServerVersion'];
} }
@@ -102,6 +101,7 @@ class SqlsrvDriver implements Dibi\Driver
? $this->createResultDriver($res) ? $this->createResultDriver($res)
: null; : null;
} }
return null; return null;
} }
@@ -125,6 +125,7 @@ class SqlsrvDriver implements Dibi\Driver
$row = sqlsrv_fetch_array($res, SQLSRV_FETCH_NUMERIC); $row = sqlsrv_fetch_array($res, SQLSRV_FETCH_NUMERIC);
return Dibi\Helpers::intVal($row[0]); return Dibi\Helpers::intVal($row[0]);
} }
return null; return null;
} }
@@ -133,7 +134,7 @@ class SqlsrvDriver implements Dibi\Driver
* Begins a transaction (if supported). * Begins a transaction (if supported).
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function begin(string $savepoint = null): void public function begin(?string $savepoint = null): void
{ {
sqlsrv_begin_transaction($this->connection); sqlsrv_begin_transaction($this->connection);
} }
@@ -143,7 +144,7 @@ class SqlsrvDriver implements Dibi\Driver
* Commits statements in a transaction. * Commits statements in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function commit(string $savepoint = null): void public function commit(?string $savepoint = null): void
{ {
sqlsrv_commit($this->connection); sqlsrv_commit($this->connection);
} }
@@ -153,7 +154,7 @@ class SqlsrvDriver implements Dibi\Driver
* Rollback changes in a transaction. * Rollback changes in a transaction.
* @throws Dibi\DriverException * @throws Dibi\DriverException
*/ */
public function rollback(string $savepoint = null): void public function rollback(?string $savepoint = null): void
{ {
sqlsrv_rollback($this->connection); sqlsrv_rollback($this->connection);
} }
@@ -163,7 +164,7 @@ class SqlsrvDriver implements Dibi\Driver
* Returns the connection resource. * Returns the connection resource.
* @return resource|null * @return resource|null
*/ */
public function getResource() public function getResource(): mixed
{ {
return is_resource($this->connection) ? $this->connection : null; return is_resource($this->connection) ? $this->connection : null;
} }
@@ -262,7 +263,6 @@ class SqlsrvDriver implements Dibi\Driver
} elseif ($limit !== null) { } elseif ($limit !== null) {
$sql = sprintf('SELECT TOP (%d) * FROM (%s) t', $limit, $sql); $sql = sprintf('SELECT TOP (%d) * FROM (%s) t', $limit, $sql);
} }
} elseif ($limit !== null) { } elseif ($limit !== null) {
// requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx // requires ORDER BY, see https://technet.microsoft.com/en-us/library/gg699618(v=sql.110).aspx
$sql = sprintf('%s OFFSET %d ROWS FETCH NEXT %d ROWS ONLY', rtrim($sql), $offset, $limit); $sql = sprintf('%s OFFSET %d ROWS FETCH NEXT %d ROWS ONLY', rtrim($sql), $offset, $limit);

View File

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

View File

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

View File

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

View File

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

View File

@@ -27,6 +27,8 @@ namespace Dibi;
* @method Fluent innerJoin(...$table) * @method Fluent innerJoin(...$table)
* @method Fluent rightJoin(...$table) * @method Fluent rightJoin(...$table)
* @method Fluent outerJoin(...$table) * @method Fluent outerJoin(...$table)
* @method Fluent union(Fluent $fluent)
* @method Fluent unionAll(Fluent $fluent)
* @method Fluent as(...$field) * @method Fluent as(...$field)
* @method Fluent on(...$cond) * @method Fluent on(...$cond)
* @method Fluent and(...$cond) * @method Fluent and(...$cond)
@@ -43,12 +45,15 @@ namespace Dibi;
*/ */
class Fluent implements IDataSource class Fluent implements IDataSource
{ {
use Strict; public const
AffectedRows = 'a',
Identifier = 'n',
Remove = false;
public const REMOVE = false; /** @deprecated use Fluent::Remove */
public const REMOVE = self::Remove;
/** @var array */ public static array $masks = [
public static $masks = [
'SELECT' => ['SELECT', 'DISTINCT', 'FROM', 'WHERE', 'GROUP BY', 'SELECT' => ['SELECT', 'DISTINCT', 'FROM', 'WHERE', 'GROUP BY',
'HAVING', 'ORDER BY', 'LIMIT', 'OFFSET', ], 'HAVING', 'ORDER BY', 'LIMIT', 'OFFSET', ],
'UPDATE' => ['UPDATE', 'SET', 'WHERE', 'ORDER BY', 'LIMIT'], 'UPDATE' => ['UPDATE', 'SET', 'WHERE', 'ORDER BY', 'LIMIT'],
@@ -56,8 +61,8 @@ class Fluent implements IDataSource
'DELETE' => ['DELETE', 'FROM', 'USING', 'WHERE', 'ORDER BY', 'LIMIT'], 'DELETE' => ['DELETE', 'FROM', 'USING', 'WHERE', 'ORDER BY', 'LIMIT'],
]; ];
/** @var array default modifiers for arrays */ /** default modifiers for arrays */
public static $modifiers = [ public static array $modifiers = [
'SELECT' => '%n', 'SELECT' => '%n',
'FROM' => '%n', 'FROM' => '%n',
'IN' => '%in', 'IN' => '%in',
@@ -69,8 +74,8 @@ class Fluent implements IDataSource
'GROUP BY' => '%by', 'GROUP BY' => '%by',
]; ];
/** @var array clauses separators */ /** clauses separators */
public static $separators = [ public static array $separators = [
'SELECT' => ',', 'SELECT' => ',',
'FROM' => ',', 'FROM' => ',',
'WHERE' => 'AND', 'WHERE' => 'AND',
@@ -84,41 +89,30 @@ class Fluent implements IDataSource
'INTO' => false, 'INTO' => false,
]; ];
/** @var array clauses */ /** clauses */
public static $clauseSwitches = [ public static array $clauseSwitches = [
'JOIN' => 'FROM', 'JOIN' => 'FROM',
'INNER JOIN' => 'FROM', 'INNER JOIN' => 'FROM',
'LEFT JOIN' => 'FROM', 'LEFT JOIN' => 'FROM',
'RIGHT JOIN' => 'FROM', 'RIGHT JOIN' => 'FROM',
]; ];
/** @var Connection */ private Connection $connection;
private $connection; private array $setups = [];
private ?string $command = null;
/** @var array */ private array $clauses = [];
private $setups = []; private array $flags = [];
/** @var string|null */
private $command;
/** @var array */
private $clauses = [];
/** @var array */
private $flags = [];
/** @var array|null */
private $cursor; private $cursor;
/** @var HashMap normalized clauses */ /** normalized clauses */
private static $normalizer; private static HashMap $normalizer;
public function __construct(Connection $connection) public function __construct(Connection $connection)
{ {
$this->connection = $connection; $this->connection = $connection;
if (self::$normalizer === null) { if (!isset(self::$normalizer)) {
self::$normalizer = new HashMap([self::class, '_formatClause']); self::$normalizer = new HashMap([self::class, '_formatClause']);
} }
} }
@@ -127,7 +121,7 @@ class Fluent implements IDataSource
/** /**
* Appends new argument to the clause. * Appends new argument to the clause.
*/ */
public function __call(string $clause, array $args): self public function __call(string $clause, array $args): static
{ {
$clause = self::$normalizer->$clause; $clause = self::$normalizer->$clause;
@@ -136,6 +130,7 @@ class Fluent implements IDataSource
if (isset(self::$masks[$clause])) { if (isset(self::$masks[$clause])) {
$this->clauses = array_fill_keys(self::$masks[$clause], null); $this->clauses = array_fill_keys(self::$masks[$clause], null);
} }
$this->cursor = &$this->clauses[$clause]; $this->cursor = &$this->clauses[$clause];
$this->cursor = []; $this->cursor = [];
$this->command = $clause; $this->command = $clause;
@@ -151,7 +146,7 @@ class Fluent implements IDataSource
$this->cursor = &$this->clauses[$clause]; $this->cursor = &$this->clauses[$clause];
// TODO: really delete? // TODO: really delete?
if ($args === [self::REMOVE]) { if ($args === [self::Remove]) {
$this->cursor = null; $this->cursor = null;
return $this; return $this;
} }
@@ -165,10 +160,9 @@ class Fluent implements IDataSource
$this->cursor[] = $sep; $this->cursor[] = $sep;
} }
} }
} else { } else {
// append to currect flow // append to currect flow
if ($args === [self::REMOVE]) { if ($args === [self::Remove]) {
return $this; return $this;
} }
@@ -203,6 +197,7 @@ class Fluent implements IDataSource
if ($arg instanceof self) { if ($arg instanceof self) {
$arg = new Literal("($arg)"); $arg = new Literal("($arg)");
} }
$this->cursor[] = $arg; $this->cursor[] = $arg;
} }
@@ -213,7 +208,7 @@ class Fluent implements IDataSource
/** /**
* Switch to a clause. * Switch to a clause.
*/ */
public function clause(string $clause): self public function clause(string $clause): static
{ {
$this->cursor = &$this->clauses[self::$normalizer->$clause]; $this->cursor = &$this->clauses[self::$normalizer->$clause];
if ($this->cursor === null) { if ($this->cursor === null) {
@@ -227,7 +222,7 @@ class Fluent implements IDataSource
/** /**
* Removes a clause. * Removes a clause.
*/ */
public function removeClause(string $clause): self public function removeClause(string $clause): static
{ {
$this->clauses[self::$normalizer->$clause] = null; $this->clauses[self::$normalizer->$clause] = null;
return $this; return $this;
@@ -237,7 +232,7 @@ class Fluent implements IDataSource
/** /**
* Change a SQL flag. * Change a SQL flag.
*/ */
public function setFlag(string $flag, bool $value = true): self public function setFlag(string $flag, bool $value = true): static
{ {
$flag = strtoupper($flag); $flag = strtoupper($flag);
if ($value) { if ($value) {
@@ -245,6 +240,7 @@ class Fluent implements IDataSource
} else { } else {
unset($this->flags[$flag]); unset($this->flags[$flag]);
} }
return $this; return $this;
} }
@@ -276,7 +272,7 @@ class Fluent implements IDataSource
/** /**
* Adds Result setup. * Adds Result setup.
*/ */
public function setupResult(string $method): self public function setupResult(string $method): static
{ {
$this->setups[] = func_get_args(); $this->setups[] = func_get_args();
return $this; return $this;
@@ -288,28 +284,25 @@ class Fluent implements IDataSource
/** /**
* Generates and executes SQL query. * Generates and executes SQL query.
* @return Result|int|null result set or number of affected rows * Returns result set or number of affected rows
* @return ($return is self::Identifier|self::AffectedRows ? int : Result)
* @throws Exception * @throws Exception
*/ */
public function execute(string $return = null) public function execute(?string $return = null): Result|int|null
{ {
$res = $this->query($this->_export()); $res = $this->query($this->_export());
switch ($return) { return match ($return) {
case \dibi::IDENTIFIER: self::Identifier => $this->connection->getInsertId(),
return $this->connection->getInsertId(); self::AffectedRows => $this->connection->getAffectedRows(),
case \dibi::AFFECTED_ROWS: default => $res,
return $this->connection->getAffectedRows(); };
default:
return $res;
}
} }
/** /**
* Generates, executes SQL query and fetches the single row. * Generates, executes SQL query and fetches the single row.
* @return Row|array|null
*/ */
public function fetch() public function fetch(): Row|array|null
{ {
return $this->command === 'SELECT' && !$this->clauses['LIMIT'] return $this->command === 'SELECT' && !$this->clauses['LIMIT']
? $this->query($this->_export(null, ['%lmt', 1]))->fetch() ? $this->query($this->_export(null, ['%lmt', 1]))->fetch()
@@ -319,9 +312,9 @@ class Fluent implements IDataSource
/** /**
* Like fetch(), but returns only first field. * Like fetch(), but returns only first field.
* @return mixed value on success, null if no next record * Returns value on success, null if no next record
*/ */
public function fetchSingle() public function fetchSingle(): mixed
{ {
return $this->command === 'SELECT' && !$this->clauses['LIMIT'] return $this->command === 'SELECT' && !$this->clauses['LIMIT']
? $this->query($this->_export(null, ['%lmt', 1]))->fetchSingle() ? $this->query($this->_export(null, ['%lmt', 1]))->fetchSingle()
@@ -332,7 +325,7 @@ class Fluent implements IDataSource
/** /**
* Fetches all records from table. * Fetches all records from table.
*/ */
public function fetchAll(int $offset = null, int $limit = null): array public function fetchAll(?int $offset = null, ?int $limit = null): array
{ {
return $this->query($this->_export(null, ['%ofs %lmt', $offset, $limit]))->fetchAll(); return $this->query($this->_export(null, ['%ofs %lmt', $offset, $limit]))->fetchAll();
} }
@@ -351,7 +344,7 @@ class Fluent implements IDataSource
/** /**
* Fetches all records from table like $key => $value pairs. * Fetches all records from table like $key => $value pairs.
*/ */
public function fetchPairs(string $key = null, string $value = null): array public function fetchPairs(?string $key = null, ?string $value = null): array
{ {
return $this->query($this->_export())->fetchPairs($key, $value); return $this->query($this->_export())->fetchPairs($key, $value);
} }
@@ -360,7 +353,7 @@ class Fluent implements IDataSource
/** /**
* Required by the IteratorAggregate interface. * Required by the IteratorAggregate interface.
*/ */
public function getIterator(int $offset = null, int $limit = null): ResultIterator public function getIterator(?int $offset = null, ?int $limit = null): ResultIterator
{ {
return $this->query($this->_export(null, ['%ofs %lmt', $offset, $limit]))->getIterator(); return $this->query($this->_export(null, ['%ofs %lmt', $offset, $limit]))->getIterator();
} }
@@ -369,7 +362,7 @@ class Fluent implements IDataSource
/** /**
* Generates and prints SQL query or it's part. * Generates and prints SQL query or it's part.
*/ */
public function test(string $clause = null): bool public function test(?string $clause = null): bool
{ {
return $this->connection->test($this->_export($clause)); return $this->connection->test($this->_export($clause));
} }
@@ -390,6 +383,7 @@ class Fluent implements IDataSource
$method = array_shift($setup); $method = array_shift($setup);
$res->$method(...$setup); $res->$method(...$setup);
} }
return $res; return $res;
} }
@@ -408,19 +402,14 @@ class Fluent implements IDataSource
*/ */
final public function __toString(): string final public function __toString(): string
{ {
try { return $this->connection->translate($this->_export());
return $this->connection->translate($this->_export());
} catch (\Throwable $e) {
trigger_error($e->getMessage(), E_USER_ERROR);
return '';
}
} }
/** /**
* Generates parameters for Translator. * Generates parameters for Translator.
*/ */
protected function _export(string $clause = null, array $args = []): array protected function _export(?string $clause = null, array $args = []): array
{ {
if ($clause === null) { if ($clause === null) {
$data = $this->clauses; $data = $this->clauses;
@@ -428,7 +417,6 @@ class Fluent implements IDataSource
$args = array_merge(['%lmt %ofs', $data['LIMIT'][0] ?? null, $data['OFFSET'][0] ?? null], $args); $args = array_merge(['%lmt %ofs', $data['LIMIT'][0] ?? null, $data['OFFSET'][0] ?? null], $args);
unset($data['LIMIT'], $data['OFFSET']); unset($data['LIMIT'], $data['OFFSET']);
} }
} else { } else {
$clause = self::$normalizer->$clause; $clause = self::$normalizer->$clause;
if (array_key_exists($clause, $this->clauses)) { if (array_key_exists($clause, $this->clauses)) {
@@ -444,6 +432,7 @@ class Fluent implements IDataSource
if ($clause === $this->command && $this->flags) { if ($clause === $this->command && $this->flags) {
$args[] = implode(' ', array_keys($this->flags)); $args[] = implode(' ', array_keys($this->flags));
} }
foreach ($statement as $arg) { foreach ($statement as $arg) {
$args[] = $arg; $args[] = $arg;
} }
@@ -464,6 +453,7 @@ class Fluent implements IDataSource
$s .= 'By'; $s .= 'By';
trigger_error("Did you mean '$s'?", E_USER_NOTICE); trigger_error("Did you mean '$s'?", E_USER_NOTICE);
} }
return strtoupper(preg_replace('#[a-z](?=[A-Z])#', '$0 ', $s)); return strtoupper(preg_replace('#[a-z](?=[A-Z])#', '$0 ', $s));
} }
@@ -475,6 +465,7 @@ class Fluent implements IDataSource
$this->clauses[$clause] = &$val; $this->clauses[$clause] = &$val;
unset($val); unset($val);
} }
$this->cursor = &$foo; $this->cursor = &$foo;
} }
} }

View File

@@ -14,6 +14,7 @@ namespace Dibi;
* Lazy cached storage. * Lazy cached storage.
* @internal * @internal
*/ */
#[\AllowDynamicProperties]
abstract class HashMapBase abstract class HashMapBase
{ {
/** @var callable */ /** @var callable */
@@ -45,20 +46,21 @@ abstract class HashMapBase
*/ */
final class HashMap extends HashMapBase final class HashMap extends HashMapBase
{ {
public function __set(string $nm, $val) public function __set(string $nm, mixed $val): void
{ {
if ($nm === '') { if ($nm === '') {
$nm = "\xFF"; $nm = "\xFF";
} }
$this->$nm = $val; $this->$nm = $val;
} }
public function __get(string $nm) public function __get(string $nm): mixed
{ {
if ($nm === '') { if ($nm === '') {
$nm = "\xFF"; $nm = "\xFF";
return isset($this->$nm) ? $this->$nm : $this->$nm = $this->getCallback()(''); return isset($this->$nm) && true ? $this->$nm : $this->$nm = $this->getCallback()('');
} else { } else {
return $this->$nm = $this->getCallback()($nm); return $this->$nm = $this->getCallback()($nm);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,28 +17,21 @@ namespace Dibi;
*/ */
class Result implements IDataSource class Result implements IDataSource
{ {
use Strict; private ?ResultDriver $driver;
/** @var ResultDriver|null */ /** Translate table */
private $driver; private array $types = [];
private ?Reflection\Result $meta;
/** @var array Translate table */ /** Already fetched? Used for allowance for first seek(0) */
private $types = []; private bool $fetched = false;
/** @var Reflection\Result|null */ /** returned object class */
private $meta; private ?string $rowClass = Row::class;
/** @var bool Already fetched? Used for allowance for first seek(0) */
private $fetched = false;
/** @var string|null returned object class */
private $rowClass = Row::class;
/** @var callable|null returned object factory */ /** @var callable|null returned object factory */
private $rowFactory; private $rowFactory;
private array $formats = [];
/** @var array format */
private $formats = [];
public function __construct(ResultDriver $driver, bool $normalize = true) public function __construct(ResultDriver $driver, bool $normalize = true)
@@ -133,7 +126,7 @@ class Result implements IDataSource
/** /**
* Set fetched object class. This class should extend the Row class. * Set fetched object class. This class should extend the Row class.
*/ */
public function setRowClass(?string $class): self public function setRowClass(?string $class): static
{ {
$this->rowClass = $class; $this->rowClass = $class;
return $this; return $this;
@@ -152,7 +145,7 @@ class Result implements IDataSource
/** /**
* Set a factory to create fetched object instances. These should extend the Row class. * Set a factory to create fetched object instances. These should extend the Row class.
*/ */
public function setRowFactory(callable $callback): self public function setRowFactory(callable $callback): static
{ {
$this->rowFactory = $callback; $this->rowFactory = $callback;
return $this; return $this;
@@ -162,14 +155,14 @@ class Result implements IDataSource
/** /**
* Fetches the row at current position, process optional type conversion. * Fetches the row at current position, process optional type conversion.
* and moves the internal cursor to the next position * and moves the internal cursor to the next position
* @return Row|array|null
*/ */
final public function fetch() final public function fetch(): mixed
{ {
$row = $this->getResultDriver()->fetch(true); $row = $this->getResultDriver()->fetch(true);
if ($row === null) { if ($row === null) {
return null; return null;
} }
$this->fetched = true; $this->fetched = true;
$this->normalize($row); $this->normalize($row);
if ($this->rowFactory) { if ($this->rowFactory) {
@@ -177,20 +170,22 @@ class Result implements IDataSource
} elseif ($this->rowClass) { } elseif ($this->rowClass) {
return new $this->rowClass($row); return new $this->rowClass($row);
} }
return $row; return $row;
} }
/** /**
* Like fetch(), but returns only first field. * Like fetch(), but returns only first field.
* @return mixed value on success, null if no next record * Returns value on success, null if no next record
*/ */
final public function fetchSingle() final public function fetchSingle(): mixed
{ {
$row = $this->getResultDriver()->fetch(true); $row = $this->getResultDriver()->fetch(true);
if ($row === null) { if ($row === null) {
return null; return null;
} }
$this->fetched = true; $this->fetched = true;
$this->normalize($row); $this->normalize($row);
return reset($row); return reset($row);
@@ -201,9 +196,9 @@ class Result implements IDataSource
* Fetches all records from table. * Fetches all records from table.
* @return Row[]|array[] * @return Row[]|array[]
*/ */
final public function fetchAll(int $offset = null, int $limit = null): array final public function fetchAll(?int $offset = null, ?int $limit = null): array
{ {
$limit = $limit ?? -1; $limit ??= -1;
$this->seek($offset ?: 0); $this->seek($offset ?: 0);
$row = $this->fetch(); $row = $this->fetch();
if (!$row) { if (!$row) {
@@ -215,6 +210,7 @@ class Result implements IDataSource
if ($limit === 0) { if ($limit === 0) {
break; break;
} }
$limit--; $limit--;
$data[] = $row; $data[] = $row;
} while ($row = $this->fetch()); } while ($row = $this->fetch());
@@ -234,7 +230,7 @@ class Result implements IDataSource
*/ */
final public function fetchAssoc(string $assoc): array final public function fetchAssoc(string $assoc): array
{ {
if (strpos($assoc, ',') !== false) { if (str_contains($assoc, ',')) {
return $this->oldFetchAssoc($assoc); return $this->oldFetchAssoc($assoc);
} }
@@ -287,7 +283,6 @@ class Result implements IDataSource
} else { } else {
$x = &$x->{$assoc[$i + 1]}; $x = &$x->{$assoc[$i + 1]};
} }
} elseif ($as !== '|') { // associative-array node } elseif ($as !== '|') { // associative-array node
$x = &$x[(string) $row->$as]; $x = &$x[(string) $row->$as];
} }
@@ -345,7 +340,6 @@ class Result implements IDataSource
} else { } else {
$x = &$x[$assoc[$i + 1]]; $x = &$x[$assoc[$i + 1]];
} }
} elseif ($as === '@') { // "object" node } elseif ($as === '@') { // "object" node
if ($x === null) { if ($x === null) {
$x = clone $row; $x = clone $row;
@@ -354,7 +348,6 @@ class Result implements IDataSource
} else { } else {
$x = &$x->{$assoc[$i + 1]}; $x = &$x->{$assoc[$i + 1]};
} }
} else { // associative-array node } else { // associative-array node
$x = &$x[(string) $row->$as]; $x = &$x[(string) $row->$as];
} }
@@ -376,7 +369,7 @@ class Result implements IDataSource
* Fetches all records from table like $key => $value pairs. * Fetches all records from table like $key => $value pairs.
* @throws \InvalidArgumentException * @throws \InvalidArgumentException
*/ */
final public function fetchPairs(string $key = null, string $value = null): array final public function fetchPairs(?string $key = null, ?string $value = null): array
{ {
$this->seek(0); $this->seek(0);
$row = $this->fetch(); $row = $this->fetch();
@@ -398,6 +391,7 @@ class Result implements IDataSource
do { do {
$data[] = $row[$key]; $data[] = $row[$key];
} while ($row = $this->fetch()); } while ($row = $this->fetch());
return $data; return $data;
} }
@@ -412,6 +406,7 @@ class Result implements IDataSource
do { do {
$data[] = $row[$value]; $data[] = $row[$value];
} while ($row = $this->fetch()); } while ($row = $this->fetch());
return $data; return $data;
} }
@@ -455,22 +450,29 @@ class Result implements IDataSource
if (!isset($row[$key])) { // null if (!isset($row[$key])) { // null
continue; continue;
} }
$value = $row[$key]; $value = $row[$key];
$format = $this->formats[$type] ?? null; $format = $this->formats[$type] ?? null;
if ($type === null || $format === 'native') { if ($type === null || $format === 'native') {
$row[$key] = $value; $row[$key] = $value;
} elseif ($type === Type::TEXT) { } elseif ($type === Type::Text) {
$row[$key] = (string) $value; $row[$key] = (string) $value;
} elseif ($type === Type::INTEGER) { } elseif ($type === Type::Integer) {
$row[$key] = is_float($tmp = $value * 1) $row[$key] = is_float($tmp = $value * 1)
? (is_string($value) ? $value : (int) $value) ? (is_string($value) ? $value : (int) $value)
: $tmp; : $tmp;
} elseif ($type === Type::FLOAT) { } elseif ($type === Type::Float) {
$value = ltrim((string) $value, '0'); if (!is_string($value)) {
$row[$key] = (float) $value;
continue;
}
$negative = ($value[0] ?? null) === '-';
$value = ltrim($value, '0-');
$p = strpos($value, '.'); $p = strpos($value, '.');
$e = strpos($value, 'e'); $e = strpos($value, 'e');
if ($p !== false && $e === false) { if ($p !== false && $e === false) {
@@ -478,31 +480,36 @@ class Result implements IDataSource
} elseif ($p !== false && $e !== false) { } elseif ($p !== false && $e !== false) {
$value = rtrim($value, '.'); $value = rtrim($value, '.');
} }
if ($value === '' || $value[0] === '.') { if ($value === '' || $value[0] === '.') {
$value = '0' . $value; $value = '0' . $value;
} }
if ($negative) {
$value = '-' . $value;
}
$row[$key] = $value === str_replace(',', '.', (string) ($float = (float) $value)) $row[$key] = $value === str_replace(',', '.', (string) ($float = (float) $value))
? $float ? $float
: $value; : $value;
} elseif ($type === Type::BOOL) { } elseif ($type === Type::Bool) {
$row[$key] = ((bool) $value) && $value !== 'f' && $value !== 'F'; $row[$key] = ((bool) $value) && $value !== 'f' && $value !== 'F';
} elseif ($type === Type::DATETIME || $type === Type::DATE || $type === Type::TIME) { } elseif ($type === Type::DateTime || $type === Type::Date || $type === Type::Time) {
if ($value && substr((string) $value, 0, 3) !== '000') { // '', null, false, '0000-00-00', ... if ($value && !str_starts_with((string) $value, '0000-00')) { // '', null, false, '0000-00-00', ...
$value = new DateTime($value); $value = new DateTime($value);
$row[$key] = $format ? $value->format($format) : $value; $row[$key] = $format ? $value->format($format) : $value;
} else { } else {
$row[$key] = null; $row[$key] = null;
} }
} elseif ($type === Type::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;
@@ -513,7 +520,6 @@ class Result implements IDataSource
} else { } else {
$row[$key] = json_decode($value, $format === 'array'); $row[$key] = json_decode($value, $format === 'array');
} }
} else { } else {
throw new \RuntimeException('Unexpected type ' . $type); throw new \RuntimeException('Unexpected type ' . $type);
} }
@@ -525,7 +531,7 @@ class Result implements IDataSource
* Define column type. * Define column type.
* @param string|null $type use constant Type::* * @param string|null $type use constant Type::*
*/ */
final public function setType(string $column, ?string $type): self final public function setType(string $column, ?string $type): static
{ {
$this->types[$column] = $type; $this->types[$column] = $type;
return $this; return $this;
@@ -553,7 +559,7 @@ class Result implements IDataSource
/** /**
* Sets type format. * Sets type format.
*/ */
final public function setFormat(string $type, ?string $format): self final public function setFormat(string $type, ?string $format): static
{ {
$this->formats[$type] = $format; $this->formats[$type] = $format;
return $this; return $this;
@@ -563,7 +569,7 @@ class Result implements IDataSource
/** /**
* Sets type formats. * Sets type formats.
*/ */
final public function setFormats(array $formats): self final public function setFormats(array $formats): static
{ {
$this->formats = $formats; $this->formats = $formats;
return $this; return $this;
@@ -587,9 +593,10 @@ class Result implements IDataSource
*/ */
public function getInfo(): Reflection\Result public function getInfo(): Reflection\Result
{ {
if ($this->meta === null) { if (!isset($this->meta)) {
$this->meta = new Reflection\Result($this->getResultDriver()); $this->meta = new Reflection\Result($this->getResultDriver());
} }
return $this->meta; return $this->meta;
} }

View File

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

View File

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

View File

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

View File

@@ -15,40 +15,19 @@ namespace Dibi;
*/ */
final class Translator final class Translator
{ {
use Strict; private Connection $connection;
private Driver $driver;
/** @var Connection */ private int $cursor = 0;
private $connection; private array $args;
/** @var Driver */
private $driver;
/** @var int */
private $cursor = 0;
/** @var array */
private $args;
/** @var string[] */ /** @var string[] */
private $errors; private array $errors;
private bool $comment = false;
/** @var bool */ private int $ifLevel = 0;
private $comment = false; private int $ifLevelStart = 0;
private ?int $limit = null;
/** @var int */ private ?int $offset = null;
private $ifLevel = 0; private HashMap $identifiers;
/** @var int */
private $ifLevelStart = 0;
/** @var int|null */
private $limit;
/** @var int|null */
private $offset;
/** @var HashMap */
private $identifiers;
public function __construct(Connection $connection) public function __construct(Connection $connection)
@@ -69,6 +48,7 @@ final class Translator
while (count($args) === 1 && is_array($args[0])) { // implicit array expansion while (count($args) === 1 && is_array($args[0])) { // implicit array expansion
$args = array_values($args[0]); $args = array_values($args[0]);
} }
$this->args = $args; $this->args = $args;
$this->errors = []; $this->errors = [];
@@ -95,27 +75,27 @@ final class Translator
// note: this can change $this->args & $this->cursor & ... // note: this can change $this->args & $this->cursor & ...
. preg_replace_callback( . preg_replace_callback(
<<<'XX' <<<'XX'
/ /
(?=[`['":%?]) ## speed-up (?=[`['":%?]) ## speed-up
(?: (?:
`(.+?)`| ## 1) `identifier` `(.+?)`| ## 1) `identifier`
\[(.+?)\]| ## 2) [identifier] \[(.+?)\]| ## 2) [identifier]
(')((?:''|[^'])*)'| ## 3,4) string (')((?:''|[^'])*)'| ## 3,4) string
(")((?:""|[^"])*)"| ## 5,6) "string" (")((?:""|[^"])*)"| ## 5,6) "string"
('|")| ## 7) lone quote ('|")| ## 7) lone quote
:(\S*?:)([a-zA-Z0-9._]?)| ## 8,9) :substitution: :(\S*?:)([a-zA-Z0-9._]?)| ## 8,9) :substitution:
%([a-zA-Z~][a-zA-Z0-9~]{0,5})| ## 10) modifier %([a-zA-Z~][a-zA-Z0-9~]{0,5})| ## 10) modifier
(\?) ## 11) placeholder (\?) ## 11) placeholder
)/xs )/xs
XX XX,
,
[$this, 'cb'], [$this, 'cb'],
substr($arg, $toSkip) substr($arg, $toSkip),
); );
if (preg_last_error()) { if (preg_last_error()) {
throw new PcreException; throw new PcreException;
} }
} }
continue; continue;
} }
@@ -138,8 +118,10 @@ XX
if ($lastArr === $cursor - 1) { if ($lastArr === $cursor - 1) {
$sql[] = ','; $sql[] = ',';
} }
$sql[] = $this->formatValue($arg, $commandIns ? 'l' : 'a'); $sql[] = $this->formatValue($arg, $commandIns ? 'l' : 'a');
} }
$lastArr = $cursor; $lastArr = $cursor;
continue; continue;
} }
@@ -148,7 +130,6 @@ XX
$sql[] = $this->formatValue($arg, null); $sql[] = $this->formatValue($arg, null);
} // while } // while
if ($comment) { if ($comment) {
$sql[] = '*/'; $sql[] = '*/';
} }
@@ -170,9 +151,8 @@ XX
/** /**
* Apply modifier to single value. * Apply modifier to single value.
* @param mixed $value
*/ */
public function formatValue($value, ?string $modifier): string public function formatValue(mixed $value, ?string $modifier): string
{ {
if ($this->comment) { if ($this->comment) {
return '...'; return '...';
@@ -207,20 +187,21 @@ XX
$v = $this->formatValue($v, $pair[1]); $v = $this->formatValue($v, $pair[1]);
if ($pair[1] === 'l' || $pair[1] === 'in') { if ($pair[1] === 'l' || $pair[1] === 'in') {
$op = 'IN '; $op = 'IN ';
} elseif (strpos($pair[1], 'like') !== false) { } elseif (str_contains($pair[1], 'like')) {
$op = 'LIKE '; $op = 'LIKE ';
} elseif ($v === 'NULL') { } elseif ($v === 'NULL') {
$op = 'IS '; $op = 'IS ';
} else { } else {
$op = '= '; $op = '= ';
} }
$vx[] = $k . $op . $v; $vx[] = $k . $op . $v;
} }
} else { } else {
$vx[] = $this->formatValue($v, 'ex'); $vx[] = $this->formatValue($v, 'ex');
} }
} }
return '(' . implode(') ' . strtoupper($modifier) . ' (', $vx) . ')'; return '(' . implode(') ' . strtoupper($modifier) . ' (', $vx) . ')';
case 'n': // key, key, ... identifier names case 'n': // key, key, ... identifier names
@@ -232,15 +213,17 @@ XX
$vx[] = $this->identifiers->{$pair[0]}; $vx[] = $this->identifiers->{$pair[0]};
} }
} }
return implode(', ', $vx); return implode(', ', $vx);
case 'a': // key=val, key=val, ... case 'a': // key=val, key=val, ...
foreach ($value as $k => $v) { foreach ($value as $k => $v) {
$pair = explode('%', $k, 2); // split into identifier & modifier $pair = explode('%', (string) $k, 2); // split into identifier & modifier
$vx[] = $this->identifiers->{$pair[0]} . '=' $vx[] = $this->identifiers->{$pair[0]} . '='
. $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null)); . $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null));
} }
return implode(', ', $vx); return implode(', ', $vx);
@@ -250,6 +233,7 @@ XX
$pair = explode('%', (string) $k, 2); // split into identifier & modifier $pair = explode('%', (string) $k, 2); // split into identifier & modifier
$vx[] = $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null)); $vx[] = $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null));
} }
return '(' . (($vx || $modifier === 'l') ? implode(', ', $vx) : 'NULL') . ')'; return '(' . (($vx || $modifier === 'l') ? implode(', ', $vx) : 'NULL') . ')';
@@ -259,6 +243,7 @@ XX
$kx[] = $this->identifiers->{$pair[0]}; $kx[] = $this->identifiers->{$pair[0]};
$vx[] = $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null)); $vx[] = $this->formatValue($v, $pair[1] ?? (is_array($v) ? 'ex!' : null));
} }
return '(' . implode(', ', $kx) . ') VALUES (' . implode(', ', $vx) . ')'; return '(' . implode(', ', $kx) . ') VALUES (' . implode(', ', $vx) . ')';
case 'm': // (key, key, ...) VALUES (val, val, ...), (val, val, ...), ... case 'm': // (key, key, ...) VALUES (val, val, ...), (val, val, ...), ...
@@ -272,7 +257,7 @@ XX
$proto = array_keys($v); $proto = array_keys($v);
} }
} else { } else {
return $this->errors[] = '**Unexpected type ' . (is_object($v) ? get_class($v) : gettype($v)) . '**'; return $this->errors[] = '**Unexpected type ' . get_debug_type($v) . '**';
} }
$pair = explode('%', $k, 2); // split into identifier & modifier $pair = explode('%', $k, 2); // split into identifier & modifier
@@ -281,9 +266,11 @@ XX
$vx[$k2][] = $this->formatValue($v2, $pair[1] ?? (is_array($v2) ? 'ex!' : null)); $vx[$k2][] = $this->formatValue($v2, $pair[1] ?? (is_array($v2) ? 'ex!' : null));
} }
} }
foreach ($vx as $k => $v) { foreach ($vx as $k => $v) {
$vx[$k] = '(' . implode(', ', $v) . ')'; $vx[$k] = '(' . implode(', ', $v) . ')';
} }
return '(' . implode(', ', $kx) . ') VALUES ' . implode(', ', $vx); return '(' . implode(', ', $kx) . ') VALUES ' . implode(', ', $vx);
case 'by': // key ASC, key DESC case 'by': // key ASC, key DESC
@@ -297,6 +284,7 @@ XX
$vx[] = $this->identifiers->$v; $vx[] = $this->identifiers->$v;
} }
} }
return implode(', ', $vx); return implode(', ', $vx);
case 'ex!': case 'ex!':
@@ -310,10 +298,24 @@ XX
foreach ($value as $v) { foreach ($value as $v) {
$vx[] = $this->formatValue($v, $modifier); $vx[] = $this->formatValue($v, $modifier);
} }
return implode(', ', $vx); return implode(', ', $vx);
} }
} }
if (is_object($value)
&& $modifier === null
&& !$value instanceof Literal
&& !$value instanceof Expression
&& $result = $this->connection->translateObject($value)
) {
return $this->connection->translate(...$result->getValues());
}
// object-to-scalar procession
if ($value instanceof \BackedEnum && is_scalar($value->value)) {
$value = $value->value;
}
// with modifier procession // with modifier procession
if ($modifier) { if ($modifier) {
@@ -333,7 +335,7 @@ XX
) { ) {
// continue // continue
} else { } else {
$type = is_object($value) ? get_class($value) : gettype($value); $type = get_debug_type($value);
return $this->errors[] = "**Invalid combination of type $type and modifier %$modifier**"; return $this->errors[] = "**Invalid combination of type $type and modifier %$modifier**";
} }
} }
@@ -400,6 +402,7 @@ XX
} elseif (!$value instanceof \DateTimeInterface) { } elseif (!$value instanceof \DateTimeInterface) {
$value = new DateTime($value); $value = new DateTime($value);
} }
return $modifier === 'd' return $modifier === 'd'
? $this->driver->escapeDate($value) ? $this->driver->escapeDate($value)
: $this->driver->escapeDateTime($value); : $this->driver->escapeDateTime($value);
@@ -420,25 +423,25 @@ XX
$value = substr($value, 0, $toSkip) $value = substr($value, 0, $toSkip)
. preg_replace_callback( . preg_replace_callback(
<<<'XX' <<<'XX'
/ /
(?=[`['":]) (?=[`['":])
(?: (?:
`(.+?)`| `(.+?)`|
\[(.+?)\]| \[(.+?)]|
(')((?:''|[^'])*)'| (')((?:''|[^'])*)'|
(")((?:""|[^"])*)"| (")((?:""|[^"])*)"|
('|")| (['"])|
:(\S*?:)([a-zA-Z0-9._]?) :(\S*?:)([a-zA-Z0-9._]?)
)/sx )/sx
XX XX,
,
[$this, 'cb'], [$this, 'cb'],
substr($value, $toSkip) substr($value, $toSkip),
); );
if (preg_last_error()) { if (preg_last_error()) {
throw new PcreException; throw new PcreException;
} }
} }
return $value; return $value;
case 'SQL': // preserve as real SQL (TODO: rename to %sql) case 'SQL': // preserve as real SQL (TODO: rename to %sql)
@@ -469,7 +472,6 @@ XX
} }
} }
// without modifier procession // without modifier procession
if (is_string($value)) { if (is_string($value)) {
return $this->driver->escapeText($value); return $this->driver->escapeText($value);
@@ -499,7 +501,7 @@ XX
return $this->connection->translate(...$value->getValues()); return $this->connection->translate(...$value->getValues());
} else { } else {
$type = is_object($value) ? get_class($value) : gettype($value); $type = get_debug_type($value);
return $this->errors[] = "**Unexpected $type**"; return $this->errors[] = "**Unexpected $type**";
} }
} }
@@ -551,6 +553,7 @@ XX
$this->comment = true; $this->comment = true;
return '/*'; return '/*';
} }
return ''; return '';
} elseif ($mod === 'else') { } elseif ($mod === 'else') {
@@ -563,7 +566,6 @@ XX
$this->comment = true; $this->comment = true;
return '/*'; return '/*';
} }
} elseif ($mod === 'end') { } elseif ($mod === 'end') {
$this->ifLevel--; $this->ifLevel--;
if ($this->ifLevelStart === $this->ifLevel + 1) { if ($this->ifLevelStart === $this->ifLevel + 1) {
@@ -572,6 +574,7 @@ XX
$this->comment = false; $this->comment = false;
return '*/'; return '*/';
} }
return ''; return '';
} elseif ($mod === 'ex') { // array expansion } elseif ($mod === 'ex') { // array expansion
@@ -586,6 +589,7 @@ XX
} else { } else {
$this->limit = Helpers::intVal($arg); $this->limit = Helpers::intVal($arg);
} }
return ''; return '';
} elseif ($mod === 'ofs') { // apply offset } elseif ($mod === 'ofs') { // apply offset
@@ -596,6 +600,7 @@ XX
} else { } else {
$this->offset = Helpers::intVal($arg); $this->offset = Helpers::intVal($arg);
} }
return ''; return '';
} else { // default processing } else { // default processing
@@ -649,6 +654,7 @@ XX
$v = $this->driver->escapeIdentifier($v); $v = $this->driver->escapeIdentifier($v);
} }
} }
return implode('.', $parts); return implode('.', $parts);
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
# for php-coveralls # for php-coveralls
service_name: travis-ci service_name: github-actions
coverage_clover: coverage.xml coverage_clover: coverage.xml
json_path: coverage.json json_path: coverage.json

View File

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

View File

@@ -0,0 +1,89 @@
[sqlite] ; default
driver = sqlite
database = :memory:
system = sqlite
[sqlite pdo]
driver = pdo
dsn = "sqlite::memory:"
system = sqlite
[mysql 5.7]
driver = mysqli
host = "127.0.0.1"
database = dibi_test
username = root
password = root
port = 3306
system = mysql
[mysql 5.7pdo]
driver = pdo
dsn = "mysql:host=127.0.0.1;port=3306;dbname=dibi_test"
user = root
password = root
system = mysql
[mysql 8.0]
driver = mysqli
host = "127.0.0.1"
database = dibi_test
username = root
password = root
port = 3307
system = mysql
[mysql 8.0pdo]
driver = pdo
dsn = "mysql:host=127.0.0.1;port=3307;dbname=dibi_test"
user = root
password = root
system = mysql
[postgre 9.6]
driver = postgre
host = "127.0.0.1"
database = dibi_test
username = postgres
password = postgres
port = 5432
system = postgre
[postgre 9.6pdo]
driver = pdo
dsn = "pgsql:host=127.0.0.1;port=5432;dbname=dibi_test"
user = postgres
password = postgres
system = postgre
[postgre 13]
driver = postgre
host = "127.0.0.1"
database = dibi_test
username = postgres
password = postgres
port = 5433
system = postgre
[postgre 13pdo]
driver = pdo
dsn = "pgsql:host=127.0.0.1;port=5433;dbname=dibi_test"
user = postgres
password = postgres
system = postgre
[sqlsrv]
driver = sqlsrv
host = "localhost"
username = SA
password = "YourStrong!Passw0rd"
database = dibi_test
port = 1433
system = sqlsrv
;[sqlsrv pdo]
;driver = pdo
;dsn = "sqlsrv:Server=localhost,1433;Database=dibi_test"
;user = SA
;password = "YourStrong!Passw0rd"
;system = sqlsrv

View File

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

View File

@@ -1,38 +0,0 @@
[sqlite] ; default
driver = sqlite
database = :memory:
system = sqlite
[sqlite pdo]
driver = pdo
dsn = "sqlite::memory:"
system = sqlite
[mysql improved]
driver = mysqli
host = 127.0.0.1
username = root
password =
charset = utf8
system = mysql
[mysql pdo]
driver = pdo
dsn = "mysql:host=127.0.0.1"
username = root
password =
system = mysql
[postgre]
driver = postgre
host = 127.0.0.1
username = postgres
password =
system = postgre
[postgre pdo]
driver = pdo
dsn = "pgsql:host=127.0.0.1;dbname=dibi_test"
username = postgres
password =
system = postgre

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,57 +15,131 @@ $conn = new Dibi\Connection($config);
$conn->loadFile(__DIR__ . "/data/$config[system].sql"); $conn->loadFile(__DIR__ . "/data/$config[system].sql");
/*Assert::exception(function () use ($conn) { /*
$conn->rollback(); Assert::exception(
}, Dibi\Exception::class); fn() => $conn->rollback(),
Dibi\Exception::class,
);
Assert::exception(function () use ($conn) { Assert::exception(
$conn->commit(); fn() => $conn->commit(),
}, Dibi\Exception::class); Dibi\Exception::class,
);
$conn->begin(); $conn->begin();
Assert::exception(function () use ($conn) { Assert::exception(
$conn->begin(); fn() => $conn->begin(),
}, Dibi\Exception::class); Dibi\Exception::class,
);
*/ */
$conn->begin(); test('begin() & rollback()', function () use ($conn) {
Assert::same(3, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle()); $conn->begin();
$conn->query('INSERT INTO [products]', [ Assert::same(3, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
'title' => 'Test product',
]);
Assert::same(4, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
$conn->rollback();
Assert::same(3, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
$conn->begin();
$conn->query('INSERT INTO [products]', [
'title' => 'Test product',
]);
$conn->commit();
Assert::same(4, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
Assert::exception(function () use ($conn) {
$conn->transaction(function () use ($conn) {
$conn->query('INSERT INTO [products]', [
'title' => 'Test product',
]);
throw new Exception('my exception');
});
}, \Throwable::class, 'my exception');
Assert::same(4, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
$conn->transaction(function () use ($conn) {
$conn->query('INSERT INTO [products]', [ $conn->query('INSERT INTO [products]', [
'title' => 'Test product', 'title' => 'Test product',
]); ]);
Assert::same(4, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
$conn->rollback();
Assert::same(3, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
}); });
Assert::same(5, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
test('begin() & commit()', function () use ($conn) {
$conn->begin();
$conn->query('INSERT INTO [products]', [
'title' => 'Test product',
]);
$conn->commit();
Assert::same(4, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
});
test('transaction() fail', function () use ($conn) {
Assert::exception(
fn() => $conn->transaction(function (Dibi\Connection $connection) {
$connection->query('INSERT INTO [products]', [
'title' => 'Test product',
]);
throw new Exception('my exception');
}),
Throwable::class,
'my exception',
);
Assert::same(4, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
});
test('transaction() success', function () use ($conn) {
$conn->transaction(function (Dibi\Connection $connection) {
$connection->query('INSERT INTO [products]', [
'title' => 'Test product',
]);
});
Assert::same(5, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
});
test('nested transaction() call fail', function () use ($conn) {
Assert::exception(
fn() => $conn->transaction(function (Dibi\Connection $connection) {
$connection->query('INSERT INTO [products]', [
'title' => 'Test product',
]);
$connection->transaction(function (Dibi\Connection $connection2) {
$connection2->query('INSERT INTO [products]', [
'title' => 'Test product',
]);
throw new Exception('my exception');
});
}),
Throwable::class,
'my exception',
);
Assert::same(5, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
});
test('nested transaction() call success', function () use ($conn) {
$conn->transaction(function (Dibi\Connection $connection) {
$connection->query('INSERT INTO [products]', [
'title' => 'Test product',
]);
$connection->transaction(function (Dibi\Connection $connection2) {
$connection2->query('INSERT INTO [products]', [
'title' => 'Test product',
]);
});
});
Assert::same(7, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
});
test('begin(), commit() & rollback() calls are forbidden in transaction()', function () use ($conn) {
Assert::exception(
fn() => $conn->transaction(function (Dibi\Connection $connection) {
$connection->begin();
}),
LogicException::class,
Dibi\Connection::class . '::begin() call is forbidden inside a transaction() callback',
);
Assert::exception(
fn() => $conn->transaction(function (Dibi\Connection $connection) {
$connection->commit();
}),
LogicException::class,
Dibi\Connection::class . '::commit() call is forbidden inside a transaction() callback',
);
Assert::exception(
fn() => $conn->transaction(function (Dibi\Connection $connection) {
$connection->rollback();
}),
LogicException::class,
Dibi\Connection::class . '::rollback() call is forbidden inside a transaction() callback',
);
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -13,22 +13,28 @@ function buildPdoDriver(?int $errorMode)
if ($errorMode !== null) { if ($errorMode !== null) {
$pdo->setAttribute(PDO::ATTR_ERRMODE, $errorMode); $pdo->setAttribute(PDO::ATTR_ERRMODE, $errorMode);
} }
new Dibi\Drivers\PdoDriver(['resource' => $pdo]); new Dibi\Drivers\PdoDriver(['resource' => $pdo]);
} }
// PDO error mode: exception // PDO error mode: exception
Assert::exception(function () { Assert::exception(
buildPdoDriver(PDO::ERRMODE_EXCEPTION); fn() => buildPdoDriver(PDO::ERRMODE_EXCEPTION),
}, Dibi\DriverException::class, 'PDO connection in exception or warning error mode is not supported.'); Dibi\DriverException::class,
'PDO connection in exception or warning error mode is not supported.',
);
// PDO error mode: warning // PDO error mode: warning
Assert::exception(function () { Assert::exception(
buildPdoDriver(PDO::ERRMODE_WARNING); fn() => buildPdoDriver(PDO::ERRMODE_WARNING),
}, Dibi\DriverException::class, 'PDO connection in exception or warning error mode is not supported.'); Dibi\DriverException::class,
'PDO connection in exception or warning error mode is not supported.',
);
test('PDO error mode: explicitly set silent', function () { test(
buildPdoDriver(PDO::ERRMODE_SILENT); 'PDO error mode: explicitly set silent',
}); fn() => buildPdoDriver(PDO::ERRMODE_SILENT)
);

View File

@@ -28,14 +28,14 @@ Assert::same(4, $res->getColumnCount());
Assert::same( Assert::same(
['product_id', 'order_id', 'name', 'xXx'], ['product_id', 'order_id', 'name', 'xXx'],
$info->getColumnNames() $info->getColumnNames(),
); );
if (!in_array($config['driver'], ['sqlite', 'pdo', 'sqlsrv'], true)) { if (!in_array($config['driver'], ['sqlite', 'sqlite3', 'pdo', 'sqlsrv'], true)) {
Assert::same( Assert::same(
['products.product_id', 'orders.order_id', 'customers.name', 'xXx'], ['products.product_id', 'orders.order_id', 'customers.name', 'xXx'],
$info->getColumnNames(true) $info->getColumnNames(true),
); );
} }
@@ -43,7 +43,7 @@ if (!in_array($config['driver'], ['sqlite', 'pdo', 'sqlsrv'], true)) {
$columns = $info->getColumns(); $columns = $info->getColumns();
Assert::same('product_id', $columns[0]->getName()); Assert::same('product_id', $columns[0]->getName());
if (!in_array($config['driver'], ['sqlite', 'pdo', 'sqlsrv'], true)) { if (!in_array($config['driver'], ['sqlite', 'sqlite3', 'pdo', 'sqlsrv'], true)) {
Assert::same('products', $columns[0]->getTableName()); Assert::same('products', $columns[0]->getTableName());
} }
Assert::null($columns[0]->getVendorInfo('xxx')); Assert::null($columns[0]->getVendorInfo('xxx'));

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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