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

Compare commits

...

46 Commits

Author SHA1 Message Date
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
David Grudl
d09b462eef Released version 4.2.1 2021-03-01 15:17:15 +01:00
David Grudl
4c796a0e0f readme: added support me 2021-02-05 21:53:00 +01:00
Jan Kuchař
304af5185d PostgreSQL driver: escaping of save point name (#383) 2021-02-05 21:52:45 +01:00
David Grudl
e046935137 Strict: refactoring 2020-12-30 12:44:04 +01:00
Jan Kuchař
1a77048225 PostgreReflector: removed version check (#381) 2020-12-23 10:31:03 +01:00
David Grudl
ca74488636 Released version 4.2.0 2020-11-25 21:02:22 +01:00
David Grudl
07f994a0b5 added Connection::transaction() 2020-11-25 20:59:58 +01:00
David Grudl
5fa5acb724 Connection: added option [result][formatTimeInterval] that sets time-interval column decoding 2020-11-02 15:28:28 +01:00
David Grudl
98563d8165 Connection: added option [result][normalize] [Closes #367] 2020-11-02 15:28:28 +01:00
David Grudl
c464960239 Connection, Result: added format 'native' 2020-11-02 15:28:28 +01:00
David Grudl
a9e90d0b22 Connection, Results: refactorings, added Result::setFormats() 2020-11-02 15:28:28 +01:00
David Grudl
decb30de1e Connection::translateArg() removed (BC break) 2020-11-02 15:28:28 +01:00
David Grudl
f4e71e8855 requires PHP 7.2 2020-11-02 15:28:28 +01:00
David Grudl
39f59e0f08 opened 4.2-dev 2020-11-02 15:28:28 +01:00
David Grudl
0d2f643795 strict comparison 2020-11-02 15:27:54 +01:00
David Grudl
70d4246866 Tracy\Panel: table is sortable 2020-11-02 15:05:55 +01:00
David Grudl
34a1665915 coding style 2020-11-02 15:05:55 +01:00
David Grudl
b27db4a9aa updated gitattributes 2020-11-02 15:05:55 +01:00
David Grudl
212dd1ae55 updated phpstan 2020-11-02 15:05:55 +01:00
David Grudl
b9683f8a3c updated nette/coding-standard 2020-11-02 15:05:55 +01:00
David Grudl
177a800bff PdoDriver: changes error mode do ERRMODE_SILENT (for PHP 8.0) 2020-11-02 15:05:55 +01:00
David Grudl
fa6a1203a9 fixed compatibility with PHP 8 [Closes #379] 2020-11-02 15:05:55 +01:00
David Grudl
3f7171c7a4 tested on PHP 8.0 2020-10-30 13:50:54 +01:00
David Grudl
e4b6e769ee Dibi\Helpers::getSuggestion(): item may be an int, type cast fix (#378) 2020-10-14 12:00:23 +02:00
David Grudl
24d0b069d8 Helpers::getSuggestion(): support for reflection objects moved to Strict 2020-10-14 12:00:04 +02:00
58 changed files with 840 additions and 415 deletions

6
.gitattributes vendored
View File

@@ -2,5 +2,9 @@
.gitignore export-ignore .gitignore export-ignore
.github export-ignore .github export-ignore
.travis.yml export-ignore .travis.yml export-ignore
appveyor.yml export-ignore ecs.php export-ignore
phpstan.neon export-ignore
tests/ export-ignore tests/ export-ignore
*.sh eol=lf
*.php* diff=php linguist-language=PHP

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@v2
- 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@v2
- 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 --ignore-platform-reqs
- 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@v2
- 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

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

@@ -0,0 +1,121 @@
name: Tests
on: [push, pull_request]
env:
php-extensions: mbstring, intl, mysqli, pgsql, sqlsrv-5.9.0preview1, pdo_sqlsrv-5.9.0preview1
php-tools: "composer:v2, pecl"
jobs:
tests:
runs-on: ubuntu-latest
strategy:
matrix:
php: ['7.2', '7.3', '7.4', '8.0', '8.1']
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
--entrypoint sh mysql:8 -c "exec docker-entrypoint.sh mysqld --default-authentication-plugin=mysql_native_password"
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-tools/bin/sqlcmd -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'SELECT 1'"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v2
- 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-tools/bin/sqlcmd -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'CREATE DATABASE dibi_test'
- 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@v2
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,77 +0,0 @@
language: php
php:
- 7.1
- 7.2
- 7.3
- 7.4
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
php: 7.4
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 ^2 --no-progress
script:
- php temp/coding-standard/ecs check src tests examples --config tests/coding-standard.yml
- 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
sudo: false
cache:
directories:
- $HOME/.composer/cache
notifications:
email: false

View File

@@ -15,17 +15,17 @@ init:
- SET ANSICON=121x90 (121x90) - SET ANSICON=121x90 (121x90)
install: install:
# Install PHP 7.1 # Install PHP 7.2
- IF EXIST c:\php7 (SET PHP=0) ELSE (SET PHP=1) - IF EXIST c:\php7 (SET PHP=0) ELSE (SET PHP=1)
- IF %PHP%==1 mkdir c:\php7 - IF %PHP%==1 mkdir c:\php7
- IF %PHP%==1 cd c:\php7 - IF %PHP%==1 cd c:\php7
- 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-7.2.18-Win32-VC15-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.8.0/Windows-7.2.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-7.2\x64\php_sqlsrv_72_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

View File

@@ -11,7 +11,7 @@
} }
], ],
"require": { "require": {
"php": ">=7.1" "php": ">=7.2"
}, },
"require-dev": { "require-dev": {
"tracy/tracy": "~2.2", "tracy/tracy": "~2.2",
@@ -26,12 +26,12 @@
"classmap": ["src/"] "classmap": ["src/"]
}, },
"scripts": { "scripts": {
"phpstan": "phpstan analyse --autoload-file vendor/autoload.php --level 5 --configuration tests/phpstan.neon src", "phpstan": "phpstan analyse",
"tester": "tester tests -s" "tester": "tester tests -s"
}, },
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "4.1-dev" "dev-master": "4.2-dev"
} }
} }
} }

21
ecs.php Normal file
View File

@@ -0,0 +1,21 @@
<?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

@@ -1,4 +1,9 @@
parameters: parameters:
level: 5
paths:
- src
ignoreErrors: ignoreErrors:
# The namespace is referenced, not the class. # The namespace is referenced, not the class.
- '#Class dibi referenced with incorrect case: Dibi#' - '#Class dibi referenced with incorrect case: Dibi#'

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)
@@ -15,12 +15,14 @@ Database access functions in PHP are not standardised. This library
hides the differences between them, and above all, it gives you a very handy interface. hides the differences between them, and above all, it gives you a very handy interface.
Support Project Support Me
--------------- ----------
Do you like Dibi? Are you looking forward to the new features? Do you like Dibi? Are you looking forward to the new features?
[![Donate](https://files.nette.org/icons/donation-1.svg?)](https://nette.org/make-donation?to=dibi) [![Buy me a coffee](https://files.nette.org/icons/donation-3.svg)](https://github.com/sponsors/dg)
Thank you!
Installation Installation
@@ -32,7 +34,7 @@ Install Dibi via Composer:
composer require dibi/dibi composer require dibi/dibi
``` ```
The Dibi 4.1 requires PHP version 7.1 and supports PHP up to 7.4. The Dibi 4.2 requires PHP version 7.2 and supports PHP up to 8.1.
Usage Usage

View File

@@ -22,10 +22,14 @@ class DibiExtension22 extends Nette\DI\CompilerExtension
/** @var bool|null */ /** @var bool|null */
private $debugMode; private $debugMode;
/** @var bool|null */
private $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 +42,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']);

View File

@@ -45,7 +45,7 @@ class Panel implements Tracy\IBarPanel
public function register(Dibi\Connection $connection): void public function register(Dibi\Connection $connection): void
{ {
Tracy\Debugger::getBar()->addPanel($this); Tracy\Debugger::getBar()->addPanel($this);
Tracy\Debugger::getBlueScreen()->addPanel([__CLASS__, 'renderException']); Tracy\Debugger::getBlueScreen()->addPanel([self::class, 'renderException']);
$connection->onEvent[] = [$this, 'logEvent']; $connection->onEvent[] = [$this, 'logEvent'];
} }
@@ -121,7 +121,9 @@ class Panel implements Tracy\IBarPanel
if ($this->explain && $event->type === Event::SELECT) { if ($this->explain && $event->type === Event::SELECT) {
$backup = [$connection->onEvent, \dibi::$numOfQueries, \dibi::$totalTime]; $backup = [$connection->onEvent, \dibi::$numOfQueries, \dibi::$totalTime];
$connection->onEvent = null; $connection->onEvent = null;
$cmd = is_string($this->explain) ? $this->explain : ($connection->getConfig('driver') === 'oracle' ? 'EXPLAIN PLAN FOR' : 'EXPLAIN'); $cmd = is_string($this->explain)
? $this->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"), true);
} catch (Dibi\Exception $e) { } catch (Dibi\Exception $e) {
@@ -129,7 +131,7 @@ class Panel implements Tracy\IBarPanel
[$connection->onEvent, \dibi::$numOfQueries, \dibi::$totalTime] = $backup; [$connection->onEvent, \dibi::$numOfQueries, \dibi::$totalTime] = $backup;
} }
$s .= '<tr><td>' . number_format($event->time * 1000, 3, '.', ''); $s .= '<tr><td data-order="' . $event->time . '">' . number_format($event->time * 1000, 3, '.', '');
if ($explain) { if ($explain) {
static $counter; static $counter;
$counter++; $counter++;
@@ -141,7 +143,7 @@ class Panel implements Tracy\IBarPanel
$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');
} }
$s .= "</td><td>{$event->count}</td>"; $s .= "</td><td>{$event->count}</td>";
@@ -157,7 +159,7 @@ class Panel implements Tracy\IBarPanel
. ($totalTime === null ? '' : ', time: ' . number_format($totalTime * 1000, 1, '.', '') . 'ms') . ', ' . ($totalTime === null ? '' : ', time: ' . number_format($totalTime * 1000, 1, '.', '') . 'ms') . ', '
. htmlspecialchars($this->getConnectionName($singleConnection)) . '</h1> . htmlspecialchars($this->getConnectionName($singleConnection)) . '</h1>
<div class="tracy-inner tracy-DibiProfiler"> <div class="tracy-inner tracy-DibiProfiler">
<table> <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>
' . $s . ' ' . $s . '
</table> </table>

View File

@@ -28,6 +28,9 @@ class Connection implements IConnection
/** @var array Current connection configuration */ /** @var array Current connection configuration */
private $config; private $config;
/** @var string[] resultset formats */
private $formats;
/** @var Driver|null */ /** @var Driver|null */
private $driver; private $driver;
@@ -37,17 +40,26 @@ class Connection implements IConnection
/** @var HashMap Substitutes for identifiers */ /** @var HashMap Substitutes for identifiers */
private $substitutes; private $substitutes;
private $transactionDepth = 0;
/** /**
* Connection options: (see driver-specific options too) * Connection options: (see driver-specific options too)
* - lazy (bool) => if true, connection will be established only when required * - lazy (bool) => if true, connection will be established only when required
* - result (array) => result set options * - result (array) => result set options
* - formatDateTime => date-time format (if empty, DateTime objects will be returned) * - normalize => normalizes result fields (default: true)
* - formatJson => json format ( * - formatDateTime => date-time format
* "string" for leaving value as is, * empty for decoding as Dibi\DateTime (default)
* "object" for decoding json as \stdClass, * "..." formatted according to given format, see https://www.php.net/manual/en/datetime.format.php
* "array" for decoding json as an array - default * "native" for leaving value as is
* ) * - formatTimeInterval => time-interval format
* empty for decoding as DateInterval (default)
* "..." formatted according to given format, see https://www.php.net/manual/en/dateinterval.format.php
* "native" for leaving value as is
* - formatJson => json format
* "array" for decoding json as an array (default)
* "object" for decoding json as \stdClass
* "native" for leaving value as is
* - profiler (array) * - profiler (array)
* - run (bool) => enable profiler? * - run (bool) => enable profiler?
* - file => file to log * - file => file to log
@@ -65,9 +77,15 @@ class Connection implements IConnection
Helpers::alias($config, 'result|formatDateTime', 'resultDateTime'); Helpers::alias($config, 'result|formatDateTime', 'resultDateTime');
$config['driver'] = $config['driver'] ?? 'mysqli'; $config['driver'] = $config['driver'] ?? 'mysqli';
$config['name'] = $name; $config['name'] = $name;
$config['result']['formatJson'] = $config['result']['formatJson'] ?? 'array';
$this->config = $config; $this->config = $config;
$this->formats = [
Type::DATE => $this->config['result']['formatDate'],
Type::DATETIME => $this->config['result']['formatDateTime'],
Type::JSON => $this->config['result']['formatJson'] ?? 'array',
Type::TIME_INTERVAL => $this->config['result']['formatTimeInterval'] ?? null,
];
// profiler // profiler
if (isset($config['profiler']['file']) && (!isset($config['profiler']['run']) || $config['profiler']['run'])) { if (isset($config['profiler']['file']) && (!isset($config['profiler']['run']) || $config['profiler']['run'])) {
$filter = $config['profiler']['filter'] ?? Event::QUERY; $filter = $config['profiler']['filter'] ?? Event::QUERY;
@@ -200,7 +218,7 @@ class Connection implements IConnection
*/ */
final public function query(...$args): Result final public function query(...$args): Result
{ {
return $this->nativeQuery($this->translateArgs($args)); return $this->nativeQuery($this->translate(...$args));
} }
@@ -211,7 +229,10 @@ class Connection implements IConnection
*/ */
final public function translate(...$args): string final public function translate(...$args): string
{ {
return $this->translateArgs($args); if (!$this->driver) {
$this->connect();
}
return (clone $this->translator)->translate($args);
} }
@@ -222,7 +243,7 @@ class Connection implements IConnection
final public function test(...$args): bool final public function test(...$args): bool
{ {
try { try {
Helpers::dump($this->translateArgs($args)); Helpers::dump($this->translate(...$args));
return true; return true;
} catch (Exception $e) { } catch (Exception $e) {
@@ -243,19 +264,7 @@ class Connection implements IConnection
*/ */
final public function dataSource(...$args): DataSource final public function dataSource(...$args): DataSource
{ {
return new DataSource($this->translateArgs($args), $this); return new DataSource($this->translate(...$args), $this);
}
/**
* Generates SQL query.
*/
protected function translateArgs(array $args): string
{
if (!$this->driver) {
$this->connect();
}
return (clone $this->translator)->translate($args);
} }
@@ -328,6 +337,10 @@ class Connection implements IConnection
*/ */
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();
} }
@@ -352,6 +365,10 @@ class Connection implements IConnection
*/ */
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();
} }
@@ -376,6 +393,10 @@ class Connection implements IConnection
*/ */
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();
} }
@@ -395,15 +416,42 @@ class Connection implements IConnection
} }
/**
* @return mixed
*/
public function transaction(callable $callback)
{
if ($this->transactionDepth === 0) {
$this->begin();
}
$this->transactionDepth++;
try {
$res = $callback($this);
} catch (\Throwable $e) {
$this->transactionDepth--;
if ($this->transactionDepth === 0) {
$this->rollback();
}
throw $e;
}
$this->transactionDepth--;
if ($this->transactionDepth === 0) {
$this->commit();
}
return $res;
}
/** /**
* Result set factory. * Result set factory.
*/ */
public function createResultSet(ResultDriver $resultDriver): Result public function createResultSet(ResultDriver $resultDriver): Result
{ {
$res = new Result($resultDriver); return (new Result($resultDriver, $this->config['result']['normalize'] ?? true))
return $res->setFormat(Type::DATE, $this->config['result']['formatDate']) ->setFormats($this->formats);
->setFormat(Type::DATETIME, $this->config['result']['formatDateTime'])
->setFormat(Type::JSON, $this->config['result']['formatJson']);
} }
@@ -562,7 +610,7 @@ class Connection implements IConnection
*/ */
public function __wakeup() public function __wakeup()
{ {
throw new NotSupportedException('You cannot serialize or unserialize ' . get_class($this) . ' instances.'); throw new NotSupportedException('You cannot serialize or unserialize ' . static::class . ' instances.');
} }
@@ -571,7 +619,7 @@ class Connection implements IConnection
*/ */
public function __sleep() public function __sleep()
{ {
throw new NotSupportedException('You cannot serialize or unserialize ' . get_class($this) . ' instances.'); throw new NotSupportedException('You cannot serialize or unserialize ' . static::class . ' instances.');
} }

View File

@@ -53,11 +53,9 @@ class DataSource implements IDataSource
*/ */
public function __construct(string $sql, Connection $connection) public function __construct(string $sql, Connection $connection)
{ {
if (strpbrk($sql, " \t\r\n") === false) { $this->sql = strpbrk($sql, " \t\r\n") === false
$this->sql = $connection->getDriver()->escapeIdentifier($sql); // table name ? $connection->getDriver()->escapeIdentifier($sql) // table name
} else { : '(' . $sql . ') t'; // SQL command
$this->sql = '(' . $sql . ') t'; // SQL command
}
$this->connection = $connection; $this->connection = $connection;
} }
@@ -84,12 +82,9 @@ class DataSource implements IDataSource
*/ */
public function where($cond): self public function where($cond): self
{ {
if (is_array($cond)) { $this->conds[] = is_array($cond)
// TODO: not consistent with select and orderBy ? $cond // TODO: not consistent with select and orderBy
$this->conds[] = $cond; : func_get_args();
} else {
$this->conds[] = func_get_args();
}
$this->result = $this->count = null; $this->result = $this->count = null;
return $this; return $this;
} }
@@ -232,12 +227,18 @@ class DataSource implements IDataSource
public function __toString(): string public function __toString(): string
{ {
try { try {
return $this->connection->translate(' return $this->connection->translate(
SELECT %n', (empty($this->cols) ? '*' : $this->cols), ' "\nSELECT %n",
FROM %SQL', $this->sql, ' (empty($this->cols) ? '*' : $this->cols),
%ex', $this->conds ? ['WHERE %and', $this->conds] : null, ' "\nFROM %SQL",
%ex', $this->sorting ? ['ORDER BY %by', $this->sorting] : null, ' $this->sql,
%ofs %lmt', $this->offset, $this->limit "\n%ex",
$this->conds ? ['WHERE %and', $this->conds] : null,
"\n%ex",
$this->sorting ? ['ORDER BY %by', $this->sorting] : null,
"\n%ofs %lmt",
$this->offset,
$this->limit
); );
} catch (\Throwable $e) { } catch (\Throwable $e) {
trigger_error($e->getMessage(), E_USER_ERROR); trigger_error($e->getMessage(), E_USER_ERROR);

View File

@@ -62,11 +62,9 @@ class FirebirdDriver implements Dibi\Driver
'buffers' => 0, 'buffers' => 0,
]; ];
if (empty($config['persistent'])) { $this->connection = empty($config['persistent'])
$this->connection = @ibase_connect($config['database'], $config['username'], $config['password'], $config['charset'], $config['buffers']); // intentionally @ ? @ibase_connect($config['database'], $config['username'], $config['password'], $config['charset'], $config['buffers']) // intentionally @
} else { : @ibase_pconnect($config['database'], $config['username'], $config['password'], $config['charset'], $config['buffers']); // intentionally @
$this->connection = @ibase_pconnect($config['database'], $config['username'], $config['password'], $config['charset'], $config['buffers']); // intentionally @
}
if (!is_resource($this->connection)) { if (!is_resource($this->connection)) {
throw new Dibi\DriverException(ibase_errmsg(), ibase_errcode()); throw new Dibi\DriverException(ibase_errmsg(), ibase_errcode());
@@ -90,11 +88,13 @@ class FirebirdDriver implements Dibi\Driver
*/ */
public function query(string $sql): ?Dibi\ResultDriver public function query(string $sql): ?Dibi\ResultDriver
{ {
$resource = $this->inTransaction ? $this->transaction : $this->connection; $resource = $this->inTransaction
? $this->transaction
: $this->connection;
$res = ibase_query($resource, $sql); $res = ibase_query($resource, $sql);
if ($res === false) { if ($res === false) {
if (ibase_errcode() == self::ERROR_EXCEPTION_THROWN) { if (ibase_errcode() === self::ERROR_EXCEPTION_THROWN) {
preg_match('/exception (\d+) (\w+) (.*)/i', ibase_errmsg(), $match); preg_match('/exception (\d+) (\w+) (.*)/i', ibase_errmsg(), $match);
throw new Dibi\ProcedureException($match[3], $match[1], $match[2], $sql); throw new Dibi\ProcedureException($match[3], $match[1], $match[2], $sql);

View File

@@ -38,8 +38,8 @@ class FirebirdReflector implements Dibi\Reflector
SELECT TRIM(RDB\$RELATION_NAME), SELECT TRIM(RDB\$RELATION_NAME),
CASE RDB\$VIEW_BLR WHEN NULL THEN 'TRUE' ELSE 'FALSE' END CASE RDB\$VIEW_BLR WHEN NULL THEN 'TRUE' ELSE 'FALSE' END
FROM RDB\$RELATIONS FROM RDB\$RELATIONS
WHERE RDB\$SYSTEM_FLAG = 0;" WHERE RDB\$SYSTEM_FLAG = 0;
); ");
$tables = []; $tables = [];
while ($row = $res->fetch(false)) { while ($row = $res->fetch(false)) {
$tables[] = [ $tables[] = [
@@ -84,9 +84,8 @@ class FirebirdReflector implements Dibi\Reflector
FROM RDB\$RELATION_FIELDS r FROM RDB\$RELATION_FIELDS r
LEFT JOIN RDB\$FIELDS f ON r.RDB\$FIELD_SOURCE = f.RDB\$FIELD_NAME LEFT JOIN RDB\$FIELDS f ON r.RDB\$FIELD_SOURCE = f.RDB\$FIELD_NAME
WHERE r.RDB\$RELATION_NAME = '$table' WHERE r.RDB\$RELATION_NAME = '$table'
ORDER BY r.RDB\$FIELD_POSITION;" ORDER BY r.RDB\$FIELD_POSITION;
");
);
$columns = []; $columns = [];
while ($row = $res->fetch(true)) { while ($row = $res->fetch(true)) {
$key = $row['FIELD_NAME']; $key = $row['FIELD_NAME'];
@@ -121,8 +120,8 @@ class FirebirdReflector implements Dibi\Reflector
LEFT JOIN RDB\$INDICES i ON i.RDB\$INDEX_NAME = s.RDB\$INDEX_NAME LEFT JOIN RDB\$INDICES i ON i.RDB\$INDEX_NAME = s.RDB\$INDEX_NAME
LEFT JOIN RDB\$RELATION_CONSTRAINTS r ON r.RDB\$INDEX_NAME = s.RDB\$INDEX_NAME LEFT JOIN RDB\$RELATION_CONSTRAINTS r ON r.RDB\$INDEX_NAME = s.RDB\$INDEX_NAME
WHERE UPPER(i.RDB\$RELATION_NAME) = '$table' WHERE UPPER(i.RDB\$RELATION_NAME) = '$table'
ORDER BY s.RDB\$FIELD_POSITION" ORDER BY s.RDB\$FIELD_POSITION
); ");
$indexes = []; $indexes = [];
while ($row = $res->fetch(true)) { while ($row = $res->fetch(true)) {
$key = $row['INDEX_NAME']; $key = $row['INDEX_NAME'];
@@ -149,8 +148,8 @@ class FirebirdReflector implements Dibi\Reflector
LEFT JOIN RDB\$RELATION_CONSTRAINTS r ON r.RDB\$INDEX_NAME = s.RDB\$INDEX_NAME LEFT JOIN RDB\$RELATION_CONSTRAINTS r ON r.RDB\$INDEX_NAME = s.RDB\$INDEX_NAME
WHERE UPPER(i.RDB\$RELATION_NAME) = '$table' WHERE UPPER(i.RDB\$RELATION_NAME) = '$table'
AND r.RDB\$CONSTRAINT_TYPE = 'FOREIGN KEY' AND r.RDB\$CONSTRAINT_TYPE = 'FOREIGN KEY'
ORDER BY s.RDB\$FIELD_POSITION" ORDER BY s.RDB\$FIELD_POSITION
); ");
$keys = []; $keys = [];
while ($row = $res->fetch(true)) { while ($row = $res->fetch(true)) {
$key = $row['INDEX_NAME']; $key = $row['INDEX_NAME'];
@@ -174,8 +173,8 @@ class FirebirdReflector implements Dibi\Reflector
FROM RDB\$INDICES FROM RDB\$INDICES
WHERE RDB\$RELATION_NAME = UPPER('$table') WHERE RDB\$RELATION_NAME = UPPER('$table')
AND RDB\$UNIQUE_FLAG IS NULL AND RDB\$UNIQUE_FLAG IS NULL
AND RDB\$FOREIGN_KEY IS NULL;" AND RDB\$FOREIGN_KEY IS NULL;
); ");
$indices = []; $indices = [];
while ($row = $res->fetch(false)) { while ($row = $res->fetch(false)) {
$indices[] = $row[0]; $indices[] = $row[0];
@@ -196,8 +195,8 @@ class FirebirdReflector implements Dibi\Reflector
AND ( AND (
RDB\$UNIQUE_FLAG IS NOT NULL RDB\$UNIQUE_FLAG IS NOT NULL
OR RDB\$FOREIGN_KEY IS NOT NULL OR RDB\$FOREIGN_KEY IS NOT NULL
);" );
); ");
$constraints = []; $constraints = [];
while ($row = $res->fetch(false)) { while ($row = $res->fetch(false)) {
$constraints[] = $row[0]; $constraints[] = $row[0];
@@ -212,7 +211,8 @@ class FirebirdReflector implements Dibi\Reflector
*/ */
public function getTriggersMeta(string $table = null): array public function getTriggersMeta(string $table = null): array
{ {
$res = $this->driver->query(" $res = $this->driver->query(
"
SELECT TRIM(RDB\$TRIGGER_NAME) AS TRIGGER_NAME, SELECT TRIM(RDB\$TRIGGER_NAME) AS TRIGGER_NAME,
TRIM(RDB\$RELATION_NAME) AS TABLE_NAME, TRIM(RDB\$RELATION_NAME) AS TABLE_NAME,
CASE RDB\$TRIGGER_TYPE CASE RDB\$TRIGGER_TYPE
@@ -261,7 +261,9 @@ class FirebirdReflector implements Dibi\Reflector
$q = 'SELECT TRIM(RDB$TRIGGER_NAME) $q = 'SELECT TRIM(RDB$TRIGGER_NAME)
FROM RDB$TRIGGERS FROM RDB$TRIGGERS
WHERE RDB$SYSTEM_FLAG = 0'; WHERE RDB$SYSTEM_FLAG = 0';
$q .= $table === null ? ';' : " AND RDB\$RELATION_NAME = UPPER('$table')"; $q .= $table === null
? ';'
: " AND RDB\$RELATION_NAME = UPPER('$table')";
$res = $this->driver->query($q); $res = $this->driver->query($q);
$triggers = []; $triggers = [];
@@ -307,8 +309,8 @@ class FirebirdReflector implements Dibi\Reflector
p.RDB\$PARAMETER_NUMBER AS PARAMETER_NUMBER p.RDB\$PARAMETER_NUMBER AS PARAMETER_NUMBER
FROM RDB\$PROCEDURE_PARAMETERS p FROM RDB\$PROCEDURE_PARAMETERS p
LEFT JOIN RDB\$FIELDS f ON f.RDB\$FIELD_NAME = p.RDB\$FIELD_SOURCE LEFT JOIN RDB\$FIELDS f ON f.RDB\$FIELD_NAME = p.RDB\$FIELD_SOURCE
ORDER BY p.RDB\$PARAMETER_TYPE, p.RDB\$PARAMETER_NUMBER;" ORDER BY p.RDB\$PARAMETER_TYPE, p.RDB\$PARAMETER_NUMBER;
); ");
$procedures = []; $procedures = [];
while ($row = $res->fetch(true)) { while ($row = $res->fetch(true)) {
$key = $row['PROCEDURE_NAME']; $key = $row['PROCEDURE_NAME'];
@@ -330,8 +332,8 @@ class FirebirdReflector implements Dibi\Reflector
{ {
$res = $this->driver->query(' $res = $this->driver->query('
SELECT TRIM(RDB$PROCEDURE_NAME) SELECT TRIM(RDB$PROCEDURE_NAME)
FROM RDB$PROCEDURES;' FROM RDB$PROCEDURES;
); ');
$procedures = []; $procedures = [];
while ($row = $res->fetch(false)) { while ($row = $res->fetch(false)) {
$procedures[] = $row[0]; $procedures[] = $row[0];
@@ -348,8 +350,8 @@ class FirebirdReflector implements Dibi\Reflector
$res = $this->driver->query(' $res = $this->driver->query('
SELECT TRIM(RDB$GENERATOR_NAME) SELECT TRIM(RDB$GENERATOR_NAME)
FROM RDB$GENERATORS FROM RDB$GENERATORS
WHERE RDB$SYSTEM_FLAG = 0;' WHERE RDB$SYSTEM_FLAG = 0;
); ');
$generators = []; $generators = [];
while ($row = $res->fetch(false)) { while ($row = $res->fetch(false)) {
$generators[] = $row[0]; $generators[] = $row[0];
@@ -366,8 +368,8 @@ class FirebirdReflector implements Dibi\Reflector
$res = $this->driver->query(' $res = $this->driver->query('
SELECT TRIM(RDB$FUNCTION_NAME) SELECT TRIM(RDB$FUNCTION_NAME)
FROM RDB$FUNCTIONS FROM RDB$FUNCTIONS
WHERE RDB$SYSTEM_FLAG = 0;' WHERE RDB$SYSTEM_FLAG = 0;
); ');
$functions = []; $functions = [];
while ($row = $res->fetch(false)) { while ($row = $res->fetch(false)) {
$functions[] = $row[0]; $functions[] = $row[0];

View File

@@ -62,10 +62,12 @@ class FirebirdResult implements Dibi\ResultDriver
*/ */
public function fetch(bool $assoc): ?array public function fetch(bool $assoc): ?array
{ {
$result = $assoc ? @ibase_fetch_assoc($this->resultSet, IBASE_TEXT) : @ibase_fetch_row($this->resultSet, IBASE_TEXT); // intentionally @ $result = $assoc
? @ibase_fetch_assoc($this->resultSet, IBASE_TEXT)
: @ibase_fetch_row($this->resultSet, IBASE_TEXT); // intentionally @
if (ibase_errcode()) { if (ibase_errcode()) {
if (ibase_errcode() == FirebirdDriver::ERROR_EXCEPTION_THROWN) { if (ibase_errcode() === FirebirdDriver::ERROR_EXCEPTION_THROWN) {
preg_match('/exception (\d+) (\w+) (.*)/is', ibase_errmsg(), $match); preg_match('/exception (\d+) (\w+) (.*)/is', ibase_errmsg(), $match);
throw new Dibi\ProcedureException($match[3], $match[1], $match[2]); throw new Dibi\ProcedureException($match[3], $match[1], $match[2]);

View File

@@ -72,7 +72,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;
@@ -200,7 +200,9 @@ class MySqliDriver implements Dibi\Driver
*/ */
public function getAffectedRows(): ?int public function getAffectedRows(): ?int
{ {
return $this->connection->affected_rows === -1 ? null : $this->connection->affected_rows; return $this->connection->affected_rows === -1
? null
: $this->connection->affected_rows;
} }

View File

@@ -54,11 +54,9 @@ class OdbcDriver implements Dibi\Driver
'dsn' => ini_get('odbc.default_db'), 'dsn' => ini_get('odbc.default_db'),
]; ];
if (empty($config['persistent'])) { $this->connection = empty($config['persistent'])
$this->connection = @odbc_connect($config['dsn'], $config['username'] ?? '', $config['password'] ?? ''); // intentionally @ ? @odbc_connect($config['dsn'], $config['username'] ?? '', $config['password'] ?? '') // intentionally @
} else { : @odbc_pconnect($config['dsn'], $config['username'] ?? '', $config['password'] ?? ''); // intentionally @
$this->connection = @odbc_pconnect($config['dsn'], $config['username'] ?? '', $config['password'] ?? ''); // intentionally @
}
} }
if (!is_resource($this->connection)) { if (!is_resource($this->connection)) {
@@ -94,7 +92,9 @@ class OdbcDriver implements Dibi\Driver
} elseif (is_resource($res)) { } elseif (is_resource($res)) {
$this->affectedRows = Dibi\Helpers::false2Null(odbc_num_rows($res)); $this->affectedRows = Dibi\Helpers::false2Null(odbc_num_rows($res));
return odbc_num_fields($res) ? $this->createResultDriver($res) : null; return odbc_num_fields($res)
? $this->createResultDriver($res)
: null;
} }
return null; return null;
} }
@@ -124,7 +124,7 @@ class OdbcDriver implements Dibi\Driver
*/ */
public function begin(string $savepoint = null): void public function begin(string $savepoint = null): void
{ {
if (!odbc_autocommit($this->connection, 0/*false*/)) { if (!odbc_autocommit($this->connection, PHP_VERSION_ID < 80000 ? 0 : false)) {
throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection)); throw new Dibi\DriverException(odbc_errormsg($this->connection) . ' ' . odbc_error($this->connection));
} }
} }
@@ -139,7 +139,7 @@ class OdbcDriver implements Dibi\Driver
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, 1/*true*/); odbc_autocommit($this->connection, PHP_VERSION_ID < 80000 ? 1 : true);
} }
@@ -152,7 +152,7 @@ class OdbcDriver implements Dibi\Driver
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, 1/*true*/); odbc_autocommit($this->connection, PHP_VERSION_ID < 80000 ? 1 : true);
} }

View File

@@ -96,7 +96,9 @@ class OracleDriver implements Dibi\Driver
} elseif (is_resource($res)) { } elseif (is_resource($res)) {
$this->affectedRows = Dibi\Helpers::false2Null(oci_num_rows($res)); $this->affectedRows = Dibi\Helpers::false2Null(oci_num_rows($res));
return oci_num_fields($res) ? $this->createResultDriver($res) : null; return oci_num_fields($res)
? $this->createResultDriver($res)
: null;
} }
} else { } else {
$err = oci_error($this->connection); $err = oci_error($this->connection);

View File

@@ -56,9 +56,15 @@ class PdoDriver implements Dibi\Driver
if ($config['resource'] instanceof PDO) { if ($config['resource'] instanceof PDO) {
$this->connection = $config['resource']; $this->connection = $config['resource'];
unset($config['resource'], $config['pdo']); unset($config['resource'], $config['pdo']);
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.');
}
} 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']);
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
} catch (\PDOException $e) { } catch (\PDOException $e) {
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.');
@@ -67,10 +73,6 @@ class PdoDriver implements Dibi\Driver
} }
} }
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.');
}
$this->driverName = $this->connection->getAttribute(PDO::ATTR_DRIVER_NAME); $this->driverName = $this->connection->getAttribute(PDO::ATTR_DRIVER_NAME);
$this->serverVersion = (string) ($config['version'] ?? @$this->connection->getAttribute(PDO::ATTR_SERVER_VERSION)); // @ - may be not supported $this->serverVersion = (string) ($config['version'] ?? @$this->connection->getAttribute(PDO::ATTR_SERVER_VERSION)); // @ - may be not supported
} }
@@ -232,21 +234,17 @@ class PdoDriver implements Dibi\Driver
*/ */
public function escapeText(string $value): string public function escapeText(string $value): string
{ {
if ($this->driverName === 'odbc') { return $this->driverName === 'odbc'
return "'" . str_replace("'", "''", $value) . "'"; ? "'" . str_replace("'", "''", $value) . "'"
} else { : $this->connection->quote($value, PDO::PARAM_STR);
return $this->connection->quote($value, PDO::PARAM_STR);
}
} }
public function escapeBinary(string $value): string public function escapeBinary(string $value): string
{ {
if ($this->driverName === 'odbc') { return $this->driverName === 'odbc'
return "'" . str_replace("'", "''", $value) . "'"; ? "'" . str_replace("'", "''", $value) . "'"
} else { : $this->connection->quote($value, PDO::PARAM_LOB);
return $this->connection->quote($value, PDO::PARAM_LOB);
}
} }

View File

@@ -85,7 +85,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 + [ $row += [
'table' => null, 'table' => null,
'native_type' => 'VAR_STRING', 'native_type' => 'VAR_STRING',
]; ];

View File

@@ -11,6 +11,7 @@ namespace Dibi\Drivers;
use Dibi; use Dibi;
use Dibi\Helpers; use Dibi\Helpers;
use PgSql;
/** /**
@@ -29,7 +30,7 @@ class PostgreDriver implements Dibi\Driver
{ {
use Dibi\Strict; use Dibi\Strict;
/** @var resource */ /** @var resource|PgSql\Connection */
private $connection; private $connection;
/** @var int|null Affected rows */ /** @var int|null Affected rows */
@@ -68,15 +69,13 @@ class PostgreDriver implements Dibi\Driver
set_error_handler(function (int $severity, string $message) use (&$error) { set_error_handler(function (int $severity, string $message) use (&$error) {
$error = $message; $error = $message;
}); });
if (empty($config['persistent'])) { $this->connection = empty($config['persistent'])
$this->connection = pg_connect($string, $connectType); ? pg_connect($string, $connectType)
} else { : pg_pconnect($string, $connectType);
$this->connection = pg_pconnect($string, $connectType);
}
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.');
} }
@@ -122,7 +121,7 @@ class PostgreDriver implements Dibi\Driver
if ($res === false) { if ($res === false) {
throw static::createException(pg_last_error($this->connection), null, $sql); throw static::createException(pg_last_error($this->connection), null, $sql);
} elseif (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);
@@ -171,12 +170,9 @@ class PostgreDriver implements Dibi\Driver
*/ */
public function getInsertId(?string $sequence): ?int public function getInsertId(?string $sequence): ?int
{ {
if ($sequence === null) { $res = $sequence === null
// PostgreSQL 8.1 is needed ? $this->query('SELECT LASTVAL()') // PostgreSQL 8.1 is needed
$res = $this->query('SELECT LASTVAL()'); : $this->query("SELECT CURRVAL('$sequence')");
} else {
$res = $this->query("SELECT CURRVAL('$sequence')");
}
if (!$res) { if (!$res) {
return null; return null;
@@ -193,7 +189,7 @@ class PostgreDriver implements Dibi\Driver
*/ */
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 {$this->escapeIdentifier($savepoint)}" : 'START TRANSACTION');
} }
@@ -203,7 +199,7 @@ class PostgreDriver implements Dibi\Driver
*/ */
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 {$this->escapeIdentifier($savepoint)}" : 'COMMIT');
} }
@@ -213,7 +209,7 @@ class PostgreDriver implements Dibi\Driver
*/ */
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 {$this->escapeIdentifier($savepoint)}" : 'ROLLBACK');
} }
@@ -232,7 +228,9 @@ class PostgreDriver implements Dibi\Driver
*/ */
public function getResource() public function getResource()
{ {
return is_resource($this->connection) ? $this->connection : null; return is_resource($this->connection) || $this->connection instanceof PgSql\Connection
? $this->connection
: null;
} }
@@ -263,7 +261,7 @@ 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) . "'";
@@ -272,7 +270,7 @@ class PostgreDriver implements Dibi\Driver
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) . "'";

View File

@@ -28,9 +28,6 @@ class PostgreReflector implements Dibi\Reflector
public function __construct(Dibi\Driver $driver, string $version) public function __construct(Dibi\Driver $driver, string $version)
{ {
if ($version < 7.4) {
throw new Dibi\DriverException('Reflection requires PostgreSQL 7.4 and newer.');
}
$this->driver = $driver; $this->driver = $driver;
$this->version = $version; $this->version = $version;
} }
@@ -105,7 +102,7 @@ class PostgreReflector implements Dibi\Reflector
a.atttypmod-4 AS character_maximum_length, a.atttypmod-4 AS character_maximum_length,
NOT a.attnotnull AS is_nullable, NOT a.attnotnull AS is_nullable,
a.attnum AS ordinal_position, a.attnum AS ordinal_position,
adef.adsrc AS column_default pg_get_expr(adef.adbin, adef.adrelid) AS column_default
FROM FROM
pg_attribute a pg_attribute a
JOIN pg_type ON a.atttypid = pg_type.oid JOIN pg_type ON a.atttypid = pg_type.oid
@@ -251,7 +248,10 @@ class PostgreReflector implements Dibi\Reflector
$references[$row['name']] = array_combine($l, $f); $references[$row['name']] = array_combine($l, $f);
} }
if (isset($references[$row['name']][$row['lnum']]) && $references[$row['name']][$row['lnum']] === $row['fnum']) { if (
isset($references[$row['name']][$row['lnum']])
&& $references[$row['name']][$row['lnum']] === $row['fnum']
) {
$fKeys[$row['name']]['local'][] = $row['local']; $fKeys[$row['name']]['local'][] = $row['local'];
$fKeys[$row['name']]['foreign'][] = $row['foreign']; $fKeys[$row['name']]['foreign'][] = $row['foreign'];
} }

View File

@@ -11,6 +11,7 @@ namespace Dibi\Drivers;
use Dibi; use Dibi;
use Dibi\Helpers; use Dibi\Helpers;
use PgSql;
/** /**
@@ -20,7 +21,7 @@ class PostgreResult implements Dibi\ResultDriver
{ {
use Dibi\Strict; use Dibi\Strict;
/** @var resource */ /** @var resource|PgSql\Result */
private $resultSet; private $resultSet;
/** @var bool */ /** @var bool */
@@ -28,7 +29,7 @@ class PostgreResult implements Dibi\ResultDriver
/** /**
* @param resource $resultSet * @param resource|PgSql\Result $resultSet
*/ */
public function __construct($resultSet) public function __construct($resultSet)
{ {
@@ -97,7 +98,9 @@ class PostgreResult implements Dibi\ResultDriver
'table' => pg_field_table($this->resultSet, $i), 'table' => pg_field_table($this->resultSet, $i),
'nativetype' => pg_field_type($this->resultSet, $i), 'nativetype' => pg_field_type($this->resultSet, $i),
]; ];
$row['fullname'] = $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name']; $row['fullname'] = $row['table']
? $row['table'] . '.' . $row['name']
: $row['name'];
$columns[] = $row; $columns[] = $row;
} }
return $columns; return $columns;
@@ -106,12 +109,14 @@ class PostgreResult implements Dibi\ResultDriver
/** /**
* Returns the result set resource. * Returns the result set resource.
* @return resource|null * @return resource|PgSql\Result|null
*/ */
public function getResultResource() public function getResultResource()
{ {
$this->autoFree = false; $this->autoFree = false;
return is_resource($this->resultSet) ? $this->resultSet : null; return is_resource($this->resultSet) || $this->resultSet instanceof PgSql\Result
? $this->resultSet
: null;
} }

View File

@@ -286,8 +286,12 @@ class SqliteDriver implements Dibi\Driver
/** /**
* Registers an aggregating user defined function for use in SQL statements. * Registers an aggregating user defined function for use in SQL statements.
*/ */
public function registerAggregateFunction(string $name, callable $rowCallback, callable $agrCallback, int $numArgs = -1): void public function registerAggregateFunction(
{ string $name,
callable $rowCallback,
callable $agrCallback,
int $numArgs = -1
): void {
$this->connection->createAggregate($name, $rowCallback, $agrCallback, $numArgs); $this->connection->createAggregate($name, $rowCallback, $agrCallback, $numArgs);
} }
} }

View File

@@ -64,7 +64,7 @@ class SqliteReflector implements Dibi\Reflector
'fullname' => "$table.$column", 'fullname' => "$table.$column",
'nativetype' => strtoupper($type[0]), 'nativetype' => strtoupper($type[0]),
'size' => isset($type[1]) ? (int) $type[1] : null, 'size' => isset($type[1]) ? (int) $type[1] : null,
'nullable' => $row['notnull'] == '0', 'nullable' => $row['notnull'] === 0,
'default' => $row['dflt_value'], 'default' => $row['dflt_value'],
'autoincrement' => $row['pk'] && $type[0] === 'INTEGER', 'autoincrement' => $row['pk'] && $type[0] === 'INTEGER',
'vendor' => $row, 'vendor' => $row,
@@ -98,7 +98,7 @@ class SqliteReflector implements Dibi\Reflector
$column = $indexes[$index]['columns'][0]; $column = $indexes[$index]['columns'][0];
$primary = false; $primary = false;
foreach ($columns as $info) { foreach ($columns as $info) {
if ($column == $info['name']) { if ($column === $info['name']) {
$primary = $info['vendor']['pk']; $primary = $info['vendor']['pk'];
break; break;
} }

View File

@@ -53,6 +53,9 @@ 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'];
@@ -63,13 +66,15 @@ class SqlsrvDriver implements Dibi\Driver
$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'];
} }
@@ -98,7 +103,9 @@ class SqlsrvDriver implements Dibi\Driver
} elseif (is_resource($res)) { } elseif (is_resource($res)) {
$this->affectedRows = Helpers::false2Null(sqlsrv_rows_affected($res)); $this->affectedRows = Helpers::false2Null(sqlsrv_rows_affected($res));
return sqlsrv_num_fields($res) ? $this->createResultDriver($res) : null; return sqlsrv_num_fields($res)
? $this->createResultDriver($res)
: null;
} }
return null; return null;
} }

View File

@@ -119,7 +119,7 @@ class Fluent implements IDataSource
$this->connection = $connection; $this->connection = $connection;
if (self::$normalizer === null) { if (self::$normalizer === null) {
self::$normalizer = new HashMap([__CLASS__, '_formatClause']); self::$normalizer = new HashMap([self::class, '_formatClause']);
} }
} }
@@ -311,11 +311,9 @@ class Fluent implements IDataSource
*/ */
public function fetch() public function fetch()
{ {
if ($this->command === 'SELECT' && !$this->clauses['LIMIT']) { return $this->command === 'SELECT' && !$this->clauses['LIMIT']
return $this->query($this->_export(null, ['%lmt', 1]))->fetch(); ? $this->query($this->_export(null, ['%lmt', 1]))->fetch()
} else { : $this->query($this->_export())->fetch();
return $this->query($this->_export())->fetch();
}
} }
@@ -325,11 +323,9 @@ class Fluent implements IDataSource
*/ */
public function fetchSingle() public function fetchSingle()
{ {
if ($this->command === 'SELECT' && !$this->clauses['LIMIT']) { return $this->command === 'SELECT' && !$this->clauses['LIMIT']
return $this->query($this->_export(null, ['%lmt', 1]))->fetchSingle(); ? $this->query($this->_export(null, ['%lmt', 1]))->fetchSingle()
} else { : $this->query($this->_export())->fetchSingle();
return $this->query($this->_export())->fetchSingle();
}
} }

View File

@@ -143,8 +143,8 @@ class Helpers
{ {
$best = null; $best = null;
$min = (strlen($value) / 4 + 1) * 10 + .1; $min = (strlen($value) / 4 + 1) * 10 + .1;
foreach (array_unique($items, SORT_REGULAR) as $item) { $items = array_map('strval', $items);
$item = is_object($item) ? $item->getName() : $item; foreach (array_unique($items) as $item) {
if (($len = levenshtein($item, $value, 10, 11, 10)) > 0 && $len < $min) { if (($len = levenshtein($item, $value, 10, 11, 10)) > 0 && $len < $min) {
$min = $len; $min = $len;
$best = $item; $best = $item;
@@ -181,6 +181,7 @@ class Helpers
{ {
static $patterns = [ static $patterns = [
'^_' => Type::TEXT, // PostgreSQL arrays '^_' => Type::TEXT, // PostgreSQL arrays
'RANGE$' => Type::TEXT, // PostgreSQL range types
'BYTEA|BLOB|BIN' => Type::BINARY, 'BYTEA|BLOB|BIN' => Type::BINARY,
'TEXT|CHAR|POINT|INTERVAL|STRING' => Type::TEXT, 'TEXT|CHAR|POINT|INTERVAL|STRING' => Type::TEXT,
'YEAR|BYTE|COUNTER|SERIAL|INT|LONG|SHORT|^TINY$' => Type::INTEGER, 'YEAR|BYTE|COUNTER|SERIAL|INT|LONG|SHORT|^TINY$' => Type::INTEGER,
@@ -205,7 +206,7 @@ class Helpers
public static function getTypeCache(): HashMap public static function getTypeCache(): HashMap
{ {
if (self::$types === null) { if (self::$types === null) {
self::$types = new HashMap([__CLASS__, 'detectType']); self::$types = new HashMap([self::class, 'detectType']);
} }
return self::$types; return self::$types;
} }

View File

@@ -72,7 +72,9 @@ class Column
public function getTableName(): ?string public function getTableName(): ?string
{ {
return isset($this->info['table']) && $this->info['table'] != null ? $this->info['table'] : null; // intentionally == return isset($this->info['table']) && $this->info['table'] != null // intentionally ==
? $this->info['table']
: null;
} }

View File

@@ -82,7 +82,9 @@ class Result
{ {
if ($this->columns === null) { if ($this->columns === null) {
$this->columns = []; $this->columns = [];
$reflector = $this->driver instanceof Dibi\Reflector ? $this->driver : null; $reflector = $this->driver instanceof Dibi\Reflector
? $this->driver
: null;
foreach ($this->driver->getResultColumns() as $info) { foreach ($this->driver->getResultColumns() as $info) {
$this->columns[] = $this->names[strtolower($info['name'])] = new Column($reflector, $info); $this->columns[] = $this->names[strtolower($info['name'])] = new Column($reflector, $info);
} }

View File

@@ -41,10 +41,12 @@ class Result implements IDataSource
private $formats = []; private $formats = [];
public function __construct(ResultDriver $driver) public function __construct(ResultDriver $driver, bool $normalize = true)
{ {
$this->driver = $driver; $this->driver = $driver;
$this->detectTypes(); if ($normalize) {
$this->detectTypes();
}
} }
@@ -83,7 +85,9 @@ class Result implements IDataSource
*/ */
final public function seek(int $row): bool final public function seek(int $row): bool
{ {
return ($row !== 0 || $this->fetched) ? $this->getResultDriver()->seek($row) : true; return ($row !== 0 || $this->fetched)
? $this->getResultDriver()->seek($row)
: true;
} }
@@ -199,7 +203,7 @@ class Result implements IDataSource
*/ */
final public function fetchAll(int $offset = null, int $limit = null): array final public function fetchAll(int $offset = null, int $limit = null): array
{ {
$limit = $limit === null ? -1 : $limit; $limit = $limit ?? -1;
$this->seek($offset ?: 0); $this->seek($offset ?: 0);
$row = $this->fetch(); $row = $this->fetch();
if (!$row) { if (!$row) {
@@ -357,11 +361,9 @@ class Result implements IDataSource
} }
if ($x === null) { // build leaf if ($x === null) { // build leaf
if ($leaf === '=') { $x = $leaf === '='
$x = $row->toArray(); ? $row->toArray()
} else { : $row;
$x = $row;
}
} }
} while ($row = $this->fetch()); } while ($row = $this->fetch());
@@ -454,8 +456,12 @@ class Result implements IDataSource
continue; continue;
} }
$value = $row[$key]; $value = $row[$key];
$format = $this->formats[$type] ?? null;
if ($type === Type::TEXT) { if ($type === null || $format === 'native') {
$row[$key] = $value;
} elseif ($type === Type::TEXT) {
$row[$key] = (string) $value; $row[$key] = (string) $value;
} elseif ($type === Type::INTEGER) { } elseif ($type === Type::INTEGER) {
@@ -483,31 +489,31 @@ class Result implements IDataSource
$row[$key] = ((bool) $value) && $value !== 'f' && $value !== 'F'; $row[$key] = ((bool) $value) && $value !== 'f' && $value !== 'F';
} elseif ($type === Type::DATETIME || $type === Type::DATE || $type === Type::TIME) { } elseif ($type === Type::DATETIME || $type === Type::DATE || $type === Type::TIME) {
if ($value && substr((string) $value, 0, 3) !== '000') { // '', null, false, '0000-00-00', ... if ($value && substr((string) $value, 0, 7) !== '0000-00') { // '', null, false, '0000-00-00', ...
$value = new DateTime($value); $value = new DateTime($value);
$row[$key] = empty($this->formats[$type]) ? $value : $value->format($this->formats[$type]); $row[$key] = $format ? $value->format($format) : $value;
} else { } else {
$row[$key] = null; $row[$key] = null;
} }
} elseif ($type === Type::TIME_INTERVAL) { } elseif ($type === Type::TIME_INTERVAL) {
preg_match('#^(-?)(\d+)\D(\d+)\D(\d+)\z#', $value, $m); preg_match('#^(-?)(\d+)\D(\d+)\D(\d+)\z#', $value, $m);
$row[$key] = 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");
$row[$key]->invert = (int) (bool) $m[1]; $value->invert = (int) (bool) $m[1];
$row[$key] = $format ? $value->format($format) : $value;
} elseif ($type === Type::BINARY) { } elseif ($type === Type::BINARY) {
$row[$key] = is_string($value) ? $this->getResultDriver()->unescapeBinary($value) : $value; $row[$key] = is_string($value)
? $this->getResultDriver()->unescapeBinary($value)
: $value;
} elseif ($type === Type::JSON) { } elseif ($type === Type::JSON) {
if ($this->formats[$type] === 'string') { if ($format === 'string') { // back compatibility with 'native'
$row[$key] = $value; $row[$key] = $value;
} else { } else {
$row[$key] = json_decode($value, $this->formats[$type] === 'array'); $row[$key] = json_decode($value, $format === 'array');
} }
} elseif ($type === null) {
$row[$key] = $value;
} else { } else {
throw new \RuntimeException('Unexpected type ' . $type); throw new \RuntimeException('Unexpected type ' . $type);
} }
@@ -545,7 +551,7 @@ class Result implements IDataSource
/** /**
* Sets date format. * Sets type format.
*/ */
final public function setFormat(string $type, ?string $format): self final public function setFormat(string $type, ?string $format): self
{ {
@@ -554,6 +560,16 @@ class Result implements IDataSource
} }
/**
* Sets type formats.
*/
final public function setFormats(array $formats): self
{
$this->formats = $formats;
return $this;
}
/** /**
* Returns data format. * Returns data format.
*/ */

View File

@@ -44,6 +44,7 @@ class ResultIterator implements \Iterator, \Countable
} }
#[\ReturnTypeWillChange]
/** /**
* Returns the key of the current element. * Returns the key of the current element.
* @return mixed * @return mixed
@@ -54,6 +55,7 @@ class ResultIterator implements \Iterator, \Countable
} }
#[\ReturnTypeWillChange]
/** /**
* Returns the current element. * Returns the current element.
* @return mixed * @return mixed

View File

@@ -37,7 +37,7 @@ class Row implements \ArrayAccess, \IteratorAggregate, \Countable
{ {
$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 || substr((string) $time, 0, 7) === '0000-00') { // '', null, false, '0000-00-00', ...
return null; return null;
} }
$time = new DateTime($time); $time = new DateTime($time);
@@ -53,40 +53,47 @@ class Row implements \ArrayAccess, \IteratorAggregate, \Countable
} }
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;
} }
#[\ReturnTypeWillChange]
final public function offsetGet($nm) final public function offsetGet($nm)
{ {
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

@@ -29,9 +29,12 @@ trait Strict
*/ */
public function __call(string $name, array $args) public function __call(string $name, array $args)
{ {
$class = method_exists($this, $name) ? 'parent' : get_class($this); $class = method_exists($this, $name) ? 'parent' : static::class;
$items = (new ReflectionClass($this))->getMethods(ReflectionMethod::IS_PUBLIC); $items = (new ReflectionClass($this))->getMethods(ReflectionMethod::IS_PUBLIC);
$hint = ($t = Helpers::getSuggestion($items, $name)) ? ", did you mean $t()?" : '.'; $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"); throw new \LogicException("Call to undefined method $class::$name()$hint");
} }
@@ -42,9 +45,12 @@ trait Strict
*/ */
public static function __callStatic(string $name, array $args) public static function __callStatic(string $name, array $args)
{ {
$rc = new ReflectionClass(get_called_class()); $rc = new ReflectionClass(static::class);
$items = array_intersect($rc->getMethods(ReflectionMethod::IS_PUBLIC), $rc->getMethods(ReflectionMethod::IS_STATIC)); $items = array_filter($rc->getMethods(\ReflectionMethod::IS_STATIC), function ($m) { return $m->isPublic(); });
$hint = ($t = Helpers::getSuggestion($items, $name)) ? ", did you mean $t()?" : '.'; $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"); throw new \LogicException("Call to undefined static method {$rc->getName()}::$name()$hint");
} }
@@ -62,8 +68,11 @@ trait Strict
return $ret; return $ret;
} }
$rc = new ReflectionClass($this); $rc = new ReflectionClass($this);
$items = array_diff($rc->getProperties(ReflectionProperty::IS_PUBLIC), $rc->getProperties(ReflectionProperty::IS_STATIC)); $items = array_filter($rc->getProperties(ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); });
$hint = ($t = Helpers::getSuggestion($items, $name)) ? ", did you mean $$t?" : '.'; $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"); throw new \LogicException("Attempt to read undeclared property {$rc->getName()}::$$name$hint");
} }
@@ -75,8 +84,11 @@ trait Strict
public function __set(string $name, $value) public function __set(string $name, $value)
{ {
$rc = new ReflectionClass($this); $rc = new ReflectionClass($this);
$items = array_diff($rc->getProperties(ReflectionProperty::IS_PUBLIC), $rc->getProperties(ReflectionProperty::IS_STATIC)); $items = array_filter($rc->getProperties(ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); });
$hint = ($t = Helpers::getSuggestion($items, $name)) ? ", did you mean $$t?" : '.'; $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"); throw new \LogicException("Attempt to write to undeclared property {$rc->getName()}::$$name$hint");
} }
@@ -93,7 +105,7 @@ trait Strict
*/ */
public function __unset(string $name) public function __unset(string $name)
{ {
$class = get_class($this); $class = static::class;
throw new \LogicException("Attempt to unset undeclared property $class::$$name."); throw new \LogicException("Attempt to unset undeclared property $class::$$name.");
} }
} }

View File

@@ -93,7 +93,8 @@ final class Translator
} else { } else {
$sql[] = substr($arg, 0, $toSkip) $sql[] = substr($arg, 0, $toSkip)
// note: this can change $this->args & $this->cursor & ... // note: this can change $this->args & $this->cursor & ...
. preg_replace_callback(<<<'XX' . preg_replace_callback(
<<<'XX'
/ /
(?=[`['":%?]) ## speed-up (?=[`['":%?]) ## speed-up
(?: (?:
@@ -290,7 +291,7 @@ XX
if (is_array($v)) { if (is_array($v)) {
$vx[] = $this->formatValue($v, 'ex'); $vx[] = $this->formatValue($v, 'ex');
} elseif (is_string($k)) { } elseif (is_string($k)) {
$v = (is_string($v) && strncasecmp($v, 'd', 1)) || $v > 0 ? 'ASC' : 'DESC'; $v = (is_string($v) ? strncasecmp($v, 'd', 1) : $v > 0) ? 'ASC' : 'DESC';
$vx[] = $this->identifiers->$k . ' ' . $v; $vx[] = $this->identifiers->$k . ' ' . $v;
} else { } else {
$vx[] = $this->identifiers->$v; $vx[] = $this->identifiers->$v;
@@ -323,7 +324,13 @@ XX
} elseif ($value instanceof Expression && $modifier === 'ex') { } elseif ($value instanceof Expression && $modifier === 'ex') {
return $this->connection->translate(...$value->getValues()); return $this->connection->translate(...$value->getValues());
} elseif ($value instanceof \DateTimeInterface && ($modifier === 'd' || $modifier === 't' || $modifier === 'dt')) { } elseif (
$value instanceof \DateTimeInterface
&& ($modifier === 'd'
|| $modifier === 't'
|| $modifier === 'dt'
)
) {
// continue // continue
} else { } else {
$type = is_object($value) ? get_class($value) : gettype($value); $type = is_object($value) ? get_class($value) : gettype($value);
@@ -333,21 +340,29 @@ XX
switch ($modifier) { switch ($modifier) {
case 's': // string case 's': // string
return $value === null ? 'NULL' : $this->driver->escapeText((string) $value); return $value === null
? 'NULL'
: $this->driver->escapeText((string) $value);
case 'bin':// binary case 'bin':// binary
return $value === null ? 'NULL' : $this->driver->escapeBinary($value); return $value === null
? 'NULL'
: $this->driver->escapeBinary($value);
case 'b': // boolean case 'b': // boolean
return $value === null ? 'NULL' : $this->driver->escapeBool((bool) $value); return $value === null
? 'NULL'
: $this->driver->escapeBool((bool) $value);
case 'sN': // string or null case 'sN': // string or null
case 'sn': case 'sn':
return $value == '' ? 'NULL' : $this->driver->escapeText((string) $value); // notice two equal signs return $value === '' || $value === 0 || $value === null
? 'NULL'
: $this->driver->escapeText((string) $value);
case 'iN': // signed int or null case 'iN': // signed int or null
if ($value == '') { if ($value === '' || $value === 0 || $value === null) {
$value = null; return 'NULL';
} }
// break omitted // break omitted
case 'i': // signed int case 'i': // signed int
@@ -385,7 +400,9 @@ XX
} elseif (!$value instanceof \DateTimeInterface) { } elseif (!$value instanceof \DateTimeInterface) {
$value = new DateTime($value); $value = new DateTime($value);
} }
return $modifier === 'd' ? $this->driver->escapeDate($value) : $this->driver->escapeDateTime($value); return $modifier === 'd'
? $this->driver->escapeDate($value)
: $this->driver->escapeDateTime($value);
case 'by': case 'by':
case 'n': // composed identifier name case 'n': // composed identifier name
@@ -401,7 +418,8 @@ XX
$toSkip = strcspn($value, '`[\'":'); $toSkip = strcspn($value, '`[\'":');
if (strlen($value) !== $toSkip) { if (strlen($value) !== $toSkip) {
$value = substr($value, 0, $toSkip) $value = substr($value, 0, $toSkip)
. preg_replace_callback(<<<'XX' . preg_replace_callback(
<<<'XX'
/ /
(?=[`['":]) (?=[`['":])
(?: (?:
@@ -609,7 +627,9 @@ XX
if ($matches[8]) { // SQL identifier substitution if ($matches[8]) { // SQL identifier substitution
$m = substr($matches[8], 0, -1); $m = substr($matches[8], 0, -1);
$m = $this->connection->getSubstitutes()->$m; $m = $this->connection->getSubstitutes()->$m;
return $matches[9] == '' ? $this->formatValue($m, null) : $m . $matches[9]; // value or identifier return $matches[9] === ''
? $this->formatValue($m, null)
: $m . $matches[9]; // value or identifier
} }
throw new \Exception('this should be never executed'); throw new \Exception('this should be never executed');

View File

@@ -30,6 +30,6 @@ class Type
final public function __construct() final public function __construct()
{ {
throw new \LogicException('Cannot instantiate static class ' . __CLASS__); throw new \LogicException('Cannot instantiate static class ' . self::class);
} }
} }

View File

@@ -25,6 +25,7 @@ declare(strict_types=1);
* @method static void begin(string $savepoint = null) * @method static void begin(string $savepoint = null)
* @method static void commit(string $savepoint = null) * @method static void commit(string $savepoint = null)
* @method static void rollback(string $savepoint = null) * @method static void rollback(string $savepoint = null)
* @method static mixed transaction(callable $callback)
* @method static Dibi\Reflection\Database getDatabaseInfo() * @method static Dibi\Reflection\Database getDatabaseInfo()
* @method static Dibi\Fluent command() * @method static Dibi\Fluent command()
* @method static Dibi\Fluent select(...$args) * @method static Dibi\Fluent select(...$args)
@@ -44,7 +45,7 @@ class dibi
/** version */ /** version */
public const public const
VERSION = '4.1.3'; VERSION = '4.2.5';
/** sorting order */ /** sorting order */
public const public const
@@ -75,7 +76,7 @@ class dibi
*/ */
final public function __construct() final public function __construct()
{ {
throw new LogicException('Cannot instantiate static class ' . get_class($this)); throw new LogicException('Cannot instantiate static class ' . static::class);
} }

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

@@ -1,7 +0,0 @@
imports:
- { resource: '../temp/coding-standard/coding-standard-php71.yml' }
parameters:
skip:
PhpCsFixer\Fixer\Operator\TernaryToNullCoalescingFixer:
- src/Dibi/HashMap.php # issue #260

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

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

@@ -30,21 +30,102 @@ Assert::exception(function () use ($conn) {
*/ */
$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', $conn->query('INSERT INTO [products]', [
]); 'title' => 'Test product',
Assert::same(4, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle()); ]);
$conn->rollback(); Assert::same(4, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle());
Assert::same(3, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle()); $conn->rollback();
Assert::same(3, (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());
});
$conn->begin(); test('transaction() fail', function () use ($conn) {
$conn->query('INSERT INTO [products]', [ Assert::exception(function () use ($conn) {
'title' => 'Test product', $conn->transaction(function (Dibi\Connection $connection) {
]); $connection->query('INSERT INTO [products]', [
$conn->commit(); 'title' => 'Test product',
Assert::same(4, (int) $conn->query('SELECT COUNT(*) FROM [products]')->fetchSingle()); ]);
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(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',
]);
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(function () use ($conn) {
$conn->transaction(function (Dibi\Connection $connection) {
$connection->begin();
});
}, \LogicException::class, Dibi\Connection::class . '::begin() call is forbidden inside a transaction() callback');
Assert::exception(function () use ($conn) {
$conn->transaction(function (Dibi\Connection $connection) {
$connection->commit();
});
}, \LogicException::class, Dibi\Connection::class . '::commit() call is forbidden inside a transaction() callback');
Assert::exception(function () use ($conn) {
$conn->transaction(function (Dibi\Connection $connection) {
$connection->rollback();
});
}, \LogicException::class, Dibi\Connection::class . '::rollback() call is forbidden inside a transaction() callback');
});

View File

@@ -14,8 +14,6 @@ Assert::same('1978-01-23 11:40:00.000000', (string) new DateTime(254400000));
Assert::same('1978-01-23 11:40:00.000000', (string) (new DateTime)->setTimestamp(254400000)); Assert::same('1978-01-23 11:40:00.000000', (string) (new DateTime)->setTimestamp(254400000));
Assert::same(254400000, (new DateTime(254400000))->getTimestamp()); Assert::same(254400000, (new DateTime(254400000))->getTimestamp());
Assert::same('2050-08-13 11:40:00.000000', (string) new DateTime(2544000000));
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(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

@@ -32,8 +32,3 @@ Assert::exception(function () {
test('PDO error mode: explicitly set silent', function () { test('PDO error mode: explicitly set silent', function () {
buildPdoDriver(PDO::ERRMODE_SILENT); buildPdoDriver(PDO::ERRMODE_SILENT);
}); });
test('PDO error mode: implicitly set silent', function () {
buildPdoDriver(null);
});

View File

@@ -32,7 +32,7 @@ Assert::same(
); );
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

@@ -24,6 +24,17 @@ class MockResult extends Dibi\Result
} }
test('', function () {
$result = new MockResult;
$result->setType('col', Type::TEXT);
$result->setFormat(Type::TEXT, 'native');
Assert::same(['col' => null], $result->test(['col' => null]));
Assert::same(['col' => true], $result->test(['col' => true]));
Assert::same(['col' => false], $result->test(['col' => false]));
});
test('', function () { test('', function () {
$result = new MockResult; $result = new MockResult;
$result->setType('col', Type::BOOL); $result->setType('col', Type::BOOL);
@@ -147,7 +158,14 @@ test('', function () {
Assert::same(['col' => 1], $result->test(['col' => true])); Assert::same(['col' => 1], $result->test(['col' => true]));
Assert::same(['col' => 0], $result->test(['col' => false])); Assert::same(['col' => 0], $result->test(['col' => false]));
Assert::same(['col' => 0], @$result->test(['col' => ''])); // triggers warning in PHP 7.1 if (PHP_VERSION_ID < 80000) {
Assert::same(['col' => 0], @$result->test(['col' => ''])); // triggers warning since PHP 7.1
} else {
Assert::exception(function () use ($result) {
Assert::same(['col' => 0], $result->test(['col' => '']));
}, TypeError::class);
}
Assert::same(['col' => 0], $result->test(['col' => '0'])); Assert::same(['col' => 0], $result->test(['col' => '0']));
Assert::same(['col' => 1], $result->test(['col' => '1'])); Assert::same(['col' => 1], $result->test(['col' => '1']));
Assert::same(['col' => 10], $result->test(['col' => '10'])); Assert::same(['col' => 10], $result->test(['col' => '10']));

View File

@@ -35,6 +35,10 @@ Assert::error(function () use ($row) {
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(function () use ($row) {

View File

@@ -23,7 +23,9 @@ Assert::equal(1, $conn->getInsertId());
$conn->query( $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', 'aab', 'aaa' 'UpdAAB',
'aab',
'aaa'
); );
$conn->query('INSERT INTO %n DEFAULT VALUES', 'aab'); $conn->query('INSERT INTO %n DEFAULT VALUES', 'aab');

View File

@@ -1,9 +1,5 @@
<?php <?php
/**
* @phpversion 5.5
*/
declare(strict_types=1); declare(strict_types=1);
use Tester\Assert; use Tester\Assert;

View File

@@ -19,12 +19,17 @@ Assert::same(
SELECT * SELECT *
FROM [customers] FROM [customers]
/* WHERE ... LIKE ... */'), /* WHERE ... LIKE ... */'),
$conn->translate(
$conn->translate(' '
SELECT * SELECT *
FROM [customers] FROM [customers]
%if', isset($name), 'WHERE [name] LIKE %s', 'xxx', '%end' %if',
)); isset($name),
'WHERE [name] LIKE %s',
'xxx',
'%end'
)
);
// if & else & end (last end is optional) // if & else & end (last end is optional)
@@ -32,11 +37,14 @@ Assert::same(
reformat(' reformat('
SELECT * SELECT *
FROM [customers] /* ... */'), FROM [customers] /* ... */'),
$conn->translate(
$conn->translate(' '
SELECT * SELECT *
FROM %if', true, '[customers] %else [products]' FROM %if',
)); true,
'[customers] %else [products]'
)
);
// if & else & (optional) end // if & else & (optional) end
@@ -48,14 +56,14 @@ WHERE [id] > 0
/* AND ...=... /* AND ...=...
*/ AND [bar]=1 */ AND [bar]=1
'), '),
$conn->translate(' $conn->translate('
SELECT * SELECT *
FROM [people] FROM [people]
WHERE [id] > 0 WHERE [id] > 0
%if', false, 'AND [foo]=%i', 1, ' %if', false, 'AND [foo]=%i', 1, '
%else %if', true, 'AND [bar]=%i', 1, ' %else %if', true, 'AND [bar]=%i', 1, '
')); ')
);
// nested condition // nested condition
@@ -76,24 +84,35 @@ WHERE
/* AND ...=1 */ /* AND ...=1 */
/* 1 LIMIT 10 */", /* 1 LIMIT 10 */",
]), ]),
$conn->translate(
$conn->translate(' '
SELECT * SELECT *
FROM [customers] FROM [customers]
WHERE WHERE
%if', true, '[name] LIKE %s', 'xxx', ' %if',
%if', false, 'AND [admin]=1 %end true,
'[name] LIKE %s',
'xxx',
'
%if',
false,
'AND [admin]=1 %end
%else 1 LIMIT 10 %end' %else 1 LIMIT 10 %end'
)); )
);
// limit & offset // limit & offset
Assert::same( Assert::same(
'SELECT * FROM foo /* (limit 3) (offset 5) */', 'SELECT * FROM foo /* (limit 3) (offset 5) */',
$conn->translate( $conn->translate(
'SELECT * FROM foo', 'SELECT * FROM foo',
'%if', false, '%if',
'%lmt', 3, false,
'%ofs', 5, '%lmt',
'%end' 3,
)); '%ofs',
5,
'%end'
)
);

View File

@@ -1,7 +1,7 @@
<?php <?php
/** /**
* @dataProvider ../databases.ini * @dataProvider ../databases.ini !=sqlsrv
*/ */
declare(strict_types=1); declare(strict_types=1);

View File

@@ -261,7 +261,7 @@ if ($config['system'] === 'postgre') {
'sqlite' => "SELECT * FROM products WHERE (title LIKE 'C%' ESCAPE '\\' AND title LIKE '%r' ESCAPE '\\') OR title LIKE '%a\n\\%\\_\\\\''\"%' ESCAPE '\\'", 'sqlite' => "SELECT * FROM products WHERE (title LIKE 'C%' ESCAPE '\\' AND title LIKE '%r' ESCAPE '\\') OR title LIKE '%a\n\\%\\_\\\\''\"%' ESCAPE '\\'",
'odbc' => "SELECT * FROM products WHERE (title LIKE 'C%' AND title LIKE '%r') OR title LIKE '%a\n[%][_]\\''\"%'", 'odbc' => "SELECT * FROM products WHERE (title LIKE 'C%' AND title LIKE '%r') OR title LIKE '%a\n[%][_]\\''\"%'",
'sqlsrv' => "SELECT * FROM products WHERE (title LIKE 'C%' AND title LIKE '%r') OR title LIKE '%a\n[%][_]\\''\"%'", 'sqlsrv' => "SELECT * FROM products WHERE (title LIKE 'C%' AND title LIKE '%r') OR title LIKE '%a\n[%][_]\\''\"%'",
"SELECT * FROM products WHERE (title LIKE 'C%' AND title LIKE '%r') OR title LIKE '%a\\n\\%\\_\\\\\\\\\'\"%'", "SELECT * FROM products WHERE (title LIKE 'C%' AND title LIKE '%r') OR title LIKE '%a\\n\\%\\_\\\\\\\\\\'\"%'",
]), ]),
$conn->translate($args[0], $args[1], $args[2], $args[3]) $conn->translate($args[0], $args[1], $args[2], $args[3])
); );
@@ -279,7 +279,7 @@ Assert::match(
CONCAT(last_name, ', ', first_name) AS full_name CONCAT(last_name, ', ', first_name) AS full_name
GROUP BY `user` GROUP BY `user`
HAVING MAX(salary) > %i 123 HAVING MAX(salary) > %i 123
INTO OUTFILE '/tmp/result\'.txt' INTO OUTFILE '/tmp/result\\'.txt'
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\\\"' FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\\\"'
LINES TERMINATED BY '\\\\n' LINES TERMINATED BY '\\\\n'
", ",
@@ -344,7 +344,7 @@ WHERE (`test`.`a` LIKE '1995-03-01'
OR `b8` IN (RAND() `col1` > `col2` ) OR `b8` IN (RAND() `col1` > `col2` )
OR `b9` IN (RAND(), [col1] > [col2] ) OR `b9` IN (RAND(), [col1] > [col2] )
OR `b10` IN ( ) OR `b10` IN ( )
AND `c` = 'embedded \' string' AND `c` = 'embedded \\' string'
OR `d`=10 OR `d`=10
OR `e`=NULL OR `e`=NULL
OR `true`= 1 OR `true`= 1
@@ -437,7 +437,6 @@ WHERE ([test].[a] LIKE '1995-03-01'
OR [str_not_null]='hello' OR [str_not_null]='hello'
LIMIT 10", LIMIT 10",
]), ]),
$conn->translate('SELECT * $conn->translate('SELECT *
FROM [db.table] FROM [db.table]
WHERE ([test.a] LIKE %d', '1995-03-01', ' WHERE ([test.a] LIKE %d', '1995-03-01', '
@@ -671,7 +670,6 @@ Assert::same(
'sqlsrv' => "UPDATE [colors] SET [color]=N'blue', [price]=-12.4, [spec]=-9E-005, [spec2]=1000, [spec3]=10000, [spec4]=10000 WHERE [price]=123.5", 'sqlsrv' => "UPDATE [colors] SET [color]=N'blue', [price]=-12.4, [spec]=-9E-005, [spec2]=1000, [spec3]=10000, [spec4]=10000 WHERE [price]=123.5",
"UPDATE [colors] SET [color]='blue', [price]=-12.4, [spec]=-9E-005, [spec2]=1000, [spec3]=10000, [spec4]=10000 WHERE [price]=123.5", "UPDATE [colors] SET [color]='blue', [price]=-12.4, [spec]=-9E-005, [spec2]=1000, [spec3]=10000, [spec4]=10000 WHERE [price]=123.5",
]), ]),
$conn->translate('UPDATE [colors] SET', [ $conn->translate('UPDATE [colors] SET', [
'color' => 'blue', 'color' => 'blue',
'price' => -12.4, 'price' => -12.4,

View File

@@ -23,6 +23,10 @@ try {
$config = reset($config); $config = reset($config);
} }
if (isset($config['port'])) {
$config['port'] = (int) $config['port'];
}
// lock // lock
define('TEMP_DIR', __DIR__ . '/../tmp'); define('TEMP_DIR', __DIR__ . '/../tmp');

View File

@@ -36,7 +36,7 @@ Assert::same('SELECT', $e->getSql());
$e = Assert::exception(function () use ($conn) { $e = Assert::exception(function () use ($conn) {
$conn->query('INSERT INTO products (product_id, title) VALUES (1, "New")'); $conn->query('INSERT INTO products (product_id, title) VALUES (1, "New")');
}, Dibi\UniqueConstraintViolationException::class, "%a?%Duplicate entry '1' for key 'PRIMARY'", 1062); }, Dibi\UniqueConstraintViolationException::class, "%a?%Duplicate entry '1' for key '%a?%PRIMARY'", 1062);
Assert::same("INSERT INTO products (product_id, title) VALUES (1, 'New')", $e->getSql()); Assert::same("INSERT INTO products (product_id, title) VALUES (1, 'New')", $e->getSql());

View File

@@ -31,7 +31,7 @@ Assert::same("INSERT INTO products (product_id, title) VALUES (1, 'New')", $e->g
$e = Assert::exception(function () use ($conn) { $e = Assert::exception(function () use ($conn) {
$conn->query('INSERT INTO products (title) VALUES (NULL)'); $conn->query('INSERT INTO products (title) VALUES (NULL)');
}, Dibi\NotNullConstraintViolationException::class, '%a?%null value in column "title" violates not-null constraint%A?%', '23502'); }, Dibi\NotNullConstraintViolationException::class, '%a?%null value in column "title"%a%violates not-null constraint%A?%', '23502');
Assert::same('INSERT INTO products (title) VALUES (NULL)', $e->getSql()); Assert::same('INSERT INTO products (title) VALUES (NULL)', $e->getSql());

View File

@@ -12,5 +12,5 @@ extension=php_pdo_pgsql.dll
extension=php_pdo_sqlite.dll extension=php_pdo_sqlite.dll
extension=php_pgsql.dll extension=php_pgsql.dll
extension=php_sqlite3.dll extension=php_sqlite3.dll
extension=php_sqlsrv_71_ts.dll extension=php_sqlsrv_ts.dll
extension=php_odbc.dll extension=php_odbc.dll